2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-23 16:00:34 +03:00

chore: dist size improvements

chore: configure terser to mangle internal properties

chore: do not use default value assignment in function args
This commit is contained in:
pimlie
2019-09-14 18:08:21 +02:00
committed by Pim
parent dcdd47a557
commit ee12bfcc90
32 changed files with 248 additions and 147 deletions
+43 -6
View File
@@ -5,6 +5,7 @@ import babel from 'rollup-plugin-babel'
import replace from 'rollup-plugin-replace' import replace from 'rollup-plugin-replace'
import { terser } from 'rollup-plugin-terser' import { terser } from 'rollup-plugin-terser'
import defaultsDeep from 'lodash/defaultsDeep' import defaultsDeep from 'lodash/defaultsDeep'
import { defaultOptions } from '../src/shared/constants'
const pkg = require('../package.json') const pkg = require('../package.json')
@@ -35,6 +36,41 @@ const babelConfig = () => ({
] ]
}) })
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({ function rollupConfig({
plugins = [], plugins = [],
...config ...config
@@ -51,17 +87,18 @@ function rollupConfig({
'process.env.VERSION': `"${version}"`, 'process.env.VERSION': `"${version}"`,
'process.server' : isBrowserBuild ? 'false' : 'true', 'process.server' : isBrowserBuild ? 'false' : 'true',
/* remove unused stuff from deepmerge */ /* remove unused stuff from deepmerge */
// remove react stuff from is-mergeable-object // remove react stuff from is-mergeable-object
'|| isReactElement(value)': '|| false', '|| isReactElement(value)': '|| false',
// we always provide an arrayMerge, remove default // we always provide an arrayMerge, remove default
'|| defaultArrayMerge' : '', '|| defaultArrayMerge' : '',
// we dont provide a custom merge // clone is a deprecated option we dont use
'options.clone' : 'false', 'options.clone' : 'false',
// we dont provide a custom merge // we dont provide a custom merge
'options.customMerge' : 'false', 'options.customMerge' : 'false',
// dont use this // we dont use this helper
'deepmerge.all = ' : 'false;' 'deepmerge.all = ' : 'false;',
// we dont use symbols on our objects
'.concat(getEnumerableOwnPropertySymbols(target))': ''
} }
} }
@@ -103,7 +140,7 @@ export default [
file: pkg.web.replace('.js', '.min.js'), file: pkg.web.replace('.js', '.min.js'),
}, },
plugins: [ plugins: [
terser() terser(terserOpts)
] ]
}, },
// common js build // common js build
@@ -137,7 +174,7 @@ export default [
format: 'es' format: 'es'
}, },
plugins: [ plugins: [
terser() terser(terserOpts)
], ],
external: Object.keys(pkg.dependencies) external: Object.keys(pkg.dependencies)
} }
+7 -6
View File
@@ -1,13 +1,14 @@
import { toArray } from '../utils/array' import { toArray } from '../utils/array'
import { querySelector, removeAttribute } from '../utils/elements'
const callbacks = [] const callbacks = []
export function isDOMLoaded (d = document) { export function isDOMLoaded (d) {
return d.readyState !== 'loading' return (d || document).readyState !== 'loading'
} }
export function isDOMComplete (d = document) { export function isDOMComplete (d) {
return d.readyState === 'complete' return (d || document).readyState === 'complete'
} }
export function waitDOMLoaded () { export function waitDOMLoaded () {
@@ -69,7 +70,7 @@ export function applyCallbacks (matchElement) {
let elements = [] let elements = []
if (!matchElement) { if (!matchElement) {
elements = toArray(document.querySelectorAll(selector)) elements = toArray(querySelector(selector))
} }
if (matchElement && matchElement.matches(selector)) { if (matchElement && matchElement.matches(selector)) {
@@ -95,7 +96,7 @@ export function applyCallbacks (matchElement) {
* attribute after ssr and if we dont remove it the node * attribute after ssr and if we dont remove it the node
* will fail isEqualNode on the client * will fail isEqualNode on the client
*/ */
element.removeAttribute('onload') removeAttribute(element, 'onload')
callback(element) callback(element)
} }
+15 -4
View File
@@ -17,7 +17,9 @@ import updateClientMetaInfo from './updateClientMetaInfo'
* *
* @return {Object} - new meta info * @return {Object} - new meta info
*/ */
export default function refresh (rootVm, options = {}) { export default function refresh (rootVm, options) {
options = options || {}
// make sure vue-meta was initiated // make sure vue-meta was initiated
if (!rootVm[rootConfigKey]) { if (!rootVm[rootConfigKey]) {
showWarningNotSupported() showWarningNotSupported()
@@ -30,11 +32,16 @@ export default function refresh (rootVm, options = {}) {
const metaInfo = getMetaInfo(options, rawInfo, clientSequences, rootVm) const metaInfo = getMetaInfo(options, rawInfo, clientSequences, rootVm)
const { appId } = rootVm[rootConfigKey] const { appId } = rootVm[rootConfigKey]
const tags = updateClientMetaInfo(appId, options, metaInfo) let tags = updateClientMetaInfo(appId, options, metaInfo)
// emit "event" with new info // emit "event" with new info
if (tags && isFunction(metaInfo.changed)) { if (tags && isFunction(metaInfo.changed)) {
metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags) metaInfo.changed(metaInfo, tags.tagsAdded, tags.tagsRemoved)
tags = {
addedTags: tags.tagsAdded,
removedTags: tags.tagsRemoved
}
} }
const appsMetaInfo = getAppsMetaInfo() const appsMetaInfo = getAppsMetaInfo()
@@ -46,5 +53,9 @@ export default function refresh (rootVm, options = {}) {
clearAppsMetaInfo(true) clearAppsMetaInfo(true)
} }
return { vm: rootVm, metaInfo, tags } return {
vm: rootVm,
metaInfo: metaInfo, // eslint-disable-line object-shorthand
tags
}
} }
+4 -2
View File
@@ -11,7 +11,7 @@ export function triggerUpdate (rootVm, hookName) {
rootVm[rootConfigKey].initialized = null rootVm[rootConfigKey].initialized = null
} }
if (rootVm[rootConfigKey].initialized && !rootVm[rootConfigKey].paused) { if (rootVm[rootConfigKey].initialized && !rootVm[rootConfigKey].pausing) {
// batch potential DOM updates to prevent extraneous re-rendering // batch potential DOM updates to prevent extraneous re-rendering
batchUpdate(() => rootVm.$meta().refresh()) batchUpdate(() => rootVm.$meta().refresh())
} }
@@ -24,7 +24,9 @@ export function triggerUpdate (rootVm, hookName) {
* @param {Function} callback - the update to perform * @param {Function} callback - the update to perform
* @return {Number} id - a new ID * @return {Number} id - a new ID
*/ */
export function batchUpdate (callback, timeout = 10) { export function batchUpdate (callback, timeout) {
timeout = timeout || 10
clearTimeout(batchId) clearTimeout(batchId)
batchId = setTimeout(() => { batchId = setTimeout(() => {
+9 -8
View File
@@ -1,7 +1,7 @@
import { metaInfoOptionKeys, metaInfoAttributeKeys, tagsSupportingOnload } from '../shared/constants' import { metaInfoOptionKeys, metaInfoAttributeKeys, tagsSupportingOnload } from '../shared/constants'
import { isArray } from '../utils/is-type' import { isArray } from '../utils/is-type'
import { includes } from '../utils/array' import { includes } from '../utils/array'
import { getTag } from '../utils/elements' import { getTag, removeAttribute } from '../utils/elements'
import { addCallbacks, addListeners } from './load' import { addCallbacks, addListeners } from './load'
import { updateAttribute, updateTag, updateTitle } from './updaters' import { updateAttribute, updateTag, updateTitle } from './updaters'
@@ -10,7 +10,8 @@ import { updateAttribute, updateTag, updateTitle } from './updaters'
* *
* @param {Object} newInfo - the meta info to update to * @param {Object} newInfo - the meta info to update to
*/ */
export default function updateClientMetaInfo (appId, options = {}, newInfo) { export default function updateClientMetaInfo (appId, options, newInfo) {
options = options || {}
const { ssrAttribute, ssrAppId } = options const { ssrAttribute, ssrAppId } = options
// only cache tags for current update // only cache tags for current update
@@ -21,7 +22,7 @@ export default function updateClientMetaInfo (appId, options = {}, newInfo) {
// if this is a server render, then dont update // if this is a server render, then dont update
if (appId === ssrAppId && htmlTag.hasAttribute(ssrAttribute)) { if (appId === ssrAppId && htmlTag.hasAttribute(ssrAttribute)) {
// remove the server render attribute so we can update on (next) changes // remove the server render attribute so we can update on (next) changes
htmlTag.removeAttribute(ssrAttribute) removeAttribute(htmlTag, ssrAttribute)
// add load callbacks if the // add load callbacks if the
let addLoadListeners = false let addLoadListeners = false
@@ -39,8 +40,8 @@ export default function updateClientMetaInfo (appId, options = {}, newInfo) {
} }
// initialize tracked changes // initialize tracked changes
const addedTags = {} const tagsAdded = {}
const removedTags = {} const tagsRemoved = {}
for (const type in newInfo) { for (const type in newInfo) {
// ignore these // ignore these
@@ -75,10 +76,10 @@ export default function updateClientMetaInfo (appId, options = {}, newInfo) {
) )
if (newTags.length) { if (newTags.length) {
addedTags[type] = newTags tagsAdded[type] = newTags
removedTags[type] = oldTags tagsRemoved[type] = oldTags
} }
} }
return { addedTags, removedTags } return { tagsAdded, tagsRemoved }
} }
+6 -3
View File
@@ -1,5 +1,6 @@
import { booleanHtmlAttributes } from '../../shared/constants' import { booleanHtmlAttributes } from '../../shared/constants'
import { includes } from '../../utils/array' import { includes } from '../../utils/array'
import { removeAttribute } from '../../utils/elements'
// keep a local map of attribute values // keep a local map of attribute values
// instead of adding it to the html // instead of adding it to the html
@@ -11,11 +12,13 @@ export const attributeMap = {}
* @param {Object} attrs - the new document html attributes * @param {Object} attrs - the new document html attributes
* @param {HTMLElement} tag - the HTMLElement tag to update with new attrs * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs
*/ */
export default function updateAttribute (appId, { attribute } = {}, type, attrs, tag) { export default function updateAttribute (appId, options, type, attrs, tag) {
const { attribute } = options || {}
const vueMetaAttrString = tag.getAttribute(attribute) const vueMetaAttrString = tag.getAttribute(attribute)
if (vueMetaAttrString) { if (vueMetaAttrString) {
attributeMap[type] = JSON.parse(decodeURI(vueMetaAttrString)) attributeMap[type] = JSON.parse(decodeURI(vueMetaAttrString))
tag.removeAttribute(attribute) removeAttribute(tag, attribute)
} }
const data = attributeMap[type] || {} const data = attributeMap[type] || {}
@@ -62,7 +65,7 @@ export default function updateAttribute (appId, { attribute } = {}, type, attrs,
tag.setAttribute(attr, attrValue) tag.setAttribute(attr, attrValue)
} else { } else {
tag.removeAttribute(attr) removeAttribute(tag, attr)
} }
} }
+11 -11
View File
@@ -10,8 +10,8 @@ import { queryElements, getElementsKey } from '../../utils/elements.js'
* @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} - a representation of what tags changed * @return {Object} - a representation of what tags changed
*/ */
export default function updateTag (appId, options = {}, type, tags, head, body) { export default function updateTag (appId, options, type, tags, head, body) {
const { attribute, tagIDKeyName } = options const { attribute, tagIDKeyName } = options || {}
const dataAttributes = commonDataAttributes.slice() const dataAttributes = commonDataAttributes.slice()
dataAttributes.push(tagIDKeyName) dataAttributes.push(tagIDKeyName)
@@ -46,20 +46,20 @@ export default function updateTag (appId, options = {}, type, tags, head, body)
const newElement = document.createElement(type) const newElement = document.createElement(type)
newElement.setAttribute(attribute, appId) newElement.setAttribute(attribute, appId)
for (const attr in tag) { Object.keys(tag).forEach((attr) => {
/* istanbul ignore next */ /* istanbul ignore next */
if (!tag.hasOwnProperty(attr) || includes(tagProperties, attr)) { if (includes(tagProperties, attr)) {
continue return
} }
if (attr === 'innerHTML') { if (attr === 'innerHTML') {
newElement.innerHTML = tag.innerHTML newElement.innerHTML = tag.innerHTML
continue return
} }
if (attr === 'json') { if (attr === 'json') {
newElement.innerHTML = JSON.stringify(tag.json) newElement.innerHTML = JSON.stringify(tag.json)
continue return
} }
if (attr === 'cssText') { if (attr === 'cssText') {
@@ -69,12 +69,12 @@ export default function updateTag (appId, options = {}, type, tags, head, body)
} else { } else {
newElement.appendChild(document.createTextNode(tag.cssText)) newElement.appendChild(document.createTextNode(tag.cssText))
} }
continue return
} }
if (attr === 'callback') { if (attr === 'callback') {
newElement.onload = () => tag[attr](newElement) newElement.onload = () => tag[attr](newElement)
continue return
} }
const _attr = includes(dataAttributes, attr) const _attr = includes(dataAttributes, attr)
@@ -83,12 +83,12 @@ export default function updateTag (appId, options = {}, type, tags, head, body)
const isBooleanAttribute = includes(booleanHtmlAttributes, attr) const isBooleanAttribute = includes(booleanHtmlAttributes, attr)
if (isBooleanAttribute && !tag[attr]) { if (isBooleanAttribute && !tag[attr]) {
continue return
} }
const value = isBooleanAttribute ? '' : tag[attr] const value = isBooleanAttribute ? '' : tag[attr]
newElement.setAttribute(_attr, value) newElement.setAttribute(_attr, value)
} })
const oldElements = currentElements[getElementsKey(tag)] const oldElements = currentElements[getElementsKey(tag)]
+1 -1
View File
@@ -10,7 +10,7 @@ import { hasMetaInfo } from './shared/meta-helpers'
* Plugin install function. * Plugin install function.
* @param {Function} Vue - the Vue constructor. * @param {Function} Vue - the Vue constructor.
*/ */
function install (Vue, options = {}) { function install (Vue, options) {
if (Vue.__vuemeta_installed) { if (Vue.__vuemeta_installed) {
return return
} }
+3 -2
View File
@@ -3,8 +3,9 @@ import { serverSequences } from '../shared/escaping'
import { setOptions } from '../shared/options' import { setOptions } from '../shared/options'
import generateServerInjector from './generateServerInjector' import generateServerInjector from './generateServerInjector'
export default function generate (rawInfo, options = {}) { export default function generate (rawInfo, options) {
const metaInfo = getMetaInfo(setOptions(options), rawInfo, serverSequences) options = setOptions(options)
const metaInfo = getMetaInfo(options, rawInfo, serverSequences)
const serverInjector = generateServerInjector(options, metaInfo) const serverInjector = generateServerInjector(options, metaInfo)
return serverInjector.injectors return serverInjector.injectors
+2 -1
View File
@@ -7,7 +7,8 @@ import { booleanHtmlAttributes } from '../../shared/constants'
* @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, addSrrAttribute) { export default function attributeGenerator (options, type, data, addSrrAttribute) {
const { attribute, ssrAttribute } = options || {}
let attributeStr = '' let attributeStr = ''
for (const attr in data) { for (const attr in data) {
+4 -1
View File
@@ -14,7 +14,10 @@ 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, { appId, body = false, pbody = false, ln = false } = {}) { export default function tagGenerator (options, type, tags, generatorOptions) {
const { ssrAppId, attribute, tagIDKeyName } = options || {}
const { appId, body = false, pbody = false, ln = false } = generatorOptions || {}
const dataAttributes = [tagIDKeyName, ...commonDataAttributes] const dataAttributes = [tagIDKeyName, ...commonDataAttributes]
if (!tags || !tags.length) { if (!tags || !tags.length) {
+3 -1
View File
@@ -5,7 +5,9 @@
* @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, { ln } = {}) { export default function titleGenerator (options, type, data, generatorOptions) {
const { ln } = generatorOptions || {}
if (!data) { if (!data) {
return '' return ''
} }
+1 -1
View File
@@ -13,7 +13,7 @@ import generateServerInjector from './generateServerInjector'
* @vm {Object} - Vue instance - ideally the root component * @vm {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 (rootVm, options = {}) { export default function inject (rootVm, options) {
// make sure vue-meta was initiated // make sure vue-meta was initiated
if (!rootVm[rootConfigKey]) { if (!rootVm[rootConfigKey]) {
showWarningNotSupported() showWarningNotSupported()
+12 -9
View File
@@ -6,7 +6,8 @@ import { addNavGuards } from './nav-guards'
import { pause, resume } from './pausing' import { pause, resume } from './pausing'
import { getOptions } from './options' import { getOptions } from './options'
export default function $meta (options = {}) { export default function $meta (options) {
options = options || {}
/** /**
* Returns an injector for server-side rendering. * Returns an injector for server-side rendering.
* @this {Object} - the Vue instance (a root component) * @this {Object} - the Vue instance (a root component)
@@ -15,16 +16,18 @@ export default function $meta (options = {}) {
const $root = this.$root const $root = this.$root
return { return {
getOptions: () => getOptions(options), 'getOptions': () => getOptions(options),
setOptions: ({ refreshOnceOnNavigation } = {}) => { 'setOptions': (newOptions) => {
if (refreshOnceOnNavigation) { const refreshNavKey = 'refreshOnceOnNavigation'
if (newOptions && newOptions[refreshNavKey]) {
options.refreshOnceOnNavigation = newOptions[refreshNavKey]
addNavGuards($root) addNavGuards($root)
} }
}, },
refresh: () => refresh($root, options), 'refresh': () => refresh($root, options),
inject: () => process.server ? inject($root, options) : showWarningNotSupportedInBrowserBundle('inject'), 'inject': () => process.server ? inject($root, options) : showWarningNotSupportedInBrowserBundle('inject'),
pause: () => pause($root), 'pause': () => pause($root),
resume: () => resume($root), 'resume': () => resume($root),
addApp: appId => addApp($root, appId, options) 'addApp': appId => addApp($root, appId, options)
} }
} }
+10 -7
View File
@@ -59,25 +59,28 @@ export const defaultOptions = {
ssrAppId ssrAppId
} }
// might be a bit ugly, but minimizes the browser bundles a bit
const defaultInfoKeys = Object.keys(defaultInfo)
// The metaInfo property keys which are used to disable escaping // The metaInfo property keys which are used to disable escaping
export const disableOptionKeys = [ export const disableOptionKeys = [
'__dangerouslyDisableSanitizers', defaultInfoKeys[12],
'__dangerouslyDisableSanitizersByTagID' defaultInfoKeys[13]
] ]
// List of metaInfo property keys which are configuration options (and dont generate html) // List of metaInfo property keys which are configuration options (and dont generate html)
export const metaInfoOptionKeys = [ export const metaInfoOptionKeys = [
'titleChunk', defaultInfoKeys[1],
'titleTemplate', defaultInfoKeys[2],
'changed', 'changed',
...disableOptionKeys ...disableOptionKeys
] ]
// List of metaInfo property keys which only generates attributes and no tags // List of metaInfo property keys which only generates attributes and no tags
export const metaInfoAttributeKeys = [ export const metaInfoAttributeKeys = [
'htmlAttrs', defaultInfoKeys[3],
'headAttrs', defaultInfoKeys[4],
'bodyAttrs' defaultInfoKeys[5]
] ]
// HTML elements which support the onload event // HTML elements which support the onload event
+2 -1
View File
@@ -83,7 +83,8 @@ export function escape (info, options, escapeOptions, escapeKeys) {
return escaped return escaped
} }
export function escapeMetaInfo (options, info, escapeSequences = []) { export function escapeMetaInfo (options, info, escapeSequences) {
escapeSequences = escapeSequences || []
// do not use destructuring for seq, it increases transpiled size // do not use destructuring for seq, it increases transpiled size
// due to var checks while we are guaranteed the structure of the cb // due to var checks while we are guaranteed the structure of the cb
const escapeOptions = { const escapeOptions = {
+8 -5
View File
@@ -3,8 +3,8 @@ import { defaultInfo } from './constants'
import { merge } from './merge' import { merge } from './merge'
import { inMetaInfoBranch } from './meta-helpers' import { inMetaInfoBranch } from './meta-helpers'
export function getComponentMetaInfo (options = {}, component) { export function getComponentMetaInfo (options, component) {
return getComponentOption(options, component, defaultInfo) return getComponentOption(options || {}, component, defaultInfo)
} }
/** /**
@@ -21,14 +21,17 @@ export function getComponentMetaInfo (options = {}, component) {
* @param {Object} [result={}] - result so far * @param {Object} [result={}] - result so far
* @return {Object} result - final aggregated result * @return {Object} result - final aggregated result
*/ */
export function getComponentOption (options = {}, component, result = {}) { export function getComponentOption (options, component, result) {
const { keyName } = options result = result || {}
const { $metaInfo, $options, $children } = component
if (component._inactive) { if (component._inactive) {
return result return result
} }
options = options || {}
const { keyName } = options
const { $metaInfo, $options, $children } = component
// only collect option data if it exists // only collect option data if it exists
if ($options[keyName]) { if ($options[keyName]) {
// if $metaInfo exists then [keyName] was defined as a function // if $metaInfo exists then [keyName] was defined as a function
+5 -2
View File
@@ -9,7 +9,10 @@ import { applyTemplate } from './template'
* @param {Object} component - the Vue instance to get meta info from * @param {Object} component - the Vue instance to get meta info from
* @return {Object} - returned meta info * @return {Object} - returned meta info
*/ */
export default function getMetaInfo (options = {}, info, escapeSequences = [], component) { export default function getMetaInfo (options, info, escapeSequences, component) {
options = options || {}
escapeSequences = escapeSequences || []
const { tagIDKeyName } = options const { tagIDKeyName } = options
// Remove all "template" tags from meta // Remove all "template" tags from meta
@@ -32,7 +35,7 @@ export default function getMetaInfo (options = {}, info, escapeSequences = [], c
if (info.meta) { if (info.meta) {
// remove meta items with duplicate vmid's // remove meta items with duplicate vmid's
info.meta = info.meta.filter((metaItem, index, arr) => { info.meta = info.meta.filter((metaItem, index, arr) => {
const hasVmid = metaItem.hasOwnProperty(tagIDKeyName) const hasVmid = !!metaItem[tagIDKeyName]
if (!hasVmid) { if (!hasVmid) {
return true return true
} }
+13 -6
View File
@@ -33,8 +33,10 @@ export function arrayMerge ({ component, tagIDKeyName, metaTemplateKeyName, cont
// when sourceItem explictly defines contentKeyName or innerHTML as undefined, its // when sourceItem explictly defines contentKeyName or innerHTML as undefined, its
// an indication that we need to skip the default behaviour or child has preference over parent // an indication that we need to skip the default behaviour or child has preference over parent
// which means we keep the targetItem and ignore/remove the sourceItem // which means we keep the targetItem and ignore/remove the sourceItem
if ((sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined) || if (
(sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined)) { (contentKeyName in sourceItem && sourceItem[contentKeyName] === undefined) ||
('innerHTML' in sourceItem && sourceItem.innerHTML === undefined)
) {
destination.push(targetItem) destination.push(targetItem)
// remove current index from source array so its not concatenated to destination below // remove current index from source array so its not concatenated to destination below
source.splice(sourceIndex, 1) source.splice(sourceIndex, 1)
@@ -75,11 +77,15 @@ export function arrayMerge ({ component, tagIDKeyName, metaTemplateKeyName, cont
return destination.concat(source) return destination.concat(source)
} }
export function merge (target, source, options = {}) { let warningShown = false
export function merge (target, source, options) {
options = options || {}
// remove properties explicitly set to false so child components can // remove properties explicitly set to false so child components can
// optionally _not_ overwrite the parents content // optionally _not_ overwrite the parents content
// (for array properties this is checked in arrayMerge) // (for array properties this is checked in arrayMerge)
if (source.hasOwnProperty('title') && source.title === undefined) { if (source.title === undefined) {
delete source.title delete source.title
} }
@@ -89,9 +95,10 @@ export function merge (target, source, options = {}) {
} }
for (const key in source[attrKey]) { for (const key in source[attrKey]) {
if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) { if (key in source[attrKey] && source[attrKey][key] === undefined) {
if (includes(booleanHtmlAttributes, key)) { if (includes(booleanHtmlAttributes, key) && !warningShown) {
warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details') warn('VueMeta: Please note that since v2 the value undefined is not used to indicate boolean attributes anymore, see migration guide for details')
warningShown = true
} }
delete source[attrKey][key] delete source[attrKey][key]
} }
+4 -2
View File
@@ -2,11 +2,13 @@ import { isUndefined, isObject } from '../utils/is-type'
import { rootConfigKey } from './constants' import { rootConfigKey } from './constants'
// Vue $root instance has a _vueMeta object property, otherwise its a boolean true // Vue $root instance has a _vueMeta object property, otherwise its a boolean true
export function hasMetaInfo (vm = this) { export function hasMetaInfo (vm) {
vm = vm || this
return vm && (vm[rootConfigKey] === true || isObject(vm[rootConfigKey])) return vm && (vm[rootConfigKey] === true || isObject(vm[rootConfigKey]))
} }
// a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
export function inMetaInfoBranch (vm = this) { export function inMetaInfoBranch (vm) {
vm = vm || this
return vm && !isUndefined(vm[rootConfigKey]) return vm && !isUndefined(vm[rootConfigKey])
} }
+22 -20
View File
@@ -15,19 +15,22 @@ export default function createMixin (Vue, options) {
// watch for client side component updates // watch for client side component updates
return { return {
beforeCreate () { beforeCreate () {
const $root = this.$root // https://github.com/terser/terser/issues/458
const $options = this.$options const $this = this
const $isServer = this.$isServer const $root = $this.$root
const $options = $this.$options
const $isServer = $this.$isServer
const $nextTick = $this.$nextTick
Object.defineProperty(this, '_hasMetaInfo', { Object.defineProperty($this, '_hasMetaInfo', {
configurable: true, configurable: true,
get () { get () {
// Show deprecation warning once when devtools enabled // Show deprecation warning once when devtools enabled
if (Vue.config.devtools && !$root[rootConfigKey]._shown) { if (Vue.config.devtools && !$root[rootConfigKey].deprecationWarningShown) {
warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead') warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead')
$root[rootConfigKey]._shown = true $root[rootConfigKey].deprecationWarningShown = true
} }
return hasMetaInfo(this) return hasMetaInfo($this)
} }
}) })
@@ -45,10 +48,10 @@ export default function createMixin (Vue, options) {
// to speed up updates we keep track of branches which have a component with vue-meta info defined // to speed up updates we keep track of branches which have a component with vue-meta info defined
// if _vueMeta = true it has info, if _vueMeta = false a child has info // if _vueMeta = true it has info, if _vueMeta = false a child has info
if (!this[rootConfigKey]) { if (!$this[rootConfigKey]) {
this[rootConfigKey] = true $this[rootConfigKey] = true
let parent = this.$parent let parent = $this.$parent
while (parent && parent !== $root) { while (parent && parent !== $root) {
if (isUndefined(parent[rootConfigKey])) { if (isUndefined(parent[rootConfigKey])) {
parent[rootConfigKey] = false parent[rootConfigKey] = false
@@ -60,9 +63,7 @@ export default function createMixin (Vue, options) {
// coerce function-style metaInfo to a computed prop so we can observe // coerce function-style metaInfo to a computed prop so we can observe
// it on creation // it on creation
if (isFunction($options[options.keyName])) { if (isFunction($options[options.keyName])) {
if (!$options.computed) { $options.computed = $options.computed || {}
$options.computed = {}
}
$options.computed.$metaInfo = $options[options.keyName] $options.computed.$metaInfo = $options[options.keyName]
if (!$isServer) { if (!$isServer) {
@@ -70,7 +71,7 @@ export default function createMixin (Vue, options) {
// when it changes (i.e. automatically handle async actions that affect metaInfo) // when it changes (i.e. automatically handle async actions that affect metaInfo)
// credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux) // credit for this suggestion goes to [Sébastien Chopin](https://github.com/Atinux)
ensuredPush($options, 'created', () => { ensuredPush($options, 'created', () => {
this.$watch('$metaInfo', () => { $this.$watch('$metaInfo', () => {
triggerUpdate($root, 'watcher') triggerUpdate($root, 'watcher')
}) })
}) })
@@ -88,7 +89,7 @@ export default function createMixin (Vue, options) {
ensuredPush($options, 'beforeMount', () => { ensuredPush($options, 'beforeMount', () => {
// if this Vue-app was server rendered, set the appId to 'ssr' // if this Vue-app was server rendered, set the appId to 'ssr'
// only one SSR app per page is supported // only one SSR app per page is supported
if ($root.$el && $root.$el.hasAttribute && $root.$el.hasAttribute('data-server-rendered')) { if ($root.$el && $root.$el.nodeType === 1 && $root.$el.hasAttribute('data-server-rendered')) {
$root[rootConfigKey].appId = options.ssrAppId $root[rootConfigKey].appId = options.ssrAppId
} }
}) })
@@ -101,7 +102,7 @@ export default function createMixin (Vue, options) {
$root[rootConfigKey].initializing = true $root[rootConfigKey].initializing = true
// refresh meta in nextTick so all child components have loaded // refresh meta in nextTick so all child components have loaded
this.$nextTick(function () { $nextTick(function () {
const { tags, metaInfo } = $root.$meta().refresh() const { tags, metaInfo } = $root.$meta().refresh()
// After ssr hydration (identifier by tags === false) check // After ssr hydration (identifier by tags === false) check
@@ -111,7 +112,7 @@ export default function createMixin (Vue, options) {
// current hook was called // current hook was called
// (during initialization all changes are blocked) // (during initialization all changes are blocked)
if (tags === false && $root[rootConfigKey].initialized === null) { if (tags === false && $root[rootConfigKey].initialized === null) {
this.$nextTick(() => triggerUpdate($root, 'initializing')) $nextTick(() => triggerUpdate($root, 'init'))
} }
$root[rootConfigKey].initialized = true $root[rootConfigKey].initialized = true
@@ -145,23 +146,24 @@ export default function createMixin (Vue, options) {
}, },
// TODO: move back into beforeCreate when Vue issue is resolved // TODO: move back into beforeCreate when Vue issue is resolved
destroyed () { destroyed () {
const $this = this
// do not trigger refresh: // do not trigger refresh:
// - when the component doesnt have a parent // - when the component doesnt have a parent
// - doesnt have metaInfo defined // - doesnt have metaInfo defined
if (!this.$parent || !hasMetaInfo(this)) { if (!$this.$parent || !hasMetaInfo($this)) {
return return
} }
// Wait that element is hidden before refreshing meta tags (to support animations) // Wait that element is hidden before refreshing meta tags (to support animations)
const interval = setInterval(() => { const interval = setInterval(() => {
if (this.$el && this.$el.offsetParent !== null) { if ($this.$el && $this.$el.offsetParent !== null) {
/* istanbul ignore next line */ /* istanbul ignore next line */
return return
} }
clearInterval(interval) clearInterval(interval)
triggerUpdate(this.$root, 'destroyed') triggerUpdate($this.$root, 'destroyed')
}, 50) }, 50)
} }
} }
+10 -9
View File
@@ -1,26 +1,27 @@
import { isFunction } from '../utils/is-type' import { isFunction } from '../utils/is-type'
import { rootConfigKey } from './constants' import { rootConfigKey } from './constants'
import { pause, resume } from './pausing'
export function addNavGuards (rootVm) { export function addNavGuards (rootVm) {
const router = rootVm.$router
// return when nav guards already added or no router exists // return when nav guards already added or no router exists
if (rootVm[rootConfigKey].navGuards || !rootVm.$router) { if (rootVm[rootConfigKey].navGuards || !router) {
/* istanbul ignore next */ /* istanbul ignore next */
return return
} }
rootVm[rootConfigKey].navGuards = true rootVm[rootConfigKey].navGuards = true
const $router = rootVm.$router router.beforeEach((to, from, next) => {
const $meta = rootVm.$meta() pause(rootVm)
$router.beforeEach((to, from, next) => {
$meta.pause()
next() next()
}) })
$router.afterEach(() => { router.afterEach(() => {
const { metaInfo } = $meta.resume() const { metaInfo } = resume(rootVm)
if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
if (metaInfo && isFunction(metaInfo.afterNavigation)) {
metaInfo.afterNavigation(metaInfo) metaInfo.afterNavigation(metaInfo)
} }
}) })
+15 -6
View File
@@ -5,13 +5,22 @@ export function setOptions (options) {
// combine options // combine options
options = isObject(options) ? options : {} options = isObject(options) ? options : {}
for (const key in defaultOptions) { // The options are set like this so they can
if (!options[key]) { // be minified by terser while keeping the
options[key] = defaultOptions[key] // user api intact
} // terser --mangle-properties keep_quoted=strict
/* eslint-disable dot-notation */
return {
keyName: options['keyName'] || defaultOptions.keyName,
attribute: options['attribute'] || defaultOptions.attribute,
ssrAttribute: options['ssrAttribute'] || defaultOptions.ssrAttribute,
tagIDKeyName: options['tagIDKeyName'] || defaultOptions.tagIDKeyName,
contentKeyName: options['contentKeyName'] || defaultOptions.contentKeyName,
metaTemplateKeyName: options['metaTemplateKeyName'] || defaultOptions.metaTemplateKeyName,
ssrAppId: options['ssrAppId'] || defaultOptions.ssrAppId,
refreshOnceOnNavigation: !!options['refreshOnceOnNavigation']
} }
/* eslint-enable dot-notation */
return options
} }
export function getOptions (options) { export function getOptions (options) {
+6 -6
View File
@@ -1,15 +1,15 @@
import { rootConfigKey } from './constants' import { rootConfigKey } from './constants'
export function pause (rootVm, refresh = true) { export function pause (rootVm, refresh) {
rootVm[rootConfigKey].paused = true rootVm[rootConfigKey].pausing = true
return () => resume(refresh) return () => resume(rootVm, refresh)
} }
export function resume (rootVm, refresh = true) { export function resume (rootVm, refresh) {
rootVm[rootConfigKey].paused = false rootVm[rootConfigKey].pausing = false
if (refresh) { if (refresh || refresh === undefined) {
return rootVm.$meta().refresh() return rootVm.$meta().refresh()
} }
} }
+1 -4
View File
@@ -14,10 +14,7 @@ export function applyTemplate ({ component, metaTemplateKeyName, contentKeyName
// return early if no template defined // return early if no template defined
if (!template) { if (!template) {
// cleanup faulty template properties // cleanup faulty template properties
if (headObject.hasOwnProperty(metaTemplateKeyName)) { delete headObject[metaTemplateKeyName]
delete headObject[metaTemplateKeyName]
}
return false return false
} }
+3 -3
View File
@@ -11,17 +11,17 @@
// which means the polyfills are removed for other build formats // which means the polyfills are removed for other build formats
const polyfill = process.env.NODE_ENV === 'test' const polyfill = process.env.NODE_ENV === 'test'
export function findIndex (array, predicate) { export function findIndex (array, predicate, thisArg) {
if (polyfill && !Array.prototype.findIndex) { if (polyfill && !Array.prototype.findIndex) {
// idx needs to be a Number, for..in returns string // idx needs to be a Number, for..in returns string
for (let idx = 0; idx < array.length; idx++) { for (let idx = 0; idx < array.length; idx++) {
if (predicate.call(arguments[2], array[idx], idx, array)) { if (predicate.call(thisArg, array[idx], idx, array)) {
return idx return idx
} }
} }
return -1 return -1
} }
return array.findIndex(predicate, arguments[2]) return array.findIndex(predicate, thisArg)
} }
export function toArray (arg) { export function toArray (arg) {
+11 -3
View File
@@ -1,5 +1,7 @@
import { toArray } from './array' import { toArray } from './array'
export const querySelector = (arg, el) => (el || document).querySelectorAll(arg)
export function getTag (tags, tag) { export function getTag (tags, tag) {
if (!tags[tag]) { if (!tags[tag]) {
tags[tag] = document.getElementsByTagName(tag)[0] tags[tag] = document.getElementsByTagName(tag)[0]
@@ -14,7 +16,9 @@ export function getElementsKey ({ body, pbody }) {
: (pbody ? 'pbody' : 'head') : (pbody ? 'pbody' : 'head')
} }
export function queryElements (parentNode, { appId, attribute, type, tagIDKeyName }, attributes = {}) { export function queryElements (parentNode, { appId, attribute, type, tagIDKeyName }, attributes) {
attributes = attributes || {}
const queries = [ const queries = [
`${type}[${attribute}="${appId}"]`, `${type}[${attribute}="${appId}"]`,
`${type}[data-${tagIDKeyName}]` `${type}[data-${tagIDKeyName}]`
@@ -27,9 +31,13 @@ export function queryElements (parentNode, { appId, attribute, type, tagIDKeyNam
return query return query
}) })
return toArray(parentNode.querySelectorAll(queries.join(', '))) return toArray(querySelector(queries.join(', '), parentNode))
} }
export function removeElementsByAppId ({ attribute }, appId) { export function removeElementsByAppId ({ attribute }, appId) {
toArray(document.querySelectorAll(`[${attribute}="${appId}"]`)).map(el => el.remove()) toArray(querySelector(`[${attribute}="${appId}"]`)).map(el => el.remove())
}
export function removeAttribute (el, attributeName) {
el.removeAttribute(attributeName)
} }
+2 -2
View File
@@ -257,7 +257,7 @@ describe('components', () => {
expect(guards.after).toBeDefined() expect(guards.after).toBeDefined()
guards.before(null, null, () => {}) guards.before(null, null, () => {})
expect(wrapper.vm.$root._vueMeta.paused).toBe(true) expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
guards.after() guards.after()
expect(afterNavigation).toHaveBeenCalled() expect(afterNavigation).toHaveBeenCalled()
@@ -292,7 +292,7 @@ describe('components', () => {
expect(guards.after).toBeDefined() expect(guards.after).toBeDefined()
guards.before(null, null, () => {}) guards.before(null, null, () => {})
expect(wrapper.vm.$root._vueMeta.paused).toBe(true) expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
guards.after() guards.after()
expect(afterNavigation).toHaveBeenCalled() expect(afterNavigation).toHaveBeenCalled()
+7 -7
View File
@@ -148,13 +148,13 @@ describe('plugin', () => {
warn.mockRestore() warn.mockRestore()
}) })
test('updates can be paused and resumed', async () => { test('updates can be pausing 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)
// because triggerUpdate & batchUpdate reside in the same file we cant mock them both, // because triggerUpdate & batchUpdate reside in the same file we cant mock them both,
// so just recreate the triggerUpdate fn by copying its implementation // so just recreate the triggerUpdate fn by copying its implementation
const triggerUpdateSpy = triggerUpdate.mockImplementation((vm, hookName) => { const triggerUpdateSpy = triggerUpdate.mockImplementation((vm, hookName) => {
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.pausing) {
// batch potential DOM updates to prevent extraneous re-rendering // batch potential DOM updates to prevent extraneous re-rendering
batchUpdateSpy(() => vm.$meta().refresh()) batchUpdateSpy(() => vm.$meta().refresh())
} }
@@ -185,7 +185,7 @@ describe('plugin', () => {
// no batchUpdate on initialization // no batchUpdate on initialization
expect(wrapper.vm.$root._vueMeta.initialized).toBe(false) expect(wrapper.vm.$root._vueMeta.initialized).toBe(false)
expect(wrapper.vm.$root._vueMeta.paused).toBeFalsy() expect(wrapper.vm.$root._vueMeta.pausing).toBeFalsy()
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1) expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
expect(batchUpdateSpy).not.toHaveBeenCalled() expect(batchUpdateSpy).not.toHaveBeenCalled()
jest.clearAllMocks() jest.clearAllMocks()
@@ -196,7 +196,7 @@ describe('plugin', () => {
// batchUpdate on normal update // batchUpdate on normal update
expect(wrapper.vm.$root._vueMeta.initialized).toBe(true) expect(wrapper.vm.$root._vueMeta.initialized).toBe(true)
expect(wrapper.vm.$root._vueMeta.paused).toBeFalsy() expect(wrapper.vm.$root._vueMeta.pausing).toBeFalsy()
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1) expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
expect(batchUpdateSpy).toHaveBeenCalledTimes(1) expect(batchUpdateSpy).toHaveBeenCalledTimes(1)
jest.clearAllMocks() jest.clearAllMocks()
@@ -205,9 +205,9 @@ describe('plugin', () => {
title = 'third title' title = 'third title'
wrapper.setProps({ title }) wrapper.setProps({ title })
// no batchUpdate when paused // no batchUpdate when pausing
expect(wrapper.vm.$root._vueMeta.initialized).toBe(true) expect(wrapper.vm.$root._vueMeta.initialized).toBe(true)
expect(wrapper.vm.$root._vueMeta.paused).toBe(true) expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1) expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
expect(batchUpdateSpy).not.toHaveBeenCalled() expect(batchUpdateSpy).not.toHaveBeenCalled()
jest.clearAllMocks() jest.clearAllMocks()
@@ -225,7 +225,7 @@ describe('plugin', () => {
// because triggerUpdate & batchUpdate reside in the same file we cant mock them both, // because triggerUpdate & batchUpdate reside in the same file we cant mock them both,
// so just recreate the triggerUpdate fn by copying its implementation // so just recreate the triggerUpdate fn by copying its implementation
triggerUpdate.mockImplementation((vm, hookName) => { triggerUpdate.mockImplementation((vm, hookName) => {
if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) { if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.pausing) {
// batch potential DOM updates to prevent extraneous re-rendering // batch potential DOM updates to prevent extraneous re-rendering
batchUpdateSpy(refreshSpy) batchUpdateSpy(refreshSpy)
} }
+2 -2
View File
@@ -23,7 +23,7 @@ describe('updaters', () => {
add: (tags) => { add: (tags) => {
typeTests.add.expect.forEach((expected, index) => { typeTests.add.expect.forEach((expected, index) => {
if (!['title', 'htmlAttrs', 'headAttrs', 'bodyAttrs'].includes(type)) { if (!['title', 'htmlAttrs', 'headAttrs', 'bodyAttrs'].includes(type)) {
expect(tags.addedTags[type][index].outerHTML).toBe(expected) expect(tags.tagsAdded[type][index].outerHTML).toBe(expected)
} }
expect(html.outerHTML).toContain(expected) expect(html.outerHTML).toContain(expected)
}) })
@@ -37,7 +37,7 @@ describe('updaters', () => {
typeTests.change.expect.forEach((expected, index) => { typeTests.change.expect.forEach((expected, index) => {
if (!['title', 'htmlAttrs', 'headAttrs', 'bodyAttrs'].includes(type)) { if (!['title', 'htmlAttrs', 'headAttrs', 'bodyAttrs'].includes(type)) {
expect(tags.addedTags[type][index].outerHTML).toBe(expected) expect(tags.tagsAdded[type][index].outerHTML).toBe(expected)
} }
expect(html.outerHTML).toContain(expected) expect(html.outerHTML).toContain(expected)
}) })
+1 -1
View File
@@ -19,7 +19,7 @@ export const useDist = stdEnv.test && stdEnv.ci
export function getVueMetaPath (browser) { export function getVueMetaPath (browser) {
if (useDist) { if (useDist) {
return path.resolve(__dirname, `../..${browser ? '/dist/vue-meta.js' : ''}`) return path.resolve(__dirname, `../..${browser ? '/dist/vue-meta.min.js' : ''}`)
} }
process.server = !browser process.server = !browser
+5 -5
View File
@@ -71,9 +71,9 @@ const metaInfoData = {
return () => { return () => {
const tags = defaultTest() const tags = defaultTest()
expect(tags.addedTags.meta.length).toBe(1) expect(tags.tagsAdded.meta.length).toBe(1)
// TODO: not sure if we really expect this // TODO: not sure if we really expect this
expect(tags.removedTags.meta.length).toBe(1) expect(tags.tagsRemoved.meta.length).toBe(1)
} }
} }
} }
@@ -143,9 +143,9 @@ const metaInfoData = {
} }
const tags = defaultTest() const tags = defaultTest()
expect(tags.addedTags.script[0].parentNode.tagName).toBe('HEAD') expect(tags.tagsAdded.script[0].parentNode.tagName).toBe('HEAD')
expect(tags.addedTags.script[1].parentNode.tagName).toBe('BODY') expect(tags.tagsAdded.script[1].parentNode.tagName).toBe('BODY')
expect(tags.addedTags.script[2].parentNode.tagName).toBe('BODY') expect(tags.tagsAdded.script[2].parentNode.tagName).toBe('BODY')
} else { } else {
// ssr doesnt generate data-body tags // ssr doesnt generate data-body tags
const bodyPrepended = this.expect[1] const bodyPrepended = this.expect[1]