mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-08 15:42:25 +03:00
feat: make ssr work
feat: update build script chore: use jiti instead of babel-node for examples
This commit is contained in:
@@ -3,7 +3,8 @@
|
||||
"@nuxtjs/eslint-config-typescript"
|
||||
],
|
||||
"globals": {
|
||||
"__DEV__": true
|
||||
"__DEV__": true,
|
||||
"__BROWSER__": false,
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
|
||||
+1
-4
@@ -1,9 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2019
|
||||
- Declan de Wet
|
||||
- Sébastien Chopin
|
||||
- All the amazing contributors (https://github.com/nuxt/vue-meta/graphs/contributors)
|
||||
Copyright (c) 2021 - Pim (@pimlie)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -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'
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -1,3 +1,8 @@
|
||||
if (!window.users) {
|
||||
window.users = []
|
||||
console.warn('window.users was not set')
|
||||
}
|
||||
|
||||
window.users.push({
|
||||
id: 1,
|
||||
name: 'Leanne Graham',
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
if (!window.users) {
|
||||
window.users = []
|
||||
console.warn('window.users was not set')
|
||||
}
|
||||
|
||||
window.users.push({
|
||||
id: 2,
|
||||
name: 'Ervin Howell',
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
if (!window.users) {
|
||||
window.users = []
|
||||
console.warn('window.users was not set')
|
||||
}
|
||||
|
||||
window.users.push({
|
||||
id: 3,
|
||||
name: 'Clementine Bauch',
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
if (!window.users) {
|
||||
window.users = []
|
||||
console.warn('window.users was not set')
|
||||
}
|
||||
|
||||
window.users.push({
|
||||
id: 4,
|
||||
name: 'Patricia Lebsack',
|
||||
|
||||
@@ -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*$/, '')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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
@@ -1,17 +1,20 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import consola from 'consola'
|
||||
import express from 'express'
|
||||
import rewrite from 'express-urlrewrite'
|
||||
import webpack from 'webpack'
|
||||
import webpackDevMiddleware from 'webpack-dev-middleware'
|
||||
import WebpackConfig from './webpack.config'
|
||||
import { renderPage } from './ssr/server'
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const consola = require('consola')
|
||||
const express = require('express')
|
||||
const rewrite = require('express-urlrewrite')
|
||||
const webpack = require('webpack')
|
||||
const webpackDevMiddleware = require('webpack-dev-middleware')
|
||||
const webpackConfig = require('./webpack.config')
|
||||
const jiti = require('./jiti')
|
||||
|
||||
const { renderPage } = jiti('./ssr/server.js')
|
||||
// const { renderPage } = require('./ssr/server')
|
||||
|
||||
const app = express()
|
||||
|
||||
app.use(
|
||||
webpackDevMiddleware(webpack(WebpackConfig), {
|
||||
webpackDevMiddleware(webpack(webpackConfig(true)), {
|
||||
publicPath: '/__build__/',
|
||||
writeToDisk: true,
|
||||
stats: {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<!doctype html>
|
||||
<html {{ htmlAttrs.text(true) }}>
|
||||
<head {{ headAttrs.text() }}>
|
||||
{{ head(true) }}
|
||||
<html {{ htmlAttrs }}>
|
||||
<head {{ headAttrs }}>
|
||||
{{ head }}
|
||||
<link rel="stylesheet" href="/global.css">
|
||||
</head>
|
||||
<body {{ bodyAttrs.text() }}>
|
||||
{{ bodyPrepend(true) }}
|
||||
<body {{ bodyAttrs }}>
|
||||
<body-prepend id="body-prepend">{{ bodyPrepend }}</body-prepend>
|
||||
|
||||
<a href="/">← Examples index</a>
|
||||
{{ app }}
|
||||
|
||||
<script src="/__build__/ssr.js"></script>
|
||||
{{ bodyAppend(true) }}
|
||||
{{ bodyAppend }}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+15
-24
@@ -1,8 +1,9 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import { createSSRApp } from 'vue'
|
||||
import { renderToStringWithMeta } from 'vue-meta'
|
||||
|
||||
import template from 'lodash/template'
|
||||
import { renderToString } from '@vue/server-renderer'
|
||||
import { App, createRouter, metaManager } from '../vue-router/main'
|
||||
|
||||
const templateFile = path.resolve(__dirname, 'app.template.html')
|
||||
@@ -15,41 +16,31 @@ process.server = true
|
||||
|
||||
export async function renderPage ({ url }) {
|
||||
console.log('renderPage', url)
|
||||
|
||||
const app = createSSRApp(App)
|
||||
const router = createRouter('/ssr', true)
|
||||
|
||||
app.use(router)
|
||||
// app.use(metaManager)
|
||||
app.use(metaManager)
|
||||
|
||||
console.log('renderPage', 'push')
|
||||
await router.push(url.substr(4))
|
||||
|
||||
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({
|
||||
app: appHtml,
|
||||
htmlAttrs: {
|
||||
text: () => {}
|
||||
},
|
||||
headAttrs: {
|
||||
text: () => {}
|
||||
},
|
||||
bodyAttrs: {
|
||||
text: () => {}
|
||||
},
|
||||
head: () => {},
|
||||
bodyPrepend: () => {},
|
||||
bodyAppend: () => {}
|
||||
// ...app.$meta().inject()
|
||||
htmlAttrs: ctx.teleports.htmlAttrs || '',
|
||||
headAttrs: ctx.teleports.headAttrs || '',
|
||||
bodyAttrs: ctx.teleports.bodyAttrs || '',
|
||||
head: ctx.teleports.head || '',
|
||||
bodyPrepend: ctx.teleports['body-prepend'] || '',
|
||||
bodyAppend: ctx.teleports.body || ''
|
||||
})
|
||||
|
||||
return pageHtml
|
||||
|
||||
+14
-11
@@ -29,7 +29,7 @@ export default {
|
||||
{ tag: 'link', rel: 'stylesheet', href: 'style2.css' }
|
||||
]
|
||||
},
|
||||
body: 'body-script1.js',
|
||||
body: 'body-script1.js', // TODO: fix
|
||||
htmlAttrs: {
|
||||
amp: true,
|
||||
lang: ['en', 'nl']
|
||||
@@ -44,7 +44,7 @@ export default {
|
||||
// TODO { content: 'window.a = "<br/>"; </script><script>alert(\'asdasd\');' },
|
||||
// TODO { rawContent: 'window.b = "<br/>"; </script><script> alert(\'123321\');' },
|
||||
{ src: 'body-script2.js', to: 'body' },
|
||||
{ src: 'body-script3.js', to: '#put-it-here' }
|
||||
{ src: 'body-script3.js', to: 'body-prepend' }
|
||||
]
|
||||
/* esi: {
|
||||
children: [
|
||||
@@ -115,20 +115,15 @@ export default {
|
||||
}
|
||||
}
|
||||
setTimeout(() => walk(metadata), 1000) */
|
||||
|
||||
return {
|
||||
metadata
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<metainfo>
|
||||
<template v-slot:base="{ content, metainfo }">http://nuxt.dev:3000{{ content }}</template>
|
||||
/*
|
||||
<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:og(title)="{ content, metainfo, og }">
|
||||
{{ content }} - {{ og.description }} - {{ metainfo.description }} - Hello Again
|
||||
</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" src="user-1.js"></component>
|
||||
<component is="script" src="user-2.js"></component>
|
||||
@@ -147,6 +142,14 @@ export default {
|
||||
<template v-slot:body>
|
||||
<component is="script" src="user-4.js"></component>
|
||||
</template>
|
||||
*/
|
||||
return {
|
||||
metadata
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<metainfo>
|
||||
<template v-slot:body><br/></template>
|
||||
</metainfo>
|
||||
|
||||
<div id="app">
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body-prepend id="body-prepend"></body-prepend>
|
||||
<a href="/">← Examples index</a>
|
||||
<div id="app"></div>
|
||||
<div id="put-it-here"></div>
|
||||
<script src="/__build__/vue-router.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { h } from 'vue'
|
||||
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 ChildComponent from './Child'
|
||||
|
||||
@@ -20,7 +20,7 @@ const decisionMaker5000000 = resolveOption((prevValue, context) => {
|
||||
}
|
||||
})
|
||||
|
||||
const metaManager = createManager({
|
||||
const metaManager = createMetaManager({
|
||||
...defaultConfig,
|
||||
esi: {
|
||||
group: true,
|
||||
|
||||
+81
-77
@@ -1,89 +1,93 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import webpack from 'webpack'
|
||||
import WebpackBar from 'webpackbar'
|
||||
import { VueLoaderPlugin } from 'vue-loader'
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const webpack = require('webpack')
|
||||
const WebpackBar = require('webpackbar')
|
||||
const { VueLoaderPlugin } = require('vue-loader')
|
||||
|
||||
// const srcDir = path.join(__dirname, '..', 'src')
|
||||
|
||||
export default {
|
||||
devtool: 'inline-source-map',
|
||||
mode: 'development',
|
||||
entry: fs.readdirSync(__dirname)
|
||||
.reduce((entries, dir) => {
|
||||
const fullDir = path.join(__dirname, dir)
|
||||
module.exports = (isBrowser) => {
|
||||
const extraAliases = {}
|
||||
if (isBrowser) {
|
||||
extraAliases['./ssr$'] = path.resolve(__dirname, '../build/stub.js')
|
||||
}
|
||||
|
||||
if (dir === 'ssr') {
|
||||
entries[dir] = path.join(fullDir, 'browser.js')
|
||||
} else if (dir === 'vue-router') {
|
||||
const possibleEntries = ['browser', 'app']
|
||||
for (const entryName of possibleEntries) {
|
||||
const entry = path.join(fullDir, entryName + '.js')
|
||||
return {
|
||||
devtool: 'inline-source-map',
|
||||
mode: 'development',
|
||||
entry: fs.readdirSync(__dirname)
|
||||
.reduce((entries, dir) => {
|
||||
const fullDir = path.join(__dirname, dir)
|
||||
|
||||
if (fs.statSync(fullDir).isDirectory() && fs.existsSync(entry)) {
|
||||
entries[dir] = entry
|
||||
break
|
||||
if (dir === 'ssr') {
|
||||
entries[dir] = path.join(fullDir, 'browser.js')
|
||||
} 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
|
||||
}, {}),
|
||||
output: {
|
||||
path: path.join(__dirname, '__build__'),
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[id].chunk.js',
|
||||
publicPath: '/__build__/'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
use: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: 'vue-loader'
|
||||
return entries
|
||||
}, {}),
|
||||
output: {
|
||||
path: path.join(__dirname, '__build__'),
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[id].chunk.js',
|
||||
publicPath: '/__build__/'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
use: 'vue-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
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
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', 'd.ts', '.ts', '.js', '.vue'],
|
||||
alias: {
|
||||
// this isn't technically needed, since the default `vue` entry for bundlers
|
||||
// is a simple `export * from '@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/')
|
||||
},
|
||||
// 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'),
|
||||
__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
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'jest-environment-jsdom-global',
|
||||
preset: 'ts-jest',
|
||||
|
||||
expand: true,
|
||||
|
||||
@@ -41,6 +42,7 @@ module.exports = {
|
||||
],
|
||||
|
||||
globals: {
|
||||
__DEV__: true
|
||||
__DEV__: true,
|
||||
__BROWSER__: true,
|
||||
}
|
||||
}
|
||||
|
||||
+30
-28
@@ -2,13 +2,16 @@
|
||||
"name": "vue-meta",
|
||||
"version": "2.3.3",
|
||||
"description": "Manage HTML metadata in Vue.js components with SSR support",
|
||||
"main": "dist/vue-meta.common.js",
|
||||
"web": "dist/vue-meta.js",
|
||||
"module": "dist/vue-meta.esm.js",
|
||||
"typings": "types/index.d.ts",
|
||||
"main": "dist/vue-meta.cjs.js",
|
||||
"unpkg": "dist/vue-meta.global.js",
|
||||
"jsdelivr": "dist/vue-meta.global.js",
|
||||
"module": "dist/vue-meta.esm-bundler.js",
|
||||
"typings": "dist/vue-meta.d.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist",
|
||||
"types/*.d.ts"
|
||||
"dist/*.js",
|
||||
"types/*.d.ts",
|
||||
"README.md"
|
||||
],
|
||||
"homepage": "https://github.com/nuxt/vue-meta",
|
||||
"bugs": "https://github.com/nuxt/vue-meta/issues",
|
||||
@@ -30,12 +33,12 @@
|
||||
],
|
||||
"author": "Pim (@pimlie)",
|
||||
"scripts": {
|
||||
"build": "rimraf dist && rollup -c scripts/rollup.config.js",
|
||||
"build": "yarn clean && rollup -c build/rollup.config.js",
|
||||
"clean": "rimraf dist/*",
|
||||
"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:build": "vuepress build docs",
|
||||
"examples": "babel-node --extensions '.ts,.js' examples/server.js",
|
||||
"lint": "eslint --ext .js,.ts src test examples",
|
||||
"prerelease": "git checkout master && git pull -r",
|
||||
"release": "yarn lint && yarn test && standard-version",
|
||||
@@ -43,21 +46,25 @@
|
||||
"test:e2e-ssr": "jest test/e2e/ssr",
|
||||
"test:e2e-browser": "jest test/e2e/browser",
|
||||
"test:unit": "jest test/unit",
|
||||
"test:types": "tsc -p types/test"
|
||||
"test:types": "tsc --build tsconfig.json"
|
||||
},
|
||||
"pperDependencies": {
|
||||
"vue": "next"
|
||||
"peerDependencies": {
|
||||
"@vue/server-renderer": "^3.0.5",
|
||||
"vue": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/node": "^7.12.10",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.12.1",
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"@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-env": "^1.16.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||
"@typescript-eslint/parser": "^4.13.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
||||
"@typescript-eslint/parser": "^4.14.0",
|
||||
"@vue/compiler-sfc": "^3.0.5",
|
||||
"@vue/server-renderer": "^3.0.5",
|
||||
"@vue/server-test-utils": "^1.1.2",
|
||||
@@ -69,7 +76,7 @@
|
||||
"babel-plugin-global-define": "^1.0.3",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"browserstack-local": "^1.4.8",
|
||||
"chromedriver": "^87.0.5",
|
||||
"chromedriver": "^88.0.0",
|
||||
"codecov": "^3.8.1",
|
||||
"consola": "^2.15.0",
|
||||
"eslint": "^7.18.0",
|
||||
@@ -79,17 +86,13 @@
|
||||
"jest": "^26.6.3",
|
||||
"jest-environment-jsdom": "^26.6.2",
|
||||
"jest-environment-jsdom-global": "^2.0.4",
|
||||
"jiti": "^1.3.0",
|
||||
"jsdom": "^16.4.0",
|
||||
"lodash": "^4.17.20",
|
||||
"node-env-file": "^0.1.8",
|
||||
"puppeteer-core": "^5.5.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.36.2",
|
||||
"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": "^2.38.0",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"rollup-plugin-typescript2": "^0.29.0",
|
||||
"selenium-webdriver": "^4.0.0-alpha.8",
|
||||
@@ -97,15 +100,14 @@
|
||||
"tib": "^0.7.5",
|
||||
"ts-jest": "^26.4.4",
|
||||
"ts-loader": "^8.0.14",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.1.3",
|
||||
"vue": "^3.0.0",
|
||||
"vue": "^3.0.5",
|
||||
"vue-jest": "^3.0.7",
|
||||
"vue-loader": "^16.0.0",
|
||||
"vue-router": "next",
|
||||
"webpack": "^5.15.0",
|
||||
"webpack-bundle-analyzer": "^4.3.0",
|
||||
"webpack-cli": "^4.3.1",
|
||||
"webpack": "^5.17.0",
|
||||
"webpack-bundle-analyzer": "^4.4.0",
|
||||
"webpack-cli": "^4.4.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpackbar": "^4.0.0"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -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
@@ -1,78 +1,22 @@
|
||||
import { h, defineComponent, Teleport, VNode, VNodeProps } from 'vue'
|
||||
import { isArray, isFunction } from '@vue/shared'
|
||||
import { renderMeta } from './render'
|
||||
import { useMetainfo, getCurrentManager } from './useApi'
|
||||
import { defineComponent, VNodeProps } from 'vue'
|
||||
import { getCurrentManager } from './useApi'
|
||||
import { MetainfoActive } from './types'
|
||||
|
||||
export interface MetainfoProps {
|
||||
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({
|
||||
name: 'Metainfo',
|
||||
inheritAttrs: false,
|
||||
setup (_, { slots }) {
|
||||
const metainfo = useMetainfo()
|
||||
|
||||
return () => {
|
||||
const teleports: any = {}
|
||||
|
||||
const manager = getCurrentManager()
|
||||
if (!manager) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const key in metainfo) {
|
||||
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])
|
||||
})
|
||||
return manager.render({ slots })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Vendored
+1
@@ -1,2 +1,3 @@
|
||||
// Global compile-time constants
|
||||
declare let __DEV__: boolean
|
||||
declare let __BROWSER__: boolean
|
||||
|
||||
+10
-2
@@ -1,5 +1,13 @@
|
||||
import * as deepestResolver from './resolvers/deepest'
|
||||
|
||||
export { defaultConfig } from './config'
|
||||
export { createManager } from './manager'
|
||||
export { createMetaManager } from './manager'
|
||||
export { resolveOption } from './resolvers'
|
||||
export * from './useApi'
|
||||
|
||||
export * from './ssr'
|
||||
export * from './types'
|
||||
export * from './useApi'
|
||||
|
||||
export {
|
||||
deepestResolver
|
||||
}
|
||||
|
||||
@@ -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
@@ -1,21 +1,46 @@
|
||||
import { App, reactive, onUnmounted, ComponentInternalInstance } from 'vue'
|
||||
import { isFunction } from '@vue/shared'
|
||||
import { h, reactive, onUnmounted, Teleport, VNode, Comment } from 'vue'
|
||||
import { isArray, isFunction } from '@vue/shared'
|
||||
import { createMergedObject } from './object-merge'
|
||||
import { applyMetaPlugin } from './install'
|
||||
// import * as deepestResolver from './resolvers/deepest'
|
||||
import { Config, Resolver, MetainfoInput, MetaContext, MetaProxy } from './types'
|
||||
import { renderMeta } from './render'
|
||||
import { metaInfoKey } from './symbols'
|
||||
import { Metainfo } from './Metainfo'
|
||||
import type { ResolveMethod } from './object-merge'
|
||||
import type { Manager, Config, Resolver, MetaContext, MetainfoActive } from './types'
|
||||
|
||||
export type Manager = {
|
||||
readonly config: Config
|
||||
export const ssrAttribute = 'data-vm-ssr'
|
||||
|
||||
install(app: App): void
|
||||
addMeta(obj: MetainfoInput, vm?: ComponentInternalInstance): MetaProxy
|
||||
export const active: MetainfoActive = reactive({})
|
||||
|
||||
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 createManager (config: Config, resolver: Resolver | ResolveMethod): Manager {
|
||||
export function createMetaManager (config: Config, resolver: Resolver | ResolveMethod): Manager {
|
||||
const resolve: ResolveMethod = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
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)
|
||||
|
||||
let cleanedUpSsr = false
|
||||
|
||||
// TODO: validate resolver
|
||||
const manager: Manager = {
|
||||
config,
|
||||
|
||||
install (app) {
|
||||
applyMetaPlugin(app, this, active)
|
||||
app.component('Metainfo', Metainfo)
|
||||
|
||||
app.config.globalProperties.$metaManager = manager
|
||||
app.provide(metaInfoKey, active)
|
||||
},
|
||||
|
||||
addMeta (metaObj, vm) {
|
||||
@@ -52,6 +82,62 @@ export function createManager (config: Config, resolver: Resolver | ResolveMetho
|
||||
meta,
|
||||
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])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ export type MergeSource = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
export type MergedObjectValue = boolean | number | string | MergedObject | any
|
||||
|
||||
export type MergedObject = {
|
||||
|
||||
@@ -51,7 +51,7 @@ export const createHandler: (context: MergeContext, resolveContext: ResolveConte
|
||||
},
|
||||
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) {
|
||||
const isArrayItem = isArray(target)
|
||||
@@ -113,7 +113,7 @@ export const createHandler: (context: MergeContext, resolveContext: ResolveConte
|
||||
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) {
|
||||
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
|
||||
},
|
||||
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) {
|
||||
const isArrayItem = isArray(target)
|
||||
@@ -174,7 +174,7 @@ export const createHandler: (context: MergeContext, resolveContext: ResolveConte
|
||||
resolved = clone(resolved)
|
||||
}
|
||||
|
||||
console.log('SET VALUE', resolved)
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved
|
||||
} else {
|
||||
|
||||
+26
-8
@@ -233,19 +233,37 @@ export function renderAttributes (
|
||||
key: string,
|
||||
data: TODO,
|
||||
config: TODO = {}
|
||||
): void {
|
||||
): RenderedMetainfoNode | void {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
|
||||
const { attributesFor } = config
|
||||
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const el = document.querySelector(attributesFor)
|
||||
if (!__BROWSER__) {
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
to: '',
|
||||
vnode: h(`ssr-${attributesFor}`, data)
|
||||
}
|
||||
}
|
||||
|
||||
if (el) {
|
||||
cachedElements[attributesFor] = {
|
||||
el,
|
||||
attrs: []
|
||||
}
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor))
|
||||
|
||||
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
@@ -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
@@ -1,10 +1,6 @@
|
||||
import { ComponentInternalInstance } from 'vue'
|
||||
import type { App, VNode, ComponentInternalInstance } from 'vue'
|
||||
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 MetainfoInput = {
|
||||
@@ -50,3 +46,27 @@ export type Resolver = {
|
||||
setup?: ResolveSetup
|
||||
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
@@ -1,7 +1,6 @@
|
||||
import { inject, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||
import { Manager } from './manager'
|
||||
import { metaInfoKey } from './symbols'
|
||||
import { MetainfoActive, MetainfoInput, MetaProxy } from './types'
|
||||
import type { Manager, MetainfoActive, MetainfoInput, MetaProxy } from './types'
|
||||
|
||||
export function getCurrentManager (vm?: ComponentInternalInstance): Manager {
|
||||
if (!vm) {
|
||||
|
||||
@@ -478,10 +478,10 @@ describe('render', () => {
|
||||
const setAttribute = jest.fn()
|
||||
const removeAttribute = jest.fn()
|
||||
|
||||
const doc = jest.spyOn(document, 'querySelector').mockReturnValue({
|
||||
const doc = jest.spyOn(document, 'querySelectorAll').mockReturnValue([{
|
||||
setAttribute,
|
||||
removeAttribute
|
||||
})
|
||||
}])
|
||||
|
||||
const context = {}
|
||||
|
||||
|
||||
+1
-1
@@ -30,6 +30,6 @@
|
||||
"include": [
|
||||
"src/global.d.ts",
|
||||
"src/**/*.ts",
|
||||
"__tests__/**/*.ts"
|
||||
"test/**/*.ts"
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user