mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-09 00:22:26 +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:
@@ -5,6 +5,7 @@ import babel from 'rollup-plugin-babel'
|
||||
import replace from 'rollup-plugin-replace'
|
||||
import { terser } from 'rollup-plugin-terser'
|
||||
import defaultsDeep from 'lodash/defaultsDeep'
|
||||
import { defaultOptions } from '../src/shared/constants'
|
||||
|
||||
const pkg = require('../package.json')
|
||||
|
||||
@@ -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({
|
||||
plugins = [],
|
||||
...config
|
||||
@@ -51,17 +87,18 @@ function rollupConfig({
|
||||
'process.env.VERSION': `"${version}"`,
|
||||
'process.server' : isBrowserBuild ? 'false' : 'true',
|
||||
/* remove unused stuff from deepmerge */
|
||||
|
||||
// remove react stuff from is-mergeable-object
|
||||
'|| isReactElement(value)': '|| false',
|
||||
// we always provide an arrayMerge, remove default
|
||||
'|| defaultArrayMerge' : '',
|
||||
// we dont provide a custom merge
|
||||
// clone is a deprecated option we dont use
|
||||
'options.clone' : 'false',
|
||||
// we dont provide a custom merge
|
||||
'options.customMerge' : 'false',
|
||||
// dont use this
|
||||
'deepmerge.all = ' : 'false;'
|
||||
// we dont use this helper
|
||||
'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'),
|
||||
},
|
||||
plugins: [
|
||||
terser()
|
||||
terser(terserOpts)
|
||||
]
|
||||
},
|
||||
// common js build
|
||||
@@ -137,7 +174,7 @@ export default [
|
||||
format: 'es'
|
||||
},
|
||||
plugins: [
|
||||
terser()
|
||||
terser(terserOpts)
|
||||
],
|
||||
external: Object.keys(pkg.dependencies)
|
||||
}
|
||||
|
||||
+7
-6
@@ -1,13 +1,14 @@
|
||||
import { toArray } from '../utils/array'
|
||||
import { querySelector, removeAttribute } from '../utils/elements'
|
||||
|
||||
const callbacks = []
|
||||
|
||||
export function isDOMLoaded (d = document) {
|
||||
return d.readyState !== 'loading'
|
||||
export function isDOMLoaded (d) {
|
||||
return (d || document).readyState !== 'loading'
|
||||
}
|
||||
|
||||
export function isDOMComplete (d = document) {
|
||||
return d.readyState === 'complete'
|
||||
export function isDOMComplete (d) {
|
||||
return (d || document).readyState === 'complete'
|
||||
}
|
||||
|
||||
export function waitDOMLoaded () {
|
||||
@@ -69,7 +70,7 @@ export function applyCallbacks (matchElement) {
|
||||
|
||||
let elements = []
|
||||
if (!matchElement) {
|
||||
elements = toArray(document.querySelectorAll(selector))
|
||||
elements = toArray(querySelector(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
|
||||
* will fail isEqualNode on the client
|
||||
*/
|
||||
element.removeAttribute('onload')
|
||||
removeAttribute(element, 'onload')
|
||||
|
||||
callback(element)
|
||||
}
|
||||
|
||||
+15
-4
@@ -17,7 +17,9 @@ import updateClientMetaInfo from './updateClientMetaInfo'
|
||||
*
|
||||
* @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
|
||||
if (!rootVm[rootConfigKey]) {
|
||||
showWarningNotSupported()
|
||||
@@ -30,11 +32,16 @@ export default function refresh (rootVm, options = {}) {
|
||||
const metaInfo = getMetaInfo(options, rawInfo, clientSequences, rootVm)
|
||||
|
||||
const { appId } = rootVm[rootConfigKey]
|
||||
const tags = updateClientMetaInfo(appId, options, metaInfo)
|
||||
let tags = updateClientMetaInfo(appId, options, metaInfo)
|
||||
|
||||
// emit "event" with new info
|
||||
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()
|
||||
@@ -46,5 +53,9 @@ export default function refresh (rootVm, options = {}) {
|
||||
clearAppsMetaInfo(true)
|
||||
}
|
||||
|
||||
return { vm: rootVm, metaInfo, tags }
|
||||
return {
|
||||
vm: rootVm,
|
||||
metaInfo: metaInfo, // eslint-disable-line object-shorthand
|
||||
tags
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export function triggerUpdate (rootVm, hookName) {
|
||||
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
|
||||
batchUpdate(() => rootVm.$meta().refresh())
|
||||
}
|
||||
@@ -24,7 +24,9 @@ export function triggerUpdate (rootVm, hookName) {
|
||||
* @param {Function} callback - the update to perform
|
||||
* @return {Number} id - a new ID
|
||||
*/
|
||||
export function batchUpdate (callback, timeout = 10) {
|
||||
export function batchUpdate (callback, timeout) {
|
||||
timeout = timeout || 10
|
||||
|
||||
clearTimeout(batchId)
|
||||
|
||||
batchId = setTimeout(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { metaInfoOptionKeys, metaInfoAttributeKeys, tagsSupportingOnload } from '../shared/constants'
|
||||
import { isArray } from '../utils/is-type'
|
||||
import { includes } from '../utils/array'
|
||||
import { getTag } from '../utils/elements'
|
||||
import { getTag, removeAttribute } from '../utils/elements'
|
||||
import { addCallbacks, addListeners } from './load'
|
||||
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
|
||||
*/
|
||||
export default function updateClientMetaInfo (appId, options = {}, newInfo) {
|
||||
export default function updateClientMetaInfo (appId, options, newInfo) {
|
||||
options = options || {}
|
||||
const { ssrAttribute, ssrAppId } = options
|
||||
|
||||
// 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 (appId === ssrAppId && htmlTag.hasAttribute(ssrAttribute)) {
|
||||
// remove the server render attribute so we can update on (next) changes
|
||||
htmlTag.removeAttribute(ssrAttribute)
|
||||
removeAttribute(htmlTag, ssrAttribute)
|
||||
|
||||
// add load callbacks if the
|
||||
let addLoadListeners = false
|
||||
@@ -39,8 +40,8 @@ export default function updateClientMetaInfo (appId, options = {}, newInfo) {
|
||||
}
|
||||
|
||||
// initialize tracked changes
|
||||
const addedTags = {}
|
||||
const removedTags = {}
|
||||
const tagsAdded = {}
|
||||
const tagsRemoved = {}
|
||||
|
||||
for (const type in newInfo) {
|
||||
// ignore these
|
||||
@@ -75,10 +76,10 @@ export default function updateClientMetaInfo (appId, options = {}, newInfo) {
|
||||
)
|
||||
|
||||
if (newTags.length) {
|
||||
addedTags[type] = newTags
|
||||
removedTags[type] = oldTags
|
||||
tagsAdded[type] = newTags
|
||||
tagsRemoved[type] = oldTags
|
||||
}
|
||||
}
|
||||
|
||||
return { addedTags, removedTags }
|
||||
return { tagsAdded, tagsRemoved }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { booleanHtmlAttributes } from '../../shared/constants'
|
||||
import { includes } from '../../utils/array'
|
||||
import { removeAttribute } from '../../utils/elements'
|
||||
|
||||
// keep a local map of attribute values
|
||||
// instead of adding it to the html
|
||||
@@ -11,11 +12,13 @@ export const attributeMap = {}
|
||||
* @param {Object} attrs - the new document html attributes
|
||||
* @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)
|
||||
if (vueMetaAttrString) {
|
||||
attributeMap[type] = JSON.parse(decodeURI(vueMetaAttrString))
|
||||
tag.removeAttribute(attribute)
|
||||
removeAttribute(tag, attribute)
|
||||
}
|
||||
|
||||
const data = attributeMap[type] || {}
|
||||
@@ -62,7 +65,7 @@ export default function updateAttribute (appId, { attribute } = {}, type, attrs,
|
||||
|
||||
tag.setAttribute(attr, attrValue)
|
||||
} else {
|
||||
tag.removeAttribute(attr)
|
||||
removeAttribute(tag, attr)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -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
|
||||
* @return {Object} - a representation of what tags changed
|
||||
*/
|
||||
export default function updateTag (appId, options = {}, type, tags, head, body) {
|
||||
const { attribute, tagIDKeyName } = options
|
||||
export default function updateTag (appId, options, type, tags, head, body) {
|
||||
const { attribute, tagIDKeyName } = options || {}
|
||||
|
||||
const dataAttributes = commonDataAttributes.slice()
|
||||
dataAttributes.push(tagIDKeyName)
|
||||
@@ -46,20 +46,20 @@ export default function updateTag (appId, options = {}, type, tags, head, body)
|
||||
const newElement = document.createElement(type)
|
||||
newElement.setAttribute(attribute, appId)
|
||||
|
||||
for (const attr in tag) {
|
||||
Object.keys(tag).forEach((attr) => {
|
||||
/* istanbul ignore next */
|
||||
if (!tag.hasOwnProperty(attr) || includes(tagProperties, attr)) {
|
||||
continue
|
||||
if (includes(tagProperties, attr)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (attr === 'innerHTML') {
|
||||
newElement.innerHTML = tag.innerHTML
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
if (attr === 'json') {
|
||||
newElement.innerHTML = JSON.stringify(tag.json)
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
if (attr === 'cssText') {
|
||||
@@ -69,12 +69,12 @@ export default function updateTag (appId, options = {}, type, tags, head, body)
|
||||
} else {
|
||||
newElement.appendChild(document.createTextNode(tag.cssText))
|
||||
}
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
if (attr === 'callback') {
|
||||
newElement.onload = () => tag[attr](newElement)
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
const _attr = includes(dataAttributes, attr)
|
||||
@@ -83,12 +83,12 @@ export default function updateTag (appId, options = {}, type, tags, head, body)
|
||||
|
||||
const isBooleanAttribute = includes(booleanHtmlAttributes, attr)
|
||||
if (isBooleanAttribute && !tag[attr]) {
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
const value = isBooleanAttribute ? '' : tag[attr]
|
||||
newElement.setAttribute(_attr, value)
|
||||
}
|
||||
})
|
||||
|
||||
const oldElements = currentElements[getElementsKey(tag)]
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import { hasMetaInfo } from './shared/meta-helpers'
|
||||
* Plugin install function.
|
||||
* @param {Function} Vue - the Vue constructor.
|
||||
*/
|
||||
function install (Vue, options = {}) {
|
||||
function install (Vue, options) {
|
||||
if (Vue.__vuemeta_installed) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ import { serverSequences } from '../shared/escaping'
|
||||
import { setOptions } from '../shared/options'
|
||||
import generateServerInjector from './generateServerInjector'
|
||||
|
||||
export default function generate (rawInfo, options = {}) {
|
||||
const metaInfo = getMetaInfo(setOptions(options), rawInfo, serverSequences)
|
||||
export default function generate (rawInfo, options) {
|
||||
options = setOptions(options)
|
||||
const metaInfo = getMetaInfo(options, rawInfo, serverSequences)
|
||||
|
||||
const serverInjector = generateServerInjector(options, metaInfo)
|
||||
return serverInjector.injectors
|
||||
|
||||
@@ -7,7 +7,8 @@ import { booleanHtmlAttributes } from '../../shared/constants'
|
||||
* @param {Object} data - the attributes to generate
|
||||
* @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 = ''
|
||||
|
||||
for (const attr in data) {
|
||||
|
||||
@@ -14,7 +14,10 @@ import {
|
||||
* @param {(Array<Object>|Object)} tags - an array of tag objects or a single object in case of base
|
||||
* @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]
|
||||
|
||||
if (!tags || !tags.length) {
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
* @param {String} data - the title text
|
||||
* @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) {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import generateServerInjector from './generateServerInjector'
|
||||
* @vm {Object} - Vue instance - ideally the root component
|
||||
* @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
|
||||
if (!rootVm[rootConfigKey]) {
|
||||
showWarningNotSupported()
|
||||
|
||||
+12
-9
@@ -6,7 +6,8 @@ import { addNavGuards } from './nav-guards'
|
||||
import { pause, resume } from './pausing'
|
||||
import { getOptions } from './options'
|
||||
|
||||
export default function $meta (options = {}) {
|
||||
export default function $meta (options) {
|
||||
options = options || {}
|
||||
/**
|
||||
* Returns an injector for server-side rendering.
|
||||
* @this {Object} - the Vue instance (a root component)
|
||||
@@ -15,16 +16,18 @@ export default function $meta (options = {}) {
|
||||
const $root = this.$root
|
||||
|
||||
return {
|
||||
getOptions: () => getOptions(options),
|
||||
setOptions: ({ refreshOnceOnNavigation } = {}) => {
|
||||
if (refreshOnceOnNavigation) {
|
||||
'getOptions': () => getOptions(options),
|
||||
'setOptions': (newOptions) => {
|
||||
const refreshNavKey = 'refreshOnceOnNavigation'
|
||||
if (newOptions && newOptions[refreshNavKey]) {
|
||||
options.refreshOnceOnNavigation = newOptions[refreshNavKey]
|
||||
addNavGuards($root)
|
||||
}
|
||||
},
|
||||
refresh: () => refresh($root, options),
|
||||
inject: () => process.server ? inject($root, options) : showWarningNotSupportedInBrowserBundle('inject'),
|
||||
pause: () => pause($root),
|
||||
resume: () => resume($root),
|
||||
addApp: appId => addApp($root, appId, options)
|
||||
'refresh': () => refresh($root, options),
|
||||
'inject': () => process.server ? inject($root, options) : showWarningNotSupportedInBrowserBundle('inject'),
|
||||
'pause': () => pause($root),
|
||||
'resume': () => resume($root),
|
||||
'addApp': appId => addApp($root, appId, options)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -59,25 +59,28 @@ export const defaultOptions = {
|
||||
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
|
||||
export const disableOptionKeys = [
|
||||
'__dangerouslyDisableSanitizers',
|
||||
'__dangerouslyDisableSanitizersByTagID'
|
||||
defaultInfoKeys[12],
|
||||
defaultInfoKeys[13]
|
||||
]
|
||||
|
||||
// List of metaInfo property keys which are configuration options (and dont generate html)
|
||||
export const metaInfoOptionKeys = [
|
||||
'titleChunk',
|
||||
'titleTemplate',
|
||||
defaultInfoKeys[1],
|
||||
defaultInfoKeys[2],
|
||||
'changed',
|
||||
...disableOptionKeys
|
||||
]
|
||||
|
||||
// List of metaInfo property keys which only generates attributes and no tags
|
||||
export const metaInfoAttributeKeys = [
|
||||
'htmlAttrs',
|
||||
'headAttrs',
|
||||
'bodyAttrs'
|
||||
defaultInfoKeys[3],
|
||||
defaultInfoKeys[4],
|
||||
defaultInfoKeys[5]
|
||||
]
|
||||
|
||||
// HTML elements which support the onload event
|
||||
|
||||
@@ -83,7 +83,8 @@ export function escape (info, options, escapeOptions, escapeKeys) {
|
||||
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
|
||||
// due to var checks while we are guaranteed the structure of the cb
|
||||
const escapeOptions = {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { defaultInfo } from './constants'
|
||||
import { merge } from './merge'
|
||||
import { inMetaInfoBranch } from './meta-helpers'
|
||||
|
||||
export function getComponentMetaInfo (options = {}, component) {
|
||||
return getComponentOption(options, component, defaultInfo)
|
||||
export function getComponentMetaInfo (options, component) {
|
||||
return getComponentOption(options || {}, component, defaultInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,14 +21,17 @@ export function getComponentMetaInfo (options = {}, component) {
|
||||
* @param {Object} [result={}] - result so far
|
||||
* @return {Object} result - final aggregated result
|
||||
*/
|
||||
export function getComponentOption (options = {}, component, result = {}) {
|
||||
const { keyName } = options
|
||||
const { $metaInfo, $options, $children } = component
|
||||
export function getComponentOption (options, component, result) {
|
||||
result = result || {}
|
||||
|
||||
if (component._inactive) {
|
||||
return result
|
||||
}
|
||||
|
||||
options = options || {}
|
||||
const { keyName } = options
|
||||
const { $metaInfo, $options, $children } = component
|
||||
|
||||
// only collect option data if it exists
|
||||
if ($options[keyName]) {
|
||||
// if $metaInfo exists then [keyName] was defined as a function
|
||||
|
||||
@@ -9,7 +9,10 @@ import { applyTemplate } from './template'
|
||||
* @param {Object} component - the Vue instance to get meta info from
|
||||
* @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
|
||||
// Remove all "template" tags from meta
|
||||
|
||||
@@ -32,7 +35,7 @@ export default function getMetaInfo (options = {}, info, escapeSequences = [], c
|
||||
if (info.meta) {
|
||||
// remove meta items with duplicate vmid's
|
||||
info.meta = info.meta.filter((metaItem, index, arr) => {
|
||||
const hasVmid = metaItem.hasOwnProperty(tagIDKeyName)
|
||||
const hasVmid = !!metaItem[tagIDKeyName]
|
||||
if (!hasVmid) {
|
||||
return true
|
||||
}
|
||||
|
||||
+13
-6
@@ -33,8 +33,10 @@ export function arrayMerge ({ component, tagIDKeyName, metaTemplateKeyName, cont
|
||||
// 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
|
||||
// which means we keep the targetItem and ignore/remove the sourceItem
|
||||
if ((sourceItem.hasOwnProperty(contentKeyName) && sourceItem[contentKeyName] === undefined) ||
|
||||
(sourceItem.hasOwnProperty('innerHTML') && sourceItem.innerHTML === undefined)) {
|
||||
if (
|
||||
(contentKeyName in sourceItem && sourceItem[contentKeyName] === undefined) ||
|
||||
('innerHTML' in sourceItem && sourceItem.innerHTML === undefined)
|
||||
) {
|
||||
destination.push(targetItem)
|
||||
// remove current index from source array so its not concatenated to destination below
|
||||
source.splice(sourceIndex, 1)
|
||||
@@ -75,11 +77,15 @@ export function arrayMerge ({ component, tagIDKeyName, metaTemplateKeyName, cont
|
||||
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
|
||||
// optionally _not_ overwrite the parents content
|
||||
// (for array properties this is checked in arrayMerge)
|
||||
if (source.hasOwnProperty('title') && source.title === undefined) {
|
||||
if (source.title === undefined) {
|
||||
delete source.title
|
||||
}
|
||||
|
||||
@@ -89,9 +95,10 @@ export function merge (target, source, options = {}) {
|
||||
}
|
||||
|
||||
for (const key in source[attrKey]) {
|
||||
if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
|
||||
if (includes(booleanHtmlAttributes, key)) {
|
||||
if (key in source[attrKey] && source[attrKey][key] === undefined) {
|
||||
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')
|
||||
warningShown = true
|
||||
}
|
||||
delete source[attrKey][key]
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ import { isUndefined, isObject } from '../utils/is-type'
|
||||
import { rootConfigKey } from './constants'
|
||||
|
||||
// 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]))
|
||||
}
|
||||
|
||||
// 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])
|
||||
}
|
||||
|
||||
+22
-20
@@ -15,19 +15,22 @@ export default function createMixin (Vue, options) {
|
||||
// watch for client side component updates
|
||||
return {
|
||||
beforeCreate () {
|
||||
const $root = this.$root
|
||||
const $options = this.$options
|
||||
const $isServer = this.$isServer
|
||||
// https://github.com/terser/terser/issues/458
|
||||
const $this = this
|
||||
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,
|
||||
get () {
|
||||
// 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')
|
||||
$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
|
||||
// if _vueMeta = true it has info, if _vueMeta = false a child has info
|
||||
if (!this[rootConfigKey]) {
|
||||
this[rootConfigKey] = true
|
||||
if (!$this[rootConfigKey]) {
|
||||
$this[rootConfigKey] = true
|
||||
|
||||
let parent = this.$parent
|
||||
let parent = $this.$parent
|
||||
while (parent && parent !== $root) {
|
||||
if (isUndefined(parent[rootConfigKey])) {
|
||||
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
|
||||
// it on creation
|
||||
if (isFunction($options[options.keyName])) {
|
||||
if (!$options.computed) {
|
||||
$options.computed = {}
|
||||
}
|
||||
$options.computed = $options.computed || {}
|
||||
$options.computed.$metaInfo = $options[options.keyName]
|
||||
|
||||
if (!$isServer) {
|
||||
@@ -70,7 +71,7 @@ export default function createMixin (Vue, options) {
|
||||
// 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)
|
||||
ensuredPush($options, 'created', () => {
|
||||
this.$watch('$metaInfo', () => {
|
||||
$this.$watch('$metaInfo', () => {
|
||||
triggerUpdate($root, 'watcher')
|
||||
})
|
||||
})
|
||||
@@ -88,7 +89,7 @@ export default function createMixin (Vue, options) {
|
||||
ensuredPush($options, 'beforeMount', () => {
|
||||
// if this Vue-app was server rendered, set the appId to 'ssr'
|
||||
// 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
|
||||
}
|
||||
})
|
||||
@@ -101,7 +102,7 @@ export default function createMixin (Vue, options) {
|
||||
$root[rootConfigKey].initializing = true
|
||||
|
||||
// refresh meta in nextTick so all child components have loaded
|
||||
this.$nextTick(function () {
|
||||
$nextTick(function () {
|
||||
const { tags, metaInfo } = $root.$meta().refresh()
|
||||
|
||||
// After ssr hydration (identifier by tags === false) check
|
||||
@@ -111,7 +112,7 @@ export default function createMixin (Vue, options) {
|
||||
// current hook was called
|
||||
// (during initialization all changes are blocked)
|
||||
if (tags === false && $root[rootConfigKey].initialized === null) {
|
||||
this.$nextTick(() => triggerUpdate($root, 'initializing'))
|
||||
$nextTick(() => triggerUpdate($root, 'init'))
|
||||
}
|
||||
|
||||
$root[rootConfigKey].initialized = true
|
||||
@@ -145,23 +146,24 @@ export default function createMixin (Vue, options) {
|
||||
},
|
||||
// TODO: move back into beforeCreate when Vue issue is resolved
|
||||
destroyed () {
|
||||
const $this = this
|
||||
// do not trigger refresh:
|
||||
// - when the component doesnt have a parent
|
||||
// - doesnt have metaInfo defined
|
||||
if (!this.$parent || !hasMetaInfo(this)) {
|
||||
if (!$this.$parent || !hasMetaInfo($this)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Wait that element is hidden before refreshing meta tags (to support animations)
|
||||
const interval = setInterval(() => {
|
||||
if (this.$el && this.$el.offsetParent !== null) {
|
||||
if ($this.$el && $this.$el.offsetParent !== null) {
|
||||
/* istanbul ignore next line */
|
||||
return
|
||||
}
|
||||
|
||||
clearInterval(interval)
|
||||
|
||||
triggerUpdate(this.$root, 'destroyed')
|
||||
triggerUpdate($this.$root, 'destroyed')
|
||||
}, 50)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import { isFunction } from '../utils/is-type'
|
||||
import { rootConfigKey } from './constants'
|
||||
import { pause, resume } from './pausing'
|
||||
|
||||
export function addNavGuards (rootVm) {
|
||||
const router = rootVm.$router
|
||||
|
||||
// return when nav guards already added or no router exists
|
||||
if (rootVm[rootConfigKey].navGuards || !rootVm.$router) {
|
||||
if (rootVm[rootConfigKey].navGuards || !router) {
|
||||
/* istanbul ignore next */
|
||||
return
|
||||
}
|
||||
|
||||
rootVm[rootConfigKey].navGuards = true
|
||||
|
||||
const $router = rootVm.$router
|
||||
const $meta = rootVm.$meta()
|
||||
|
||||
$router.beforeEach((to, from, next) => {
|
||||
$meta.pause()
|
||||
router.beforeEach((to, from, next) => {
|
||||
pause(rootVm)
|
||||
next()
|
||||
})
|
||||
|
||||
$router.afterEach(() => {
|
||||
const { metaInfo } = $meta.resume()
|
||||
if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
|
||||
router.afterEach(() => {
|
||||
const { metaInfo } = resume(rootVm)
|
||||
|
||||
if (metaInfo && isFunction(metaInfo.afterNavigation)) {
|
||||
metaInfo.afterNavigation(metaInfo)
|
||||
}
|
||||
})
|
||||
|
||||
+15
-6
@@ -5,13 +5,22 @@ export function setOptions (options) {
|
||||
// combine options
|
||||
options = isObject(options) ? options : {}
|
||||
|
||||
for (const key in defaultOptions) {
|
||||
if (!options[key]) {
|
||||
options[key] = defaultOptions[key]
|
||||
}
|
||||
// The options are set like this so they can
|
||||
// be minified by terser while keeping the
|
||||
// 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']
|
||||
}
|
||||
|
||||
return options
|
||||
/* eslint-enable dot-notation */
|
||||
}
|
||||
|
||||
export function getOptions (options) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { rootConfigKey } from './constants'
|
||||
|
||||
export function pause (rootVm, refresh = true) {
|
||||
rootVm[rootConfigKey].paused = true
|
||||
export function pause (rootVm, refresh) {
|
||||
rootVm[rootConfigKey].pausing = true
|
||||
|
||||
return () => resume(refresh)
|
||||
return () => resume(rootVm, refresh)
|
||||
}
|
||||
|
||||
export function resume (rootVm, refresh = true) {
|
||||
rootVm[rootConfigKey].paused = false
|
||||
export function resume (rootVm, refresh) {
|
||||
rootVm[rootConfigKey].pausing = false
|
||||
|
||||
if (refresh) {
|
||||
if (refresh || refresh === undefined) {
|
||||
return rootVm.$meta().refresh()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,7 @@ export function applyTemplate ({ component, metaTemplateKeyName, contentKeyName
|
||||
// return early if no template defined
|
||||
if (!template) {
|
||||
// cleanup faulty template properties
|
||||
if (headObject.hasOwnProperty(metaTemplateKeyName)) {
|
||||
delete headObject[metaTemplateKeyName]
|
||||
}
|
||||
|
||||
delete headObject[metaTemplateKeyName]
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -11,17 +11,17 @@
|
||||
// which means the polyfills are removed for other build formats
|
||||
const polyfill = process.env.NODE_ENV === 'test'
|
||||
|
||||
export function findIndex (array, predicate) {
|
||||
export function findIndex (array, predicate, thisArg) {
|
||||
if (polyfill && !Array.prototype.findIndex) {
|
||||
// idx needs to be a Number, for..in returns string
|
||||
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 -1
|
||||
}
|
||||
return array.findIndex(predicate, arguments[2])
|
||||
return array.findIndex(predicate, thisArg)
|
||||
}
|
||||
|
||||
export function toArray (arg) {
|
||||
|
||||
+11
-3
@@ -1,5 +1,7 @@
|
||||
import { toArray } from './array'
|
||||
|
||||
export const querySelector = (arg, el) => (el || document).querySelectorAll(arg)
|
||||
|
||||
export function getTag (tags, tag) {
|
||||
if (!tags[tag]) {
|
||||
tags[tag] = document.getElementsByTagName(tag)[0]
|
||||
@@ -14,7 +16,9 @@ export function getElementsKey ({ body, pbody }) {
|
||||
: (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 = [
|
||||
`${type}[${attribute}="${appId}"]`,
|
||||
`${type}[data-${tagIDKeyName}]`
|
||||
@@ -27,9 +31,13 @@ export function queryElements (parentNode, { appId, attribute, type, tagIDKeyNam
|
||||
return query
|
||||
})
|
||||
|
||||
return toArray(parentNode.querySelectorAll(queries.join(', ')))
|
||||
return toArray(querySelector(queries.join(', '), parentNode))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ describe('components', () => {
|
||||
expect(guards.after).toBeDefined()
|
||||
|
||||
guards.before(null, null, () => {})
|
||||
expect(wrapper.vm.$root._vueMeta.paused).toBe(true)
|
||||
expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
|
||||
|
||||
guards.after()
|
||||
expect(afterNavigation).toHaveBeenCalled()
|
||||
@@ -292,7 +292,7 @@ describe('components', () => {
|
||||
expect(guards.after).toBeDefined()
|
||||
|
||||
guards.before(null, null, () => {})
|
||||
expect(wrapper.vm.$root._vueMeta.paused).toBe(true)
|
||||
expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
|
||||
|
||||
guards.after()
|
||||
expect(afterNavigation).toHaveBeenCalled()
|
||||
|
||||
@@ -148,13 +148,13 @@ describe('plugin', () => {
|
||||
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 batchUpdateSpy = batchUpdate.mockImplementation(_batchUpdate)
|
||||
// because triggerUpdate & batchUpdate reside in the same file we cant mock them both,
|
||||
// so just recreate the triggerUpdate fn by copying its implementation
|
||||
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
|
||||
batchUpdateSpy(() => vm.$meta().refresh())
|
||||
}
|
||||
@@ -185,7 +185,7 @@ describe('plugin', () => {
|
||||
|
||||
// no batchUpdate on initialization
|
||||
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(batchUpdateSpy).not.toHaveBeenCalled()
|
||||
jest.clearAllMocks()
|
||||
@@ -196,7 +196,7 @@ describe('plugin', () => {
|
||||
|
||||
// batchUpdate on normal update
|
||||
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(batchUpdateSpy).toHaveBeenCalledTimes(1)
|
||||
jest.clearAllMocks()
|
||||
@@ -205,9 +205,9 @@ describe('plugin', () => {
|
||||
title = 'third 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.paused).toBe(true)
|
||||
expect(wrapper.vm.$root._vueMeta.pausing).toBe(true)
|
||||
expect(triggerUpdateSpy).toHaveBeenCalledTimes(1)
|
||||
expect(batchUpdateSpy).not.toHaveBeenCalled()
|
||||
jest.clearAllMocks()
|
||||
@@ -225,7 +225,7 @@ describe('plugin', () => {
|
||||
// because triggerUpdate & batchUpdate reside in the same file we cant mock them both,
|
||||
// so just recreate the triggerUpdate fn by copying its implementation
|
||||
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
|
||||
batchUpdateSpy(refreshSpy)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ describe('updaters', () => {
|
||||
add: (tags) => {
|
||||
typeTests.add.expect.forEach((expected, index) => {
|
||||
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)
|
||||
})
|
||||
@@ -37,7 +37,7 @@ describe('updaters', () => {
|
||||
|
||||
typeTests.change.expect.forEach((expected, index) => {
|
||||
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)
|
||||
})
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export const useDist = stdEnv.test && stdEnv.ci
|
||||
|
||||
export function getVueMetaPath (browser) {
|
||||
if (useDist) {
|
||||
return path.resolve(__dirname, `../..${browser ? '/dist/vue-meta.js' : ''}`)
|
||||
return path.resolve(__dirname, `../..${browser ? '/dist/vue-meta.min.js' : ''}`)
|
||||
}
|
||||
|
||||
process.server = !browser
|
||||
|
||||
@@ -71,9 +71,9 @@ const metaInfoData = {
|
||||
return () => {
|
||||
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
|
||||
expect(tags.removedTags.meta.length).toBe(1)
|
||||
expect(tags.tagsRemoved.meta.length).toBe(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,9 +143,9 @@ const metaInfoData = {
|
||||
}
|
||||
const tags = defaultTest()
|
||||
|
||||
expect(tags.addedTags.script[0].parentNode.tagName).toBe('HEAD')
|
||||
expect(tags.addedTags.script[1].parentNode.tagName).toBe('BODY')
|
||||
expect(tags.addedTags.script[2].parentNode.tagName).toBe('BODY')
|
||||
expect(tags.tagsAdded.script[0].parentNode.tagName).toBe('HEAD')
|
||||
expect(tags.tagsAdded.script[1].parentNode.tagName).toBe('BODY')
|
||||
expect(tags.tagsAdded.script[2].parentNode.tagName).toBe('BODY')
|
||||
} else {
|
||||
// ssr doesnt generate data-body tags
|
||||
const bodyPrepended = this.expect[1]
|
||||
|
||||
Reference in New Issue
Block a user