2
0
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:
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 { 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
View File
@@ -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
View File
@@ -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
}
}
+4 -2
View File
@@ -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(() => {
+9 -8
View File
@@ -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 }
}
+6 -3
View File
@@ -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
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
* @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
View File
@@ -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 -2
View File
@@ -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
+2 -1
View File
@@ -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) {
+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
* @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) {
+3 -1
View File
@@ -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 ''
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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 = {
+8 -5
View File
@@ -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
+5 -2
View File
@@ -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
View File
@@ -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]
}
+4 -2
View File
@@ -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
View File
@@ -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)
}
}
+10 -9
View File
@@ -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
View File
@@ -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) {
+6 -6
View File
@@ -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()
}
}
+1 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
+2 -2
View File
@@ -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()
+7 -7
View File
@@ -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)
}
+2 -2
View File
@@ -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
View File
@@ -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
+5 -5
View File
@@ -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]