juejin-vue-meta
Version:
manage page meta info in Vue 2.0 server-rendered apps
1,054 lines (919 loc) • 32.3 kB
JavaScript
/**
* vue-meta v1.0.4
* (c) 2017 Declan de Wet
* @license MIT
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.VueMeta = factory());
}(this, (function () { 'use strict';
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/* eslint-disable no-unused-vars */
var getOwnPropertySymbols = Object.getOwnPropertySymbols;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var propIsEnumerable = Object.prototype.propertyIsEnumerable;
function toObject(val) {
if (val === null || val === undefined) {
throw new TypeError('Object.assign cannot be called with null or undefined');
}
return Object(val);
}
function shouldUseNative() {
try {
if (!Object.assign) {
return false;
}
// Detect buggy property enumeration order in older V8 versions.
// https://bugs.chromium.org/p/v8/issues/detail?id=4118
var test1 = new String('abc'); // eslint-disable-line no-new-wrappers
test1[5] = 'de';
if (Object.getOwnPropertyNames(test1)[0] === '5') {
return false;
}
// https://bugs.chromium.org/p/v8/issues/detail?id=3056
var test2 = {};
for (var i = 0; i < 10; i++) {
test2['_' + String.fromCharCode(i)] = i;
}
var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
return test2[n];
});
if (order2.join('') !== '0123456789') {
return false;
}
// https://bugs.chromium.org/p/v8/issues/detail?id=3056
var test3 = {};
'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
test3[letter] = letter;
});
if (Object.keys(Object.assign({}, test3)).join('') !==
'abcdefghijklmnopqrst') {
return false;
}
return true;
} catch (err) {
// We don't expect any of the above to throw, but better to be safe.
return false;
}
}
var index = shouldUseNative() ? Object.assign : function (target, source) {
var arguments$1 = arguments;
var from;
var to = toObject(target);
var symbols;
for (var s = 1; s < arguments.length; s++) {
from = Object(arguments$1[s]);
for (var key in from) {
if (hasOwnProperty.call(from, key)) {
to[key] = from[key];
}
}
if (getOwnPropertySymbols) {
symbols = getOwnPropertySymbols(from);
for (var i = 0; i < symbols.length; i++) {
if (propIsEnumerable.call(from, symbols[i])) {
to[symbols[i]] = from[symbols[i]];
}
}
}
}
return to;
};
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var index$1 = createCommonjsModule(function (module, exports) {
(function (root, factory) {
if (typeof undefined === 'function' && undefined.amd) {
undefined(factory);
} else {
module.exports = factory();
}
}(commonjsGlobal, function () {
function isMergeableObject(val) {
var nonNullObject = val && typeof val === 'object';
return nonNullObject
&& Object.prototype.toString.call(val) !== '[object RegExp]'
&& Object.prototype.toString.call(val) !== '[object Date]'
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {}
}
function cloneIfNecessary(value, optionsArgument) {
var clone = optionsArgument && optionsArgument.clone === true;
return (clone && isMergeableObject(value)) ? deepmerge(emptyTarget(value), value, optionsArgument) : value
}
function defaultArrayMerge(target, source, optionsArgument) {
var destination = target.slice();
source.forEach(function(e, i) {
if (typeof destination[i] === 'undefined') {
destination[i] = cloneIfNecessary(e, optionsArgument);
} else if (isMergeableObject(e)) {
destination[i] = deepmerge(target[i], e, optionsArgument);
} else if (target.indexOf(e) === -1) {
destination.push(cloneIfNecessary(e, optionsArgument));
}
});
return destination
}
function mergeObject(target, source, optionsArgument) {
var destination = {};
if (isMergeableObject(target)) {
Object.keys(target).forEach(function (key) {
destination[key] = cloneIfNecessary(target[key], optionsArgument);
});
}
Object.keys(source).forEach(function (key) {
if (!isMergeableObject(source[key]) || !target[key]) {
destination[key] = cloneIfNecessary(source[key], optionsArgument);
} else {
destination[key] = deepmerge(target[key], source[key], optionsArgument);
}
});
return destination
}
function deepmerge(target, source, optionsArgument) {
var array = Array.isArray(source);
var options = optionsArgument || { arrayMerge: defaultArrayMerge };
var arrayMerge = options.arrayMerge || defaultArrayMerge;
if (array) {
return Array.isArray(target) ? arrayMerge(target, source, optionsArgument) : cloneIfNecessary(source, optionsArgument)
} else {
return mergeObject(target, source, optionsArgument)
}
}
deepmerge.all = function deepmergeAll(array, optionsArgument) {
if (!Array.isArray(array) || array.length < 2) {
throw new Error('first argument should be an array with at least two elements')
}
// we are sure there are at least 2 values, so it is safe to have no initial value
return array.reduce(function(prev, next) {
return deepmerge(prev, next, optionsArgument)
})
};
return deepmerge
}));
});
/**
* lodash (Custom Build) <https://lodash.com/>
* Build: `lodash modularize exports="npm" -o ./`
* Copyright jQuery Foundation and other contributors <https://jquery.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/** `Object#toString` result references. */
var objectTag = '[object Object]';
/**
* Checks if `value` is a host object in IE < 9.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a host object, else `false`.
*/
function isHostObject(value) {
// Many host objects are `Object` objects that can coerce to strings
// despite having improperly defined `toString` methods.
var result = false;
if (value != null && typeof value.toString != 'function') {
try {
result = !!(value + '');
} catch (e) {}
}
return result;
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/** Used for built-in method references. */
var funcProto = Function.prototype;
var objectProto = Object.prototype;
/** Used to resolve the decompiled source of functions. */
var funcToString = funcProto.toString;
/** Used to check objects for own properties. */
var hasOwnProperty$1 = objectProto.hasOwnProperty;
/** Used to infer the `Object` constructor. */
var objectCtorString = funcToString.call(Object);
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var objectToString = objectProto.toString;
/** Built-in value references. */
var getPrototype = overArg(Object.getPrototypeOf, Object);
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return !!value && typeof value == 'object';
}
/**
* Checks if `value` is a plain object, that is, an object created by the
* `Object` constructor or one with a `[[Prototype]]` of `null`.
*
* @static
* @memberOf _
* @since 0.8.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
* @example
*
* function Foo() {
* this.a = 1;
* }
*
* _.isPlainObject(new Foo);
* // => false
*
* _.isPlainObject([1, 2, 3]);
* // => false
*
* _.isPlainObject({ 'x': 0, 'y': 0 });
* // => true
*
* _.isPlainObject(Object.create(null));
* // => true
*/
function isPlainObject(value) {
if (!isObjectLike(value) ||
objectToString.call(value) != objectTag || isHostObject(value)) {
return false;
}
var proto = getPrototype(value);
if (proto === null) {
return true;
}
var Ctor = hasOwnProperty$1.call(proto, 'constructor') && proto.constructor;
return (typeof Ctor == 'function' &&
Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
}
var index$2 = isPlainObject;
/**
* checks if passed argument is an array
* @param {any} arr - the object to check
* @return {Boolean} - true if `arr` is an array
*/
function isArray (arr) {
return Array.isArray
? Array.isArray(arr)
: Object.prototype.toString.call(arr) === '[object Array]'
}
/**
* Returns the `opts.option` $option value of the given `opts.component`.
* If methods are encountered, they will be bound to the component context.
* If `opts.deep` is true, will recursively merge all child component
* `opts.option` $option values into the returned result.
*
* @param {Object} opts - options
* @param {Object} opts.component - Vue component to fetch option data from
* @param {String} opts.option - what option to look for
* @param {Boolean} opts.deep - look for data in child components as well?
* @param {Function} opts.arrayMerge - how should arrays be merged?
* @param {Object} [result={}] - result so far
* @return {Object} result - final aggregated result
*/
function getComponentOption (opts, result) {
if ( result === void 0 ) result = {};
var component = opts.component;
var option = opts.option;
var deep = opts.deep;
var arrayMerge = opts.arrayMerge;
var $options = component.$options;
// only collect option data if it exists
if (typeof $options[option] !== 'undefined' && $options[option] !== null) {
var data = $options[option];
// if option is a function, replace it with it's result
if (typeof data === 'function') {
data = data.call(component);
}
if (typeof data === 'object') {
// merge with existing options
result = index$1(result, data, {
clone: true,
arrayMerge: arrayMerge
});
} else {
result = data;
}
}
// collect & aggregate child options if deep = true
if (deep && component.$children.length) {
component.$children.forEach(function (childComponent) {
result = getComponentOption({
component: childComponent,
option: option,
deep: deep,
arrayMerge: arrayMerge
}, result);
});
}
return result
}
var escapeHTML = function (str) { return typeof window === 'undefined'
// server-side escape sequence
? String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
// client-side escape sequence
: String(str)
.replace(/&/g, '\u0026')
.replace(/</g, '\u003c')
.replace(/>/g, '\u003e')
.replace(/"/g, '\u0022')
.replace(/'/g, '\u0027'); };
function _getMetaInfo (options) {
if ( options === void 0 ) options = {};
var keyName = options.keyName;
var tagIDKeyName = options.tagIDKeyName;
/**
* Returns the correct meta info for the given component
* (child components will overwrite parent meta info)
*
* @param {Object} component - the Vue instance to get meta info from
* @return {Object} - returned meta info
*/
return function getMetaInfo (component) {
// set some sane defaults
var defaultInfo = {
title: '',
titleChunk: '',
titleTemplate: '%s',
htmlAttrs: {},
bodyAttrs: {},
meta: [],
base: [],
link: [],
style: [],
script: [],
noscript: [],
__dangerouslyDisableSanitizers: []
};
// collect & aggregate all metaInfo $options
var info = getComponentOption({
component: component,
option: keyName,
deep: true,
arrayMerge: function arrayMerge (target, source) {
// we concat the arrays without merging objects contained therein,
// but we check for a `vmid` property on each object in the array
// using an O(1) lookup associative array exploit
// note the use of "for in" - we are looping through arrays here, not
// plain objects
var destination = [];
for (var targetIndex in target) {
var targetItem = target[targetIndex];
var shared = false;
for (var sourceIndex in source) {
var sourceItem = source[sourceIndex];
if (targetItem[tagIDKeyName] && targetItem[tagIDKeyName] === sourceItem[tagIDKeyName]) {
shared = true;
break
}
}
if (!shared) {
destination.push(targetItem);
}
}
return destination.concat(source)
}
});
info.title = info.title || '';
// backup the title chunk in case user wants access to it
if (info.title) {
info.titleChunk = info.title;
}
// replace title with populated template
if (info.titleTemplate && info.titleChunk) {
info.title = info.titleTemplate.replace(/%s/g, info.titleChunk);
}
// convert base tag to an array so it can be handled the same way
// as the other tags
if (info.base) {
info.base = Object.keys(info.base).length ? [info.base] : [];
}
// sanitizes potentially dangerous characters
var escape = function (info) { return Object.keys(info).reduce(function (escaped, key) {
var ref = info.__dangerouslyDisableSanitizers;
var isDisabled = ref && ref.indexOf(key) > -1;
var val = info[key];
escaped[key] = val;
if (key === '__dangerouslyDisableSanitizers') {
return escaped
}
if (!isDisabled) {
if (typeof val === 'string') {
escaped[key] = escapeHTML(val);
} else if (index$2(val)) {
escaped[key] = escape(val);
} else if (isArray(val)) {
escaped[key] = val.map(escape);
} else {
escaped[key] = val;
}
} else {
escaped[key] = val;
}
return escaped
}, {}); };
// merge with defaults
info = index$1(defaultInfo, info);
// begin sanitization
info = escape(info);
return info
}
}
function _titleGenerator (options) {
if ( options === void 0 ) options = {};
var attribute = options.attribute;
/**
* Generates title output for the server
*
* @param {'title'} type - the string "title"
* @param {String} data - the title text
* @return {Object} - the title generator
*/
return function titleGenerator (type, data) {
return {
text: function text () {
return ("<" + type + " " + attribute + "=\"true\">" + data + "</" + type + ">")
}
}
}
}
function _attrsGenerator (options) {
if ( options === void 0 ) options = {};
var attribute = options.attribute;
/**
* Generates tag attributes for use on the server.
*
* @param {('bodyAttrs'|'htmlAttrs')} type - the type of attributes to generate
* @param {Object} data - the attributes to generate
* @return {Object} - the attribute generator
*/
return function attrsGenerator (type, data) {
return {
text: function text () {
var attributeStr = '';
var watchedAttrs = [];
for (var attr in data) {
if (data.hasOwnProperty(attr)) {
watchedAttrs.push(attr);
attributeStr += (typeof data[attr] !== 'undefined'
? (attr + "=\"" + (data[attr]) + "\"")
: attr) + " ";
}
}
attributeStr += attribute + "=\"" + (watchedAttrs.join(',')) + "\"";
return attributeStr.trim()
}
}
}
}
function _tagGenerator (options) {
if ( options === void 0 ) options = {};
var attribute = options.attribute;
/**
* Generates meta, base, link, style, script, noscript tags for use on the server
*
* @param {('meta'|'base'|'link'|'style'|'script'|'noscript')} the name of the tag
* @param {(Array<Object>|Object)} tags - an array of tag objects or a single object in case of base
* @return {Object} - the tag generator
*/
return function tagGenerator (type, tags) {
return {
text: function text () {
// build a string containing all tags of this type
return tags.reduce(function (tagsStr, tag) {
// build a string containing all attributes of this tag
var attrs = Object.keys(tag).reduce(function (attrsStr, attr) {
switch (attr) {
// these attributes are treated as children on the tag
case 'innerHTML':
case 'cssText':
case 'once':
return attrsStr
// these form the attribute list for this tag
default:
if (attr === options.tagIDKeyName) {
return (attrsStr + " data-" + attr + "=\"" + (tag[attr]) + "\"")
}
return typeof tag[attr] === 'undefined'
? (attrsStr + " " + attr)
: (attrsStr + " " + attr + "=\"" + (tag[attr]) + "\"")
}
}, '').trim();
// grab child content from one of these attributes, if possible
var content = tag.innerHTML || tag.cssText || '';
// these tag types will have content inserted
var closed = ['noscript', 'script', 'style'].indexOf(type) === -1;
// generate tag exactly without any other redundance attribute
var observeTag = tag.once
? ''
: (attribute + "=\"true\" ");
// the final string for this specific tag
return closed
? (tagsStr + "<" + type + " " + observeTag + attrs + "/>")
: (tagsStr + "<" + type + " " + observeTag + attrs + ">" + content + "</" + type + ">")
}, '')
}
}
}
}
function _generateServerInjector (options) {
if ( options === void 0 ) options = {};
/**
* Converts a meta info property to one that can be stringified on the server
*
* @param {String} type - the type of data to convert
* @param {(String|Object|Array<Object>)} data - the data value
* @return {Object} - the new injector
*/
return function generateServerInjector (type, data) {
switch (type) {
case 'title':
return _titleGenerator(options)(type, data)
case 'htmlAttrs':
case 'bodyAttrs':
return _attrsGenerator(options)(type, data)
default:
return _tagGenerator(options)(type, data)
}
}
}
function _inject (options) {
if ( options === void 0 ) options = {};
/**
* Converts the state of the meta info object such that each item
* can be compiled to a tag string on the server
*
* @this {Object} - Vue instance - ideally the root component
* @return {Object} - server meta info with `toString` methods
*/
return function inject () {
// get meta info with sensible defaults
var info = _getMetaInfo(options)(this.$root);
// generate server injectors
for (var key in info) {
if (info.hasOwnProperty(key) && key !== 'titleTemplate' && key !== 'titleChunk') {
info[key] = _generateServerInjector(options)(key, info[key]);
}
}
return info
}
}
function _updateTitle () {
/**
* updates the document title
*
* @param {String} title - the new title of the document
*/
return function updateTitle (title) {
if ( title === void 0 ) title = document.title;
document.title = title;
}
}
function _updateTagAttributes (options) {
if ( options === void 0 ) options = {};
var attribute = options.attribute;
/**
* updates the document's html tag attributes
*
* @param {Object} attrs - the new document html attributes
* @param {HTMLElement} tag - the HTMLElment tag to update with new attrs
*/
return function updateTagAttributes (attrs, tag) {
var vueMetaAttrString = tag.getAttribute(attribute);
var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
var toRemove = [].concat(vueMetaAttrs);
for (var attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
var val = attrs[attr] || '';
tag.setAttribute(attr, val);
if (vueMetaAttrs.indexOf(attr) === -1) {
vueMetaAttrs.push(attr);
}
var saveIndex = toRemove.indexOf(attr);
if (saveIndex !== -1) {
toRemove.splice(saveIndex, 1);
}
}
}
var i = toRemove.length - 1;
for (; i >= 0; i--) {
tag.removeAttribute(toRemove[i]);
}
if (vueMetaAttrs.length === toRemove.length) {
tag.removeAttribute(attribute);
} else {
tag.setAttribute(attribute, vueMetaAttrs.join(','));
}
}
}
// borrow the slice method
var toArray = Function.prototype.call.bind(Array.prototype.slice);
function _updateTags (options) {
if ( options === void 0 ) options = {};
var attribute = options.attribute;
/**
* Updates meta tags inside <head> on the client. Borrowed from `react-helmet`:
* https://github.com/nfl/react-helmet/blob/004d448f8de5f823d10f838b02317521180f34da/src/Helmet.js#L195-L245
*
* @param {('meta'|'base'|'link'|'style'|'script'|'noscript')} type - the name of the tag
* @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 function updateTags (type, tags, headTag) {
var nodes = headTag.querySelectorAll((type + "[" + attribute + "]"));
var oldTags = toArray(nodes);
var newTags = [];
var indexToDelete;
if (tags.length > 1) {
// remove duplicates that could have been found by merging tags
// which include a mixin with metaInfo and that mixin is used
// by multiple components on the same page
var found = [];
tags = tags.map(function (x) {
var k = JSON.stringify(x);
if (found.indexOf(k) < 0) {
found.push(k);
return x
}
}).filter(function (x) { return x; });
}
if (tags && tags.length) {
tags.forEach(function (tag) {
var newElement = document.createElement(type);
for (var attr in tag) {
if (tag.hasOwnProperty(attr)) {
if (attr === 'innerHTML') {
newElement.innerHTML = tag.innerHTML;
} else if (attr === 'cssText') {
if (newElement.styleSheet) {
newElement.styleSheet.cssText = tag.cssText;
} else {
newElement.appendChild(document.createTextNode(tag.cssText));
}
} else if (attr === options.tagIDKeyName) {
var _attr = "data-" + attr;
var value = (typeof tag[attr] === 'undefined') ? '' : tag[attr];
newElement.setAttribute(_attr, value);
} else {
var value$1 = (typeof tag[attr] === 'undefined') ? '' : tag[attr];
newElement.setAttribute(attr, value$1);
}
}
}
newElement.setAttribute(attribute, 'true');
// Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
if (oldTags.some(function (existingTag, index) {
indexToDelete = index;
return newElement.isEqualNode(existingTag)
})) {
oldTags.splice(indexToDelete, 1);
} else {
newTags.push(newElement);
}
});
}
oldTags.forEach(function (tag) { return tag.parentNode.removeChild(tag); });
newTags.forEach(function (tag) { return headTag.appendChild(tag); });
return { oldTags: oldTags, newTags: newTags }
}
}
function _updateClientMetaInfo (options) {
if ( options === void 0 ) options = {};
var ssrAttribute = options.ssrAttribute;
/**
* Performs client-side updates when new meta info is received
*
* @param {Object} newInfo - the meta info to update to
*/
return function updateClientMetaInfo (newInfo) {
var htmlTag = document.getElementsByTagName('html')[0];
// if this is not a server render, then update
if (htmlTag.getAttribute(ssrAttribute) === null) {
// initialize tracked changes
var addedTags = {};
var removedTags = {};
Object.keys(newInfo).forEach(function (key) {
switch (key) {
// update the title
case 'title':
_updateTitle(options)(newInfo.title);
break
// update attributes
case 'htmlAttrs':
case 'bodyAttrs':
_updateTagAttributes(options)(newInfo[key], key === 'htmlAttrs' ? htmlTag : document.getElementsByTagName('body')[0]);
break
// ignore these
case 'titleChunk':
case 'titleTemplate':
case 'changed':
case '__dangerouslyDisableSanitizers':
break
// catch-all update tags
default:
var ref = _updateTags(options)(key, newInfo[key], document.getElementsByTagName('head')[0]);
var oldTags = ref.oldTags;
var newTags = ref.newTags;
if (newTags.length) {
addedTags[key] = newTags;
removedTags[key] = oldTags;
}
}
});
// emit "event" with new info
if (typeof newInfo.changed === 'function') {
newInfo.changed(newInfo, addedTags, removedTags);
}
} else {
// remove the server render attribute so we can update on changes
htmlTag.removeAttribute(ssrAttribute);
}
}
}
function _refresh (options) {
if ( options === void 0 ) options = {};
/**
* When called, will update the current meta info with new meta info.
* Useful when updating meta info as the result of an asynchronous
* action that resolves after the initial render takes place.
*
* Credit to [Sébastien Chopin](https://github.com/Atinux) for the suggestion
* to implement this method.
*
* @return {Object} - new meta info
*/
return function refresh () {
var info = _getMetaInfo(options)(this.$root);
_updateClientMetaInfo(options)(info);
return info
}
}
function _$meta (options) {
if ( options === void 0 ) options = {};
/**
* Returns an injector for server-side rendering.
* @this {Object} - the Vue instance (a root component)
* @return {Object} - injector
*/
return function $meta () {
return {
inject: _inject(options).bind(this),
refresh: _refresh(options).bind(this)
}
}
}
// fallback to timers if rAF not present
var stopUpdate = clearTimeout;
var startUpdate = function (cb) { return setTimeout(cb, 0); };
/**
* Performs a batched update. Uses requestAnimationFrame to prevent
* calling a function too many times in quick succession.
* You need to pass it an ID (which can initially be `null`),
* but be sure to overwrite that ID with the return value of batchUpdate.
*
* @param {(null|Number)} id - the ID of this update
* @param {Function} callback - the update to perform
* @return {Number} id - a new ID
*/
function batchUpdate (id, callback) {
stopUpdate(id);
return startUpdate(function () {
id = null;
callback();
})
}
/**
* These are constant variables used throughout the application.
*/
// This is the name of the component option that contains all the information that
// gets converted to the various meta tags & attributes for the page.
var VUE_META_KEY_NAME = 'metaInfo';
// This is the attribute vue-meta augments on elements to know which it should
// manage and which it should ignore.
var VUE_META_ATTRIBUTE = 'data-vue-meta';
// This is the attribute that goes on the `html` tag to inform `vue-meta`
// that the server has already generated the meta tags for the initial render.
var VUE_META_SERVER_RENDERED_ATTRIBUTE = 'data-vue-meta-server-rendered';
// This is the property that tells vue-meta to overwrite (instead of append)
// an item in a tag list. For example, if you have two `meta` tag list items
// that both have `vmid` of "description", then vue-meta will overwrite the
// shallowest one with the deepest one.
var VUE_META_TAG_LIST_ID_KEY_NAME = 'vmid';
// automatic install
if (typeof Vue !== 'undefined') {
Vue.use(VueMeta);
}
/**
* Plugin install function.
* @param {Function} Vue - the Vue constructor.
*/
function VueMeta (Vue, options) {
if ( options === void 0 ) options = {};
// set some default options
var defaultOptions = {
keyName: VUE_META_KEY_NAME,
attribute: VUE_META_ATTRIBUTE,
ssrAttribute: VUE_META_SERVER_RENDERED_ATTRIBUTE,
tagIDKeyName: VUE_META_TAG_LIST_ID_KEY_NAME
};
// combine options
options = index(defaultOptions, options);
// bind the $meta method to this component instance
Vue.prototype.$meta = _$meta(options);
// store an id to keep track of DOM updates
var batchID = null;
// watch for client side component updates
Vue.mixin({
beforeCreate: function beforeCreate () {
// Add a marker to know if it uses metaInfo
if (typeof this.$options[options.keyName] !== 'undefined') {
this._hasMetaInfo = true;
}
// coerce function-style metaInfo to a computed prop so we can observe
// it on creation
if (typeof this.$options[options.keyName] === 'function') {
if (typeof this.$options.computed === 'undefined') {
this.$options.computed = {};
}
this.$options.computed.$metaInfo = this.$options[options.keyName];
}
},
created: function created () {
var this$1 = this;
// if computed $metaInfo exists, watch it for updates & trigger a refresh
// 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)
if (this.$metaInfo) {
this.$watch('$metaInfo', function () {
// batch potential DOM updates to prevent extraneous re-rendering
batchID = batchUpdate(batchID, function () { return this$1.$meta().refresh(); });
});
}
},
beforeMount: function beforeMount () {
var this$1 = this;
// batch potential DOM updates to prevent extraneous re-rendering
if (this._hasMetaInfo) {
batchID = batchUpdate(batchID, function () { return this$1.$meta().refresh(); });
}
},
destroyed: function destroyed () {
var this$1 = this;
// do not trigger refresh on the server side
if (this.$isServer) { return }
// re-render meta data when returning from a child component to parent
if (this._hasMetaInfo) {
batchID = batchUpdate(batchID, function () { return this$1.$meta().refresh(); });
}
}
});
}
var version = "1.0.4";
VueMeta.version = version;
return VueMeta;
})));