2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-24 01:00:33 +03:00

feat: make ssr work

feat: update build script

chore: use jiti instead of babel-node for examples
This commit is contained in:
pimlie
2021-01-25 00:50:46 +01:00
parent 2e2c08e1f8
commit 9cfde5b550
37 changed files with 825 additions and 1606 deletions
+2 -1
View File
@@ -3,7 +3,8 @@
"@nuxtjs/eslint-config-typescript" "@nuxtjs/eslint-config-typescript"
], ],
"globals": { "globals": {
"__DEV__": true "__DEV__": true,
"__BROWSER__": false,
}, },
"overrides": [ "overrides": [
{ {
+1 -4
View File
@@ -1,9 +1,6 @@
MIT License MIT License
Copyright (c) 2016-2019 Copyright (c) 2021 - Pim (@pimlie)
- Declan de Wet
- Sébastien Chopin
- All the amazing contributors (https://github.com/nuxt/vue-meta/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
-43
View File
@@ -1,43 +0,0 @@
const path = require('path')
module.exports = {
presets: [
['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: 3,
targets: {
ie: 9
}
}],
'@babel/preset-typescript',
],
plugins: [
'dynamic-import-node',
['global-define', {
'__DEV__': 'true'
}],
['module-resolver', {
root: '.',
extensions: ['.ts'],
alias: {
'vue-meta': path.resolve('./src/')
}
}],
],
env: {
test: {
plugins: [
'@babel/plugin-syntax-dynamic-import',
'dynamic-import-node'
],
presets: [
[ '@babel/preset-env', {
targets: {
node: 'current'
}
}],
'@babel/preset-typescript'
],
}
}
}
+157
View File
@@ -0,0 +1,157 @@
import path from 'path'
import alias from '@rollup/plugin-alias'
// import babel from '@rollup/plugin-babel'
import commonjs from '@rollup/plugin-commonjs'
import nodeResolve from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
import { terser } from 'rollup-plugin-terser'
import ts from 'rollup-plugin-typescript2'
import defaultsDeep from 'lodash/defaultsDeep'
const pkg = require('../package.json')
const banner = `/**
* ${pkg.name} v${pkg.version}
* (c) ${new Date().getFullYear()}
* - Pim (@pimlie)
* - All the amazing contributors
* @license MIT
*/
`
let didTS = false
function rollupConfig({
plugins = [],
external = [],
...config
}) {
const isBrowserBuild = !config.output || !config.output.format || config.output.format === 'iife' || config.output.file.includes('-browser.')
const isProductionBuild = config.output.file.includes('.prod.')
const replaceConfig = {
exclude: 'node_modules',
delimiters: ['', ''],
values: {
'process.server' : isBrowserBuild ? 'false' : 'true', // should not be used anymore
'__DEV__': config.output.format === 'es' && !isBrowserBuild ? "(process.env.NODE_ENV !== 'production')" : !isProductionBuild,
'__BROWSER__': isBrowserBuild,
}
}
if (isBrowserBuild) {
external = ['vue']
} else {
external = Object.keys(pkg.peerDependencies)
external.push('@vue/server-renderer')
}
const thisConfig = defaultsDeep({}, config, {
input: 'src/index.ts',
output: {
name: 'VueMeta',
format: 'iife',
sourcemap: false,
banner,
externalLiveBindings: false,
globals: {
vue: 'Vue'
}
},
external,
plugins: [
replace(replaceConfig),
nodeResolve(),
commonjs(),
ts({
check: !didTS,
tsconfig: path.resolve(__dirname, '../tsconfig.json'),
cacheRoot: path.resolve(__dirname, '../node_modules/.rts2_cache'),
tsconfigOverride: {
compilerOptions: {
sourceMap: true,
declaration: !didTS,
declarationMap: !didTS,
},
exclude: ['__tests__', 'test-dts'],
},
}),
].concat(plugins),
})
if (isBrowserBuild) {
// remove the ssr renderToString helper for browser builds
thisConfig.plugins.unshift(alias({
entries: [
{ find: '.\/ssr', replacement: path.resolve(__dirname, './stub.js') },
]
}))
}
if (config.output.file.includes('.min.')) {
const terserOpts = {
module: config.output.format === 'es',
compress: {
ecma: 2015,
pure_getters: true,
},
}
thisConfig.plugins.push(terser(terserOpts))
}
didTS = true
return thisConfig
}
export default [
// umd web build
{
output: {
file: pkg.unpkg,
},
},
// minimized umd web build
{
output: {
file: pkg.unpkg.replace('.js', '.min.js'),
},
},
// common js build
{
output: {
file: pkg.main,
format: 'cjs'
},
},
// common js build
{
output: {
file: pkg.main.replace('.js', '.prod.js'),
format: 'cjs'
},
},
// esm build
{
output: {
file: pkg.module,
format: 'es'
},
},
// browser esm build
{
output: {
file: pkg.module.replace('-bundler.js', '-browser.js'),
format: 'es'
},
},
// minimized browser esm build
{
output: {
file: pkg.module.replace('-bundler.js', '-browser.min.js'),
format: 'es'
},
}
].map(rollupConfig)
+1
View File
@@ -0,0 +1 @@
-42
View File
@@ -1,42 +0,0 @@
# Vue Meta Examples
## Prepare examples
To prepare the examples to run locally, please follow these steps:
```bash
git clone https://github.com/nuxt/vue-meta
cd examples
yarn install
```
## Run the examples
When the examples are installed locally, start the example server as follows
```js
yarn start
// or
HOST=0.0.0.0 PORT=8080 yarn start
```
and browse to `http://localhost:3000` or whatever you changed the host and port to
### SSR Example
The server side rendering example is available on the cli only, to run the SSR example just run
```bash
yarn ssr
```
## Developing
If you would like to use the examples while developing or debugging `vue-meta` features or issues, please do as follows
```js
git clone https://github.com/nuxt/vue-meta
yarn install
cd examples
yarn install
yarn dev
```
+5
View File
@@ -1,3 +1,8 @@
if (!window.users) {
window.users = []
console.warn('window.users was not set')
}
window.users.push({ window.users.push({
id: 1, id: 1,
name: 'Leanne Graham', name: 'Leanne Graham',
+5
View File
@@ -1,3 +1,8 @@
if (!window.users) {
window.users = []
console.warn('window.users was not set')
}
window.users.push({ window.users.push({
id: 2, id: 2,
name: 'Ervin Howell', name: 'Ervin Howell',
+5
View File
@@ -1,3 +1,8 @@
if (!window.users) {
window.users = []
console.warn('window.users was not set')
}
window.users.push({ window.users.push({
id: 3, id: 3,
name: 'Clementine Bauch', name: 'Clementine Bauch',
+5
View File
@@ -1,3 +1,8 @@
if (!window.users) {
window.users = []
console.warn('window.users was not set')
}
window.users.push({ window.users.push({
id: 4, id: 4,
name: 'Patricia Lebsack', name: 'Patricia Lebsack',
+45
View File
@@ -0,0 +1,45 @@
const path = require('path')
const { transformSync } = require('@babel/core')
module.exports = require('jiti')(__filename, {
cache: false,
debug: false,
transform (opts) {
const _opts = {
babelrc: false,
configFile: false,
compact: false,
retainLines: typeof opts.retainLines === 'boolean' ? opts.retainLines : true,
filename: '',
cwd: '/',
plugins: [
[require('@babel/plugin-transform-modules-commonjs'), { allowTopLevelThis: true }],
[require('@babel/plugin-transform-typescript')],
[require('babel-plugin-dynamic-import-node'), { noInterop: true }],
[require('babel-plugin-global-define'), {
__DEV__: true,
__BROWSER__: false
}],
[require('babel-plugin-module-resolver'), {
root: '.',
extensions: ['.ts'],
alias: {
'vue-meta': path.resolve(__dirname, '../src/')
}
}]
]
}
try {
return transformSync(opts.source, _opts).code || ''
} catch (err) {
return 'exports.__JITI_ERROR__ = ' + JSON.stringify({
filename: opts.filename,
line: (err.loc && err.loc.line) || 0,
column: (err.loc && err.loc.column) || 0,
code: err.code && err.code.replace('BABEL_', '').replace('PARSE_ERROR', 'ParseError'),
message: err.message.replace('/: ', '').replace(/\(.+\)\s*$/, '')
})
}
}
})
-76
View File
@@ -1,76 +0,0 @@
const {
baseParse,
transform,
generate,
processIf,
getBaseTransformPreset,
createObjectExpression,
createObjectProperty
} = require('@vue/compiler-core')
const { parse } = require('@vue/compiler-dom')
function headTransform (node, context) {
console.log('NODE', node)
if (node.type === 1 /* NodeTypes.ELEMENT */) {
return () => {
if (!context.parent.codegenNode) {
context.parent.codegenNode = createObjectExpression([])
}
const options = context.parent.codegenNode
const option = createObjectProperty(
node.tag,
node.children.length === 1 ? node.children[0] : 'null'
)
// options.properties.push(option)
}
}
}
module.exports = function (source, map) {
// TODO: add options
const ast = parse(source)
// console.log('AST', ast)
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset({
prefixIdentifiers: true
})
transform(ast, {
prefixIdentifiers: true,
nodeTransforms: [...nodeTransforms, headTransform],
directiveTransforms
})
const result = generate(ast, { mode: 'module' })
console.log(result.code)
this.callback(
null,
`
import { computed } from 'vue'
${result.code}
export default function (component) {
const setup = component.setup
component.setup = function (...args) {
console.log(component)
const __htmlMetaData = computed(() => {
})
return {
...setup.apply(this, args),
__htmlMetaData
}
}
}`,
map
)
/**/
}
+13 -10
View File
@@ -1,17 +1,20 @@
import fs from 'fs' const fs = require('fs')
import path from 'path' const path = require('path')
import consola from 'consola' const consola = require('consola')
import express from 'express' const express = require('express')
import rewrite from 'express-urlrewrite' const rewrite = require('express-urlrewrite')
import webpack from 'webpack' const webpack = require('webpack')
import webpackDevMiddleware from 'webpack-dev-middleware' const webpackDevMiddleware = require('webpack-dev-middleware')
import WebpackConfig from './webpack.config' const webpackConfig = require('./webpack.config')
import { renderPage } from './ssr/server' const jiti = require('./jiti')
const { renderPage } = jiti('./ssr/server.js')
// const { renderPage } = require('./ssr/server')
const app = express() const app = express()
app.use( app.use(
webpackDevMiddleware(webpack(WebpackConfig), { webpackDevMiddleware(webpack(webpackConfig(true)), {
publicPath: '/__build__/', publicPath: '/__build__/',
writeToDisk: true, writeToDisk: true,
stats: { stats: {
+6 -6
View File
@@ -1,16 +1,16 @@
<!doctype html> <!doctype html>
<html {{ htmlAttrs.text(true) }}> <html {{ htmlAttrs }}>
<head {{ headAttrs.text() }}> <head {{ headAttrs }}>
{{ head(true) }} {{ head }}
<link rel="stylesheet" href="/global.css"> <link rel="stylesheet" href="/global.css">
</head> </head>
<body {{ bodyAttrs.text() }}> <body {{ bodyAttrs }}>
{{ bodyPrepend(true) }} <body-prepend id="body-prepend">{{ bodyPrepend }}</body-prepend>
<a href="/">&larr; Examples index</a> <a href="/">&larr; Examples index</a>
{{ app }} {{ app }}
<script src="/__build__/ssr.js"></script> <script src="/__build__/ssr.js"></script>
{{ bodyAppend(true) }} {{ bodyAppend }}
</body> </body>
</html> </html>
+15 -24
View File
@@ -1,8 +1,9 @@
import path from 'path' import path from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { createSSRApp } from 'vue' import { createSSRApp } from 'vue'
import { renderToStringWithMeta } from 'vue-meta'
import template from 'lodash/template' import template from 'lodash/template'
import { renderToString } from '@vue/server-renderer'
import { App, createRouter, metaManager } from '../vue-router/main' import { App, createRouter, metaManager } from '../vue-router/main'
const templateFile = path.resolve(__dirname, 'app.template.html') const templateFile = path.resolve(__dirname, 'app.template.html')
@@ -15,41 +16,31 @@ process.server = true
export async function renderPage ({ url }) { export async function renderPage ({ url }) {
console.log('renderPage', url) console.log('renderPage', url)
const app = createSSRApp(App) const app = createSSRApp(App)
const router = createRouter('/ssr', true) const router = createRouter('/ssr', true)
app.use(router) app.use(router)
// app.use(metaManager) app.use(metaManager)
console.log('renderPage', 'push')
await router.push(url.substr(4)) await router.push(url.substr(4))
await router.isReady() await router.isReady()
console.log('renderPage', 'eady')
/* console.log(router)
const matchedComponents = router.getMatchedComponents()
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject({ code: 404 })
} */
const appHtml = await renderToString(app) const [appHtml, ctx] = await renderToStringWithMeta(app)
if (!ctx.teleports) {
ctx.teleports = {}
}
const pageHtml = compiled({ const pageHtml = compiled({
app: appHtml, app: appHtml,
htmlAttrs: { htmlAttrs: ctx.teleports.htmlAttrs || '',
text: () => {} headAttrs: ctx.teleports.headAttrs || '',
}, bodyAttrs: ctx.teleports.bodyAttrs || '',
headAttrs: { head: ctx.teleports.head || '',
text: () => {} bodyPrepend: ctx.teleports['body-prepend'] || '',
}, bodyAppend: ctx.teleports.body || ''
bodyAttrs: {
text: () => {}
},
head: () => {},
bodyPrepend: () => {},
bodyAppend: () => {}
// ...app.$meta().inject()
}) })
return pageHtml return pageHtml
+14 -11
View File
@@ -29,7 +29,7 @@ export default {
{ tag: 'link', rel: 'stylesheet', href: 'style2.css' } { tag: 'link', rel: 'stylesheet', href: 'style2.css' }
] ]
}, },
body: 'body-script1.js', body: 'body-script1.js', // TODO: fix
htmlAttrs: { htmlAttrs: {
amp: true, amp: true,
lang: ['en', 'nl'] lang: ['en', 'nl']
@@ -44,7 +44,7 @@ export default {
// TODO { content: 'window.a = "<br/>"; </script><script>alert(\'asdasd\');' }, // TODO { content: 'window.a = "<br/>"; </script><script>alert(\'asdasd\');' },
// TODO { rawContent: 'window.b = "<br/>"; </script><script> alert(\'123321\');' }, // TODO { rawContent: 'window.b = "<br/>"; </script><script> alert(\'123321\');' },
{ src: 'body-script2.js', to: 'body' }, { src: 'body-script2.js', to: 'body' },
{ src: 'body-script3.js', to: '#put-it-here' } { src: 'body-script3.js', to: 'body-prepend' }
] ]
/* esi: { /* esi: {
children: [ children: [
@@ -115,20 +115,15 @@ export default {
} }
} }
setTimeout(() => walk(metadata), 1000) */ setTimeout(() => walk(metadata), 1000) */
/*
return { <template v-slot:base="{ content, metainfo }">http://nuxt.dev:3000{{ content }}</template>
metadata
}
},
template: `
<metainfo>
<template v-slot:base="{ content, metainfo }">http://nuxt.dev:3000{{ content }}</template>
<template v-slot:title="{ content, metainfo }">{{ content }} - {{ metainfo.description }} - Hello</template> <template v-slot:title="{ content, metainfo }">{{ content }} - {{ metainfo.description }} - Hello</template>
<template v-slot:og(title)="{ content, metainfo, og }"> <template v-slot:og(title)="{ content, metainfo, og }">
{{ content }} - {{ og.description }} - {{ metainfo.description }} - Hello Again {{ content }} - {{ og.description }} - {{ metainfo.description }} - Hello Again
</template> </template>
<!-- // TODO: Using script triggers [Vue warn]: Template compilation error: Tags with side effect (<script> and <style>) are ignored in client component templates. --> <!-- // TODO: Using script triggers [Vue warn]: Template compilation error: Tags with side effect (<script> and <style>) are i
gnored in client component templates. -->
<component is="script">window.users = []</component> <component is="script">window.users = []</component>
<component is="script" src="user-1.js"></component> <component is="script" src="user-1.js"></component>
<component is="script" src="user-2.js"></component> <component is="script" src="user-2.js"></component>
@@ -147,6 +142,14 @@ export default {
<template v-slot:body> <template v-slot:body>
<component is="script" src="user-4.js"></component> <component is="script" src="user-4.js"></component>
</template> </template>
*/
return {
metadata
}
},
template: `
<metainfo>
<template v-slot:body><br/></template>
</metainfo> </metainfo>
<div id="app"> <div id="app">
+1 -1
View File
@@ -12,9 +12,9 @@
</style> </style>
</head> </head>
<body> <body>
<body-prepend id="body-prepend"></body-prepend>
<a href="/">&larr; Examples index</a> <a href="/">&larr; Examples index</a>
<div id="app"></div> <div id="app"></div>
<div id="put-it-here"></div>
<script src="/__build__/vue-router.js"></script> <script src="/__build__/vue-router.js"></script>
</body> </body>
</html> </html>
+2 -2
View File
@@ -1,6 +1,6 @@
import { h } from 'vue' import { h } from 'vue'
import { createRouter as createVueRouter, createMemoryHistory, createWebHistory } from 'vue-router' import { createRouter as createVueRouter, createMemoryHistory, createWebHistory } from 'vue-router'
import { createManager, defaultConfig, resolveOption, useMeta } from 'vue-meta' import { createMetaManager, defaultConfig, resolveOption, useMeta } from 'vue-meta'
import App from './App' import App from './App'
import ChildComponent from './Child' import ChildComponent from './Child'
@@ -20,7 +20,7 @@ const decisionMaker5000000 = resolveOption((prevValue, context) => {
} }
}) })
const metaManager = createManager({ const metaManager = createMetaManager({
...defaultConfig, ...defaultConfig,
esi: { esi: {
group: true, group: true,
+81 -77
View File
@@ -1,89 +1,93 @@
import fs from 'fs' const fs = require('fs')
import path from 'path' const path = require('path')
import webpack from 'webpack' const webpack = require('webpack')
import WebpackBar from 'webpackbar' const WebpackBar = require('webpackbar')
import { VueLoaderPlugin } from 'vue-loader' const { VueLoaderPlugin } = require('vue-loader')
// const srcDir = path.join(__dirname, '..', 'src') // const srcDir = path.join(__dirname, '..', 'src')
export default { module.exports = (isBrowser) => {
devtool: 'inline-source-map', const extraAliases = {}
mode: 'development', if (isBrowser) {
entry: fs.readdirSync(__dirname) extraAliases['./ssr$'] = path.resolve(__dirname, '../build/stub.js')
.reduce((entries, dir) => { }
const fullDir = path.join(__dirname, dir)
if (dir === 'ssr') { return {
entries[dir] = path.join(fullDir, 'browser.js') devtool: 'inline-source-map',
} else if (dir === 'vue-router') { mode: 'development',
const possibleEntries = ['browser', 'app'] entry: fs.readdirSync(__dirname)
for (const entryName of possibleEntries) { .reduce((entries, dir) => {
const entry = path.join(fullDir, entryName + '.js') const fullDir = path.join(__dirname, dir)
if (fs.statSync(fullDir).isDirectory() && fs.existsSync(entry)) { if (dir === 'ssr') {
entries[dir] = entry entries[dir] = path.join(fullDir, 'browser.js')
break } else if (dir === 'vue-router') {
const possibleEntries = ['browser', 'app']
for (const entryName of possibleEntries) {
const entry = path.join(fullDir, entryName + '.js')
if (fs.statSync(fullDir).isDirectory() && fs.existsSync(entry)) {
entries[dir] = entry
break
}
} }
} }
}
return entries return entries
}, {}), }, {}),
output: { output: {
path: path.join(__dirname, '__build__'), path: path.join(__dirname, '__build__'),
filename: '[name].js', filename: '[name].js',
chunkFilename: '[id].chunk.js', chunkFilename: '[id].chunk.js',
publicPath: '/__build__/' publicPath: '/__build__/'
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.tsx?$/, test: /\.tsx?$/,
use: 'ts-loader', use: 'ts-loader',
exclude: /node_modules/ exclude: /node_modules/
}, },
{ {
test: /\.js$/, test: /\.vue$/,
exclude: /node_modules/, use: 'vue-loader'
use: 'babel-loader' }
}, ]
{ },
test: /\.vue$/, resolve: {
use: 'vue-loader' extensions: ['.tsx', 'd.ts', '.ts', '.js', '.vue'],
alias: {
// this isn't technically needed, since the default `vue` entry for bundlers
// is a simple `export * = require(''@vue/runtime-dom`. However having this
// extra re-export somehow causes webpack to always invalidate the module
// on the first HMR update and causes the page to reload.
vue: 'vue/dist/vue.esm-bundler.js',
'vue-meta': path.resolve(__dirname, '../src/'),
...extraAliases
} }
] },
}, // Expose __dirname to allow automatically setting basename.
resolve: { context: __dirname,
extensions: ['.tsx', 'd.ts', '.ts', '.js', '.vue'], node: {
alias: { __dirname: true
// this isn't technically needed, since the default `vue` entry for bundlers },
// is a simple `export * from '@vue/runtime-dom`. However having this plugins: [
// extra re-export somehow causes webpack to always invalidate the module new WebpackBar(),
// on the first HMR update and causes the page to reload. new VueLoaderPlugin(),
vue: 'vue/dist/vue.esm-bundler.js', new webpack.DefinePlugin({
'vue-meta': path.resolve(__dirname, '../src/') 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
__DEV__: JSON.stringify(process.env.NODE_ENV !== 'production'),
__BROWSER__: JSON.stringify(true),
'process.client': JSON.stringify(true),
'process.server': JSON.stringify(false)
})
],
devServer: {
inline: true,
hot: true,
stats: 'minimal',
contentBase: __dirname,
overlay: true
} }
},
// Expose __dirname to allow automatically setting basename.
context: __dirname,
node: {
__dirname: true
},
plugins: [
new WebpackBar(),
new VueLoaderPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
__DEV__: JSON.stringify(process.env.NODE_ENV !== 'production'),
__VUE_OPTIONS_API__: JSON.stringify(true),
__VUE_PROD_DEVTOOLS__: JSON.stringify(true)
})
],
devServer: {
inline: true,
hot: true,
stats: 'minimal',
contentBase: __dirname,
overlay: true
} }
} }
+3 -1
View File
@@ -1,5 +1,6 @@
module.exports = { module.exports = {
testEnvironment: 'jest-environment-jsdom-global', testEnvironment: 'jest-environment-jsdom-global',
preset: 'ts-jest',
expand: true, expand: true,
@@ -41,6 +42,7 @@ module.exports = {
], ],
globals: { globals: {
__DEV__: true __DEV__: true,
__BROWSER__: true,
} }
} }
+30 -28
View File
@@ -2,13 +2,16 @@
"name": "vue-meta", "name": "vue-meta",
"version": "2.3.3", "version": "2.3.3",
"description": "Manage HTML metadata in Vue.js components with SSR support", "description": "Manage HTML metadata in Vue.js components with SSR support",
"main": "dist/vue-meta.common.js", "main": "dist/vue-meta.cjs.js",
"web": "dist/vue-meta.js", "unpkg": "dist/vue-meta.global.js",
"module": "dist/vue-meta.esm.js", "jsdelivr": "dist/vue-meta.global.js",
"typings": "types/index.d.ts", "module": "dist/vue-meta.esm-bundler.js",
"typings": "dist/vue-meta.d.ts",
"sideEffects": false,
"files": [ "files": [
"dist", "dist/*.js",
"types/*.d.ts" "types/*.d.ts",
"README.md"
], ],
"homepage": "https://github.com/nuxt/vue-meta", "homepage": "https://github.com/nuxt/vue-meta",
"bugs": "https://github.com/nuxt/vue-meta/issues", "bugs": "https://github.com/nuxt/vue-meta/issues",
@@ -30,12 +33,12 @@
], ],
"author": "Pim (@pimlie)", "author": "Pim (@pimlie)",
"scripts": { "scripts": {
"build": "rimraf dist && rollup -c scripts/rollup.config.js", "build": "yarn clean && rollup -c build/rollup.config.js",
"clean": "rimraf dist/*",
"coverage": "codecov", "coverage": "codecov",
"dev": "babel-node examples/server.js", "dev": "yarn clean && node examples/server.js",
"docs": "vuepress dev --host 0.0.0.0 --port 3000 docs", "docs": "vuepress dev --host 0.0.0.0 --port 3000 docs",
"docs:build": "vuepress build docs", "docs:build": "vuepress build docs",
"examples": "babel-node --extensions '.ts,.js' examples/server.js",
"lint": "eslint --ext .js,.ts src test examples", "lint": "eslint --ext .js,.ts src test examples",
"prerelease": "git checkout master && git pull -r", "prerelease": "git checkout master && git pull -r",
"release": "yarn lint && yarn test && standard-version", "release": "yarn lint && yarn test && standard-version",
@@ -43,21 +46,25 @@
"test:e2e-ssr": "jest test/e2e/ssr", "test:e2e-ssr": "jest test/e2e/ssr",
"test:e2e-browser": "jest test/e2e/browser", "test:e2e-browser": "jest test/e2e/browser",
"test:unit": "jest test/unit", "test:unit": "jest test/unit",
"test:types": "tsc -p types/test" "test:types": "tsc --build tsconfig.json"
}, },
"pperDependencies": { "peerDependencies": {
"vue": "next" "@vue/server-renderer": "^3.0.5",
"vue": "^3.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.12.10", "@babel/core": "^7.12.10",
"@babel/node": "^7.12.10", "@babel/plugin-transform-modules-commonjs": "^7.12.1",
"@babel/preset-env": "^7.12.11",
"@babel/preset-typescript": "^7.12.7", "@babel/preset-typescript": "^7.12.7",
"@nuxtjs/eslint-config-typescript": "^5.0.0", "@nuxtjs/eslint-config-typescript": "^5.0.0",
"@rollup/plugin-alias": "^3.1.1",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-node-resolve": "^11.1.0",
"@rollup/plugin-replace": "^2.3.4",
"@types/webpack": "^4.41.26", "@types/webpack": "^4.41.26",
"@types/webpack-env": "^1.16.0", "@types/webpack-env": "^1.16.0",
"@typescript-eslint/eslint-plugin": "^4.13.0", "@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.13.0", "@typescript-eslint/parser": "^4.14.0",
"@vue/compiler-sfc": "^3.0.5", "@vue/compiler-sfc": "^3.0.5",
"@vue/server-renderer": "^3.0.5", "@vue/server-renderer": "^3.0.5",
"@vue/server-test-utils": "^1.1.2", "@vue/server-test-utils": "^1.1.2",
@@ -69,7 +76,7 @@
"babel-plugin-global-define": "^1.0.3", "babel-plugin-global-define": "^1.0.3",
"babel-plugin-module-resolver": "^4.1.0", "babel-plugin-module-resolver": "^4.1.0",
"browserstack-local": "^1.4.8", "browserstack-local": "^1.4.8",
"chromedriver": "^87.0.5", "chromedriver": "^88.0.0",
"codecov": "^3.8.1", "codecov": "^3.8.1",
"consola": "^2.15.0", "consola": "^2.15.0",
"eslint": "^7.18.0", "eslint": "^7.18.0",
@@ -79,17 +86,13 @@
"jest": "^26.6.3", "jest": "^26.6.3",
"jest-environment-jsdom": "^26.6.2", "jest-environment-jsdom": "^26.6.2",
"jest-environment-jsdom-global": "^2.0.4", "jest-environment-jsdom-global": "^2.0.4",
"jiti": "^1.3.0",
"jsdom": "^16.4.0", "jsdom": "^16.4.0",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"node-env-file": "^0.1.8", "node-env-file": "^0.1.8",
"puppeteer-core": "^5.5.0", "puppeteer-core": "^5.5.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^2.36.2", "rollup": "^2.38.0",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.2.0",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.29.0", "rollup-plugin-typescript2": "^0.29.0",
"selenium-webdriver": "^4.0.0-alpha.8", "selenium-webdriver": "^4.0.0-alpha.8",
@@ -97,15 +100,14 @@
"tib": "^0.7.5", "tib": "^0.7.5",
"ts-jest": "^26.4.4", "ts-jest": "^26.4.4",
"ts-loader": "^8.0.14", "ts-loader": "^8.0.14",
"ts-node": "^9.1.1",
"typescript": "^4.1.3", "typescript": "^4.1.3",
"vue": "^3.0.0", "vue": "^3.0.5",
"vue-jest": "^3.0.7", "vue-jest": "^3.0.7",
"vue-loader": "^16.0.0", "vue-loader": "^16.0.0",
"vue-router": "next", "vue-router": "next",
"webpack": "^5.15.0", "webpack": "^5.17.0",
"webpack-bundle-analyzer": "^4.3.0", "webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.3.1", "webpack-cli": "^4.4.0",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^3.11.2",
"webpackbar": "^4.0.0" "webpackbar": "^4.0.0"
} }
-181
View File
@@ -1,181 +0,0 @@
import commonjs from 'rollup-plugin-commonjs'
import nodeResolve from 'rollup-plugin-node-resolve'
import json from 'rollup-plugin-json'
import babel from 'rollup-plugin-babel'
import replace from 'rollup-plugin-replace'
import { terser } from 'rollup-plugin-terser'
import defaultsDeep from 'lodash/defaultsDeep'
import { defaultOptions } from '../src/shared/constants'
const pkg = require('../package.json')
const version = pkg.version
const banner = `/**
* vue-meta v${version}
* (c) ${new Date().getFullYear()}
* - Declan de Wet
* - Sébastien Chopin (@Atinux)
* - Pim (@pimlie)
* - All the amazing contributors
* @license MIT
*/
`
const babelConfig = () => ({
presets: [
['@babel/preset-env', {
/*useBuiltIns: 'usage',
corejs: 2,*/
targets: {
node: 8,
ie: 9,
safari: '5.1'
}
}]
]
})
const internalObjectProperties = [
// Plugin options
// NOTE, see shared/options for why/how this is possible to do
...Object.keys(defaultOptions),
'refreshOnceOnNavigation',
// Runtime state props on $root._vueMeta
'appId',
'pausing',
'navGuards',
'initialized',
'initializing',
'deprecationWarningShown',
// updateClientMetaInfo return props
'tagsAdded',
'tagsRemoved',
// escapeOptions
'doEscape',
// deepmerge
'isMergeableObject',
'arrayMerge'
]
const terserOpts = {
nameCache: {},
mangle: {
properties: {
//debug: '___DEBUGGGG___',
// minimize all object properties except when they are quotes like obj['prop']
keep_quoted: "strict",
// and minimize props listed in internalObjectProperties
regex: new RegExp(`^(${internalObjectProperties.join('|')})$`)
}
}
}
function rollupConfig({
plugins = [],
...config
}) {
const isBrowserBuild = !config.output || !config.output.format || config.output.format === 'umd' || config.output.file.includes('.browser.')
const replaceConfig = {
exclude: 'node_modules/(?!is-mergeable-object)',
delimiters: ['', ''],
values: {
// replaceConfig needs to have some values
'const polyfill = process.env.NODE_ENV === \'test\'': 'const polyfill = true',
'process.env.VERSION': `"${version}"`,
'process.server' : isBrowserBuild ? 'false' : 'true',
/* remove unused stuff from deepmerge */
// remove react stuff from is-mergeable-object
'|| isReactElement(value)': '|| false',
// we always provide an arrayMerge, remove default
'|| defaultArrayMerge' : '',
// clone is a deprecated option we dont use
'options.clone ' : 'false ',
// we dont provide a custom merge
'options.customMerge)' : 'false)',
// we dont use this helper
'deepmerge.all = ' : 'false;',
// we dont use symbols on our objects
'.concat(getEnumerableOwnPropertySymbols(target))': ''
}
}
/* / keep simple polyfills when babel plugin is used for build
if (plugins && plugins.some(p => p.name === 'babel')) {
replaceConfig.values = {
'const polyfill = process.env.NODE_ENV === \'test\'': 'const polyfill = true',
}
}*/
return defaultsDeep({}, config, {
input: 'src/index.js',
output: {
name: 'VueMeta',
format: 'umd',
sourcemap: false,
banner
},
plugins: [
json(),
nodeResolve(),
replace(replaceConfig),
commonjs(),
babel(babelConfig()),
].concat(plugins),
})
}
export default [
// umd web build
{
output: {
file: pkg.web,
}
},
// minimized umd web build
{
output: {
file: pkg.web.replace('.js', '.min.js'),
},
plugins: [
terser(terserOpts)
]
},
// common js build
{
output: {
file: pkg.main,
format: 'cjs'
},
external: Object.keys(pkg.dependencies)
},
// esm build
{
output: {
file: pkg.web.replace('.js', '.esm.js'),
format: 'es'
},
external: Object.keys(pkg.dependencies)
},
// browser esm build
{
output: {
file: pkg.web.replace('.js', '.esm.browser.js'),
format: 'es'
},
external: Object.keys(pkg.dependencies)
},
// minimized browser esm build
{
output: {
file: pkg.web.replace('.js', '.esm.browser.min.js'),
format: 'es'
},
plugins: [
terser(terserOpts)
],
external: Object.keys(pkg.dependencies)
}
].map(rollupConfig)
-36
View File
@@ -1,36 +0,0 @@
import { readFileSync, writeFileSync } from 'fs'
import updateSection from 'update-section'
import { name, main, version } from '../package.json'
console.log(`Updating CDN info to latest v${version} release...`)
const readmePath = './README.md'
const cdnUrl = `https://unpkg.com/${name}@${version}/${main}`
const minifiedUrl = cdnUrl.replace('.js', '.min.js')
const content = readFileSync(readmePath, 'utf-8')
const update = `
<!-- start CDN generator - do **NOT** remove this comment -->
**Uncompressed:**
\`\`\`html
<script src="${cdnUrl}"></script>
\`\`\`
**Minified:**
\`\`\`html
<script src="${minifiedUrl}"></script>
\`\`\`
<!-- end CDN generator - do **NOT** remove this comment -->
`.trim().replace(/ {2}/gm, '')
const updated = updateSection(
content,
update,
(line) => (/<!-- start CDN generator/).test(line),
(line) => (/<!-- end CDN generator/).test(line)
)
writeFileSync(readmePath, updated)
console.log('CDN info updated.')
+3 -59
View File
@@ -1,78 +1,22 @@
import { h, defineComponent, Teleport, VNode, VNodeProps } from 'vue' import { defineComponent, VNodeProps } from 'vue'
import { isArray, isFunction } from '@vue/shared' import { getCurrentManager } from './useApi'
import { renderMeta } from './render'
import { useMetainfo, getCurrentManager } from './useApi'
import { MetainfoActive } from './types' import { MetainfoActive } from './types'
export interface MetainfoProps { export interface MetainfoProps {
metainfo: MetainfoActive metainfo: MetainfoActive
} }
export function addVnode (teleports: any, to: string, vnode: VNode | Array<VNode>) {
if (!teleports[to]) {
teleports[to] = []
}
if (isArray(vnode)) {
teleports[to].push(...vnode)
return
}
teleports[to].push(vnode)
}
export const MetainfoImpl = defineComponent({ export const MetainfoImpl = defineComponent({
name: 'Metainfo', name: 'Metainfo',
inheritAttrs: false, inheritAttrs: false,
setup (_, { slots }) { setup (_, { slots }) {
const metainfo = useMetainfo()
return () => { return () => {
const teleports: any = {}
const manager = getCurrentManager() const manager = getCurrentManager()
if (!manager) { if (!manager) {
return return
} }
for (const key in metainfo) { return manager.render({ slots })
const config = manager.config[key] || {}
const vnodes = renderMeta(
{ metainfo, slots },
key,
metainfo[key],
config
)
if (!vnodes) {
continue
}
const defaultTo =
(key !== 'base' && metainfo[key].to) || config.to || 'head'
if (isArray(vnodes)) {
for (const { to, vnode } of vnodes) {
addVnode(teleports, to || defaultTo, vnode)
}
continue
}
const { to, vnode } = vnodes
addVnode(teleports, to || defaultTo, vnode)
}
for (const tag of ['default', 'head', 'body']) {
const slotFn = slots[tag]
if (isFunction(slotFn)) {
addVnode(teleports, tag === 'default' ? 'head' : tag, slotFn({ metainfo }))
}
}
return Object.keys(teleports).map((to) => {
return h(Teleport, { to }, teleports[to])
})
} }
} }
}) })
+1
View File
@@ -1,2 +1,3 @@
// Global compile-time constants // Global compile-time constants
declare let __DEV__: boolean declare let __DEV__: boolean
declare let __BROWSER__: boolean
+10 -2
View File
@@ -1,5 +1,13 @@
import * as deepestResolver from './resolvers/deepest'
export { defaultConfig } from './config' export { defaultConfig } from './config'
export { createManager } from './manager' export { createMetaManager } from './manager'
export { resolveOption } from './resolvers' export { resolveOption } from './resolvers'
export * from './useApi'
export * from './ssr'
export * from './types' export * from './types'
export * from './useApi'
export {
deepestResolver
}
-17
View File
@@ -1,17 +0,0 @@
import { App } from 'vue'
import { Metainfo } from './Metainfo'
import { metaInfoKey } from './symbols'
import { Manager } from './manager'
declare module '@vue/runtime-core' {
interface ComponentInternalInstance {
$metaManager: Manager
}
}
export function applyMetaPlugin (app: App, manager: Manager, active: Object) {
app.component('Metainfo', Metainfo)
app.config.globalProperties.$metaManager = manager
app.provide(metaInfoKey, active)
}
+99 -13
View File
@@ -1,21 +1,46 @@
import { App, reactive, onUnmounted, ComponentInternalInstance } from 'vue' import { h, reactive, onUnmounted, Teleport, VNode, Comment } from 'vue'
import { isFunction } from '@vue/shared' import { isArray, isFunction } from '@vue/shared'
import { createMergedObject } from './object-merge' import { createMergedObject } from './object-merge'
import { applyMetaPlugin } from './install' import { renderMeta } from './render'
// import * as deepestResolver from './resolvers/deepest' import { metaInfoKey } from './symbols'
import { Config, Resolver, MetainfoInput, MetaContext, MetaProxy } from './types' import { Metainfo } from './Metainfo'
import type { ResolveMethod } from './object-merge' import type { ResolveMethod } from './object-merge'
import type { Manager, Config, Resolver, MetaContext, MetainfoActive } from './types'
export type Manager = { export const ssrAttribute = 'data-vm-ssr'
readonly config: Config
install(app: App): void export const active: MetainfoActive = reactive({})
addMeta(obj: MetainfoInput, vm?: ComponentInternalInstance): MetaProxy
export function addVnode (teleports: any, to: string, _vnodes: VNode | Array<VNode>) {
const vnodes = (isArray(_vnodes) ? _vnodes : [_vnodes]) as Array<VNode>
if (!__BROWSER__) {
// dont add ssrAttribute for attribute vnode placeholder
if (!to.endsWith('Attrs')) {
vnodes.forEach((vnode) => {
if (!vnode.props) {
vnode.props = {}
}
vnode.props[ssrAttribute] = true
})
}
} else {
// Comments shouldnt have any use on the client as they are not reactive anyway
vnodes.forEach((vnode, idx) => {
if (vnode.type === Comment) {
vnodes.splice(idx, 1)
}
})
}
if (!teleports[to]) {
teleports[to] = []
}
teleports[to].push(...vnodes)
} }
export const active = reactive({}) export function createMetaManager (config: Config, resolver: Resolver | ResolveMethod): Manager {
export function createManager (config: Config, resolver: Resolver | ResolveMethod): Manager {
const resolve: ResolveMethod = (options, contexts, active, key, pathSegments) => { const resolve: ResolveMethod = (options, contexts, active, key, pathSegments) => {
if (isFunction(resolver)) { if (isFunction(resolver)) {
return resolver(options, contexts, active, key, pathSegments) return resolver(options, contexts, active, key, pathSegments)
@@ -26,12 +51,17 @@ export function createManager (config: Config, resolver: Resolver | ResolveMetho
const { addSource, delSource } = createMergedObject(resolve, active) const { addSource, delSource } = createMergedObject(resolve, active)
let cleanedUpSsr = false
// TODO: validate resolver // TODO: validate resolver
const manager: Manager = { const manager: Manager = {
config, config,
install (app) { install (app) {
applyMetaPlugin(app, this, active) app.component('Metainfo', Metainfo)
app.config.globalProperties.$metaManager = manager
app.provide(metaInfoKey, active)
}, },
addMeta (metaObj, vm) { addMeta (metaObj, vm) {
@@ -52,6 +82,62 @@ export function createManager (config: Config, resolver: Resolver | ResolveMetho
meta, meta,
unmount unmount
} }
},
render ({ slots }: any = {}): Array<VNode> {
// cleanup ssr tags if not yet done
if (__BROWSER__ && !cleanedUpSsr) {
cleanedUpSsr = true
// Listen for DOM loaded because tags in the body couldnt be loaded
// yet once the manager does it first render
// (preferable there should only be one render on hydration)
window.addEventListener('DOMContentLoaded', () => {
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`)
if (ssrTags && ssrTags.length) {
Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el))
}
})
}
const teleports: { [key: string]: VNode | Array<VNode> } = {}
for (const key in active) {
const config = this.config[key] || {}
const vnode = renderMeta(
{ metainfo: active, slots },
key,
active[key],
config
)
if (!vnode) {
continue
}
const vnodes = isArray(vnode) ? vnode : [vnode]
const defaultTo = (key !== 'base' && active[key].to) || config.to || (config.attributesFor ? key : 'head')
for (const { to, vnode } of vnodes) {
addVnode(teleports, to || defaultTo, vnode)
}
}
if (slots) {
for (const tag in slots) {
const slotFn = slots[tag]
if (isFunction(slotFn)) {
addVnode(teleports, tag === 'default' ? 'head' : tag, slotFn({ metainfo: active }))
}
}
}
return Object.keys(teleports).map((to) => {
return h(Teleport, { to }, teleports[to])
})
} }
} }
+1
View File
@@ -6,6 +6,7 @@ export type MergeSource = {
[key: string]: any [key: string]: any
} }
// eslint-disable-next-line no-use-before-define
export type MergedObjectValue = boolean | number | string | MergedObject | any export type MergedObjectValue = boolean | number | string | MergedObject | any
export type MergedObject = { export type MergedObject = {
+5 -5
View File
@@ -51,7 +51,7 @@ export const createHandler: (context: MergeContext, resolveContext: ResolveConte
}, },
set: (target, key, value) => { set: (target, key, value) => {
const success = Reflect.set(target, key, value) const success = Reflect.set(target, key, value)
console.warn(success, 'PROXY SET\nkey:', key, '\npath:', pathSegments, '\ntarget:', isArray(target), target, '\ncontext:\n', context) // console.warn(success, 'PROXY SET\nkey:', key, '\npath:', pathSegments, '\ntarget:', isArray(target), target, '\ncontext:\n', context)
if (success) { if (success) {
const isArrayItem = isArray(target) const isArrayItem = isArray(target)
@@ -113,7 +113,7 @@ export const createHandler: (context: MergeContext, resolveContext: ResolveConte
resolved = clone(resolved) resolved = clone(resolved)
} }
console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active)) // console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
if (isArrayItem && activeSegmentKey) { if (isArrayItem && activeSegmentKey) {
active[activeSegmentKey] = resolved active[activeSegmentKey] = resolved
@@ -122,12 +122,12 @@ export const createHandler: (context: MergeContext, resolveContext: ResolveConte
} }
} }
console.log('CONTEXT.ACTIVE', context.active, '\nparent:\n', target) // console.log('CONTEXT.ACTIVE', context.active, '\nparent:\n', target)
return success return success
}, },
deleteProperty: (target, key) => { deleteProperty: (target, key) => {
const success = Reflect.deleteProperty(target, key) const success = Reflect.deleteProperty(target, key)
console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target) // console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
if (success) { if (success) {
const isArrayItem = isArray(target) const isArrayItem = isArray(target)
@@ -174,7 +174,7 @@ export const createHandler: (context: MergeContext, resolveContext: ResolveConte
resolved = clone(resolved) resolved = clone(resolved)
} }
console.log('SET VALUE', resolved) // console.log('SET VALUE', resolved)
if (isArrayItem && activeSegmentKey) { if (isArrayItem && activeSegmentKey) {
active[activeSegmentKey] = resolved active[activeSegmentKey] = resolved
} else { } else {
+26 -8
View File
@@ -233,19 +233,37 @@ export function renderAttributes (
key: string, key: string,
data: TODO, data: TODO,
config: TODO = {} config: TODO = {}
): void { ): RenderedMetainfoNode | void {
// console.info('renderAttributes', key, data, config) // console.info('renderAttributes', key, data, config)
const { attributesFor } = config const { attributesFor } = config
if (!cachedElements[attributesFor]) { if (!__BROWSER__) {
const el = document.querySelector(attributesFor) // render attributes in a placeholder vnode so Vue
// will render the string for us
return {
to: '',
vnode: h(`ssr-${attributesFor}`, data)
}
}
if (el) { if (!cachedElements[attributesFor]) {
cachedElements[attributesFor] = { const [el, el2] = Array.from(document.querySelectorAll(attributesFor))
el,
attrs: [] if (__DEV__ && !el) {
} // eslint-disable-next-line no-console
console.error('Could not find element with selector', attributesFor, ', won\'t render attributes')
return
}
if (__DEV__ && el2) {
// eslint-disable-next-line no-console
console.warn('Found multiple elements with selector', attributesFor)
}
cachedElements[attributesFor] = {
el,
attrs: []
} }
} }
+29
View File
@@ -0,0 +1,29 @@
import type { App } from 'vue'
import type { SSRContext } from '@vue/server-renderer'
// rollup doesnt like an import, cant find export so use require
const { renderToString } = require('@vue/server-renderer')
export async function renderToStringWithMeta (app: App): Promise<[string, SSRContext]> {
const ctx: SSRContext = {}
const html = await renderToString(app, ctx)
// TODO: better way of determining whether meta was rendered with the component or not
if (!ctx.teleports || !ctx.teleports.head) {
const teleports = app.config.globalProperties.$metaManager.render()
await Promise.all(teleports.map((teleport: any) => renderToString(teleport, ctx)))
}
const { teleports } = ctx
for (const target in teleports) {
if (target.endsWith('Attrs')) {
const str = teleports[target]
// match from first space to first >, these should be all rendered attributes
teleports[target] = str.slice(str.indexOf(' ') + 1, str.indexOf('>'))
}
}
return [html, ctx]
}
+25 -5
View File
@@ -1,10 +1,6 @@
import { ComponentInternalInstance } from 'vue' import type { App, VNode, ComponentInternalInstance } from 'vue'
import type { MergedObject, ResolveContext, ResolveMethod } from '../object-merge' import type { MergedObject, ResolveContext, ResolveMethod } from '../object-merge'
export type Immutable<T> = {
readonly [P in keyof T]: Immutable<T[P]>
}
export type TODO = any export type TODO = any
export type MetainfoInput = { export type MetainfoInput = {
@@ -50,3 +46,27 @@ export type Resolver = {
setup?: ResolveSetup setup?: ResolveSetup
resolve: ResolveMethod resolve: ResolveMethod
} }
export type Manager = {
readonly config: Config
install(app: App): void
addMeta(obj: MetainfoInput, vm?: ComponentInternalInstance): MetaProxy
render(ctx: { slots?: any }): Array<VNode>
}
declare module '@vue/runtime-core' {
interface ComponentInternalInstance {
$metaManager: Manager
}
}
declare global {
namespace NodeJS {
interface Process {
client: boolean
server: boolean
}
}
}
+1 -2
View File
@@ -1,7 +1,6 @@
import { inject, getCurrentInstance, ComponentInternalInstance } from 'vue' import { inject, getCurrentInstance, ComponentInternalInstance } from 'vue'
import { Manager } from './manager'
import { metaInfoKey } from './symbols' import { metaInfoKey } from './symbols'
import { MetainfoActive, MetainfoInput, MetaProxy } from './types' import type { Manager, MetainfoActive, MetainfoInput, MetaProxy } from './types'
export function getCurrentManager (vm?: ComponentInternalInstance): Manager { export function getCurrentManager (vm?: ComponentInternalInstance): Manager {
if (!vm) { if (!vm) {
+2 -2
View File
@@ -478,10 +478,10 @@ describe('render', () => {
const setAttribute = jest.fn() const setAttribute = jest.fn()
const removeAttribute = jest.fn() const removeAttribute = jest.fn()
const doc = jest.spyOn(document, 'querySelector').mockReturnValue({ const doc = jest.spyOn(document, 'querySelectorAll').mockReturnValue([{
setAttribute, setAttribute,
removeAttribute removeAttribute
}) }])
const context = {} const context = {}
+1 -1
View File
@@ -30,6 +30,6 @@
"include": [ "include": [
"src/global.d.ts", "src/global.d.ts",
"src/**/*.ts", "src/**/*.ts",
"__tests__/**/*.ts" "test/**/*.ts"
], ],
} }
+231 -949
View File
File diff suppressed because it is too large Load Diff