mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-24 02:40:33 +03:00
feat: add possibility to add additional meta info
refactor: server injectors feat: add head, bodyPrepend, bodyAppend injectors refactor: create browserbuild through rollup replace (not separate entry)
This commit is contained in:
@@ -138,5 +138,10 @@ export default function createApp () {
|
|||||||
</div>`
|
</div>`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { set } = app.$meta().addApp('custom')
|
||||||
|
set({
|
||||||
|
meta: [{ charset: 'utf-8' }]
|
||||||
|
})
|
||||||
|
|
||||||
return { app, router }
|
return { app, router }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html {{ htmlAttrs.text(true) }}>
|
<html {{ htmlAttrs.text(true) }}>
|
||||||
<head {{ headAttrs.text() }}>
|
<head {{ headAttrs.text() }}>
|
||||||
{{ title.text() }}
|
{{ head(true) }}
|
||||||
{{ meta.text() }}
|
|
||||||
<link rel="stylesheet" href="/global.css">
|
<link rel="stylesheet" href="/global.css">
|
||||||
{{ link.text() }}
|
|
||||||
{{ style.text() }}
|
|
||||||
{{ script.text() }}
|
|
||||||
{{ noscript.text() }}
|
|
||||||
</head>
|
</head>
|
||||||
<body {{ bodyAttrs.text() }}>
|
<body {{ bodyAttrs.text() }}>
|
||||||
{{ script.text({ pbody: true }) }}
|
{{ bodyPrepend(true) }}
|
||||||
{{ noscript.text({ pbody: true }) }}
|
|
||||||
|
|
||||||
<a href="/">← Examples index</a>
|
<a href="/">← Examples index</a>
|
||||||
{{ app }}
|
{{ app }}
|
||||||
|
|
||||||
<script src="/__build__/ssr.js"></script>
|
<script src="/__build__/ssr.js"></script>
|
||||||
{{ script.text({ body: true }) }}
|
{{ bodyAppend(true) }}
|
||||||
{{ noscript.text({ body: true }) }}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -64,13 +64,6 @@ const router = new Router({
|
|||||||
|
|
||||||
const App = {
|
const App = {
|
||||||
router,
|
router,
|
||||||
metaInfo () {
|
|
||||||
return {
|
|
||||||
meta: [
|
|
||||||
{ charset: 'utf=8' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `
|
template: `
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>vue-router</h1>
|
<h1>vue-router</h1>
|
||||||
@@ -86,7 +79,17 @@ const App = {
|
|||||||
|
|
||||||
const app = new Vue(App)
|
const app = new Vue(App)
|
||||||
|
|
||||||
|
const { set, remove } = app.$meta().addApp('custom')
|
||||||
|
|
||||||
|
set({
|
||||||
|
meta: [
|
||||||
|
{ charset: 'utf=8' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
setTimeout(() => remove(), 3000)
|
||||||
|
|
||||||
app.$mount('#app')
|
app.$mount('#app')
|
||||||
|
|
||||||
/*
|
/*
|
||||||
const waitFor = time => new Promise(r => setTimeout(r, time || 1000))
|
const waitFor = time => new Promise(r => setTimeout(r, time || 1000))
|
||||||
const o = {
|
const o = {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ const banner = `/**
|
|||||||
* (c) ${new Date().getFullYear()}
|
* (c) ${new Date().getFullYear()}
|
||||||
* - Declan de Wet
|
* - Declan de Wet
|
||||||
* - Sébastien Chopin (@Atinux)
|
* - Sébastien Chopin (@Atinux)
|
||||||
* - All the amazing contributors
|
* - Pim (@pimlie)
|
||||||
|
* - All the amazing contributors
|
||||||
* @license MIT
|
* @license MIT
|
||||||
*/
|
*/
|
||||||
`
|
`
|
||||||
@@ -39,13 +40,16 @@ function rollupConfig({
|
|||||||
...config
|
...config
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
|
const isBrowserBuild = !config.output || !config.output.format || config.output.format === 'umd' || config.output.file.includes('.browser.')
|
||||||
|
|
||||||
const replaceConfig = {
|
const replaceConfig = {
|
||||||
exclude: 'node_modules/**',
|
exclude: 'node_modules/**',
|
||||||
delimiters: ['', ''],
|
delimiters: ['', ''],
|
||||||
values: {
|
values: {
|
||||||
// replaceConfig needs to have some values
|
// replaceConfig needs to have some values
|
||||||
'const polyfill = process.env.NODE_ENV === \'test\'': 'const polyfill = true',
|
'const polyfill = process.env.NODE_ENV === \'test\'': 'const polyfill = true',
|
||||||
'process.env.VERSION': `"${version}"`
|
'process.env.VERSION': `"${version}"`,
|
||||||
|
'process.server' : isBrowserBuild ? 'false' : 'true'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +61,7 @@ function rollupConfig({
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
return defaultsDeep({}, config, {
|
return defaultsDeep({}, config, {
|
||||||
input: 'src/browser.js',
|
input: 'src/index.js',
|
||||||
output: {
|
output: {
|
||||||
name: 'VueMeta',
|
name: 'VueMeta',
|
||||||
format: 'umd',
|
format: 'umd',
|
||||||
@@ -92,7 +96,6 @@ export default [
|
|||||||
},
|
},
|
||||||
// common js build
|
// common js build
|
||||||
{
|
{
|
||||||
input: 'src/index.js',
|
|
||||||
output: {
|
output: {
|
||||||
file: pkg.main,
|
file: pkg.main,
|
||||||
format: 'cjs'
|
format: 'cjs'
|
||||||
@@ -101,7 +104,6 @@ export default [
|
|||||||
},
|
},
|
||||||
// esm build
|
// esm build
|
||||||
{
|
{
|
||||||
input: 'src/index.js',
|
|
||||||
output: {
|
output: {
|
||||||
file: pkg.web.replace('.js', '.esm.js'),
|
file: pkg.web.replace('.js', '.esm.js'),
|
||||||
format: 'es'
|
format: 'es'
|
||||||
@@ -110,7 +112,6 @@ export default [
|
|||||||
},
|
},
|
||||||
// browser esm build
|
// browser esm build
|
||||||
{
|
{
|
||||||
input: 'src/browser.js',
|
|
||||||
output: {
|
output: {
|
||||||
file: pkg.web.replace('.js', '.esm.browser.js'),
|
file: pkg.web.replace('.js', '.esm.browser.js'),
|
||||||
format: 'es'
|
format: 'es'
|
||||||
@@ -119,7 +120,6 @@ export default [
|
|||||||
},
|
},
|
||||||
// minimized browser esm build
|
// minimized browser esm build
|
||||||
{
|
{
|
||||||
input: 'src/browser.js',
|
|
||||||
output: {
|
output: {
|
||||||
file: pkg.web.replace('.js', '.esm.browser.min.js'),
|
file: pkg.web.replace('.js', '.esm.browser.min.js'),
|
||||||
format: 'es'
|
format: 'es'
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { version } from '../package.json'
|
|
||||||
import createMixin from './shared/mixin'
|
|
||||||
import { setOptions } from './shared/options'
|
|
||||||
import { isUndefined } from './utils/is-type'
|
|
||||||
import $meta from './client/$meta'
|
|
||||||
import { hasMetaInfo } from './shared/meta-helpers'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Plugin install function.
|
|
||||||
* @param {Function} Vue - the Vue constructor.
|
|
||||||
*/
|
|
||||||
function install (Vue, options = {}) {
|
|
||||||
if (Vue.__vuemeta_installed) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Vue.__vuemeta_installed = true
|
|
||||||
|
|
||||||
options = setOptions(options)
|
|
||||||
|
|
||||||
Vue.prototype.$meta = function () {
|
|
||||||
return $meta.call(this, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
Vue.mixin(createMixin(Vue, options))
|
|
||||||
}
|
|
||||||
|
|
||||||
// automatic install
|
|
||||||
if (!isUndefined(window) && !isUndefined(window.Vue)) {
|
|
||||||
/* istanbul ignore next */
|
|
||||||
install(window.Vue)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
version,
|
|
||||||
install,
|
|
||||||
hasMetaInfo
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { showWarningNotSupported } from '../shared/log'
|
|
||||||
import { getOptions } from '../shared/options'
|
|
||||||
import { pause, resume } from '../shared/pausing'
|
|
||||||
import refresh from './refresh'
|
|
||||||
|
|
||||||
export default function $meta (options = {}) {
|
|
||||||
/**
|
|
||||||
* Returns an injector for server-side rendering.
|
|
||||||
* @this {Object} - the Vue instance (a root component)
|
|
||||||
* @return {Object} - injector
|
|
||||||
*/
|
|
||||||
if (!this.$root._vueMeta) {
|
|
||||||
return {
|
|
||||||
getOptions: showWarningNotSupported,
|
|
||||||
refresh: showWarningNotSupported,
|
|
||||||
inject: showWarningNotSupported,
|
|
||||||
pause: showWarningNotSupported,
|
|
||||||
resume: showWarningNotSupported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
getOptions: () => getOptions(options),
|
|
||||||
refresh: () => refresh.call(this, options),
|
|
||||||
inject: () => {},
|
|
||||||
pause: () => pause.call(this),
|
|
||||||
resume: () => resume.call(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+32
-15
@@ -1,26 +1,34 @@
|
|||||||
import { clientSequences } from '../shared/escaping'
|
import { clientSequences } from '../shared/escaping'
|
||||||
|
import { showWarningNotSupported } from '../shared/log'
|
||||||
import { getComponentMetaInfo } from '../shared/getComponentOption'
|
import { getComponentMetaInfo } from '../shared/getComponentOption'
|
||||||
|
import { getAppsMetaInfo, clearAppsMetaInfo } from '../shared/additional-app'
|
||||||
import getMetaInfo from '../shared/getMetaInfo'
|
import getMetaInfo from '../shared/getMetaInfo'
|
||||||
import { isFunction } from '../utils/is-type'
|
import { isFunction } from '../utils/is-type'
|
||||||
import updateClientMetaInfo from './updateClientMetaInfo'
|
import updateClientMetaInfo from './updateClientMetaInfo'
|
||||||
|
|
||||||
export default function refresh (options = {}) {
|
/**
|
||||||
/**
|
* When called, will update the current meta info with new meta info.
|
||||||
* When called, will update the current meta info with new meta info.
|
* Useful when updating meta info as the result of an asynchronous
|
||||||
* Useful when updating meta info as the result of an asynchronous
|
* action that resolves after the initial render takes place.
|
||||||
* action that resolves after the initial render takes place.
|
*
|
||||||
*
|
* Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion
|
||||||
* Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion
|
* to implement this method.
|
||||||
* to implement this method.
|
*
|
||||||
*
|
* @return {Object} - new meta info
|
||||||
* @return {Object} - new meta info
|
*/
|
||||||
*/
|
export default function refresh (vm, options = {}) {
|
||||||
|
// make sure vue-meta was initiated
|
||||||
|
if (!vm.$root._vueMeta) {
|
||||||
|
showWarningNotSupported()
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
// collect & aggregate all metaInfo $options
|
// collect & aggregate all metaInfo $options
|
||||||
const rawInfo = getComponentMetaInfo(options, this.$root)
|
const rawInfo = getComponentMetaInfo(options, vm.$root)
|
||||||
|
|
||||||
const metaInfo = getMetaInfo(options, rawInfo, clientSequences, this.$root)
|
const metaInfo = getMetaInfo(options, rawInfo, clientSequences, vm.$root)
|
||||||
|
|
||||||
const appId = this.$root._vueMeta.appId
|
const { appId } = vm.$root._vueMeta
|
||||||
const tags = updateClientMetaInfo(appId, options, metaInfo)
|
const tags = updateClientMetaInfo(appId, options, metaInfo)
|
||||||
|
|
||||||
// emit "event" with new info
|
// emit "event" with new info
|
||||||
@@ -28,5 +36,14 @@ export default function refresh (options = {}) {
|
|||||||
metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags)
|
metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { vm: this, metaInfo, tags }
|
const appsMetaInfo = getAppsMetaInfo()
|
||||||
|
if (appsMetaInfo) {
|
||||||
|
for (const additionalAppId in appsMetaInfo) {
|
||||||
|
updateClientMetaInfo(additionalAppId, options, appsMetaInfo[additionalAppId])
|
||||||
|
delete appsMetaInfo[additionalAppId]
|
||||||
|
}
|
||||||
|
clearAppsMetaInfo(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { vm, metaInfo, tags }
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import { version } from '../package.json'
|
import { version } from '../package.json'
|
||||||
import createMixin from './shared/mixin'
|
import createMixin from './shared/mixin'
|
||||||
import { setOptions } from './shared/options'
|
import { setOptions } from './shared/options'
|
||||||
import $meta from './server/$meta'
|
import $meta from './shared/$meta'
|
||||||
import generate from './server/generate'
|
import generate from './server/generate'
|
||||||
import { hasMetaInfo } from './shared/meta-helpers'
|
import { hasMetaInfo } from './shared/meta-helpers'
|
||||||
|
|
||||||
@@ -27,6 +27,6 @@ function install (Vue, options = {}) {
|
|||||||
export default {
|
export default {
|
||||||
version,
|
version,
|
||||||
install,
|
install,
|
||||||
hasMetaInfo,
|
generate: process.server ? generate : () => {},
|
||||||
generate
|
hasMetaInfo
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { getOptions } from '../shared/options'
|
|
||||||
import { pause, resume } from '../shared/pausing'
|
|
||||||
import refresh from '../client/refresh'
|
|
||||||
import inject from './inject'
|
|
||||||
|
|
||||||
export default function $meta (options = {}) {
|
|
||||||
/**
|
|
||||||
* Returns an injector for server-side rendering.
|
|
||||||
* @this {Object} - the Vue instance (a root component)
|
|
||||||
* @return {Object} - injector
|
|
||||||
*/
|
|
||||||
return {
|
|
||||||
getOptions: () => getOptions(options),
|
|
||||||
refresh: () => refresh.call(this, options),
|
|
||||||
inject: () => inject.call(this, options),
|
|
||||||
pause: () => pause.call(this),
|
|
||||||
resume: () => resume.call(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,5 +5,7 @@ import generateServerInjector from './generateServerInjector'
|
|||||||
|
|
||||||
export default function generate (rawInfo, options = {}) {
|
export default function generate (rawInfo, options = {}) {
|
||||||
const metaInfo = getMetaInfo(setOptions(options), rawInfo, serverSequences)
|
const metaInfo = getMetaInfo(setOptions(options), rawInfo, serverSequences)
|
||||||
return generateServerInjector(options, metaInfo)
|
|
||||||
|
const serverInjector = generateServerInjector(options, metaInfo)
|
||||||
|
return serverInjector.injectors
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,24 +9,71 @@ import { titleGenerator, attributeGenerator, tagGenerator } from './generators'
|
|||||||
* @return {Object} - the new injector
|
* @return {Object} - the new injector
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default function generateServerInjector (options, newInfo) {
|
export default function generateServerInjector (options, metaInfo) {
|
||||||
|
const serverInjector = {
|
||||||
|
data: metaInfo,
|
||||||
|
extraData: undefined,
|
||||||
|
addInfo (appId, metaInfo) {
|
||||||
|
this.extraData = this.extraData || {}
|
||||||
|
this.extraData[appId] = metaInfo
|
||||||
|
},
|
||||||
|
callInjectors (opts) {
|
||||||
|
const m = this.injectors
|
||||||
|
|
||||||
|
// only call title for the head
|
||||||
|
return (opts.body || opts.pbody ? '' : m.title.text(opts)) +
|
||||||
|
m.meta.text(opts) +
|
||||||
|
m.link.text(opts) +
|
||||||
|
m.style.text(opts) +
|
||||||
|
m.script.text(opts) +
|
||||||
|
m.noscript.text(opts)
|
||||||
|
},
|
||||||
|
injectors: {
|
||||||
|
head: ln => serverInjector.callInjectors({ ln }),
|
||||||
|
bodyPrepend: ln => serverInjector.callInjectors({ ln, pbody: true }),
|
||||||
|
bodyAppend: ln => serverInjector.callInjectors({ ln, body: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const type in defaultInfo) {
|
for (const type in defaultInfo) {
|
||||||
if (metaInfoOptionKeys.includes(type)) {
|
if (metaInfoOptionKeys.includes(type)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'title') {
|
serverInjector.injectors[type] = {
|
||||||
newInfo[type] = titleGenerator(options, type, newInfo[type])
|
text (arg) {
|
||||||
continue
|
if (type === 'title') {
|
||||||
}
|
return titleGenerator(options, type, serverInjector.data[type], arg)
|
||||||
|
}
|
||||||
|
|
||||||
if (metaInfoAttributeKeys.includes(type)) {
|
if (metaInfoAttributeKeys.includes(type)) {
|
||||||
newInfo[type] = attributeGenerator(options, type, newInfo[type])
|
let str = attributeGenerator(options, type, serverInjector.data[type], arg)
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
newInfo[type] = tagGenerator(options, type, newInfo[type])
|
if (serverInjector.extraData) {
|
||||||
|
for (const appId in serverInjector.extraData) {
|
||||||
|
const data = serverInjector.extraData[appId][type]
|
||||||
|
const extraStr = attributeGenerator(options, type, data, arg)
|
||||||
|
str = `${str}${extraStr}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
let str = tagGenerator(options, type, serverInjector.data[type], arg)
|
||||||
|
|
||||||
|
if (serverInjector.extraData) {
|
||||||
|
for (const appId in serverInjector.extraData) {
|
||||||
|
const data = serverInjector.extraData[appId][type]
|
||||||
|
const extraStr = tagGenerator(options, type, data, { appId, ...arg })
|
||||||
|
str = `${str}${extraStr}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newInfo
|
return serverInjector
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,33 +8,29 @@ import { isUndefined, isArray } from '../../utils/is-type'
|
|||||||
* @param {Object} data - the attributes to generate
|
* @param {Object} data - the attributes to generate
|
||||||
* @return {Object} - the attribute generator
|
* @return {Object} - the attribute generator
|
||||||
*/
|
*/
|
||||||
export default function attributeGenerator ({ attribute, ssrAttribute } = {}, type, data) {
|
export default function attributeGenerator ({ attribute, ssrAttribute } = {}, type, data, addSrrAttribute) {
|
||||||
return {
|
let attributeStr = ''
|
||||||
text (addSrrAttribute) {
|
const watchedAttrs = []
|
||||||
let attributeStr = ''
|
|
||||||
const watchedAttrs = []
|
|
||||||
|
|
||||||
for (const attr in data) {
|
for (const attr in data) {
|
||||||
if (data.hasOwnProperty(attr)) {
|
if (data.hasOwnProperty(attr)) {
|
||||||
watchedAttrs.push(attr)
|
watchedAttrs.push(attr)
|
||||||
|
|
||||||
attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr)
|
attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr)
|
||||||
? attr
|
? attr
|
||||||
: `${attr}="${isArray(data[attr]) ? data[attr].join(' ') : data[attr]}"`
|
: `${attr}="${isArray(data[attr]) ? data[attr].join(' ') : data[attr]}"`
|
||||||
|
|
||||||
attributeStr += ' '
|
attributeStr += ' '
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attributeStr) {
|
|
||||||
attributeStr += `${attribute}="${(watchedAttrs.sort()).join(',')}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'htmlAttrs' && addSrrAttribute) {
|
|
||||||
return `${ssrAttribute}${attributeStr ? ' ' : ''}${attributeStr}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return attributeStr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attributeStr) {
|
||||||
|
attributeStr += `${attribute}="${(watchedAttrs.sort()).join(',')}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'htmlAttrs' && addSrrAttribute) {
|
||||||
|
return `${ssrAttribute}${attributeStr ? ' ' : ''}${attributeStr}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributeStr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,79 +14,76 @@ import {
|
|||||||
* @param {(Array<Object>|Object)} tags - an array of tag objects or a single object in case of base
|
* @param {(Array<Object>|Object)} tags - an array of tag objects or a single object in case of base
|
||||||
* @return {Object} - the tag generator
|
* @return {Object} - the tag generator
|
||||||
*/
|
*/
|
||||||
export default function tagGenerator ({ ssrAppId, attribute, tagIDKeyName } = {}, type, tags) {
|
export default function tagGenerator ({ ssrAppId, attribute, tagIDKeyName } = {}, type, tags, { appId, body = false, pbody = false, ln = false } = {}) {
|
||||||
const dataAttributes = [tagIDKeyName, ...commonDataAttributes]
|
const dataAttributes = [tagIDKeyName, ...commonDataAttributes]
|
||||||
|
|
||||||
return {
|
if (!tags || !tags.length) {
|
||||||
text ({ body = false, pbody = false } = {}) {
|
return ''
|
||||||
if (!tags || !tags.length) {
|
}
|
||||||
return ''
|
|
||||||
|
// build a string containing all tags of this type
|
||||||
|
return tags.reduce((tagsStr, tag) => {
|
||||||
|
if (tag.skip) {
|
||||||
|
return tagsStr
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagKeys = Object.keys(tag)
|
||||||
|
|
||||||
|
if (tagKeys.length === 0) {
|
||||||
|
return tagsStr // Bail on empty tag object
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boolean(tag.body) !== body || Boolean(tag.pbody) !== pbody) {
|
||||||
|
return tagsStr
|
||||||
|
}
|
||||||
|
|
||||||
|
let attrs = tag.once ? '' : ` ${attribute}="${appId || ssrAppId}"`
|
||||||
|
|
||||||
|
// build a string containing all attributes of this tag
|
||||||
|
for (const attr in tag) {
|
||||||
|
// these attributes are treated as children on the tag
|
||||||
|
if (tagAttributeAsInnerContent.includes(attr) || tagProperties.includes(attr)) {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// build a string containing all tags of this type
|
if (attr === 'callback') {
|
||||||
return tags.reduce((tagsStr, tag) => {
|
attrs += ` onload="this.__vm_l=1"`
|
||||||
if (tag.skip) {
|
continue
|
||||||
return tagsStr
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const tagKeys = Object.keys(tag)
|
// these form the attribute list for this tag
|
||||||
|
let prefix = ''
|
||||||
|
if (dataAttributes.includes(attr)) {
|
||||||
|
prefix = 'data-'
|
||||||
|
}
|
||||||
|
|
||||||
if (tagKeys.length === 0) {
|
const isBooleanAttr = !prefix && booleanHtmlAttributes.includes(attr)
|
||||||
return tagsStr // Bail on empty tag object
|
if (isBooleanAttr && !tag[attr]) {
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if (Boolean(tag.body) !== body || Boolean(tag.pbody) !== pbody) {
|
attrs += ` ${prefix}${attr}` + (isBooleanAttr ? '' : `="${tag[attr]}"`)
|
||||||
return tagsStr
|
|
||||||
}
|
|
||||||
|
|
||||||
let attrs = tag.once ? '' : ` ${attribute}="${ssrAppId}"`
|
|
||||||
|
|
||||||
// build a string containing all attributes of this tag
|
|
||||||
for (const attr in tag) {
|
|
||||||
// these attributes are treated as children on the tag
|
|
||||||
if (tagAttributeAsInnerContent.includes(attr) || tagProperties.includes(attr)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attr === 'callback') {
|
|
||||||
attrs += ` onload="this.__vm_l=1"`
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// these form the attribute list for this tag
|
|
||||||
let prefix = ''
|
|
||||||
if (dataAttributes.includes(attr)) {
|
|
||||||
prefix = 'data-'
|
|
||||||
}
|
|
||||||
|
|
||||||
const isBooleanAttr = !prefix && booleanHtmlAttributes.includes(attr)
|
|
||||||
if (isBooleanAttr && !tag[attr]) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs += ` ${prefix}${attr}` + (isBooleanAttr ? '' : `="${tag[attr]}"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
let json = ''
|
|
||||||
if (tag.json) {
|
|
||||||
json = JSON.stringify(tag.json)
|
|
||||||
}
|
|
||||||
|
|
||||||
// grab child content from one of these attributes, if possible
|
|
||||||
const content = tag.innerHTML || tag.cssText || json
|
|
||||||
|
|
||||||
// generate tag exactly without any other redundant attribute
|
|
||||||
|
|
||||||
// these tags have no end tag
|
|
||||||
const hasEndTag = !tagsWithoutEndTag.includes(type)
|
|
||||||
|
|
||||||
// these tag types will have content inserted
|
|
||||||
const hasContent = hasEndTag && tagsWithInnerContent.includes(type)
|
|
||||||
|
|
||||||
// the final string for this specific tag
|
|
||||||
return `${tagsStr}<${type}${attrs}${!hasContent && hasEndTag ? '/' : ''}>` +
|
|
||||||
(hasContent ? `${content}</${type}>` : '')
|
|
||||||
}, '')
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
let json = ''
|
||||||
|
if (tag.json) {
|
||||||
|
json = JSON.stringify(tag.json)
|
||||||
|
}
|
||||||
|
|
||||||
|
// grab child content from one of these attributes, if possible
|
||||||
|
const content = tag.innerHTML || tag.cssText || json
|
||||||
|
|
||||||
|
// generate tag exactly without any other redundant attribute
|
||||||
|
|
||||||
|
// these tags have no end tag
|
||||||
|
const hasEndTag = !tagsWithoutEndTag.includes(type)
|
||||||
|
|
||||||
|
// these tag types will have content inserted
|
||||||
|
const hasContent = hasEndTag && tagsWithInnerContent.includes(type)
|
||||||
|
|
||||||
|
// the final string for this specific tag
|
||||||
|
return `${tagsStr}<${type}${attrs}${!hasContent && hasEndTag ? '/' : ''}>` +
|
||||||
|
(hasContent ? `${content}</${type}>` : '') +
|
||||||
|
(ln ? '\n' : '')
|
||||||
|
}, '')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,10 @@
|
|||||||
* @param {String} data - the title text
|
* @param {String} data - the title text
|
||||||
* @return {Object} - the title generator
|
* @return {Object} - the title generator
|
||||||
*/
|
*/
|
||||||
export default function titleGenerator ({ attribute } = {}, type, data) {
|
export default function titleGenerator ({ attribute } = {}, type, data, { ln } = {}) {
|
||||||
return {
|
if (!data) {
|
||||||
text () {
|
return ''
|
||||||
if (!data) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
return `<${type}>${data}</${type}>`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return `<${type}>${data}</${type}>${ln ? '\n' : ''}`
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-13
@@ -1,24 +1,41 @@
|
|||||||
import { serverSequences } from '../shared/escaping'
|
import { serverSequences } from '../shared/escaping'
|
||||||
|
import { showWarningNotSupported } from '../shared/log'
|
||||||
import { getComponentMetaInfo } from '../shared/getComponentOption'
|
import { getComponentMetaInfo } from '../shared/getComponentOption'
|
||||||
|
import { getAppsMetaInfo, clearAppsMetaInfo } from '../shared/additional-app'
|
||||||
import getMetaInfo from '../shared/getMetaInfo'
|
import getMetaInfo from '../shared/getMetaInfo'
|
||||||
import generateServerInjector from './generateServerInjector'
|
import generateServerInjector from './generateServerInjector'
|
||||||
|
|
||||||
export default function _inject (options = {}) {
|
/**
|
||||||
/**
|
* Converts the state of the meta info object such that each item
|
||||||
* Converts the state of the meta info object such that each item
|
* can be compiled to a tag string on the server
|
||||||
* can be compiled to a tag string on the server
|
*
|
||||||
*
|
* @vm {Object} - Vue instance - ideally the root component
|
||||||
* @this {Object} - Vue instance - ideally the root component
|
* @return {Object} - server meta info with `toString` methods
|
||||||
* @return {Object} - server meta info with `toString` methods
|
*/
|
||||||
*/
|
export default function inject (vm, options = {}) {
|
||||||
|
// make sure vue-meta was initiated
|
||||||
|
if (!vm.$root._vueMeta) {
|
||||||
|
showWarningNotSupported()
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
// collect & aggregate all metaInfo $options
|
// collect & aggregate all metaInfo $options
|
||||||
const rawInfo = getComponentMetaInfo(options, this.$root)
|
const rawInfo = getComponentMetaInfo(options, vm.$root)
|
||||||
|
|
||||||
const metaInfo = getMetaInfo(options, rawInfo, serverSequences, this.$root)
|
const metaInfo = getMetaInfo(options, rawInfo, serverSequences, vm.$root)
|
||||||
|
|
||||||
// generate server injectors
|
// generate server injector
|
||||||
generateServerInjector(options, metaInfo)
|
const serverInjector = generateServerInjector(options, metaInfo)
|
||||||
|
|
||||||
return metaInfo
|
// add meta info from additional apps
|
||||||
|
const appsMetaInfo = getAppsMetaInfo()
|
||||||
|
if (appsMetaInfo) {
|
||||||
|
for (const additionalAppId in appsMetaInfo) {
|
||||||
|
serverInjector.addInfo(additionalAppId, appsMetaInfo[additionalAppId])
|
||||||
|
delete appsMetaInfo[additionalAppId]
|
||||||
|
}
|
||||||
|
clearAppsMetaInfo(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverInjector.injectors
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import refresh from '../client/refresh'
|
||||||
|
import inject from '../server/inject'
|
||||||
|
import { showWarningNotSupported } from '../shared/log'
|
||||||
|
import { addApp } from './additional-app'
|
||||||
|
import { pause, resume } from './pausing'
|
||||||
|
import { getOptions } from './options'
|
||||||
|
|
||||||
|
export default function $meta (options = {}) {
|
||||||
|
/**
|
||||||
|
* Returns an injector for server-side rendering.
|
||||||
|
* @this {Object} - the Vue instance (a root component)
|
||||||
|
* @return {Object} - injector
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
getOptions: () => getOptions(options),
|
||||||
|
refresh: () => refresh(this, options),
|
||||||
|
inject: () => process.server ? inject(this, options) : showWarningNotSupported(),
|
||||||
|
pause: () => pause(this),
|
||||||
|
resume: () => resume(this),
|
||||||
|
addApp: appId => addApp(this, appId, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import updateClientMetaInfo from '../client/updateClientMetaInfo'
|
||||||
|
import { removeElementsByAppId } from '../utils/elements'
|
||||||
|
|
||||||
|
let appsMetaInfo
|
||||||
|
|
||||||
|
export function addApp (vm, appId, options) {
|
||||||
|
return {
|
||||||
|
set: metaInfo => setMetaInfo(vm.$root, appId, options, metaInfo),
|
||||||
|
remove: () => removeMetaInfo(vm.$root, appId, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setMetaInfo (vm, appId, options, metaInfo) {
|
||||||
|
// if a vm exists _and_ its mounted then immediately update
|
||||||
|
if (vm && vm.$el) {
|
||||||
|
return updateClientMetaInfo(appId, options, metaInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store for later, the info
|
||||||
|
// will be set on the first refresh
|
||||||
|
appsMetaInfo = appsMetaInfo || {}
|
||||||
|
appsMetaInfo[appId] = metaInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeMetaInfo (vm, appId, options) {
|
||||||
|
if (vm && vm.$el) {
|
||||||
|
return removeElementsByAppId(options, appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appsMetaInfo[appId]) {
|
||||||
|
delete appsMetaInfo[appId]
|
||||||
|
clearAppsMetaInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAppsMetaInfo () {
|
||||||
|
return appsMetaInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAppsMetaInfo (force) {
|
||||||
|
if (force || !Object.keys(appsMetaInfo).length) {
|
||||||
|
appsMetaInfo = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
export function pause (refresh = true) {
|
export function pause (vm, refresh = true) {
|
||||||
this.$root._vueMeta.paused = true
|
vm.$root._vueMeta.paused = true
|
||||||
|
|
||||||
return () => resume(refresh)
|
return () => resume(refresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resume (refresh = true) {
|
export function resume (vm, refresh = true) {
|
||||||
this.$root._vueMeta.paused = false
|
vm.$root._vueMeta.paused = false
|
||||||
|
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
return this.$root.$meta().refresh()
|
return vm.$root.$meta().refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,3 +29,7 @@ export function queryElements (parentNode, { appId, attribute, type, tagIDKeyNam
|
|||||||
|
|
||||||
return toArray(parentNode.querySelectorAll(queries.join(', ')))
|
return toArray(parentNode.querySelectorAll(queries.join(', ')))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function removeElementsByAppId ({ attribute }, appId) {
|
||||||
|
toArray(document.querySelectorAll(`[${attribute}="${appId}"]`)).map(el => el.remove())
|
||||||
|
}
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ describe('client', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('afterNavigation function is called with refreshOnce: true', async () => {
|
test('afterNavigation function is called with refreshOnce: true', async () => {
|
||||||
const Vue = loadVueMetaPlugin(false, { refreshOnceOnNavigation: true })
|
const Vue = loadVueMetaPlugin({ refreshOnceOnNavigation: true })
|
||||||
const afterNavigation = jest.fn()
|
const afterNavigation = jest.fn()
|
||||||
const component = Vue.component('nav-component', {
|
const component = Vue.component('nav-component', {
|
||||||
render: h => h('div'),
|
render: h => h('div'),
|
||||||
@@ -181,7 +181,7 @@ describe('client', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('afterNavigation function is called with refreshOnce: false', async () => {
|
test('afterNavigation function is called with refreshOnce: false', async () => {
|
||||||
const Vue = loadVueMetaPlugin(false, { refreshOnceOnNavigation: false })
|
const Vue = loadVueMetaPlugin({ refreshOnceOnNavigation: false })
|
||||||
const afterNavigation = jest.fn()
|
const afterNavigation = jest.fn()
|
||||||
const component = Vue.component('nav-component', {
|
const component = Vue.component('nav-component', {
|
||||||
render: h => h('div'),
|
render: h => h('div'),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { defaultOptions } from '../../src/shared/constants'
|
|||||||
import metaInfoData from '../utils/meta-info-data'
|
import metaInfoData from '../utils/meta-info-data'
|
||||||
import { titleGenerator } from '../../src/server/generators'
|
import { titleGenerator } from '../../src/server/generators'
|
||||||
|
|
||||||
const generateServerInjector = metaInfo => _generateServerInjector(defaultOptions, metaInfo)
|
const generateServerInjector = metaInfo => _generateServerInjector(defaultOptions, metaInfo).injectors
|
||||||
|
|
||||||
describe('generators', () => {
|
describe('generators', () => {
|
||||||
for (const type in metaInfoData) {
|
for (const type in metaInfoData) {
|
||||||
@@ -78,7 +78,7 @@ describe('extra tests', () => {
|
|||||||
const title = null
|
const title = null
|
||||||
const generatedTitle = titleGenerator({}, 'title', title)
|
const generatedTitle = titleGenerator({}, 'title', title)
|
||||||
|
|
||||||
expect(generatedTitle.text()).toEqual('')
|
expect(generatedTitle).toEqual('')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('auto add ssrAttribute', () => {
|
test('auto add ssrAttribute', () => {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ describe('getComponentOption', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('fetches deeply nested component options and merges them', () => {
|
test('fetches deeply nested component options and merges them', () => {
|
||||||
const localVue = loadVueMetaPlugin(true, { keyName: 'foo' })
|
const localVue = loadVueMetaPlugin({ keyName: 'foo' })
|
||||||
localVue.component('merge-child', { render: h => h('div'), foo: { bar: 'baz' } })
|
localVue.component('merge-child', { render: h => h('div'), foo: { bar: 'baz' } })
|
||||||
|
|
||||||
const component = localVue.component('parent', {
|
const component = localVue.component('parent', {
|
||||||
@@ -92,7 +92,7 @@ describe('getComponentOption', () => {
|
|||||||
}) */
|
}) */
|
||||||
|
|
||||||
test('only traverses branches with metaInfo components', () => {
|
test('only traverses branches with metaInfo components', () => {
|
||||||
const localVue = loadVueMetaPlugin(false, { keyName: 'foo' })
|
const localVue = loadVueMetaPlugin({ keyName: 'foo' })
|
||||||
|
|
||||||
localVue.component('meta-child', {
|
localVue.component('meta-child', {
|
||||||
foo: { bar: 'baz' },
|
foo: { bar: 'baz' },
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
import { mount, VueMetaServerPlugin, loadVueMetaPlugin } from '../utils'
|
|
||||||
import { defaultOptions } from '../../src/shared/constants'
|
|
||||||
|
|
||||||
jest.mock('../../package.json', () => ({
|
|
||||||
version: 'test-version'
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('plugin', () => {
|
|
||||||
let Vue
|
|
||||||
|
|
||||||
beforeEach(() => jest.clearAllMocks())
|
|
||||||
beforeAll(() => (Vue = loadVueMetaPlugin()))
|
|
||||||
|
|
||||||
test('is loaded', () => {
|
|
||||||
const instance = new Vue({ metaInfo: {} })
|
|
||||||
expect(instance.$meta).toEqual(expect.any(Function))
|
|
||||||
|
|
||||||
expect(instance.$meta().inject).toEqual(expect.any(Function))
|
|
||||||
expect(instance.$meta().refresh).toEqual(expect.any(Function))
|
|
||||||
expect(instance.$meta().getOptions).toEqual(expect.any(Function))
|
|
||||||
|
|
||||||
expect(instance.$meta().inject()).toBeDefined()
|
|
||||||
expect(instance.$meta().refresh()).toBeDefined()
|
|
||||||
|
|
||||||
const options = instance.$meta().getOptions()
|
|
||||||
expect(options).toBeDefined()
|
|
||||||
expect(options.keyName).toBe(defaultOptions.keyName)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('component has _hasMetaInfo set to true', () => {
|
|
||||||
const Component = Vue.component('test-component', {
|
|
||||||
template: '<div>Test</div>',
|
|
||||||
[defaultOptions.keyName]: {
|
|
||||||
title: 'Hello World'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const { vm } = mount(Component, { localVue: Vue })
|
|
||||||
expect(vm._hasMetaInfo).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('plugin sets package version', () => {
|
|
||||||
expect(VueMetaServerPlugin.version).toBe('test-version')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('plugin isnt be installed twice', () => {
|
|
||||||
expect(Vue.__vuemeta_installed).toBe(true)
|
|
||||||
|
|
||||||
Vue.prototype.$meta = undefined
|
|
||||||
Vue.use({ ...VueMetaServerPlugin })
|
|
||||||
|
|
||||||
expect(Vue.prototype.$meta).toBeUndefined()
|
|
||||||
|
|
||||||
// reset Vue
|
|
||||||
Vue = loadVueMetaPlugin(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('prints deprecation warning once when using _hasMetaInfo', () => {
|
|
||||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
||||||
|
|
||||||
const Component = Vue.component('test-component', {
|
|
||||||
template: '<div>Test</div>',
|
|
||||||
[defaultOptions.keyName]: {
|
|
||||||
title: 'Hello World'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Vue.config.devtools = true
|
|
||||||
const { vm } = mount(Component, { localVue: Vue })
|
|
||||||
|
|
||||||
expect(vm._hasMetaInfo).toBe(true)
|
|
||||||
expect(warn).toHaveBeenCalledTimes(1)
|
|
||||||
|
|
||||||
expect(vm._hasMetaInfo).toBe(true)
|
|
||||||
expect(warn).toHaveBeenCalledTimes(1)
|
|
||||||
warn.mockRestore()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can use hasMetaInfo export', () => {
|
|
||||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
||||||
|
|
||||||
const Component = Vue.component('test-component', {
|
|
||||||
template: '<div>Test</div>',
|
|
||||||
[defaultOptions.keyName]: {
|
|
||||||
title: 'Hello World'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const { vm } = mount(Component, { localVue: Vue })
|
|
||||||
|
|
||||||
expect(VueMetaServerPlugin.hasMetaInfo(vm)).toBe(true)
|
|
||||||
expect(warn).not.toHaveBeenCalled()
|
|
||||||
|
|
||||||
warn.mockRestore()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('can use generate export', () => {
|
|
||||||
const rawInfo = {
|
|
||||||
meta: [{ charset: 'utf-8' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
const metaInfo = VueMetaServerPlugin.generate(rawInfo)
|
|
||||||
expect(metaInfo.meta.text()).toBe('<meta data-vue-meta="ssr" charset="utf-8">')
|
|
||||||
|
|
||||||
// no error on not provided metaInfo types
|
|
||||||
expect(metaInfo.script.text()).toBe('')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { triggerUpdate, batchUpdate } from '../../src/client/update'
|
import { triggerUpdate, batchUpdate } from '../../src/client/update'
|
||||||
import { mount, vmTick, VueMetaBrowserPlugin, loadVueMetaPlugin } from '../utils'
|
import { mount, vmTick, VueMetaPlugin, loadVueMetaPlugin } from '../utils'
|
||||||
import { defaultOptions } from '../../src/shared/constants'
|
import { defaultOptions } from '../../src/shared/constants'
|
||||||
|
|
||||||
jest.mock('../../src/client/update')
|
jest.mock('../../src/client/update')
|
||||||
@@ -15,6 +15,7 @@ describe('plugin', () => {
|
|||||||
|
|
||||||
test('not loaded when no metaInfo defined', () => {
|
test('not loaded when no metaInfo defined', () => {
|
||||||
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||||
|
process.server = false
|
||||||
|
|
||||||
const instance = new Vue()
|
const instance = new Vue()
|
||||||
expect(instance.$meta).toEqual(expect.any(Function))
|
expect(instance.$meta).toEqual(expect.any(Function))
|
||||||
@@ -23,14 +24,16 @@ describe('plugin', () => {
|
|||||||
expect(instance.$meta().refresh).toEqual(expect.any(Function))
|
expect(instance.$meta().refresh).toEqual(expect.any(Function))
|
||||||
expect(instance.$meta().getOptions).toEqual(expect.any(Function))
|
expect(instance.$meta().getOptions).toEqual(expect.any(Function))
|
||||||
|
|
||||||
expect(instance.$meta().inject()).not.toBeDefined()
|
expect(instance.$meta().inject()).toBeUndefined()
|
||||||
expect(warn).toHaveBeenCalledTimes(1)
|
expect(warn).toHaveBeenCalledTimes(1)
|
||||||
expect(instance.$meta().refresh()).not.toBeDefined()
|
expect(instance.$meta().refresh()).toEqual({})
|
||||||
expect(warn).toHaveBeenCalledTimes(2)
|
expect(warn).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
instance.$meta().getOptions()
|
instance.$meta().getOptions()
|
||||||
expect(warn).toHaveBeenCalledTimes(3)
|
expect(warn).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
warn.mockRestore()
|
warn.mockRestore()
|
||||||
|
delete process.server
|
||||||
})
|
})
|
||||||
|
|
||||||
test('is loaded', () => {
|
test('is loaded', () => {
|
||||||
@@ -62,14 +65,14 @@ describe('plugin', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('plugin sets package version', () => {
|
test('plugin sets package version', () => {
|
||||||
expect(VueMetaBrowserPlugin.version).toBe('test-version')
|
expect(VueMetaPlugin.version).toBe('test-version')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('plugin isnt be installed twice', () => {
|
test('plugin isnt be installed twice', () => {
|
||||||
expect(Vue.__vuemeta_installed).toBe(true)
|
expect(Vue.__vuemeta_installed).toBe(true)
|
||||||
|
|
||||||
Vue.prototype.$meta = undefined
|
Vue.prototype.$meta = undefined
|
||||||
Vue.use({ ...VueMetaBrowserPlugin })
|
Vue.use({ ...VueMetaPlugin })
|
||||||
|
|
||||||
expect(Vue.prototype.$meta).toBeUndefined()
|
expect(Vue.prototype.$meta).toBeUndefined()
|
||||||
|
|
||||||
@@ -77,6 +80,57 @@ describe('plugin', () => {
|
|||||||
Vue = loadVueMetaPlugin(true)
|
Vue = loadVueMetaPlugin(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('prints deprecation warning once when using _hasMetaInfo', () => {
|
||||||
|
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||||
|
|
||||||
|
const Component = Vue.component('test-component', {
|
||||||
|
template: '<div>Test</div>',
|
||||||
|
[defaultOptions.keyName]: {
|
||||||
|
title: 'Hello World'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Vue.config.devtools = true
|
||||||
|
const { vm } = mount(Component, { localVue: Vue })
|
||||||
|
|
||||||
|
expect(vm._hasMetaInfo).toBe(true)
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
expect(vm._hasMetaInfo).toBe(true)
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1)
|
||||||
|
warn.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can use hasMetaInfo export', () => {
|
||||||
|
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
||||||
|
|
||||||
|
const Component = Vue.component('test-component', {
|
||||||
|
template: '<div>Test</div>',
|
||||||
|
[defaultOptions.keyName]: {
|
||||||
|
title: 'Hello World'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { vm } = mount(Component, { localVue: Vue })
|
||||||
|
|
||||||
|
expect(VueMetaPlugin.hasMetaInfo(vm)).toBe(true)
|
||||||
|
expect(warn).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
warn.mockRestore()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can use generate export', () => {
|
||||||
|
const rawInfo = {
|
||||||
|
meta: [{ charset: 'utf-8' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const metaInfo = VueMetaPlugin.generate(rawInfo)
|
||||||
|
expect(metaInfo.meta.text()).toBe('<meta data-vue-meta="ssr" charset="utf-8">')
|
||||||
|
|
||||||
|
// no error on not provided metaInfo types
|
||||||
|
expect(metaInfo.script.text()).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
test('updates can be paused and resumed', async () => {
|
test('updates can be paused and resumed', async () => {
|
||||||
const { batchUpdate: _batchUpdate } = jest.requireActual('../../src/client/update')
|
const { batchUpdate: _batchUpdate } = jest.requireActual('../../src/client/update')
|
||||||
const batchUpdateSpy = batchUpdate.mockImplementation(_batchUpdate)
|
const batchUpdateSpy = batchUpdate.mockImplementation(_batchUpdate)
|
||||||
+2
-1
@@ -22,7 +22,8 @@ export function getVueMetaPath (browser) {
|
|||||||
return path.resolve(__dirname, `../..${browser ? '/dist/vue-meta.js' : ''}`)
|
return path.resolve(__dirname, `../..${browser ? '/dist/vue-meta.js' : ''}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path.resolve(__dirname, `../../src${browser ? '/browser' : ''}`)
|
process.server = !browser
|
||||||
|
return path.resolve(__dirname, `../../src`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function webpackRun (config) {
|
export function webpackRun (config) {
|
||||||
|
|||||||
+4
-10
@@ -2,28 +2,22 @@ import { JSDOM } from 'jsdom'
|
|||||||
import { mount, shallowMount, createWrapper, createLocalVue } from '@vue/test-utils'
|
import { mount, shallowMount, createWrapper, createLocalVue } from '@vue/test-utils'
|
||||||
import { renderToString } from '@vue/server-test-utils'
|
import { renderToString } from '@vue/server-test-utils'
|
||||||
import { defaultOptions } from '../../src/shared/constants'
|
import { defaultOptions } from '../../src/shared/constants'
|
||||||
import VueMetaBrowserPlugin from '../../src/browser'
|
import VueMetaPlugin from '../../src'
|
||||||
import VueMetaServerPlugin from '../../src'
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
mount,
|
mount,
|
||||||
shallowMount,
|
shallowMount,
|
||||||
createWrapper,
|
createWrapper,
|
||||||
renderToString,
|
renderToString,
|
||||||
VueMetaBrowserPlugin,
|
VueMetaPlugin
|
||||||
VueMetaServerPlugin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVue () {
|
export function getVue () {
|
||||||
return createLocalVue()
|
return createLocalVue()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadVueMetaPlugin (browser, options, localVue = getVue()) {
|
export function loadVueMetaPlugin (options, localVue = getVue()) {
|
||||||
if (browser) {
|
localVue.use(VueMetaPlugin, Object.assign({}, defaultOptions, options))
|
||||||
localVue.use(VueMetaBrowserPlugin, Object.assign({}, defaultOptions, options))
|
|
||||||
} else {
|
|
||||||
localVue.use(VueMetaServerPlugin, Object.assign({}, defaultOptions, options))
|
|
||||||
}
|
|
||||||
|
|
||||||
return localVue
|
return localVue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
|
process.server = true
|
||||||
|
|
||||||
jest.useFakeTimers()
|
jest.useFakeTimers()
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|||||||
Reference in New Issue
Block a user