diff --git a/.gitignore b/.gitignore
index ccf3301..8a3d346 100644
--- a/.gitignore
+++ b/.gitignore
@@ -40,7 +40,6 @@ package-lock.json
# built code
lib
-dist
es
.vue-meta
diff --git a/dist/vue-meta.common.js b/dist/vue-meta.common.js
new file mode 100644
index 0000000..1a464f6
--- /dev/null
+++ b/dist/vue-meta.common.js
@@ -0,0 +1,1296 @@
+/**
+ * vue-meta v2.0.3
+ * (c) 2019
+ * - Declan de Wet
+ * - Sébastien Chopin (@Atinux)
+ * - All the amazing contributors
+ * @license MIT
+ */
+
+'use strict';
+
+function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
+
+var deepmerge = _interopDefault(require('deepmerge'));
+
+var version = "2.0.3";
+
+// store an id to keep track of DOM updates
+var batchId = null;
+
+function triggerUpdate(vm, hookName) {
+ // if an update was triggered during initialization or when an update was triggered by the
+ // metaInfo watcher, set initialized to null
+ // then we keep falsy value but know we need to run a triggerUpdate after initialization
+ if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) {
+ vm.$root._vueMeta.initialized = null;
+ }
+
+ if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
+ // batch potential DOM updates to prevent extraneous re-rendering
+ batchUpdate(function () { return vm.$meta().refresh(); });
+ }
+}
+
+/**
+ * Performs a batched update.
+ *
+ * @param {(null|Number)} id - the ID of this update
+ * @param {Function} callback - the update to perform
+ * @return {Number} id - a new ID
+ */
+function batchUpdate(callback, timeout) {
+ if ( timeout === void 0 ) timeout = 10;
+
+ clearTimeout(batchId);
+
+ batchId = setTimeout(function () {
+ callback();
+ }, timeout);
+
+ return batchId
+}
+
+/**
+ * checks if passed argument is an array
+ * @param {any} arg - the object to check
+ * @return {Boolean} - true if `arg` is an array
+ */
+function isArray(arg) {
+ return Array.isArray(arg)
+}
+
+function isUndefined(arg) {
+ return typeof arg === 'undefined'
+}
+
+function isObject(arg) {
+ return typeof arg === 'object'
+}
+
+function isFunction(arg) {
+ return typeof arg === 'function'
+}
+
+function isString(arg) {
+ return typeof arg === 'string'
+}
+
+function ensureIsArray(arg, key) {
+ if (!key || !isObject(arg)) {
+ return isArray(arg) ? arg : []
+ }
+
+ if (!isArray(arg[key])) {
+ arg[key] = [];
+ }
+ return arg
+}
+
+function ensuredPush(object, key, el) {
+ ensureIsArray(object, key);
+
+ object[key].push(el);
+}
+
+// Vue $root instance has a _vueMeta object property, otherwise its a boolean true
+function hasMetaInfo(vm) {
+ if ( vm === void 0 ) vm = this;
+
+ return vm && (vm._vueMeta === true || isObject(vm._vueMeta))
+}
+
+// a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
+function inMetaInfoBranch(vm) {
+ if ( vm === void 0 ) vm = this;
+
+ return vm && !isUndefined(vm._vueMeta)
+}
+
+function addNavGuards(vm) {
+ // return when nav guards already added or no router exists
+ if (vm.$root._vueMeta.navGuards || !vm.$root.$router) {
+ /* istanbul ignore next */
+ return
+ }
+
+ vm.$root._vueMeta.navGuards = true;
+
+ var $router = vm.$root.$router;
+ var $meta = vm.$root.$meta();
+
+ $router.beforeEach(function (to, from, next) {
+ $meta.pause();
+ next();
+ });
+
+ $router.afterEach(function () {
+ var ref = $meta.resume();
+ var metaInfo = ref.metaInfo;
+ if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
+ metaInfo.afterNavigation(metaInfo);
+ }
+ });
+}
+
+var appId = 1;
+
+function createMixin(Vue, options) {
+ // for which Vue lifecycle hooks should the metaInfo be refreshed
+ var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount'];
+
+ // watch for client side component updates
+ return {
+ beforeCreate: function beforeCreate() {
+ var this$1 = this;
+
+ Object.defineProperty(this, '_hasMetaInfo', {
+ configurable: true,
+ get: function get() {
+ // Show deprecation warning once when devtools enabled
+ if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
+ console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); // eslint-disable-line no-console
+ this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true;
+ }
+ return hasMetaInfo(this)
+ }
+ });
+
+ // Add a marker to know if it uses metaInfo
+ // _vnode is used to know that it's attached to a real component
+ // useful if we use some mixin to add some meta tags (like nuxt-i18n)
+ if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
+ if (!this.$root._vueMeta) {
+ this.$root._vueMeta = { appId: appId };
+ appId++;
+ }
+
+ // 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._vueMeta) {
+ this._vueMeta = true;
+
+ var p = this.$parent;
+ while (p && p !== this.$root) {
+ if (isUndefined(p._vueMeta)) {
+ p._vueMeta = false;
+ }
+ p = p.$parent;
+ }
+ }
+
+ // coerce function-style metaInfo to a computed prop so we can observe
+ // it on creation
+ if (isFunction(this.$options[options.keyName])) {
+ if (!this.$options.computed) {
+ this.$options.computed = {};
+ }
+ this.$options.computed.$metaInfo = this.$options[options.keyName];
+
+ if (!this.$isServer) {
+ // 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)
+ ensuredPush(this.$options, 'created', function () {
+ this$1.$watch('$metaInfo', function () {
+ triggerUpdate(this, 'watcher');
+ });
+ });
+ }
+ }
+
+ // force an initial refresh on page load and prevent other lifecycleHooks
+ // to triggerUpdate until this initial refresh is finished
+ // this is to make sure that when a page is opened in an inactive tab which
+ // has throttled rAF/timers we still immediately set the page title
+ if (isUndefined(this.$root._vueMeta.initialized)) {
+ this.$root._vueMeta.initialized = this.$isServer;
+
+ if (!this.$root._vueMeta.initialized) {
+ ensuredPush(this.$options, 'beforeMount', function () {
+ // if this Vue-app was server rendered, set the appId to 'ssr'
+ // only one SSR app per page is supported
+ if (this$1.$root.$el && this$1.$root.$el.hasAttribute('data-server-rendered')) {
+ this$1.$root._vueMeta.appId = 'ssr';
+ }
+ });
+
+ // we use the mounted hook here as on page load
+ ensuredPush(this.$options, 'mounted', function () {
+ if (!this$1.$root._vueMeta.initialized) {
+ // used in triggerUpdate to check if a change was triggered
+ // during initialization
+ this$1.$root._vueMeta.initializing = true;
+
+ // refresh meta in nextTick so all child components have loaded
+ this$1.$nextTick(function () {
+ var this$1 = this;
+
+ var ref = this.$root.$meta().refresh();
+ var tags = ref.tags;
+ var metaInfo = ref.metaInfo;
+
+ // After ssr hydration (identifier by tags === false) check
+ // if initialized was set to null in triggerUpdate. That'd mean
+ // that during initilazation changes where triggered which need
+ // to be applied OR a metaInfo watcher was triggered before the
+ // current hook was called
+ // (during initialization all changes are blocked)
+ if (tags === false && this.$root._vueMeta.initialized === null) {
+ this.$nextTick(function () { return triggerUpdate(this$1, 'initializing'); });
+ }
+
+ this.$root._vueMeta.initialized = true;
+ delete this.$root._vueMeta.initializing;
+
+ // add the navigation guards if they havent been added yet
+ // they are needed for the afterNavigation callback
+ if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) {
+ addNavGuards(this);
+ }
+ });
+ }
+ });
+
+ // add the navigation guards if requested
+ if (options.refreshOnceOnNavigation) {
+ addNavGuards(this);
+ }
+ }
+ }
+
+ // do not trigger refresh on the server side
+ if (!this.$isServer) {
+ // no need to add this hooks on server side
+ updateOnLifecycleHook.forEach(function (lifecycleHook) {
+ ensuredPush(this$1.$options, lifecycleHook, function () { return triggerUpdate(this$1, lifecycleHook); });
+ });
+
+ // re-render meta data when returning from a child component to parent
+ ensuredPush(this.$options, 'destroyed', function () {
+ // Wait that element is hidden before refreshing meta tags (to support animations)
+ var interval = setInterval(function () {
+ if (this$1.$el && this$1.$el.offsetParent !== null) {
+ /* istanbul ignore next line */
+ return
+ }
+
+ clearInterval(interval);
+
+ if (!this$1.$parent) {
+ /* istanbul ignore next line */
+ return
+ }
+
+ triggerUpdate(this$1, 'destroyed');
+ }, 50);
+ });
+ }
+ }
+ }
+ }
+}
+
+/**
+ * These are constant variables used throughout the application.
+ */
+
+// set some sane defaults
+var defaultInfo = {
+ title: '',
+ titleChunk: '',
+ titleTemplate: '%s',
+ htmlAttrs: {},
+ bodyAttrs: {},
+ headAttrs: {},
+ base: [],
+ link: [],
+ meta: [],
+ style: [],
+ script: [],
+ noscript: [],
+ __dangerouslyDisableSanitizers: [],
+ __dangerouslyDisableSanitizersByTagID: {}
+};
+
+// 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 keyName = 'metaInfo';
+
+// This is the attribute vue-meta arguments on elements to know which it should
+// manage and which it should ignore.
+var 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 ssrAttribute = '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 tagIDKeyName = 'vmid';
+
+// This is the key name for possible meta templates
+var metaTemplateKeyName = 'template';
+
+// This is the key name for the content-holding property
+var contentKeyName = 'content';
+
+var defaultOptions = {
+ keyName: keyName,
+ attribute: attribute,
+ ssrAttribute: ssrAttribute,
+ tagIDKeyName: tagIDKeyName,
+ contentKeyName: contentKeyName,
+ metaTemplateKeyName: metaTemplateKeyName
+};
+
+// List of metaInfo property keys which are configuration options (and dont generate html)
+var metaInfoOptionKeys = [
+ 'titleChunk',
+ 'titleTemplate',
+ 'changed',
+ '__dangerouslyDisableSanitizers',
+ '__dangerouslyDisableSanitizersByTagID'
+];
+
+// The metaInfo property keys which are used to disable escaping
+var disableOptionKeys = [
+ '__dangerouslyDisableSanitizers',
+ '__dangerouslyDisableSanitizersByTagID'
+];
+
+// List of metaInfo property keys which only generates attributes and no tags
+var metaInfoAttributeKeys = [
+ 'htmlAttrs',
+ 'headAttrs',
+ 'bodyAttrs'
+];
+
+// HTML elements which dont have a head tag (shortened to our needs)
+// see: https://www.w3.org/TR/html52/document-metadata.html
+var tagsWithoutEndTag = ['base', 'meta', 'link'];
+
+// HTML elements which can have inner content (shortened to our needs)
+var tagsWithInnerContent = ['noscript', 'script', 'style'];
+
+// Attributes which are inserted as childNodes instead of HTMLAttribute
+var tagAttributeAsInnerContent = ['innerHTML', 'cssText'];
+
+// from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
+var booleanHtmlAttributes = [
+ 'allowfullscreen',
+ 'amp',
+ 'async',
+ 'autofocus',
+ 'autoplay',
+ 'checked',
+ 'compact',
+ 'controls',
+ 'declare',
+ 'default',
+ 'defaultchecked',
+ 'defaultmuted',
+ 'defaultselected',
+ 'defer',
+ 'disabled',
+ 'enabled',
+ 'formnovalidate',
+ 'hidden',
+ 'indeterminate',
+ 'inert',
+ 'ismap',
+ 'itemscope',
+ 'loop',
+ 'multiple',
+ 'muted',
+ 'nohref',
+ 'noresize',
+ 'noshade',
+ 'novalidate',
+ 'nowrap',
+ 'open',
+ 'pauseonexit',
+ 'readonly',
+ 'required',
+ 'reversed',
+ 'scoped',
+ 'seamless',
+ 'selected',
+ 'sortable',
+ 'truespeed',
+ 'typemustmatch',
+ 'visible'
+];
+
+function setOptions(options) {
+ // combine options
+ options = isObject(options) ? options : {};
+
+ for (var key in defaultOptions) {
+ if (!options[key]) {
+ options[key] = defaultOptions[key];
+ }
+ }
+
+ return options
+}
+
+function getOptions(options) {
+ var optionsCopy = {};
+ for (var key in options) {
+ optionsCopy[key] = options[key];
+ }
+ return optionsCopy
+}
+
+function pause(refresh) {
+ if ( refresh === void 0 ) refresh = true;
+
+ this.$root._vueMeta.paused = true;
+
+ return function () { return resume(refresh); }
+}
+
+function resume(refresh) {
+ if ( refresh === void 0 ) refresh = true;
+
+ this.$root._vueMeta.paused = false;
+
+ if (refresh) {
+ return this.$root.$meta().refresh()
+ }
+}
+
+function applyTemplate(ref, headObject, template, chunk) {
+ var component = ref.component;
+ var metaTemplateKeyName = ref.metaTemplateKeyName;
+ var contentKeyName = ref.contentKeyName;
+
+ if (isUndefined(template)) {
+ template = headObject[metaTemplateKeyName];
+ delete headObject[metaTemplateKeyName];
+ }
+
+ // return early if no template defined
+ if (!template) {
+ return false
+ }
+
+ if (isUndefined(chunk)) {
+ chunk = headObject[contentKeyName];
+ }
+
+ headObject[contentKeyName] = isFunction(template)
+ ? template.call(component, chunk)
+ : template.replace(/%s/g, chunk);
+
+ return true
+}
+
+/*
+ * To reduce build size, this file provides simple polyfills without
+ * overly excessive type checking and without modifying
+ * the global Array.prototype
+ * The polyfills are automatically removed in the commonjs build
+ * Also, only files in client/ & shared/ should use these functions
+ * files in server/ still use normal js function
+ */
+
+function findIndex(array, predicate) {
+ var arguments$1 = arguments;
+
+ if (!Array.prototype.findIndex) {
+ // idx needs to be a Number, for..in returns string
+ for (var idx = 0; idx < array.length; idx++) {
+ if (predicate.call(arguments$1[2], array[idx], idx, array)) {
+ return idx
+ }
+ }
+ return -1
+ }
+ return array.findIndex(predicate, arguments[2])
+}
+
+function toArray(arg) {
+ if (!Array.from) {
+ return Array.prototype.slice.call(arg)
+ }
+ return Array.from(arg)
+}
+
+function includes(array, value) {
+ if (!Array.prototype.includes) {
+ for (var idx in array) {
+ if (array[idx] === value) {
+ return true
+ }
+ }
+
+ return false
+ }
+ return array.includes(value)
+}
+
+var serverSequences = [
+ [/&/g, '&'],
+ [//g, '>'],
+ [/"/g, '"'],
+ [/'/g, ''']
+];
+
+var clientSequences = [
+ [/&/g, '\u0026'],
+ [//g, '\u003e'],
+ [/"/g, '\u0022'],
+ [/'/g, '\u0027']
+];
+
+// sanitizes potentially dangerous characters
+function escape(info, options, escapeOptions) {
+ var tagIDKeyName = options.tagIDKeyName;
+ var doEscape = escapeOptions.doEscape; if ( doEscape === void 0 ) doEscape = function (v) { return v; };
+ var escaped = {};
+
+ for (var key in info) {
+ var value = info[key];
+
+ // no need to escape configuration options
+ if (includes(metaInfoOptionKeys, key)) {
+ escaped[key] = value;
+ continue
+ }
+
+ var disableKey = disableOptionKeys[0];
+ if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
+ // this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
+ escaped[key] = value;
+ continue
+ }
+
+ var tagId = info[tagIDKeyName];
+ if (tagId) {
+ disableKey = disableOptionKeys[1];
+
+ // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
+ if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
+ escaped[key] = value;
+ continue
+ }
+ }
+
+ if (isString(value)) {
+ escaped[key] = doEscape(value);
+ } else if (isArray(value)) {
+ escaped[key] = value.map(function (v) {
+ return isObject(v)
+ ? escape(v, options, escapeOptions)
+ : doEscape(v)
+ });
+ } else if (isObject(value)) {
+ escaped[key] = escape(value, options, escapeOptions);
+ } else {
+ escaped[key] = value;
+ }
+ }
+
+ return escaped
+}
+
+function arrayMerge(ref, target, source) {
+ var component = ref.component;
+ var tagIDKeyName = ref.tagIDKeyName;
+ var metaTemplateKeyName = ref.metaTemplateKeyName;
+ var contentKeyName = ref.contentKeyName;
+
+ // we concat the arrays without merging objects contained in,
+ // but we check for a `vmid` property on each object in the array
+ // using an O(1) lookup associative array exploit
+ var destination = [];
+
+ target.forEach(function (targetItem, targetIndex) {
+ // no tagID so no need to check for duplicity
+ if (!targetItem[tagIDKeyName]) {
+ destination.push(targetItem);
+ return
+ }
+
+ var sourceIndex = findIndex(source, function (item) { return item[tagIDKeyName] === targetItem[tagIDKeyName]; });
+ var sourceItem = source[sourceIndex];
+
+ // source doesnt contain any duplicate vmid's, we can keep targetItem
+ if (sourceIndex === -1) {
+ destination.push(targetItem);
+ return
+ }
+
+ // 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)) {
+ destination.push(targetItem);
+ // remove current index from source array so its not concatenated to destination below
+ source.splice(sourceIndex, 1);
+ return
+ }
+
+ // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
+
+ // if source specifies null as content then ignore both the target as the source
+ if (sourceItem[contentKeyName] === null || sourceItem.innerHTML === null) {
+ // remove current index from source array so its not concatenated to destination below
+ source.splice(sourceIndex, 1);
+ return
+ }
+
+ // now we only need to check if the target has a template to combine it with the source
+ var targetTemplate = targetItem[metaTemplateKeyName];
+ if (!targetTemplate) {
+ return
+ }
+
+ var sourceTemplate = sourceItem[metaTemplateKeyName];
+
+ if (!sourceTemplate) {
+ // use parent template and child content
+ applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, targetTemplate);
+ } else if (!sourceItem[contentKeyName]) {
+ // use child template and parent content
+ applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, undefined, targetItem[contentKeyName]);
+ }
+ });
+
+ return destination.concat(source)
+}
+
+function merge(target, source, options) {
+ if ( options === void 0 ) 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) {
+ delete source.title;
+ }
+
+ metaInfoAttributeKeys.forEach(function (attrKey) {
+ if (!source[attrKey]) {
+ return
+ }
+
+ for (var key in source[attrKey]) {
+ if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
+ delete source[attrKey][key];
+ }
+ }
+ });
+
+ return deepmerge(target, source, {
+ arrayMerge: function (t, s) { return arrayMerge(options, t, s); }
+ })
+}
+
+/**
+ * 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 {Boolean} opts.deep - look for data in child components as well?
+ * @param {Function} opts.arrayMerge - how should arrays be merged?
+ * @param {String} opts.keyName - the name of the option to look for
+ * @param {Object} [result={}] - result so far
+ * @return {Object} result - final aggregated result
+ */
+function getComponentOption(options, component, result) {
+ if ( options === void 0 ) options = {};
+ if ( result === void 0 ) result = {};
+
+ var keyName = options.keyName;
+ var metaTemplateKeyName = options.metaTemplateKeyName;
+ var tagIDKeyName = options.tagIDKeyName;
+ var $options = component.$options;
+ var $children = component.$children;
+
+ if (component._inactive) {
+ return result
+ }
+
+ // only collect option data if it exists
+ if ($options[keyName]) {
+ var data = $options[keyName];
+
+ // if option is a function, replace it with it's result
+ if (isFunction(data)) {
+ data = data.call(component);
+ }
+
+ // ignore data if its not an object, then we keep our previous result
+ if (!isObject(data)) {
+ return result
+ }
+
+ // merge with existing options
+ result = merge(result, data, options);
+ }
+
+ // collect & aggregate child options if deep = true
+ if ($children.length) {
+ $children.forEach(function (childComponent) {
+ // check if the childComponent is in a branch
+ // return otherwise so we dont walk all component branches unnecessarily
+ if (!inMetaInfoBranch(childComponent)) {
+ return
+ }
+
+ result = getComponentOption(options, childComponent, result);
+ });
+ }
+
+ if (metaTemplateKeyName && result.meta) {
+ // apply templates if needed
+ result.meta.forEach(function (metaObject) { return applyTemplate(options, metaObject); });
+
+ // remove meta items with duplicate vmid's
+ result.meta = result.meta.filter(function (metaItem, index, arr) {
+ return (
+ // keep meta item if it doesnt has a vmid
+ !metaItem.hasOwnProperty(tagIDKeyName) ||
+ // or if it's the first item in the array with this vmid
+ index === findIndex(arr, function (item) { return item[tagIDKeyName] === metaItem[tagIDKeyName]; })
+ )
+ });
+ }
+
+ return result
+}
+
+/**
+ * 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
+ */
+function getMetaInfo(options, component, escapeSequences) {
+ if ( options === void 0 ) options = {};
+ if ( escapeSequences === void 0 ) escapeSequences = [];
+
+ // collect & aggregate all metaInfo $options
+ var info = getComponentOption(options, component, defaultInfo);
+
+ // Remove all "template" tags from meta
+
+ // 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.titleTemplate !== '%s') {
+ applyTemplate({ component: component, contentKeyName: 'title' }, info, info.titleTemplate, 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] : [];
+ }
+
+ var escapeOptions = {
+ doEscape: function (value) { return escapeSequences.reduce(function (val, ref) {
+ var v = ref[0];
+ var r = ref[1];
+
+ return val.replace(v, r);
+ }, value); }
+ };
+
+ disableOptionKeys.forEach(function (disableKey, index) {
+ if (index === 0) {
+ ensureIsArray(info, disableKey);
+ } else if (index === 1) {
+ for (var key in info[disableKey]) {
+ ensureIsArray(info[disableKey], key);
+ }
+ }
+
+ escapeOptions[disableKey] = info[disableKey];
+ });
+
+ // begin sanitization
+ info = escape(info, options, escapeOptions);
+
+ return info
+}
+
+/**
+ * Updates the document's html tag attributes
+ *
+ * @param {Object} attrs - the new document html attributes
+ * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs
+ */
+function updateAttribute(ref, attrs, tag) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+
+ var vueMetaAttrString = tag.getAttribute(attribute);
+ var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
+ var toRemove = toArray(vueMetaAttrs);
+
+ var keepIndexes = [];
+ for (var attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ var value = includes(booleanHtmlAttributes, attr)
+ ? ''
+ : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
+
+ tag.setAttribute(attr, value || '');
+
+ if (!includes(vueMetaAttrs, attr)) {
+ vueMetaAttrs.push(attr);
+ }
+
+ // filter below wont ever check -1
+ keepIndexes.push(toRemove.indexOf(attr));
+ }
+ }
+
+ var removedAttributesCount = toRemove
+ .filter(function (el, index) { return !includes(keepIndexes, index); })
+ .reduce(function (acc, attr) {
+ tag.removeAttribute(attr);
+ return acc + 1
+ }, 0);
+
+ if (vueMetaAttrs.length === removedAttributesCount) {
+ tag.removeAttribute(attribute);
+ } else {
+ tag.setAttribute(attribute, (vueMetaAttrs.sort()).join(','));
+ }
+}
+
+/**
+ * Updates the document title
+ *
+ * @param {String} title - the new title of the document
+ */
+function updateTitle(title) {
+ if ( title === void 0 ) title = document.title;
+
+ document.title = title;
+}
+
+/**
+ * Updates meta tags inside
and 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)} tags - an array of tag objects or a single object in case of base
+ * @return {Object} - a representation of what tags changed
+ */
+function updateTag(appId, ref, type, tags, headTag, bodyTag) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+ var tagIDKeyName = ref.tagIDKeyName;
+
+ var oldHeadTags = toArray(headTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"], " + type + "[data-" + tagIDKeyName + "]")));
+ var oldBodyTags = toArray(bodyTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"][data-body=\"true\"], " + type + "[data-" + tagIDKeyName + "][data-body=\"true\"]")));
+ var dataAttributes = [tagIDKeyName, 'body'];
+ var newTags = [];
+
+ 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.filter(function (x) {
+ var k = JSON.stringify(x);
+ var res = !includes(found, k);
+ found.push(k);
+ return res
+ });
+ }
+
+ if (tags.length) {
+ tags.forEach(function (tag) {
+ var newElement = document.createElement(type);
+
+ newElement.setAttribute(attribute, appId);
+
+ var oldTags = tag.body !== true ? oldHeadTags : oldBodyTags;
+
+ for (var attr in tag) {
+ if (tag.hasOwnProperty(attr)) {
+ if (attr === 'innerHTML') {
+ newElement.innerHTML = tag.innerHTML;
+ } else if (attr === 'cssText') {
+ if (newElement.styleSheet) {
+ /* istanbul ignore next */
+ newElement.styleSheet.cssText = tag.cssText;
+ } else {
+ newElement.appendChild(document.createTextNode(tag.cssText));
+ }
+ } else {
+ var _attr = includes(dataAttributes, attr)
+ ? ("data-" + attr)
+ : attr;
+ var value = isUndefined(tag[attr]) || includes(booleanHtmlAttributes, attr) ? '' : tag[attr];
+ newElement.setAttribute(_attr, value);
+ }
+ }
+ }
+
+ // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
+ var indexToDelete;
+ var hasEqualElement = oldTags.some(function (existingTag, index) {
+ indexToDelete = index;
+ return newElement.isEqualNode(existingTag)
+ });
+
+ if (hasEqualElement && (indexToDelete || indexToDelete === 0)) {
+ oldTags.splice(indexToDelete, 1);
+ } else {
+ newTags.push(newElement);
+ }
+ });
+ }
+
+ var oldTags = oldHeadTags.concat(oldBodyTags);
+ oldTags.forEach(function (tag) { return tag.parentNode.removeChild(tag); });
+ newTags.forEach(function (tag) {
+ if (tag.getAttribute('data-body') === 'true') {
+ bodyTag.appendChild(tag);
+ } else {
+ headTag.appendChild(tag);
+ }
+ });
+
+ return { oldTags: oldTags, newTags: newTags }
+}
+
+function getTag(tags, tag) {
+ if (!tags[tag]) {
+ tags[tag] = document.getElementsByTagName(tag)[0];
+ }
+
+ return tags[tag]
+}
+
+/**
+ * Performs client-side updates when new meta info is received
+ *
+ * @param {Object} newInfo - the meta info to update to
+ */
+function updateClientMetaInfo(appId, options, newInfo) {
+ if ( options === void 0 ) options = {};
+
+ var ssrAttribute = options.ssrAttribute;
+
+ // only cache tags for current update
+ var tags = {};
+
+ var htmlTag = getTag(tags, 'html');
+
+ // if this is a server render, then dont update
+ if (appId === 'ssr' && htmlTag.hasAttribute(ssrAttribute)) {
+ // remove the server render attribute so we can update on (next) changes
+ htmlTag.removeAttribute(ssrAttribute);
+ return false
+ }
+
+ // initialize tracked changes
+ var addedTags = {};
+ var removedTags = {};
+
+ for (var type in newInfo) {
+ // ignore these
+ if (includes(metaInfoOptionKeys, type)) {
+ continue
+ }
+
+ if (type === 'title') {
+ // update the title
+ updateTitle(newInfo.title);
+ continue
+ }
+
+ if (includes(metaInfoAttributeKeys, type)) {
+ var tagName = type.substr(0, 4);
+ updateAttribute(options, newInfo[type], getTag(tags, tagName));
+ continue
+ }
+
+ // tags should always be an array, ignore if it isnt
+ if (!isArray(newInfo[type])) {
+ continue
+ }
+
+ var ref = updateTag(
+ appId,
+ options,
+ type,
+ newInfo[type],
+ getTag(tags, 'head'),
+ getTag(tags, 'body')
+ );
+ var oldTags = ref.oldTags;
+ var newTags = ref.newTags;
+
+ if (newTags.length) {
+ addedTags[type] = newTags;
+ removedTags[type] = oldTags;
+ }
+ }
+
+ return { addedTags: addedTags, removedTags: removedTags }
+}
+
+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 metaInfo = getMetaInfo(options, this.$root, clientSequences);
+
+ var appId = this.$root._vueMeta.appId;
+ var tags = updateClientMetaInfo(appId, options, metaInfo);
+ // emit "event" with new info
+ if (tags && isFunction(metaInfo.changed)) {
+ metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags);
+ }
+
+ return { vm: this, metaInfo: metaInfo, tags: tags }
+ }
+}
+
+/**
+ * Generates tag attributes for use on the server.
+ *
+ * @param {('bodyAttrs'|'htmlAttrs'|'headAttrs')} type - the type of attributes to generate
+ * @param {Object} data - the attributes to generate
+ * @return {Object} - the attribute generator
+ */
+function attributeGenerator(ref, type, data) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+
+ return {
+ text: function text() {
+ var attributeStr = '';
+ var watchedAttrs = [];
+
+ for (var attr in data) {
+ if (data.hasOwnProperty(attr)) {
+ watchedAttrs.push(attr);
+
+ attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr)
+ ? attr
+ : (attr + "=\"" + (isArray(data[attr]) ? data[attr].join(' ') : data[attr]) + "\"");
+
+ attributeStr += ' ';
+ }
+ }
+
+ attributeStr += attribute + "=\"" + ((watchedAttrs.sort()).join(',')) + "\"";
+ return attributeStr
+ }
+ }
+}
+
+/**
+ * Generates title output for the server
+ *
+ * @param {'title'} type - the string "title"
+ * @param {String} data - the title text
+ * @return {Object} - the title generator
+ */
+function titleGenerator(appId, ref, type, data) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+
+ return {
+ text: function text() {
+ return ("<" + type + ">" + data + "" + type + ">")
+ }
+ }
+}
+
+/**
+ * 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)} tags - an array of tag objects or a single object in case of base
+ * @return {Object} - the tag generator
+ */
+function tagGenerator(appId, ref, type, tags) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+ var tagIDKeyName = ref.tagIDKeyName;
+
+ return {
+ text: function text(ref) {
+ if ( ref === void 0 ) ref = {};
+ var body = ref.body; if ( body === void 0 ) body = false;
+
+ // build a string containing all tags of this type
+ return tags.reduce(function (tagsStr, tag) {
+ var tagKeys = Object.keys(tag);
+
+ if (tagKeys.length === 0) {
+ return tagsStr // Bail on empty tag object
+ }
+
+ if (Boolean(tag.body) !== body) {
+ return tagsStr
+ }
+
+ // build a string containing all attributes of this tag
+ var attrs = tagKeys.reduce(function (attrsStr, attr) {
+ // these attributes are treated as children on the tag
+ if (tagAttributeAsInnerContent.includes(attr) || attr === 'once') {
+ return attrsStr
+ }
+
+ // these form the attribute list for this tag
+ var prefix = '';
+ if ([tagIDKeyName, 'body'].includes(attr)) {
+ prefix = 'data-';
+ }
+
+ return isUndefined(tag[attr]) || booleanHtmlAttributes.includes(attr)
+ ? (attrsStr + " " + prefix + attr)
+ : (attrsStr + " " + prefix + attr + "=\"" + (tag[attr]) + "\"")
+ }, '');
+
+ // grab child content from one of these attributes, if possible
+ var content = tag.innerHTML || tag.cssText || '';
+
+ // generate tag exactly without any other redundant attribute
+ var observeTag = tag.once
+ ? ''
+ : (attribute + "=\"" + appId + "\"");
+
+ // these tags have no end tag
+ var hasEndTag = !tagsWithoutEndTag.includes(type);
+
+ // these tag types will have content inserted
+ var hasContent = hasEndTag && tagsWithInnerContent.includes(type);
+
+ // the final string for this specific tag
+ return !hasContent
+ ? (tagsStr + "<" + type + " " + observeTag + attrs + (hasEndTag ? '/' : '') + ">")
+ : (tagsStr + "<" + type + " " + observeTag + attrs + ">" + content + "" + type + ">")
+ }, '')
+ }
+ }
+}
+
+/**
+ * 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)} data - the data value
+ * @return {Object} - the new injector
+ */
+
+function generateServerInjector(appId, options, type, data) {
+ if (type === 'title') {
+ return titleGenerator(appId, options, type, data)
+ }
+
+ if (metaInfoAttributeKeys.includes(type)) {
+ return attributeGenerator(options, type, data)
+ }
+
+ return tagGenerator(appId, 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 metaInfo = getMetaInfo(options, this.$root, serverSequences);
+
+ // generate server injectors
+ for (var key in metaInfo) {
+ if (!metaInfoOptionKeys.includes(key) && metaInfo.hasOwnProperty(key)) {
+ metaInfo[key] = generateServerInjector('ssr', options, key, metaInfo[key]);
+ }
+ }
+
+ return metaInfo
+ }
+}
+
+function _$meta(options) {
+ if ( options === void 0 ) options = {};
+
+ var _refresh$1 = _refresh(options);
+ var _inject$1 = _inject(options);
+
+ /**
+ * Returns an injector for server-side rendering.
+ * @this {Object} - the Vue instance (a root component)
+ * @return {Object} - injector
+ */
+ return function $meta() {
+ return {
+ getOptions: function () { return getOptions(options); },
+ refresh: _refresh$1.bind(this),
+ inject: _inject$1.bind(this),
+ pause: pause.bind(this),
+ resume: resume.bind(this)
+ }
+ }
+}
+
+/**
+ * Plugin install function.
+ * @param {Function} Vue - the Vue constructor.
+ */
+function install(Vue, options) {
+ if ( options === void 0 ) options = {};
+
+ if (Vue.__vuemeta_installed) {
+ return
+ }
+ Vue.__vuemeta_installed = true;
+
+ options = setOptions(options);
+
+ Vue.prototype.$meta = _$meta(options);
+
+ Vue.mixin(createMixin(Vue, options));
+}
+
+var index = {
+ version: version,
+ install: install,
+ hasMetaInfo: hasMetaInfo
+};
+
+module.exports = index;
diff --git a/dist/vue-meta.esm.browser.js b/dist/vue-meta.esm.browser.js
new file mode 100644
index 0000000..93adf7b
--- /dev/null
+++ b/dist/vue-meta.esm.browser.js
@@ -0,0 +1,1042 @@
+/**
+ * vue-meta v2.0.3
+ * (c) 2019
+ * - Declan de Wet
+ * - Sébastien Chopin (@Atinux)
+ * - All the amazing contributors
+ * @license MIT
+ */
+
+import deepmerge from 'deepmerge';
+
+var version = "2.0.3";
+
+// store an id to keep track of DOM updates
+let batchId = null;
+
+function triggerUpdate(vm, hookName) {
+ // if an update was triggered during initialization or when an update was triggered by the
+ // metaInfo watcher, set initialized to null
+ // then we keep falsy value but know we need to run a triggerUpdate after initialization
+ if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) {
+ vm.$root._vueMeta.initialized = null;
+ }
+
+ if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
+ // batch potential DOM updates to prevent extraneous re-rendering
+ batchUpdate(() => vm.$meta().refresh());
+ }
+}
+
+/**
+ * Performs a batched update.
+ *
+ * @param {(null|Number)} id - the ID of this update
+ * @param {Function} callback - the update to perform
+ * @return {Number} id - a new ID
+ */
+function batchUpdate(callback, timeout = 10) {
+ clearTimeout(batchId);
+
+ batchId = setTimeout(() => {
+ callback();
+ }, timeout);
+
+ return batchId
+}
+
+/**
+ * checks if passed argument is an array
+ * @param {any} arg - the object to check
+ * @return {Boolean} - true if `arg` is an array
+ */
+function isArray(arg) {
+ return Array.isArray(arg)
+}
+
+function isUndefined(arg) {
+ return typeof arg === 'undefined'
+}
+
+function isObject(arg) {
+ return typeof arg === 'object'
+}
+
+function isFunction(arg) {
+ return typeof arg === 'function'
+}
+
+function isString(arg) {
+ return typeof arg === 'string'
+}
+
+function ensureIsArray(arg, key) {
+ if (!key || !isObject(arg)) {
+ return isArray(arg) ? arg : []
+ }
+
+ if (!isArray(arg[key])) {
+ arg[key] = [];
+ }
+ return arg
+}
+
+function ensuredPush(object, key, el) {
+ ensureIsArray(object, key);
+
+ object[key].push(el);
+}
+
+// Vue $root instance has a _vueMeta object property, otherwise its a boolean true
+function hasMetaInfo(vm = this) {
+ return vm && (vm._vueMeta === true || isObject(vm._vueMeta))
+}
+
+// a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
+function inMetaInfoBranch(vm = this) {
+ return vm && !isUndefined(vm._vueMeta)
+}
+
+function addNavGuards(vm) {
+ // return when nav guards already added or no router exists
+ if (vm.$root._vueMeta.navGuards || !vm.$root.$router) {
+ /* istanbul ignore next */
+ return
+ }
+
+ vm.$root._vueMeta.navGuards = true;
+
+ const $router = vm.$root.$router;
+ const $meta = vm.$root.$meta();
+
+ $router.beforeEach((to, from, next) => {
+ $meta.pause();
+ next();
+ });
+
+ $router.afterEach(() => {
+ const { metaInfo } = $meta.resume();
+ if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
+ metaInfo.afterNavigation(metaInfo);
+ }
+ });
+}
+
+let appId = 1;
+
+function createMixin(Vue, options) {
+ // for which Vue lifecycle hooks should the metaInfo be refreshed
+ const updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount'];
+
+ // watch for client side component updates
+ return {
+ beforeCreate() {
+ Object.defineProperty(this, '_hasMetaInfo', {
+ configurable: true,
+ get() {
+ // Show deprecation warning once when devtools enabled
+ if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
+ console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); // eslint-disable-line no-console
+ this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true;
+ }
+ return hasMetaInfo(this)
+ }
+ });
+
+ // Add a marker to know if it uses metaInfo
+ // _vnode is used to know that it's attached to a real component
+ // useful if we use some mixin to add some meta tags (like nuxt-i18n)
+ if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
+ if (!this.$root._vueMeta) {
+ this.$root._vueMeta = { appId };
+ appId++;
+ }
+
+ // 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._vueMeta) {
+ this._vueMeta = true;
+
+ let p = this.$parent;
+ while (p && p !== this.$root) {
+ if (isUndefined(p._vueMeta)) {
+ p._vueMeta = false;
+ }
+ p = p.$parent;
+ }
+ }
+
+ // coerce function-style metaInfo to a computed prop so we can observe
+ // it on creation
+ if (isFunction(this.$options[options.keyName])) {
+ if (!this.$options.computed) {
+ this.$options.computed = {};
+ }
+ this.$options.computed.$metaInfo = this.$options[options.keyName];
+
+ if (!this.$isServer) {
+ // 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)
+ ensuredPush(this.$options, 'created', () => {
+ this.$watch('$metaInfo', function () {
+ triggerUpdate(this, 'watcher');
+ });
+ });
+ }
+ }
+
+ // force an initial refresh on page load and prevent other lifecycleHooks
+ // to triggerUpdate until this initial refresh is finished
+ // this is to make sure that when a page is opened in an inactive tab which
+ // has throttled rAF/timers we still immediately set the page title
+ if (isUndefined(this.$root._vueMeta.initialized)) {
+ this.$root._vueMeta.initialized = this.$isServer;
+
+ if (!this.$root._vueMeta.initialized) {
+ ensuredPush(this.$options, 'beforeMount', () => {
+ // if this Vue-app was server rendered, set the appId to 'ssr'
+ // only one SSR app per page is supported
+ if (this.$root.$el && this.$root.$el.hasAttribute('data-server-rendered')) {
+ this.$root._vueMeta.appId = 'ssr';
+ }
+ });
+
+ // we use the mounted hook here as on page load
+ ensuredPush(this.$options, 'mounted', () => {
+ if (!this.$root._vueMeta.initialized) {
+ // used in triggerUpdate to check if a change was triggered
+ // during initialization
+ this.$root._vueMeta.initializing = true;
+
+ // refresh meta in nextTick so all child components have loaded
+ this.$nextTick(function () {
+ const { tags, metaInfo } = this.$root.$meta().refresh();
+
+ // After ssr hydration (identifier by tags === false) check
+ // if initialized was set to null in triggerUpdate. That'd mean
+ // that during initilazation changes where triggered which need
+ // to be applied OR a metaInfo watcher was triggered before the
+ // current hook was called
+ // (during initialization all changes are blocked)
+ if (tags === false && this.$root._vueMeta.initialized === null) {
+ this.$nextTick(() => triggerUpdate(this, 'initializing'));
+ }
+
+ this.$root._vueMeta.initialized = true;
+ delete this.$root._vueMeta.initializing;
+
+ // add the navigation guards if they havent been added yet
+ // they are needed for the afterNavigation callback
+ if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) {
+ addNavGuards(this);
+ }
+ });
+ }
+ });
+
+ // add the navigation guards if requested
+ if (options.refreshOnceOnNavigation) {
+ addNavGuards(this);
+ }
+ }
+ }
+
+ // do not trigger refresh on the server side
+ if (!this.$isServer) {
+ // no need to add this hooks on server side
+ updateOnLifecycleHook.forEach((lifecycleHook) => {
+ ensuredPush(this.$options, lifecycleHook, () => triggerUpdate(this, lifecycleHook));
+ });
+
+ // re-render meta data when returning from a child component to parent
+ ensuredPush(this.$options, 'destroyed', () => {
+ // Wait that element is hidden before refreshing meta tags (to support animations)
+ const interval = setInterval(() => {
+ if (this.$el && this.$el.offsetParent !== null) {
+ /* istanbul ignore next line */
+ return
+ }
+
+ clearInterval(interval);
+
+ if (!this.$parent) {
+ /* istanbul ignore next line */
+ return
+ }
+
+ triggerUpdate(this, 'destroyed');
+ }, 50);
+ });
+ }
+ }
+ }
+ }
+}
+
+/**
+ * These are constant variables used throughout the application.
+ */
+
+// set some sane defaults
+const defaultInfo = {
+ title: '',
+ titleChunk: '',
+ titleTemplate: '%s',
+ htmlAttrs: {},
+ bodyAttrs: {},
+ headAttrs: {},
+ base: [],
+ link: [],
+ meta: [],
+ style: [],
+ script: [],
+ noscript: [],
+ __dangerouslyDisableSanitizers: [],
+ __dangerouslyDisableSanitizersByTagID: {}
+};
+
+// 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.
+const keyName = 'metaInfo';
+
+// This is the attribute vue-meta arguments on elements to know which it should
+// manage and which it should ignore.
+const 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.
+const ssrAttribute = '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.
+const tagIDKeyName = 'vmid';
+
+// This is the key name for possible meta templates
+const metaTemplateKeyName = 'template';
+
+// This is the key name for the content-holding property
+const contentKeyName = 'content';
+
+const defaultOptions = {
+ keyName,
+ attribute,
+ ssrAttribute,
+ tagIDKeyName,
+ contentKeyName,
+ metaTemplateKeyName
+};
+
+// List of metaInfo property keys which are configuration options (and dont generate html)
+const metaInfoOptionKeys = [
+ 'titleChunk',
+ 'titleTemplate',
+ 'changed',
+ '__dangerouslyDisableSanitizers',
+ '__dangerouslyDisableSanitizersByTagID'
+];
+
+// The metaInfo property keys which are used to disable escaping
+const disableOptionKeys = [
+ '__dangerouslyDisableSanitizers',
+ '__dangerouslyDisableSanitizersByTagID'
+];
+
+// List of metaInfo property keys which only generates attributes and no tags
+const metaInfoAttributeKeys = [
+ 'htmlAttrs',
+ 'headAttrs',
+ 'bodyAttrs'
+];
+
+// from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
+const booleanHtmlAttributes = [
+ 'allowfullscreen',
+ 'amp',
+ 'async',
+ 'autofocus',
+ 'autoplay',
+ 'checked',
+ 'compact',
+ 'controls',
+ 'declare',
+ 'default',
+ 'defaultchecked',
+ 'defaultmuted',
+ 'defaultselected',
+ 'defer',
+ 'disabled',
+ 'enabled',
+ 'formnovalidate',
+ 'hidden',
+ 'indeterminate',
+ 'inert',
+ 'ismap',
+ 'itemscope',
+ 'loop',
+ 'multiple',
+ 'muted',
+ 'nohref',
+ 'noresize',
+ 'noshade',
+ 'novalidate',
+ 'nowrap',
+ 'open',
+ 'pauseonexit',
+ 'readonly',
+ 'required',
+ 'reversed',
+ 'scoped',
+ 'seamless',
+ 'selected',
+ 'sortable',
+ 'truespeed',
+ 'typemustmatch',
+ 'visible'
+];
+
+// eslint-disable-next-line no-console
+const showWarningNotSupported = () => console.warn('This vue app/component has no vue-meta configuration');
+
+function setOptions(options) {
+ // combine options
+ options = isObject(options) ? options : {};
+
+ for (const key in defaultOptions) {
+ if (!options[key]) {
+ options[key] = defaultOptions[key];
+ }
+ }
+
+ return options
+}
+
+function getOptions(options) {
+ const optionsCopy = {};
+ for (const key in options) {
+ optionsCopy[key] = options[key];
+ }
+ return optionsCopy
+}
+
+function pause(refresh = true) {
+ this.$root._vueMeta.paused = true;
+
+ return () => resume(refresh)
+}
+
+function resume(refresh = true) {
+ this.$root._vueMeta.paused = false;
+
+ if (refresh) {
+ return this.$root.$meta().refresh()
+ }
+}
+
+function applyTemplate({ component, metaTemplateKeyName, contentKeyName }, headObject, template, chunk) {
+ if (isUndefined(template)) {
+ template = headObject[metaTemplateKeyName];
+ delete headObject[metaTemplateKeyName];
+ }
+
+ // return early if no template defined
+ if (!template) {
+ return false
+ }
+
+ if (isUndefined(chunk)) {
+ chunk = headObject[contentKeyName];
+ }
+
+ headObject[contentKeyName] = isFunction(template)
+ ? template.call(component, chunk)
+ : template.replace(/%s/g, chunk);
+
+ return true
+}
+
+/*
+ * To reduce build size, this file provides simple polyfills without
+ * overly excessive type checking and without modifying
+ * the global Array.prototype
+ * The polyfills are automatically removed in the commonjs build
+ * Also, only files in client/ & shared/ should use these functions
+ * files in server/ still use normal js function
+ */
+
+function findIndex(array, predicate) {
+ return array.findIndex(predicate, arguments[2])
+}
+
+function toArray(arg) {
+ return Array.from(arg)
+}
+
+function includes(array, value) {
+ return array.includes(value)
+}
+
+const clientSequences = [
+ [/&/g, '\u0026'],
+ [//g, '\u003e'],
+ [/"/g, '\u0022'],
+ [/'/g, '\u0027']
+];
+
+// sanitizes potentially dangerous characters
+function escape(info, options, escapeOptions) {
+ const { tagIDKeyName } = options;
+ const { doEscape = v => v } = escapeOptions;
+ const escaped = {};
+
+ for (const key in info) {
+ const value = info[key];
+
+ // no need to escape configuration options
+ if (includes(metaInfoOptionKeys, key)) {
+ escaped[key] = value;
+ continue
+ }
+
+ let [ disableKey ] = disableOptionKeys;
+ if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
+ // this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
+ escaped[key] = value;
+ continue
+ }
+
+ const tagId = info[tagIDKeyName];
+ if (tagId) {
+ disableKey = disableOptionKeys[1];
+
+ // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
+ if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
+ escaped[key] = value;
+ continue
+ }
+ }
+
+ if (isString(value)) {
+ escaped[key] = doEscape(value);
+ } else if (isArray(value)) {
+ escaped[key] = value.map((v) => {
+ return isObject(v)
+ ? escape(v, options, escapeOptions)
+ : doEscape(v)
+ });
+ } else if (isObject(value)) {
+ escaped[key] = escape(value, options, escapeOptions);
+ } else {
+ escaped[key] = value;
+ }
+ }
+
+ return escaped
+}
+
+function arrayMerge({ component, tagIDKeyName, metaTemplateKeyName, contentKeyName }, target, source) {
+ // we concat the arrays without merging objects contained in,
+ // but we check for a `vmid` property on each object in the array
+ // using an O(1) lookup associative array exploit
+ const destination = [];
+
+ target.forEach((targetItem, targetIndex) => {
+ // no tagID so no need to check for duplicity
+ if (!targetItem[tagIDKeyName]) {
+ destination.push(targetItem);
+ return
+ }
+
+ const sourceIndex = findIndex(source, item => item[tagIDKeyName] === targetItem[tagIDKeyName]);
+ const sourceItem = source[sourceIndex];
+
+ // source doesnt contain any duplicate vmid's, we can keep targetItem
+ if (sourceIndex === -1) {
+ destination.push(targetItem);
+ return
+ }
+
+ // 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)) {
+ destination.push(targetItem);
+ // remove current index from source array so its not concatenated to destination below
+ source.splice(sourceIndex, 1);
+ return
+ }
+
+ // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
+
+ // if source specifies null as content then ignore both the target as the source
+ if (sourceItem[contentKeyName] === null || sourceItem.innerHTML === null) {
+ // remove current index from source array so its not concatenated to destination below
+ source.splice(sourceIndex, 1);
+ return
+ }
+
+ // now we only need to check if the target has a template to combine it with the source
+ const targetTemplate = targetItem[metaTemplateKeyName];
+ if (!targetTemplate) {
+ return
+ }
+
+ const sourceTemplate = sourceItem[metaTemplateKeyName];
+
+ if (!sourceTemplate) {
+ // use parent template and child content
+ applyTemplate({ component, metaTemplateKeyName, contentKeyName }, sourceItem, targetTemplate);
+ } else if (!sourceItem[contentKeyName]) {
+ // use child template and parent content
+ applyTemplate({ component, metaTemplateKeyName, contentKeyName }, sourceItem, undefined, targetItem[contentKeyName]);
+ }
+ });
+
+ return destination.concat(source)
+}
+
+function merge(target, source, 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) {
+ delete source.title;
+ }
+
+ metaInfoAttributeKeys.forEach((attrKey) => {
+ if (!source[attrKey]) {
+ return
+ }
+
+ for (const key in source[attrKey]) {
+ if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
+ delete source[attrKey][key];
+ }
+ }
+ });
+
+ return deepmerge(target, source, {
+ arrayMerge: (t, s) => arrayMerge(options, t, s)
+ })
+}
+
+/**
+ * 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 {Boolean} opts.deep - look for data in child components as well?
+ * @param {Function} opts.arrayMerge - how should arrays be merged?
+ * @param {String} opts.keyName - the name of the option to look for
+ * @param {Object} [result={}] - result so far
+ * @return {Object} result - final aggregated result
+ */
+function getComponentOption(options = {}, component, result = {}) {
+ const { keyName, metaTemplateKeyName, tagIDKeyName } = options;
+ const { $options, $children } = component;
+
+ if (component._inactive) {
+ return result
+ }
+
+ // only collect option data if it exists
+ if ($options[keyName]) {
+ let data = $options[keyName];
+
+ // if option is a function, replace it with it's result
+ if (isFunction(data)) {
+ data = data.call(component);
+ }
+
+ // ignore data if its not an object, then we keep our previous result
+ if (!isObject(data)) {
+ return result
+ }
+
+ // merge with existing options
+ result = merge(result, data, options);
+ }
+
+ // collect & aggregate child options if deep = true
+ if ($children.length) {
+ $children.forEach((childComponent) => {
+ // check if the childComponent is in a branch
+ // return otherwise so we dont walk all component branches unnecessarily
+ if (!inMetaInfoBranch(childComponent)) {
+ return
+ }
+
+ result = getComponentOption(options, childComponent, result);
+ });
+ }
+
+ if (metaTemplateKeyName && result.meta) {
+ // apply templates if needed
+ result.meta.forEach(metaObject => applyTemplate(options, metaObject));
+
+ // remove meta items with duplicate vmid's
+ result.meta = result.meta.filter((metaItem, index, arr) => {
+ return (
+ // keep meta item if it doesnt has a vmid
+ !metaItem.hasOwnProperty(tagIDKeyName) ||
+ // or if it's the first item in the array with this vmid
+ index === findIndex(arr, item => item[tagIDKeyName] === metaItem[tagIDKeyName])
+ )
+ });
+ }
+
+ return result
+}
+
+/**
+ * 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
+ */
+function getMetaInfo(options = {}, component, escapeSequences = []) {
+ // collect & aggregate all metaInfo $options
+ let info = getComponentOption(options, component, defaultInfo);
+
+ // Remove all "template" tags from meta
+
+ // 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.titleTemplate !== '%s') {
+ applyTemplate({ component, contentKeyName: 'title' }, info, info.titleTemplate, 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] : [];
+ }
+
+ const escapeOptions = {
+ doEscape: value => escapeSequences.reduce((val, [v, r]) => val.replace(v, r), value)
+ };
+
+ disableOptionKeys.forEach((disableKey, index) => {
+ if (index === 0) {
+ ensureIsArray(info, disableKey);
+ } else if (index === 1) {
+ for (const key in info[disableKey]) {
+ ensureIsArray(info[disableKey], key);
+ }
+ }
+
+ escapeOptions[disableKey] = info[disableKey];
+ });
+
+ // begin sanitization
+ info = escape(info, options, escapeOptions);
+
+ return info
+}
+
+/**
+ * Updates the document's html tag attributes
+ *
+ * @param {Object} attrs - the new document html attributes
+ * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs
+ */
+function updateAttribute({ attribute } = {}, attrs, tag) {
+ const vueMetaAttrString = tag.getAttribute(attribute);
+ const vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
+ const toRemove = toArray(vueMetaAttrs);
+
+ const keepIndexes = [];
+ for (const attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ const value = includes(booleanHtmlAttributes, attr)
+ ? ''
+ : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
+
+ tag.setAttribute(attr, value || '');
+
+ if (!includes(vueMetaAttrs, attr)) {
+ vueMetaAttrs.push(attr);
+ }
+
+ // filter below wont ever check -1
+ keepIndexes.push(toRemove.indexOf(attr));
+ }
+ }
+
+ const removedAttributesCount = toRemove
+ .filter((el, index) => !includes(keepIndexes, index))
+ .reduce((acc, attr) => {
+ tag.removeAttribute(attr);
+ return acc + 1
+ }, 0);
+
+ if (vueMetaAttrs.length === removedAttributesCount) {
+ tag.removeAttribute(attribute);
+ } else {
+ tag.setAttribute(attribute, (vueMetaAttrs.sort()).join(','));
+ }
+}
+
+/**
+ * Updates the document title
+ *
+ * @param {String} title - the new title of the document
+ */
+function updateTitle(title = document.title) {
+ document.title = title;
+}
+
+/**
+ * Updates meta tags inside and 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)} tags - an array of tag objects or a single object in case of base
+ * @return {Object} - a representation of what tags changed
+ */
+function updateTag(appId, { attribute, tagIDKeyName } = {}, type, tags, headTag, bodyTag) {
+ const oldHeadTags = toArray(headTag.querySelectorAll(`${type}[${attribute}="${appId}"], ${type}[data-${tagIDKeyName}]`));
+ const oldBodyTags = toArray(bodyTag.querySelectorAll(`${type}[${attribute}="${appId}"][data-body="true"], ${type}[data-${tagIDKeyName}][data-body="true"]`));
+ const dataAttributes = [tagIDKeyName, 'body'];
+ const newTags = [];
+
+ 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
+ const found = [];
+ tags = tags.filter((x) => {
+ const k = JSON.stringify(x);
+ const res = !includes(found, k);
+ found.push(k);
+ return res
+ });
+ }
+
+ if (tags.length) {
+ tags.forEach((tag) => {
+ const newElement = document.createElement(type);
+
+ newElement.setAttribute(attribute, appId);
+
+ const oldTags = tag.body !== true ? oldHeadTags : oldBodyTags;
+
+ for (const attr in tag) {
+ if (tag.hasOwnProperty(attr)) {
+ if (attr === 'innerHTML') {
+ newElement.innerHTML = tag.innerHTML;
+ } else if (attr === 'cssText') {
+ if (newElement.styleSheet) {
+ /* istanbul ignore next */
+ newElement.styleSheet.cssText = tag.cssText;
+ } else {
+ newElement.appendChild(document.createTextNode(tag.cssText));
+ }
+ } else {
+ const _attr = includes(dataAttributes, attr)
+ ? `data-${attr}`
+ : attr;
+ const value = isUndefined(tag[attr]) || includes(booleanHtmlAttributes, attr) ? '' : tag[attr];
+ newElement.setAttribute(_attr, value);
+ }
+ }
+ }
+
+ // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
+ let indexToDelete;
+ const hasEqualElement = oldTags.some((existingTag, index) => {
+ indexToDelete = index;
+ return newElement.isEqualNode(existingTag)
+ });
+
+ if (hasEqualElement && (indexToDelete || indexToDelete === 0)) {
+ oldTags.splice(indexToDelete, 1);
+ } else {
+ newTags.push(newElement);
+ }
+ });
+ }
+
+ const oldTags = oldHeadTags.concat(oldBodyTags);
+ oldTags.forEach(tag => tag.parentNode.removeChild(tag));
+ newTags.forEach((tag) => {
+ if (tag.getAttribute('data-body') === 'true') {
+ bodyTag.appendChild(tag);
+ } else {
+ headTag.appendChild(tag);
+ }
+ });
+
+ return { oldTags, newTags }
+}
+
+function getTag(tags, tag) {
+ if (!tags[tag]) {
+ tags[tag] = document.getElementsByTagName(tag)[0];
+ }
+
+ return tags[tag]
+}
+
+/**
+ * Performs client-side updates when new meta info is received
+ *
+ * @param {Object} newInfo - the meta info to update to
+ */
+function updateClientMetaInfo(appId, options = {}, newInfo) {
+ const { ssrAttribute } = options;
+
+ // only cache tags for current update
+ const tags = {};
+
+ const htmlTag = getTag(tags, 'html');
+
+ // if this is a server render, then dont update
+ if (appId === 'ssr' && htmlTag.hasAttribute(ssrAttribute)) {
+ // remove the server render attribute so we can update on (next) changes
+ htmlTag.removeAttribute(ssrAttribute);
+ return false
+ }
+
+ // initialize tracked changes
+ const addedTags = {};
+ const removedTags = {};
+
+ for (const type in newInfo) {
+ // ignore these
+ if (includes(metaInfoOptionKeys, type)) {
+ continue
+ }
+
+ if (type === 'title') {
+ // update the title
+ updateTitle(newInfo.title);
+ continue
+ }
+
+ if (includes(metaInfoAttributeKeys, type)) {
+ const tagName = type.substr(0, 4);
+ updateAttribute(options, newInfo[type], getTag(tags, tagName));
+ continue
+ }
+
+ // tags should always be an array, ignore if it isnt
+ if (!isArray(newInfo[type])) {
+ continue
+ }
+
+ const { oldTags, newTags } = updateTag(
+ appId,
+ options,
+ type,
+ newInfo[type],
+ getTag(tags, 'head'),
+ getTag(tags, 'body')
+ );
+
+ if (newTags.length) {
+ addedTags[type] = newTags;
+ removedTags[type] = oldTags;
+ }
+ }
+
+ return { addedTags, removedTags }
+}
+
+function _refresh(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() {
+ const metaInfo = getMetaInfo(options, this.$root, clientSequences);
+
+ const appId = this.$root._vueMeta.appId;
+ const tags = updateClientMetaInfo(appId, options, metaInfo);
+ // emit "event" with new info
+ if (tags && isFunction(metaInfo.changed)) {
+ metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags);
+ }
+
+ return { vm: this, metaInfo, tags }
+ }
+}
+
+function _$meta(options = {}) {
+ const _refresh$1 = _refresh(options);
+ const inject = () => {};
+
+ /**
+ * Returns an injector for server-side rendering.
+ * @this {Object} - the Vue instance (a root component)
+ * @return {Object} - injector
+ */
+ return function $meta() {
+ if (!this.$root._vueMeta) {
+ return {
+ getOptions: showWarningNotSupported,
+ refresh: showWarningNotSupported,
+ inject: showWarningNotSupported,
+ pause: showWarningNotSupported,
+ resume: showWarningNotSupported
+ }
+ }
+
+ return {
+ getOptions: () => getOptions(options),
+ refresh: _refresh$1.bind(this),
+ inject,
+ pause: pause.bind(this),
+ resume: resume.bind(this)
+ }
+ }
+}
+
+/**
+ * Plugin install function.
+ * @param {Function} Vue - the Vue constructor.
+ */
+function install(Vue, options = {}) {
+ if (Vue.__vuemeta_installed) {
+ return
+ }
+ Vue.__vuemeta_installed = true;
+
+ options = setOptions(options);
+
+ Vue.prototype.$meta = _$meta(options);
+
+ Vue.mixin(createMixin(Vue, options));
+}
+
+// automatic install
+if (!isUndefined(window) && !isUndefined(window.Vue)) {
+ /* istanbul ignore next */
+ install(window.Vue);
+}
+
+var browser = {
+ version,
+ install,
+ hasMetaInfo
+};
+
+export default browser;
diff --git a/dist/vue-meta.esm.browser.min.js b/dist/vue-meta.esm.browser.min.js
new file mode 100644
index 0000000..2bc3c02
--- /dev/null
+++ b/dist/vue-meta.esm.browser.min.js
@@ -0,0 +1 @@
+import e from"deepmerge";let t=null;function n(e,n){e.$root._vueMeta.initialized||!e.$root._vueMeta.initializing&&"watcher"!==n||(e.$root._vueMeta.initialized=null),e.$root._vueMeta.initialized&&!e.$root._vueMeta.paused&&function(e,n=10){clearTimeout(t),t=setTimeout(()=>{e()},n)}(()=>e.$meta().refresh())}function i(e){return Array.isArray(e)}function o(e){return void 0===e}function a(e){return"object"==typeof e}function r(e){return"function"==typeof e}function s(e,t){return t&&a(e)?(i(e[t])||(e[t]=[]),e):i(e)?e:[]}function u(e,t,n){s(e,t),e[t].push(n)}function c(e=this){return e&&(!0===e._vueMeta||a(e._vueMeta))}function l(e){if(e.$root._vueMeta.navGuards||!e.$root.$router)return;e.$root._vueMeta.navGuards=!0;const t=e.$root.$router,n=e.$root.$meta();t.beforeEach((e,t,i)=>{n.pause(),i()}),t.afterEach(()=>{const{metaInfo:e}=n.resume();e&&e.afterNavigation&&r(e.afterNavigation)&&e.afterNavigation(e)})}let d=1;const h={title:"",titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},f={keyName:"metaInfo",attribute:"data-vue-meta",ssrAttribute:"data-vue-meta-server-rendered",tagIDKeyName:"vmid",contentKeyName:"content",metaTemplateKeyName:"template"},m=["titleChunk","titleTemplate","changed","__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],p=["__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],v=["htmlAttrs","headAttrs","bodyAttrs"],$=["allowfullscreen","amp","async","autofocus","autoplay","checked","compact","controls","declare","default","defaultchecked","defaultmuted","defaultselected","defer","disabled","enabled","formnovalidate","hidden","indeterminate","inert","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","pauseonexit","readonly","required","reversed","scoped","seamless","selected","sortable","truespeed","typemustmatch","visible"],y=()=>console.warn("This vue app/component has no vue-meta configuration");function g(e=!0){return this.$root._vueMeta.paused=!0,()=>b(e)}function b(e=!0){if(this.$root._vueMeta.paused=!1,e)return this.$root.$meta().refresh()}function _({component:e,metaTemplateKeyName:t,contentKeyName:n},i,a,s){return o(a)&&(a=i[t],delete i[t]),!!a&&(o(s)&&(s=i[n]),i[n]=r(a)?a.call(e,s):a.replace(/%s/g,s),!0)}function M(e,t){return e.findIndex(t,arguments[2])}function T(e){return Array.from(e)}function N(e,t){return e.includes(t)}const I=[[/&/g,"&"],[//g,">"],[/"/g,'"'],[/'/g,"'"]];function w(t,n,i={}){return n.hasOwnProperty("title")&&void 0===n.title&&delete n.title,v.forEach(e=>{if(n[e])for(const t in n[e])n[e].hasOwnProperty(t)&&void 0===n[e][t]&&delete n[e][t]}),e(t,n,{arrayMerge:(e,t)=>(function({component:e,tagIDKeyName:t,metaTemplateKeyName:n,contentKeyName:i},o,a){const r=[];return o.forEach((o,s)=>{if(!o[t])return void r.push(o);const u=M(a,e=>e[t]===o[t]),c=a[u];if(-1===u)return void r.push(o);if(c.hasOwnProperty(i)&&void 0===c[i]||c.hasOwnProperty("innerHTML")&&void 0===c.innerHTML)return r.push(o),void a.splice(u,1);if(null===c[i]||null===c.innerHTML)return void a.splice(u,1);const l=o[n];l&&(c[n]?c[i]||_({component:e,metaTemplateKeyName:n,contentKeyName:i},c,void 0,o[i]):_({component:e,metaTemplateKeyName:n,contentKeyName:i},c,l))}),r.concat(a)})(i,e,t)})}function A(e={},t,n={}){const{keyName:i,metaTemplateKeyName:s,tagIDKeyName:u}=e,{$options:c,$children:l}=t;if(t._inactive)return n;if(c[i]){let o=c[i];if(r(o)&&(o=o.call(t)),!a(o))return n;n=w(n,o,e)}return l.length&&l.forEach(t=>{(function(e=this){return e&&!o(e._vueMeta)})(t)&&(n=A(e,t,n))}),s&&n.meta&&(n.meta.forEach(t=>_(e,t)),n.meta=n.meta.filter((e,t,n)=>!e.hasOwnProperty(u)||t===M(n,t=>t[u]===e[u]))),n}function z(e={},t,n=[]){let o=A(e,t,h);o.title&&(o.titleChunk=o.title),o.titleTemplate&&"%s"!==o.titleTemplate&&_({component:t,contentKeyName:"title"},o,o.titleTemplate,o.titleChunk||""),o.base&&(o.base=Object.keys(o.base).length?[o.base]:[]);const r={doEscape:e=>n.reduce((e,[t,n])=>e.replace(t,n),e)};return p.forEach((e,t)=>{if(0===t)s(o,e);else if(1===t)for(const t in o[e])s(o[e],t);r[e]=o[e]}),o=function e(t,n,o){const{tagIDKeyName:r}=n,{doEscape:s=(e=>e)}=o,u={};for(const c in t){const l=t[c];if(N(m,c)){u[c]=l;continue}let[d]=p;if(o[d]&&N(o[d],c)){u[c]=l;continue}const h=t[r];h&&(d=p[1],o[d]&&o[d][h]&&N(o[d][h],c))?u[c]=l:"string"==typeof l?u[c]=s(l):i(l)?u[c]=l.map(t=>a(t)?e(t,n,o):s(t)):a(l)?u[c]=e(l,n,o):u[c]=l}return u}(o,e,r)}function D({attribute:e}={},t,n){const o=n.getAttribute(e),a=o?o.split(","):[],r=T(a),s=[];for(const e in t)if(t.hasOwnProperty(e)){const o=N($,e)?"":i(t[e])?t[e].join(" "):t[e];n.setAttribute(e,o||""),N(a,e)||a.push(e),s.push(r.indexOf(e))}const u=r.filter((e,t)=>!N(s,t)).reduce((e,t)=>(n.removeAttribute(t),e+1),0);a.length===u?n.removeAttribute(e):n.setAttribute(e,a.sort().join(","))}function K(e=document.title){document.title=e}function O(e,{attribute:t,tagIDKeyName:n}={},i,a,r,s){const u=T(r.querySelectorAll(`${i}[${t}="${e}"], ${i}[data-${n}]`)),c=T(s.querySelectorAll(`${i}[${t}="${e}"][data-body="true"], ${i}[data-${n}][data-body="true"]`)),l=[n,"body"],d=[];if(a.length>1){const e=[];a=a.filter(t=>{const n=JSON.stringify(t),i=!N(e,n);return e.push(n),i})}a.length&&a.forEach(n=>{const a=document.createElement(i);a.setAttribute(t,e);const r=!0!==n.body?u:c;for(const e in n)if(n.hasOwnProperty(e))if("innerHTML"===e)a.innerHTML=n.innerHTML;else if("cssText"===e)a.styleSheet?a.styleSheet.cssText=n.cssText:a.appendChild(document.createTextNode(n.cssText));else{const t=N(l,e)?`data-${e}`:e,i=o(n[e])||N($,e)?"":n[e];a.setAttribute(t,i)}let s;r.some((e,t)=>(s=t,a.isEqualNode(e)))&&(s||0===s)?r.splice(s,1):d.push(a)});const h=u.concat(c);return h.forEach(e=>e.parentNode.removeChild(e)),d.forEach(e=>{"true"===e.getAttribute("data-body")?s.appendChild(e):r.appendChild(e)}),{oldTags:h,newTags:d}}function k(e,t){return e[t]||(e[t]=document.getElementsByTagName(t)[0]),e[t]}function E(e={}){return function(){const t=z(e,this.$root,I),n=function(e,t={},n){const{ssrAttribute:o}=t,a={},r=k(a,"html");if("ssr"===e&&r.hasAttribute(o))return r.removeAttribute(o),!1;const s={},u={};for(const o in n){if(N(m,o))continue;if("title"===o){K(n.title);continue}if(N(v,o)){const e=o.substr(0,4);D(t,n[o],k(a,e));continue}if(!i(n[o]))continue;const{oldTags:r,newTags:c}=O(e,t,o,n[o],k(a,"head"),k(a,"body"));c.length&&(s[o]=c,u[o]=r)}return{addedTags:s,removedTags:u}}(this.$root._vueMeta.appId,e,t);return n&&r(t.changed)&&t.changed(t,n.addedTags,n.removedTags),{vm:this,metaInfo:t,tags:n}}}function S(e,t={}){e.__vuemeta_installed||(e.__vuemeta_installed=!0,t=function(e){e=a(e)?e:{};for(const t in f)e[t]||(e[t]=f[t]);return e}(t),e.prototype.$meta=function(e={}){const t=E(e),n=()=>{};return function(){return this.$root._vueMeta?{getOptions:()=>(function(e){const t={};for(const n in e)t[n]=e[n];return t})(e),refresh:t.bind(this),inject:n,pause:g.bind(this),resume:b.bind(this)}:{getOptions:y,refresh:y,inject:y,pause:y,resume:y}}}(t),e.mixin(function(e,t){const i=["activated","deactivated","beforeMount"];return{beforeCreate(){if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get(){return e.config.devtools&&!this.$root._vueMeta.hasMetaInfoDeprecationWarningShown&&(console.warn("VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead"),this.$root._vueMeta.hasMetaInfoDeprecationWarningShown=!0),c(this)}}),!o(this.$options[t.keyName])&&null!==this.$options[t.keyName]){if(this.$root._vueMeta||(this.$root._vueMeta={appId:d},d++),!this._vueMeta){this._vueMeta=!0;let e=this.$parent;for(;e&&e!==this.$root;)o(e._vueMeta)&&(e._vueMeta=!1),e=e.$parent}r(this.$options[t.keyName])&&(this.$options.computed||(this.$options.computed={}),this.$options.computed.$metaInfo=this.$options[t.keyName],this.$isServer||u(this.$options,"created",()=>{this.$watch("$metaInfo",function(){n(this,"watcher")})})),o(this.$root._vueMeta.initialized)&&(this.$root._vueMeta.initialized=this.$isServer,this.$root._vueMeta.initialized||(u(this.$options,"beforeMount",()=>{this.$root.$el&&this.$root.$el.hasAttribute("data-server-rendered")&&(this.$root._vueMeta.appId="ssr")}),u(this.$options,"mounted",()=>{this.$root._vueMeta.initialized||(this.$root._vueMeta.initializing=!0,this.$nextTick(function(){const{tags:e,metaInfo:i}=this.$root.$meta().refresh();!1===e&&null===this.$root._vueMeta.initialized&&this.$nextTick(()=>n(this,"initializing")),this.$root._vueMeta.initialized=!0,delete this.$root._vueMeta.initializing,!t.refreshOnceOnNavigation&&i.afterNavigation&&l(this)}))}),t.refreshOnceOnNavigation&&l(this))),this.$isServer||(i.forEach(e=>{u(this.$options,e,()=>n(this,e))}),u(this.$options,"destroyed",()=>{const e=setInterval(()=>{this.$el&&null!==this.$el.offsetParent||(clearInterval(e),this.$parent&&n(this,"destroyed"))},50)}))}}}}(e,t)))}o(window)||o(window.Vue)||S(window.Vue);export default{version:"2.0.3",install:S,hasMetaInfo:c};
diff --git a/dist/vue-meta.esm.js b/dist/vue-meta.esm.js
new file mode 100644
index 0000000..1d46f84
--- /dev/null
+++ b/dist/vue-meta.esm.js
@@ -0,0 +1,1292 @@
+/**
+ * vue-meta v2.0.3
+ * (c) 2019
+ * - Declan de Wet
+ * - Sébastien Chopin (@Atinux)
+ * - All the amazing contributors
+ * @license MIT
+ */
+
+import deepmerge from 'deepmerge';
+
+var version = "2.0.3";
+
+// store an id to keep track of DOM updates
+var batchId = null;
+
+function triggerUpdate(vm, hookName) {
+ // if an update was triggered during initialization or when an update was triggered by the
+ // metaInfo watcher, set initialized to null
+ // then we keep falsy value but know we need to run a triggerUpdate after initialization
+ if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) {
+ vm.$root._vueMeta.initialized = null;
+ }
+
+ if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
+ // batch potential DOM updates to prevent extraneous re-rendering
+ batchUpdate(function () { return vm.$meta().refresh(); });
+ }
+}
+
+/**
+ * Performs a batched update.
+ *
+ * @param {(null|Number)} id - the ID of this update
+ * @param {Function} callback - the update to perform
+ * @return {Number} id - a new ID
+ */
+function batchUpdate(callback, timeout) {
+ if ( timeout === void 0 ) timeout = 10;
+
+ clearTimeout(batchId);
+
+ batchId = setTimeout(function () {
+ callback();
+ }, timeout);
+
+ return batchId
+}
+
+/**
+ * checks if passed argument is an array
+ * @param {any} arg - the object to check
+ * @return {Boolean} - true if `arg` is an array
+ */
+function isArray(arg) {
+ return Array.isArray(arg)
+}
+
+function isUndefined(arg) {
+ return typeof arg === 'undefined'
+}
+
+function isObject(arg) {
+ return typeof arg === 'object'
+}
+
+function isFunction(arg) {
+ return typeof arg === 'function'
+}
+
+function isString(arg) {
+ return typeof arg === 'string'
+}
+
+function ensureIsArray(arg, key) {
+ if (!key || !isObject(arg)) {
+ return isArray(arg) ? arg : []
+ }
+
+ if (!isArray(arg[key])) {
+ arg[key] = [];
+ }
+ return arg
+}
+
+function ensuredPush(object, key, el) {
+ ensureIsArray(object, key);
+
+ object[key].push(el);
+}
+
+// Vue $root instance has a _vueMeta object property, otherwise its a boolean true
+function hasMetaInfo(vm) {
+ if ( vm === void 0 ) vm = this;
+
+ return vm && (vm._vueMeta === true || isObject(vm._vueMeta))
+}
+
+// a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
+function inMetaInfoBranch(vm) {
+ if ( vm === void 0 ) vm = this;
+
+ return vm && !isUndefined(vm._vueMeta)
+}
+
+function addNavGuards(vm) {
+ // return when nav guards already added or no router exists
+ if (vm.$root._vueMeta.navGuards || !vm.$root.$router) {
+ /* istanbul ignore next */
+ return
+ }
+
+ vm.$root._vueMeta.navGuards = true;
+
+ var $router = vm.$root.$router;
+ var $meta = vm.$root.$meta();
+
+ $router.beforeEach(function (to, from, next) {
+ $meta.pause();
+ next();
+ });
+
+ $router.afterEach(function () {
+ var ref = $meta.resume();
+ var metaInfo = ref.metaInfo;
+ if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
+ metaInfo.afterNavigation(metaInfo);
+ }
+ });
+}
+
+var appId = 1;
+
+function createMixin(Vue, options) {
+ // for which Vue lifecycle hooks should the metaInfo be refreshed
+ var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount'];
+
+ // watch for client side component updates
+ return {
+ beforeCreate: function beforeCreate() {
+ var this$1 = this;
+
+ Object.defineProperty(this, '_hasMetaInfo', {
+ configurable: true,
+ get: function get() {
+ // Show deprecation warning once when devtools enabled
+ if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
+ console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); // eslint-disable-line no-console
+ this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true;
+ }
+ return hasMetaInfo(this)
+ }
+ });
+
+ // Add a marker to know if it uses metaInfo
+ // _vnode is used to know that it's attached to a real component
+ // useful if we use some mixin to add some meta tags (like nuxt-i18n)
+ if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
+ if (!this.$root._vueMeta) {
+ this.$root._vueMeta = { appId: appId };
+ appId++;
+ }
+
+ // 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._vueMeta) {
+ this._vueMeta = true;
+
+ var p = this.$parent;
+ while (p && p !== this.$root) {
+ if (isUndefined(p._vueMeta)) {
+ p._vueMeta = false;
+ }
+ p = p.$parent;
+ }
+ }
+
+ // coerce function-style metaInfo to a computed prop so we can observe
+ // it on creation
+ if (isFunction(this.$options[options.keyName])) {
+ if (!this.$options.computed) {
+ this.$options.computed = {};
+ }
+ this.$options.computed.$metaInfo = this.$options[options.keyName];
+
+ if (!this.$isServer) {
+ // 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)
+ ensuredPush(this.$options, 'created', function () {
+ this$1.$watch('$metaInfo', function () {
+ triggerUpdate(this, 'watcher');
+ });
+ });
+ }
+ }
+
+ // force an initial refresh on page load and prevent other lifecycleHooks
+ // to triggerUpdate until this initial refresh is finished
+ // this is to make sure that when a page is opened in an inactive tab which
+ // has throttled rAF/timers we still immediately set the page title
+ if (isUndefined(this.$root._vueMeta.initialized)) {
+ this.$root._vueMeta.initialized = this.$isServer;
+
+ if (!this.$root._vueMeta.initialized) {
+ ensuredPush(this.$options, 'beforeMount', function () {
+ // if this Vue-app was server rendered, set the appId to 'ssr'
+ // only one SSR app per page is supported
+ if (this$1.$root.$el && this$1.$root.$el.hasAttribute('data-server-rendered')) {
+ this$1.$root._vueMeta.appId = 'ssr';
+ }
+ });
+
+ // we use the mounted hook here as on page load
+ ensuredPush(this.$options, 'mounted', function () {
+ if (!this$1.$root._vueMeta.initialized) {
+ // used in triggerUpdate to check if a change was triggered
+ // during initialization
+ this$1.$root._vueMeta.initializing = true;
+
+ // refresh meta in nextTick so all child components have loaded
+ this$1.$nextTick(function () {
+ var this$1 = this;
+
+ var ref = this.$root.$meta().refresh();
+ var tags = ref.tags;
+ var metaInfo = ref.metaInfo;
+
+ // After ssr hydration (identifier by tags === false) check
+ // if initialized was set to null in triggerUpdate. That'd mean
+ // that during initilazation changes where triggered which need
+ // to be applied OR a metaInfo watcher was triggered before the
+ // current hook was called
+ // (during initialization all changes are blocked)
+ if (tags === false && this.$root._vueMeta.initialized === null) {
+ this.$nextTick(function () { return triggerUpdate(this$1, 'initializing'); });
+ }
+
+ this.$root._vueMeta.initialized = true;
+ delete this.$root._vueMeta.initializing;
+
+ // add the navigation guards if they havent been added yet
+ // they are needed for the afterNavigation callback
+ if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) {
+ addNavGuards(this);
+ }
+ });
+ }
+ });
+
+ // add the navigation guards if requested
+ if (options.refreshOnceOnNavigation) {
+ addNavGuards(this);
+ }
+ }
+ }
+
+ // do not trigger refresh on the server side
+ if (!this.$isServer) {
+ // no need to add this hooks on server side
+ updateOnLifecycleHook.forEach(function (lifecycleHook) {
+ ensuredPush(this$1.$options, lifecycleHook, function () { return triggerUpdate(this$1, lifecycleHook); });
+ });
+
+ // re-render meta data when returning from a child component to parent
+ ensuredPush(this.$options, 'destroyed', function () {
+ // Wait that element is hidden before refreshing meta tags (to support animations)
+ var interval = setInterval(function () {
+ if (this$1.$el && this$1.$el.offsetParent !== null) {
+ /* istanbul ignore next line */
+ return
+ }
+
+ clearInterval(interval);
+
+ if (!this$1.$parent) {
+ /* istanbul ignore next line */
+ return
+ }
+
+ triggerUpdate(this$1, 'destroyed');
+ }, 50);
+ });
+ }
+ }
+ }
+ }
+}
+
+/**
+ * These are constant variables used throughout the application.
+ */
+
+// set some sane defaults
+var defaultInfo = {
+ title: '',
+ titleChunk: '',
+ titleTemplate: '%s',
+ htmlAttrs: {},
+ bodyAttrs: {},
+ headAttrs: {},
+ base: [],
+ link: [],
+ meta: [],
+ style: [],
+ script: [],
+ noscript: [],
+ __dangerouslyDisableSanitizers: [],
+ __dangerouslyDisableSanitizersByTagID: {}
+};
+
+// 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 keyName = 'metaInfo';
+
+// This is the attribute vue-meta arguments on elements to know which it should
+// manage and which it should ignore.
+var 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 ssrAttribute = '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 tagIDKeyName = 'vmid';
+
+// This is the key name for possible meta templates
+var metaTemplateKeyName = 'template';
+
+// This is the key name for the content-holding property
+var contentKeyName = 'content';
+
+var defaultOptions = {
+ keyName: keyName,
+ attribute: attribute,
+ ssrAttribute: ssrAttribute,
+ tagIDKeyName: tagIDKeyName,
+ contentKeyName: contentKeyName,
+ metaTemplateKeyName: metaTemplateKeyName
+};
+
+// List of metaInfo property keys which are configuration options (and dont generate html)
+var metaInfoOptionKeys = [
+ 'titleChunk',
+ 'titleTemplate',
+ 'changed',
+ '__dangerouslyDisableSanitizers',
+ '__dangerouslyDisableSanitizersByTagID'
+];
+
+// The metaInfo property keys which are used to disable escaping
+var disableOptionKeys = [
+ '__dangerouslyDisableSanitizers',
+ '__dangerouslyDisableSanitizersByTagID'
+];
+
+// List of metaInfo property keys which only generates attributes and no tags
+var metaInfoAttributeKeys = [
+ 'htmlAttrs',
+ 'headAttrs',
+ 'bodyAttrs'
+];
+
+// HTML elements which dont have a head tag (shortened to our needs)
+// see: https://www.w3.org/TR/html52/document-metadata.html
+var tagsWithoutEndTag = ['base', 'meta', 'link'];
+
+// HTML elements which can have inner content (shortened to our needs)
+var tagsWithInnerContent = ['noscript', 'script', 'style'];
+
+// Attributes which are inserted as childNodes instead of HTMLAttribute
+var tagAttributeAsInnerContent = ['innerHTML', 'cssText'];
+
+// from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
+var booleanHtmlAttributes = [
+ 'allowfullscreen',
+ 'amp',
+ 'async',
+ 'autofocus',
+ 'autoplay',
+ 'checked',
+ 'compact',
+ 'controls',
+ 'declare',
+ 'default',
+ 'defaultchecked',
+ 'defaultmuted',
+ 'defaultselected',
+ 'defer',
+ 'disabled',
+ 'enabled',
+ 'formnovalidate',
+ 'hidden',
+ 'indeterminate',
+ 'inert',
+ 'ismap',
+ 'itemscope',
+ 'loop',
+ 'multiple',
+ 'muted',
+ 'nohref',
+ 'noresize',
+ 'noshade',
+ 'novalidate',
+ 'nowrap',
+ 'open',
+ 'pauseonexit',
+ 'readonly',
+ 'required',
+ 'reversed',
+ 'scoped',
+ 'seamless',
+ 'selected',
+ 'sortable',
+ 'truespeed',
+ 'typemustmatch',
+ 'visible'
+];
+
+function setOptions(options) {
+ // combine options
+ options = isObject(options) ? options : {};
+
+ for (var key in defaultOptions) {
+ if (!options[key]) {
+ options[key] = defaultOptions[key];
+ }
+ }
+
+ return options
+}
+
+function getOptions(options) {
+ var optionsCopy = {};
+ for (var key in options) {
+ optionsCopy[key] = options[key];
+ }
+ return optionsCopy
+}
+
+function pause(refresh) {
+ if ( refresh === void 0 ) refresh = true;
+
+ this.$root._vueMeta.paused = true;
+
+ return function () { return resume(refresh); }
+}
+
+function resume(refresh) {
+ if ( refresh === void 0 ) refresh = true;
+
+ this.$root._vueMeta.paused = false;
+
+ if (refresh) {
+ return this.$root.$meta().refresh()
+ }
+}
+
+function applyTemplate(ref, headObject, template, chunk) {
+ var component = ref.component;
+ var metaTemplateKeyName = ref.metaTemplateKeyName;
+ var contentKeyName = ref.contentKeyName;
+
+ if (isUndefined(template)) {
+ template = headObject[metaTemplateKeyName];
+ delete headObject[metaTemplateKeyName];
+ }
+
+ // return early if no template defined
+ if (!template) {
+ return false
+ }
+
+ if (isUndefined(chunk)) {
+ chunk = headObject[contentKeyName];
+ }
+
+ headObject[contentKeyName] = isFunction(template)
+ ? template.call(component, chunk)
+ : template.replace(/%s/g, chunk);
+
+ return true
+}
+
+/*
+ * To reduce build size, this file provides simple polyfills without
+ * overly excessive type checking and without modifying
+ * the global Array.prototype
+ * The polyfills are automatically removed in the commonjs build
+ * Also, only files in client/ & shared/ should use these functions
+ * files in server/ still use normal js function
+ */
+
+function findIndex(array, predicate) {
+ var arguments$1 = arguments;
+
+ if (!Array.prototype.findIndex) {
+ // idx needs to be a Number, for..in returns string
+ for (var idx = 0; idx < array.length; idx++) {
+ if (predicate.call(arguments$1[2], array[idx], idx, array)) {
+ return idx
+ }
+ }
+ return -1
+ }
+ return array.findIndex(predicate, arguments[2])
+}
+
+function toArray(arg) {
+ if (!Array.from) {
+ return Array.prototype.slice.call(arg)
+ }
+ return Array.from(arg)
+}
+
+function includes(array, value) {
+ if (!Array.prototype.includes) {
+ for (var idx in array) {
+ if (array[idx] === value) {
+ return true
+ }
+ }
+
+ return false
+ }
+ return array.includes(value)
+}
+
+var serverSequences = [
+ [/&/g, '&'],
+ [//g, '>'],
+ [/"/g, '"'],
+ [/'/g, ''']
+];
+
+var clientSequences = [
+ [/&/g, '\u0026'],
+ [//g, '\u003e'],
+ [/"/g, '\u0022'],
+ [/'/g, '\u0027']
+];
+
+// sanitizes potentially dangerous characters
+function escape(info, options, escapeOptions) {
+ var tagIDKeyName = options.tagIDKeyName;
+ var doEscape = escapeOptions.doEscape; if ( doEscape === void 0 ) doEscape = function (v) { return v; };
+ var escaped = {};
+
+ for (var key in info) {
+ var value = info[key];
+
+ // no need to escape configuration options
+ if (includes(metaInfoOptionKeys, key)) {
+ escaped[key] = value;
+ continue
+ }
+
+ var disableKey = disableOptionKeys[0];
+ if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
+ // this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
+ escaped[key] = value;
+ continue
+ }
+
+ var tagId = info[tagIDKeyName];
+ if (tagId) {
+ disableKey = disableOptionKeys[1];
+
+ // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
+ if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
+ escaped[key] = value;
+ continue
+ }
+ }
+
+ if (isString(value)) {
+ escaped[key] = doEscape(value);
+ } else if (isArray(value)) {
+ escaped[key] = value.map(function (v) {
+ return isObject(v)
+ ? escape(v, options, escapeOptions)
+ : doEscape(v)
+ });
+ } else if (isObject(value)) {
+ escaped[key] = escape(value, options, escapeOptions);
+ } else {
+ escaped[key] = value;
+ }
+ }
+
+ return escaped
+}
+
+function arrayMerge(ref, target, source) {
+ var component = ref.component;
+ var tagIDKeyName = ref.tagIDKeyName;
+ var metaTemplateKeyName = ref.metaTemplateKeyName;
+ var contentKeyName = ref.contentKeyName;
+
+ // we concat the arrays without merging objects contained in,
+ // but we check for a `vmid` property on each object in the array
+ // using an O(1) lookup associative array exploit
+ var destination = [];
+
+ target.forEach(function (targetItem, targetIndex) {
+ // no tagID so no need to check for duplicity
+ if (!targetItem[tagIDKeyName]) {
+ destination.push(targetItem);
+ return
+ }
+
+ var sourceIndex = findIndex(source, function (item) { return item[tagIDKeyName] === targetItem[tagIDKeyName]; });
+ var sourceItem = source[sourceIndex];
+
+ // source doesnt contain any duplicate vmid's, we can keep targetItem
+ if (sourceIndex === -1) {
+ destination.push(targetItem);
+ return
+ }
+
+ // 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)) {
+ destination.push(targetItem);
+ // remove current index from source array so its not concatenated to destination below
+ source.splice(sourceIndex, 1);
+ return
+ }
+
+ // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
+
+ // if source specifies null as content then ignore both the target as the source
+ if (sourceItem[contentKeyName] === null || sourceItem.innerHTML === null) {
+ // remove current index from source array so its not concatenated to destination below
+ source.splice(sourceIndex, 1);
+ return
+ }
+
+ // now we only need to check if the target has a template to combine it with the source
+ var targetTemplate = targetItem[metaTemplateKeyName];
+ if (!targetTemplate) {
+ return
+ }
+
+ var sourceTemplate = sourceItem[metaTemplateKeyName];
+
+ if (!sourceTemplate) {
+ // use parent template and child content
+ applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, targetTemplate);
+ } else if (!sourceItem[contentKeyName]) {
+ // use child template and parent content
+ applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, undefined, targetItem[contentKeyName]);
+ }
+ });
+
+ return destination.concat(source)
+}
+
+function merge(target, source, options) {
+ if ( options === void 0 ) 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) {
+ delete source.title;
+ }
+
+ metaInfoAttributeKeys.forEach(function (attrKey) {
+ if (!source[attrKey]) {
+ return
+ }
+
+ for (var key in source[attrKey]) {
+ if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
+ delete source[attrKey][key];
+ }
+ }
+ });
+
+ return deepmerge(target, source, {
+ arrayMerge: function (t, s) { return arrayMerge(options, t, s); }
+ })
+}
+
+/**
+ * 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 {Boolean} opts.deep - look for data in child components as well?
+ * @param {Function} opts.arrayMerge - how should arrays be merged?
+ * @param {String} opts.keyName - the name of the option to look for
+ * @param {Object} [result={}] - result so far
+ * @return {Object} result - final aggregated result
+ */
+function getComponentOption(options, component, result) {
+ if ( options === void 0 ) options = {};
+ if ( result === void 0 ) result = {};
+
+ var keyName = options.keyName;
+ var metaTemplateKeyName = options.metaTemplateKeyName;
+ var tagIDKeyName = options.tagIDKeyName;
+ var $options = component.$options;
+ var $children = component.$children;
+
+ if (component._inactive) {
+ return result
+ }
+
+ // only collect option data if it exists
+ if ($options[keyName]) {
+ var data = $options[keyName];
+
+ // if option is a function, replace it with it's result
+ if (isFunction(data)) {
+ data = data.call(component);
+ }
+
+ // ignore data if its not an object, then we keep our previous result
+ if (!isObject(data)) {
+ return result
+ }
+
+ // merge with existing options
+ result = merge(result, data, options);
+ }
+
+ // collect & aggregate child options if deep = true
+ if ($children.length) {
+ $children.forEach(function (childComponent) {
+ // check if the childComponent is in a branch
+ // return otherwise so we dont walk all component branches unnecessarily
+ if (!inMetaInfoBranch(childComponent)) {
+ return
+ }
+
+ result = getComponentOption(options, childComponent, result);
+ });
+ }
+
+ if (metaTemplateKeyName && result.meta) {
+ // apply templates if needed
+ result.meta.forEach(function (metaObject) { return applyTemplate(options, metaObject); });
+
+ // remove meta items with duplicate vmid's
+ result.meta = result.meta.filter(function (metaItem, index, arr) {
+ return (
+ // keep meta item if it doesnt has a vmid
+ !metaItem.hasOwnProperty(tagIDKeyName) ||
+ // or if it's the first item in the array with this vmid
+ index === findIndex(arr, function (item) { return item[tagIDKeyName] === metaItem[tagIDKeyName]; })
+ )
+ });
+ }
+
+ return result
+}
+
+/**
+ * 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
+ */
+function getMetaInfo(options, component, escapeSequences) {
+ if ( options === void 0 ) options = {};
+ if ( escapeSequences === void 0 ) escapeSequences = [];
+
+ // collect & aggregate all metaInfo $options
+ var info = getComponentOption(options, component, defaultInfo);
+
+ // Remove all "template" tags from meta
+
+ // 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.titleTemplate !== '%s') {
+ applyTemplate({ component: component, contentKeyName: 'title' }, info, info.titleTemplate, 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] : [];
+ }
+
+ var escapeOptions = {
+ doEscape: function (value) { return escapeSequences.reduce(function (val, ref) {
+ var v = ref[0];
+ var r = ref[1];
+
+ return val.replace(v, r);
+ }, value); }
+ };
+
+ disableOptionKeys.forEach(function (disableKey, index) {
+ if (index === 0) {
+ ensureIsArray(info, disableKey);
+ } else if (index === 1) {
+ for (var key in info[disableKey]) {
+ ensureIsArray(info[disableKey], key);
+ }
+ }
+
+ escapeOptions[disableKey] = info[disableKey];
+ });
+
+ // begin sanitization
+ info = escape(info, options, escapeOptions);
+
+ return info
+}
+
+/**
+ * Updates the document's html tag attributes
+ *
+ * @param {Object} attrs - the new document html attributes
+ * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs
+ */
+function updateAttribute(ref, attrs, tag) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+
+ var vueMetaAttrString = tag.getAttribute(attribute);
+ var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
+ var toRemove = toArray(vueMetaAttrs);
+
+ var keepIndexes = [];
+ for (var attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ var value = includes(booleanHtmlAttributes, attr)
+ ? ''
+ : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
+
+ tag.setAttribute(attr, value || '');
+
+ if (!includes(vueMetaAttrs, attr)) {
+ vueMetaAttrs.push(attr);
+ }
+
+ // filter below wont ever check -1
+ keepIndexes.push(toRemove.indexOf(attr));
+ }
+ }
+
+ var removedAttributesCount = toRemove
+ .filter(function (el, index) { return !includes(keepIndexes, index); })
+ .reduce(function (acc, attr) {
+ tag.removeAttribute(attr);
+ return acc + 1
+ }, 0);
+
+ if (vueMetaAttrs.length === removedAttributesCount) {
+ tag.removeAttribute(attribute);
+ } else {
+ tag.setAttribute(attribute, (vueMetaAttrs.sort()).join(','));
+ }
+}
+
+/**
+ * Updates the document title
+ *
+ * @param {String} title - the new title of the document
+ */
+function updateTitle(title) {
+ if ( title === void 0 ) title = document.title;
+
+ document.title = title;
+}
+
+/**
+ * Updates meta tags inside and 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)} tags - an array of tag objects or a single object in case of base
+ * @return {Object} - a representation of what tags changed
+ */
+function updateTag(appId, ref, type, tags, headTag, bodyTag) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+ var tagIDKeyName = ref.tagIDKeyName;
+
+ var oldHeadTags = toArray(headTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"], " + type + "[data-" + tagIDKeyName + "]")));
+ var oldBodyTags = toArray(bodyTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"][data-body=\"true\"], " + type + "[data-" + tagIDKeyName + "][data-body=\"true\"]")));
+ var dataAttributes = [tagIDKeyName, 'body'];
+ var newTags = [];
+
+ 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.filter(function (x) {
+ var k = JSON.stringify(x);
+ var res = !includes(found, k);
+ found.push(k);
+ return res
+ });
+ }
+
+ if (tags.length) {
+ tags.forEach(function (tag) {
+ var newElement = document.createElement(type);
+
+ newElement.setAttribute(attribute, appId);
+
+ var oldTags = tag.body !== true ? oldHeadTags : oldBodyTags;
+
+ for (var attr in tag) {
+ if (tag.hasOwnProperty(attr)) {
+ if (attr === 'innerHTML') {
+ newElement.innerHTML = tag.innerHTML;
+ } else if (attr === 'cssText') {
+ if (newElement.styleSheet) {
+ /* istanbul ignore next */
+ newElement.styleSheet.cssText = tag.cssText;
+ } else {
+ newElement.appendChild(document.createTextNode(tag.cssText));
+ }
+ } else {
+ var _attr = includes(dataAttributes, attr)
+ ? ("data-" + attr)
+ : attr;
+ var value = isUndefined(tag[attr]) || includes(booleanHtmlAttributes, attr) ? '' : tag[attr];
+ newElement.setAttribute(_attr, value);
+ }
+ }
+ }
+
+ // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
+ var indexToDelete;
+ var hasEqualElement = oldTags.some(function (existingTag, index) {
+ indexToDelete = index;
+ return newElement.isEqualNode(existingTag)
+ });
+
+ if (hasEqualElement && (indexToDelete || indexToDelete === 0)) {
+ oldTags.splice(indexToDelete, 1);
+ } else {
+ newTags.push(newElement);
+ }
+ });
+ }
+
+ var oldTags = oldHeadTags.concat(oldBodyTags);
+ oldTags.forEach(function (tag) { return tag.parentNode.removeChild(tag); });
+ newTags.forEach(function (tag) {
+ if (tag.getAttribute('data-body') === 'true') {
+ bodyTag.appendChild(tag);
+ } else {
+ headTag.appendChild(tag);
+ }
+ });
+
+ return { oldTags: oldTags, newTags: newTags }
+}
+
+function getTag(tags, tag) {
+ if (!tags[tag]) {
+ tags[tag] = document.getElementsByTagName(tag)[0];
+ }
+
+ return tags[tag]
+}
+
+/**
+ * Performs client-side updates when new meta info is received
+ *
+ * @param {Object} newInfo - the meta info to update to
+ */
+function updateClientMetaInfo(appId, options, newInfo) {
+ if ( options === void 0 ) options = {};
+
+ var ssrAttribute = options.ssrAttribute;
+
+ // only cache tags for current update
+ var tags = {};
+
+ var htmlTag = getTag(tags, 'html');
+
+ // if this is a server render, then dont update
+ if (appId === 'ssr' && htmlTag.hasAttribute(ssrAttribute)) {
+ // remove the server render attribute so we can update on (next) changes
+ htmlTag.removeAttribute(ssrAttribute);
+ return false
+ }
+
+ // initialize tracked changes
+ var addedTags = {};
+ var removedTags = {};
+
+ for (var type in newInfo) {
+ // ignore these
+ if (includes(metaInfoOptionKeys, type)) {
+ continue
+ }
+
+ if (type === 'title') {
+ // update the title
+ updateTitle(newInfo.title);
+ continue
+ }
+
+ if (includes(metaInfoAttributeKeys, type)) {
+ var tagName = type.substr(0, 4);
+ updateAttribute(options, newInfo[type], getTag(tags, tagName));
+ continue
+ }
+
+ // tags should always be an array, ignore if it isnt
+ if (!isArray(newInfo[type])) {
+ continue
+ }
+
+ var ref = updateTag(
+ appId,
+ options,
+ type,
+ newInfo[type],
+ getTag(tags, 'head'),
+ getTag(tags, 'body')
+ );
+ var oldTags = ref.oldTags;
+ var newTags = ref.newTags;
+
+ if (newTags.length) {
+ addedTags[type] = newTags;
+ removedTags[type] = oldTags;
+ }
+ }
+
+ return { addedTags: addedTags, removedTags: removedTags }
+}
+
+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 metaInfo = getMetaInfo(options, this.$root, clientSequences);
+
+ var appId = this.$root._vueMeta.appId;
+ var tags = updateClientMetaInfo(appId, options, metaInfo);
+ // emit "event" with new info
+ if (tags && isFunction(metaInfo.changed)) {
+ metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags);
+ }
+
+ return { vm: this, metaInfo: metaInfo, tags: tags }
+ }
+}
+
+/**
+ * Generates tag attributes for use on the server.
+ *
+ * @param {('bodyAttrs'|'htmlAttrs'|'headAttrs')} type - the type of attributes to generate
+ * @param {Object} data - the attributes to generate
+ * @return {Object} - the attribute generator
+ */
+function attributeGenerator(ref, type, data) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+
+ return {
+ text: function text() {
+ var attributeStr = '';
+ var watchedAttrs = [];
+
+ for (var attr in data) {
+ if (data.hasOwnProperty(attr)) {
+ watchedAttrs.push(attr);
+
+ attributeStr += isUndefined(data[attr]) || booleanHtmlAttributes.includes(attr)
+ ? attr
+ : (attr + "=\"" + (isArray(data[attr]) ? data[attr].join(' ') : data[attr]) + "\"");
+
+ attributeStr += ' ';
+ }
+ }
+
+ attributeStr += attribute + "=\"" + ((watchedAttrs.sort()).join(',')) + "\"";
+ return attributeStr
+ }
+ }
+}
+
+/**
+ * Generates title output for the server
+ *
+ * @param {'title'} type - the string "title"
+ * @param {String} data - the title text
+ * @return {Object} - the title generator
+ */
+function titleGenerator(appId, ref, type, data) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+
+ return {
+ text: function text() {
+ return ("<" + type + ">" + data + "" + type + ">")
+ }
+ }
+}
+
+/**
+ * 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)} tags - an array of tag objects or a single object in case of base
+ * @return {Object} - the tag generator
+ */
+function tagGenerator(appId, ref, type, tags) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+ var tagIDKeyName = ref.tagIDKeyName;
+
+ return {
+ text: function text(ref) {
+ if ( ref === void 0 ) ref = {};
+ var body = ref.body; if ( body === void 0 ) body = false;
+
+ // build a string containing all tags of this type
+ return tags.reduce(function (tagsStr, tag) {
+ var tagKeys = Object.keys(tag);
+
+ if (tagKeys.length === 0) {
+ return tagsStr // Bail on empty tag object
+ }
+
+ if (Boolean(tag.body) !== body) {
+ return tagsStr
+ }
+
+ // build a string containing all attributes of this tag
+ var attrs = tagKeys.reduce(function (attrsStr, attr) {
+ // these attributes are treated as children on the tag
+ if (tagAttributeAsInnerContent.includes(attr) || attr === 'once') {
+ return attrsStr
+ }
+
+ // these form the attribute list for this tag
+ var prefix = '';
+ if ([tagIDKeyName, 'body'].includes(attr)) {
+ prefix = 'data-';
+ }
+
+ return isUndefined(tag[attr]) || booleanHtmlAttributes.includes(attr)
+ ? (attrsStr + " " + prefix + attr)
+ : (attrsStr + " " + prefix + attr + "=\"" + (tag[attr]) + "\"")
+ }, '');
+
+ // grab child content from one of these attributes, if possible
+ var content = tag.innerHTML || tag.cssText || '';
+
+ // generate tag exactly without any other redundant attribute
+ var observeTag = tag.once
+ ? ''
+ : (attribute + "=\"" + appId + "\"");
+
+ // these tags have no end tag
+ var hasEndTag = !tagsWithoutEndTag.includes(type);
+
+ // these tag types will have content inserted
+ var hasContent = hasEndTag && tagsWithInnerContent.includes(type);
+
+ // the final string for this specific tag
+ return !hasContent
+ ? (tagsStr + "<" + type + " " + observeTag + attrs + (hasEndTag ? '/' : '') + ">")
+ : (tagsStr + "<" + type + " " + observeTag + attrs + ">" + content + "" + type + ">")
+ }, '')
+ }
+ }
+}
+
+/**
+ * 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)} data - the data value
+ * @return {Object} - the new injector
+ */
+
+function generateServerInjector(appId, options, type, data) {
+ if (type === 'title') {
+ return titleGenerator(appId, options, type, data)
+ }
+
+ if (metaInfoAttributeKeys.includes(type)) {
+ return attributeGenerator(options, type, data)
+ }
+
+ return tagGenerator(appId, 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 metaInfo = getMetaInfo(options, this.$root, serverSequences);
+
+ // generate server injectors
+ for (var key in metaInfo) {
+ if (!metaInfoOptionKeys.includes(key) && metaInfo.hasOwnProperty(key)) {
+ metaInfo[key] = generateServerInjector('ssr', options, key, metaInfo[key]);
+ }
+ }
+
+ return metaInfo
+ }
+}
+
+function _$meta(options) {
+ if ( options === void 0 ) options = {};
+
+ var _refresh$1 = _refresh(options);
+ var _inject$1 = _inject(options);
+
+ /**
+ * Returns an injector for server-side rendering.
+ * @this {Object} - the Vue instance (a root component)
+ * @return {Object} - injector
+ */
+ return function $meta() {
+ return {
+ getOptions: function () { return getOptions(options); },
+ refresh: _refresh$1.bind(this),
+ inject: _inject$1.bind(this),
+ pause: pause.bind(this),
+ resume: resume.bind(this)
+ }
+ }
+}
+
+/**
+ * Plugin install function.
+ * @param {Function} Vue - the Vue constructor.
+ */
+function install(Vue, options) {
+ if ( options === void 0 ) options = {};
+
+ if (Vue.__vuemeta_installed) {
+ return
+ }
+ Vue.__vuemeta_installed = true;
+
+ options = setOptions(options);
+
+ Vue.prototype.$meta = _$meta(options);
+
+ Vue.mixin(createMixin(Vue, options));
+}
+
+var index = {
+ version: version,
+ install: install,
+ hasMetaInfo: hasMetaInfo
+};
+
+export default index;
diff --git a/dist/vue-meta.js b/dist/vue-meta.js
new file mode 100644
index 0000000..840bd71
--- /dev/null
+++ b/dist/vue-meta.js
@@ -0,0 +1,1243 @@
+/**
+ * vue-meta v2.0.3
+ * (c) 2019
+ * - Declan de Wet
+ * - Sébastien Chopin (@Atinux)
+ * - All the amazing contributors
+ * @license MIT
+ */
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global = global || self, global.VueMeta = factory());
+}(this, function () { 'use strict';
+
+ var version = "2.0.3";
+
+ // store an id to keep track of DOM updates
+ var batchId = null;
+
+ function triggerUpdate(vm, hookName) {
+ // if an update was triggered during initialization or when an update was triggered by the
+ // metaInfo watcher, set initialized to null
+ // then we keep falsy value but know we need to run a triggerUpdate after initialization
+ if (!vm.$root._vueMeta.initialized && (vm.$root._vueMeta.initializing || hookName === 'watcher')) {
+ vm.$root._vueMeta.initialized = null;
+ }
+
+ if (vm.$root._vueMeta.initialized && !vm.$root._vueMeta.paused) {
+ // batch potential DOM updates to prevent extraneous re-rendering
+ batchUpdate(function () { return vm.$meta().refresh(); });
+ }
+ }
+
+ /**
+ * Performs a batched update.
+ *
+ * @param {(null|Number)} id - the ID of this update
+ * @param {Function} callback - the update to perform
+ * @return {Number} id - a new ID
+ */
+ function batchUpdate(callback, timeout) {
+ if ( timeout === void 0 ) timeout = 10;
+
+ clearTimeout(batchId);
+
+ batchId = setTimeout(function () {
+ callback();
+ }, timeout);
+
+ return batchId
+ }
+
+ /**
+ * checks if passed argument is an array
+ * @param {any} arg - the object to check
+ * @return {Boolean} - true if `arg` is an array
+ */
+ function isArray(arg) {
+ return Array.isArray(arg)
+ }
+
+ function isUndefined(arg) {
+ return typeof arg === 'undefined'
+ }
+
+ function isObject(arg) {
+ return typeof arg === 'object'
+ }
+
+ function isFunction(arg) {
+ return typeof arg === 'function'
+ }
+
+ function isString(arg) {
+ return typeof arg === 'string'
+ }
+
+ function ensureIsArray(arg, key) {
+ if (!key || !isObject(arg)) {
+ return isArray(arg) ? arg : []
+ }
+
+ if (!isArray(arg[key])) {
+ arg[key] = [];
+ }
+ return arg
+ }
+
+ function ensuredPush(object, key, el) {
+ ensureIsArray(object, key);
+
+ object[key].push(el);
+ }
+
+ // Vue $root instance has a _vueMeta object property, otherwise its a boolean true
+ function hasMetaInfo(vm) {
+ if ( vm === void 0 ) vm = this;
+
+ return vm && (vm._vueMeta === true || isObject(vm._vueMeta))
+ }
+
+ // a component is in a metaInfo branch when itself has meta info or one of its (grand-)children has
+ function inMetaInfoBranch(vm) {
+ if ( vm === void 0 ) vm = this;
+
+ return vm && !isUndefined(vm._vueMeta)
+ }
+
+ function addNavGuards(vm) {
+ // return when nav guards already added or no router exists
+ if (vm.$root._vueMeta.navGuards || !vm.$root.$router) {
+ /* istanbul ignore next */
+ return
+ }
+
+ vm.$root._vueMeta.navGuards = true;
+
+ var $router = vm.$root.$router;
+ var $meta = vm.$root.$meta();
+
+ $router.beforeEach(function (to, from, next) {
+ $meta.pause();
+ next();
+ });
+
+ $router.afterEach(function () {
+ var ref = $meta.resume();
+ var metaInfo = ref.metaInfo;
+ if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
+ metaInfo.afterNavigation(metaInfo);
+ }
+ });
+ }
+
+ var appId = 1;
+
+ function createMixin(Vue, options) {
+ // for which Vue lifecycle hooks should the metaInfo be refreshed
+ var updateOnLifecycleHook = ['activated', 'deactivated', 'beforeMount'];
+
+ // watch for client side component updates
+ return {
+ beforeCreate: function beforeCreate() {
+ var this$1 = this;
+
+ Object.defineProperty(this, '_hasMetaInfo', {
+ configurable: true,
+ get: function get() {
+ // Show deprecation warning once when devtools enabled
+ if (Vue.config.devtools && !this.$root._vueMeta.hasMetaInfoDeprecationWarningShown) {
+ console.warn('VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead'); // eslint-disable-line no-console
+ this.$root._vueMeta.hasMetaInfoDeprecationWarningShown = true;
+ }
+ return hasMetaInfo(this)
+ }
+ });
+
+ // Add a marker to know if it uses metaInfo
+ // _vnode is used to know that it's attached to a real component
+ // useful if we use some mixin to add some meta tags (like nuxt-i18n)
+ if (!isUndefined(this.$options[options.keyName]) && this.$options[options.keyName] !== null) {
+ if (!this.$root._vueMeta) {
+ this.$root._vueMeta = { appId: appId };
+ appId++;
+ }
+
+ // 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._vueMeta) {
+ this._vueMeta = true;
+
+ var p = this.$parent;
+ while (p && p !== this.$root) {
+ if (isUndefined(p._vueMeta)) {
+ p._vueMeta = false;
+ }
+ p = p.$parent;
+ }
+ }
+
+ // coerce function-style metaInfo to a computed prop so we can observe
+ // it on creation
+ if (isFunction(this.$options[options.keyName])) {
+ if (!this.$options.computed) {
+ this.$options.computed = {};
+ }
+ this.$options.computed.$metaInfo = this.$options[options.keyName];
+
+ if (!this.$isServer) {
+ // 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)
+ ensuredPush(this.$options, 'created', function () {
+ this$1.$watch('$metaInfo', function () {
+ triggerUpdate(this, 'watcher');
+ });
+ });
+ }
+ }
+
+ // force an initial refresh on page load and prevent other lifecycleHooks
+ // to triggerUpdate until this initial refresh is finished
+ // this is to make sure that when a page is opened in an inactive tab which
+ // has throttled rAF/timers we still immediately set the page title
+ if (isUndefined(this.$root._vueMeta.initialized)) {
+ this.$root._vueMeta.initialized = this.$isServer;
+
+ if (!this.$root._vueMeta.initialized) {
+ ensuredPush(this.$options, 'beforeMount', function () {
+ // if this Vue-app was server rendered, set the appId to 'ssr'
+ // only one SSR app per page is supported
+ if (this$1.$root.$el && this$1.$root.$el.hasAttribute('data-server-rendered')) {
+ this$1.$root._vueMeta.appId = 'ssr';
+ }
+ });
+
+ // we use the mounted hook here as on page load
+ ensuredPush(this.$options, 'mounted', function () {
+ if (!this$1.$root._vueMeta.initialized) {
+ // used in triggerUpdate to check if a change was triggered
+ // during initialization
+ this$1.$root._vueMeta.initializing = true;
+
+ // refresh meta in nextTick so all child components have loaded
+ this$1.$nextTick(function () {
+ var this$1 = this;
+
+ var ref = this.$root.$meta().refresh();
+ var tags = ref.tags;
+ var metaInfo = ref.metaInfo;
+
+ // After ssr hydration (identifier by tags === false) check
+ // if initialized was set to null in triggerUpdate. That'd mean
+ // that during initilazation changes where triggered which need
+ // to be applied OR a metaInfo watcher was triggered before the
+ // current hook was called
+ // (during initialization all changes are blocked)
+ if (tags === false && this.$root._vueMeta.initialized === null) {
+ this.$nextTick(function () { return triggerUpdate(this$1, 'initializing'); });
+ }
+
+ this.$root._vueMeta.initialized = true;
+ delete this.$root._vueMeta.initializing;
+
+ // add the navigation guards if they havent been added yet
+ // they are needed for the afterNavigation callback
+ if (!options.refreshOnceOnNavigation && metaInfo.afterNavigation) {
+ addNavGuards(this);
+ }
+ });
+ }
+ });
+
+ // add the navigation guards if requested
+ if (options.refreshOnceOnNavigation) {
+ addNavGuards(this);
+ }
+ }
+ }
+
+ // do not trigger refresh on the server side
+ if (!this.$isServer) {
+ // no need to add this hooks on server side
+ updateOnLifecycleHook.forEach(function (lifecycleHook) {
+ ensuredPush(this$1.$options, lifecycleHook, function () { return triggerUpdate(this$1, lifecycleHook); });
+ });
+
+ // re-render meta data when returning from a child component to parent
+ ensuredPush(this.$options, 'destroyed', function () {
+ // Wait that element is hidden before refreshing meta tags (to support animations)
+ var interval = setInterval(function () {
+ if (this$1.$el && this$1.$el.offsetParent !== null) {
+ /* istanbul ignore next line */
+ return
+ }
+
+ clearInterval(interval);
+
+ if (!this$1.$parent) {
+ /* istanbul ignore next line */
+ return
+ }
+
+ triggerUpdate(this$1, 'destroyed');
+ }, 50);
+ });
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * These are constant variables used throughout the application.
+ */
+
+ // set some sane defaults
+ var defaultInfo = {
+ title: '',
+ titleChunk: '',
+ titleTemplate: '%s',
+ htmlAttrs: {},
+ bodyAttrs: {},
+ headAttrs: {},
+ base: [],
+ link: [],
+ meta: [],
+ style: [],
+ script: [],
+ noscript: [],
+ __dangerouslyDisableSanitizers: [],
+ __dangerouslyDisableSanitizersByTagID: {}
+ };
+
+ // 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 keyName = 'metaInfo';
+
+ // This is the attribute vue-meta arguments on elements to know which it should
+ // manage and which it should ignore.
+ var 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 ssrAttribute = '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 tagIDKeyName = 'vmid';
+
+ // This is the key name for possible meta templates
+ var metaTemplateKeyName = 'template';
+
+ // This is the key name for the content-holding property
+ var contentKeyName = 'content';
+
+ var defaultOptions = {
+ keyName: keyName,
+ attribute: attribute,
+ ssrAttribute: ssrAttribute,
+ tagIDKeyName: tagIDKeyName,
+ contentKeyName: contentKeyName,
+ metaTemplateKeyName: metaTemplateKeyName
+ };
+
+ // List of metaInfo property keys which are configuration options (and dont generate html)
+ var metaInfoOptionKeys = [
+ 'titleChunk',
+ 'titleTemplate',
+ 'changed',
+ '__dangerouslyDisableSanitizers',
+ '__dangerouslyDisableSanitizersByTagID'
+ ];
+
+ // The metaInfo property keys which are used to disable escaping
+ var disableOptionKeys = [
+ '__dangerouslyDisableSanitizers',
+ '__dangerouslyDisableSanitizersByTagID'
+ ];
+
+ // List of metaInfo property keys which only generates attributes and no tags
+ var metaInfoAttributeKeys = [
+ 'htmlAttrs',
+ 'headAttrs',
+ 'bodyAttrs'
+ ];
+
+ // from: https://github.com/kangax/html-minifier/blob/gh-pages/src/htmlminifier.js#L202
+ var booleanHtmlAttributes = [
+ 'allowfullscreen',
+ 'amp',
+ 'async',
+ 'autofocus',
+ 'autoplay',
+ 'checked',
+ 'compact',
+ 'controls',
+ 'declare',
+ 'default',
+ 'defaultchecked',
+ 'defaultmuted',
+ 'defaultselected',
+ 'defer',
+ 'disabled',
+ 'enabled',
+ 'formnovalidate',
+ 'hidden',
+ 'indeterminate',
+ 'inert',
+ 'ismap',
+ 'itemscope',
+ 'loop',
+ 'multiple',
+ 'muted',
+ 'nohref',
+ 'noresize',
+ 'noshade',
+ 'novalidate',
+ 'nowrap',
+ 'open',
+ 'pauseonexit',
+ 'readonly',
+ 'required',
+ 'reversed',
+ 'scoped',
+ 'seamless',
+ 'selected',
+ 'sortable',
+ 'truespeed',
+ 'typemustmatch',
+ 'visible'
+ ];
+
+ // eslint-disable-next-line no-console
+ var showWarningNotSupported = function () { return console.warn('This vue app/component has no vue-meta configuration'); };
+
+ function setOptions(options) {
+ // combine options
+ options = isObject(options) ? options : {};
+
+ for (var key in defaultOptions) {
+ if (!options[key]) {
+ options[key] = defaultOptions[key];
+ }
+ }
+
+ return options
+ }
+
+ function getOptions(options) {
+ var optionsCopy = {};
+ for (var key in options) {
+ optionsCopy[key] = options[key];
+ }
+ return optionsCopy
+ }
+
+ function pause(refresh) {
+ if ( refresh === void 0 ) refresh = true;
+
+ this.$root._vueMeta.paused = true;
+
+ return function () { return resume(refresh); }
+ }
+
+ function resume(refresh) {
+ if ( refresh === void 0 ) refresh = true;
+
+ this.$root._vueMeta.paused = false;
+
+ if (refresh) {
+ return this.$root.$meta().refresh()
+ }
+ }
+
+ function applyTemplate(ref, headObject, template, chunk) {
+ var component = ref.component;
+ var metaTemplateKeyName = ref.metaTemplateKeyName;
+ var contentKeyName = ref.contentKeyName;
+
+ if (isUndefined(template)) {
+ template = headObject[metaTemplateKeyName];
+ delete headObject[metaTemplateKeyName];
+ }
+
+ // return early if no template defined
+ if (!template) {
+ return false
+ }
+
+ if (isUndefined(chunk)) {
+ chunk = headObject[contentKeyName];
+ }
+
+ headObject[contentKeyName] = isFunction(template)
+ ? template.call(component, chunk)
+ : template.replace(/%s/g, chunk);
+
+ return true
+ }
+
+ /*
+ * To reduce build size, this file provides simple polyfills without
+ * overly excessive type checking and without modifying
+ * the global Array.prototype
+ * The polyfills are automatically removed in the commonjs build
+ * Also, only files in client/ & shared/ should use these functions
+ * files in server/ still use normal js function
+ */
+
+ function findIndex(array, predicate) {
+ var arguments$1 = arguments;
+
+ if (!Array.prototype.findIndex) {
+ // idx needs to be a Number, for..in returns string
+ for (var idx = 0; idx < array.length; idx++) {
+ if (predicate.call(arguments$1[2], array[idx], idx, array)) {
+ return idx
+ }
+ }
+ return -1
+ }
+ return array.findIndex(predicate, arguments[2])
+ }
+
+ function toArray(arg) {
+ if (!Array.from) {
+ return Array.prototype.slice.call(arg)
+ }
+ return Array.from(arg)
+ }
+
+ function includes(array, value) {
+ if (!Array.prototype.includes) {
+ for (var idx in array) {
+ if (array[idx] === value) {
+ return true
+ }
+ }
+
+ return false
+ }
+ return array.includes(value)
+ }
+
+ var clientSequences = [
+ [/&/g, '\u0026'],
+ [//g, '\u003e'],
+ [/"/g, '\u0022'],
+ [/'/g, '\u0027']
+ ];
+
+ // sanitizes potentially dangerous characters
+ function escape(info, options, escapeOptions) {
+ var tagIDKeyName = options.tagIDKeyName;
+ var doEscape = escapeOptions.doEscape; if ( doEscape === void 0 ) doEscape = function (v) { return v; };
+ var escaped = {};
+
+ for (var key in info) {
+ var value = info[key];
+
+ // no need to escape configuration options
+ if (includes(metaInfoOptionKeys, key)) {
+ escaped[key] = value;
+ continue
+ }
+
+ var disableKey = disableOptionKeys[0];
+ if (escapeOptions[disableKey] && includes(escapeOptions[disableKey], key)) {
+ // this info[key] doesnt need to escaped if the option is listed in __dangerouslyDisableSanitizers
+ escaped[key] = value;
+ continue
+ }
+
+ var tagId = info[tagIDKeyName];
+ if (tagId) {
+ disableKey = disableOptionKeys[1];
+
+ // keys which are listed in __dangerouslyDisableSanitizersByTagID for the current vmid do not need to be escaped
+ if (escapeOptions[disableKey] && escapeOptions[disableKey][tagId] && includes(escapeOptions[disableKey][tagId], key)) {
+ escaped[key] = value;
+ continue
+ }
+ }
+
+ if (isString(value)) {
+ escaped[key] = doEscape(value);
+ } else if (isArray(value)) {
+ escaped[key] = value.map(function (v) {
+ return isObject(v)
+ ? escape(v, options, escapeOptions)
+ : doEscape(v)
+ });
+ } else if (isObject(value)) {
+ escaped[key] = escape(value, options, escapeOptions);
+ } else {
+ escaped[key] = value;
+ }
+ }
+
+ return escaped
+ }
+
+ var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : 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 umd = createCommonjsModule(function (module, exports) {
+ (function (global, factory) {
+ module.exports = factory();
+ }(commonjsGlobal, (function () {
+ var isMergeableObject = function isMergeableObject(value) {
+ return isNonNullObject(value)
+ && !isSpecial(value)
+ };
+
+ function isNonNullObject(value) {
+ return !!value && typeof value === 'object'
+ }
+
+ function isSpecial(value) {
+ var stringValue = Object.prototype.toString.call(value);
+
+ return stringValue === '[object RegExp]'
+ || stringValue === '[object Date]'
+ || isReactElement(value)
+ }
+
+ // see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25
+ var canUseSymbol = typeof Symbol === 'function' && Symbol.for;
+ var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7;
+
+ function isReactElement(value) {
+ return value.$$typeof === REACT_ELEMENT_TYPE
+ }
+
+ function emptyTarget(val) {
+ return Array.isArray(val) ? [] : {}
+ }
+
+ function cloneUnlessOtherwiseSpecified(value, options) {
+ return (options.clone !== false && options.isMergeableObject(value))
+ ? deepmerge(emptyTarget(value), value, options)
+ : value
+ }
+
+ function defaultArrayMerge(target, source, options) {
+ return target.concat(source).map(function(element) {
+ return cloneUnlessOtherwiseSpecified(element, options)
+ })
+ }
+
+ function getMergeFunction(key, options) {
+ if (!options.customMerge) {
+ return deepmerge
+ }
+ var customMerge = options.customMerge(key);
+ return typeof customMerge === 'function' ? customMerge : deepmerge
+ }
+
+ function mergeObject(target, source, options) {
+ var destination = {};
+ if (options.isMergeableObject(target)) {
+ Object.keys(target).forEach(function(key) {
+ destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
+ });
+ }
+ Object.keys(source).forEach(function(key) {
+ if (!options.isMergeableObject(source[key]) || !target[key]) {
+ destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
+ } else {
+ destination[key] = getMergeFunction(key, options)(target[key], source[key], options);
+ }
+ });
+ return destination
+ }
+
+ function deepmerge(target, source, options) {
+ options = options || {};
+ options.arrayMerge = options.arrayMerge || defaultArrayMerge;
+ options.isMergeableObject = options.isMergeableObject || isMergeableObject;
+
+ var sourceIsArray = Array.isArray(source);
+ var targetIsArray = Array.isArray(target);
+ var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
+
+ if (!sourceAndTargetTypesMatch) {
+ return cloneUnlessOtherwiseSpecified(source, options)
+ } else if (sourceIsArray) {
+ return options.arrayMerge(target, source, options)
+ } else {
+ return mergeObject(target, source, options)
+ }
+ }
+
+ deepmerge.all = function deepmergeAll(array, options) {
+ if (!Array.isArray(array)) {
+ throw new Error('first argument should be an array')
+ }
+
+ return array.reduce(function(prev, next) {
+ return deepmerge(prev, next, options)
+ }, {})
+ };
+
+ var deepmerge_1 = deepmerge;
+
+ return deepmerge_1;
+
+ })));
+ });
+
+ function arrayMerge(ref, target, source) {
+ var component = ref.component;
+ var tagIDKeyName = ref.tagIDKeyName;
+ var metaTemplateKeyName = ref.metaTemplateKeyName;
+ var contentKeyName = ref.contentKeyName;
+
+ // we concat the arrays without merging objects contained in,
+ // but we check for a `vmid` property on each object in the array
+ // using an O(1) lookup associative array exploit
+ var destination = [];
+
+ target.forEach(function (targetItem, targetIndex) {
+ // no tagID so no need to check for duplicity
+ if (!targetItem[tagIDKeyName]) {
+ destination.push(targetItem);
+ return
+ }
+
+ var sourceIndex = findIndex(source, function (item) { return item[tagIDKeyName] === targetItem[tagIDKeyName]; });
+ var sourceItem = source[sourceIndex];
+
+ // source doesnt contain any duplicate vmid's, we can keep targetItem
+ if (sourceIndex === -1) {
+ destination.push(targetItem);
+ return
+ }
+
+ // 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)) {
+ destination.push(targetItem);
+ // remove current index from source array so its not concatenated to destination below
+ source.splice(sourceIndex, 1);
+ return
+ }
+
+ // we now know that targetItem is a duplicate and we should ignore it in favor of sourceItem
+
+ // if source specifies null as content then ignore both the target as the source
+ if (sourceItem[contentKeyName] === null || sourceItem.innerHTML === null) {
+ // remove current index from source array so its not concatenated to destination below
+ source.splice(sourceIndex, 1);
+ return
+ }
+
+ // now we only need to check if the target has a template to combine it with the source
+ var targetTemplate = targetItem[metaTemplateKeyName];
+ if (!targetTemplate) {
+ return
+ }
+
+ var sourceTemplate = sourceItem[metaTemplateKeyName];
+
+ if (!sourceTemplate) {
+ // use parent template and child content
+ applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, targetTemplate);
+ } else if (!sourceItem[contentKeyName]) {
+ // use child template and parent content
+ applyTemplate({ component: component, metaTemplateKeyName: metaTemplateKeyName, contentKeyName: contentKeyName }, sourceItem, undefined, targetItem[contentKeyName]);
+ }
+ });
+
+ return destination.concat(source)
+ }
+
+ function merge(target, source, options) {
+ if ( options === void 0 ) 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) {
+ delete source.title;
+ }
+
+ metaInfoAttributeKeys.forEach(function (attrKey) {
+ if (!source[attrKey]) {
+ return
+ }
+
+ for (var key in source[attrKey]) {
+ if (source[attrKey].hasOwnProperty(key) && source[attrKey][key] === undefined) {
+ delete source[attrKey][key];
+ }
+ }
+ });
+
+ return umd(target, source, {
+ arrayMerge: function (t, s) { return arrayMerge(options, t, s); }
+ })
+ }
+
+ /**
+ * 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 {Boolean} opts.deep - look for data in child components as well?
+ * @param {Function} opts.arrayMerge - how should arrays be merged?
+ * @param {String} opts.keyName - the name of the option to look for
+ * @param {Object} [result={}] - result so far
+ * @return {Object} result - final aggregated result
+ */
+ function getComponentOption(options, component, result) {
+ if ( options === void 0 ) options = {};
+ if ( result === void 0 ) result = {};
+
+ var keyName = options.keyName;
+ var metaTemplateKeyName = options.metaTemplateKeyName;
+ var tagIDKeyName = options.tagIDKeyName;
+ var $options = component.$options;
+ var $children = component.$children;
+
+ if (component._inactive) {
+ return result
+ }
+
+ // only collect option data if it exists
+ if ($options[keyName]) {
+ var data = $options[keyName];
+
+ // if option is a function, replace it with it's result
+ if (isFunction(data)) {
+ data = data.call(component);
+ }
+
+ // ignore data if its not an object, then we keep our previous result
+ if (!isObject(data)) {
+ return result
+ }
+
+ // merge with existing options
+ result = merge(result, data, options);
+ }
+
+ // collect & aggregate child options if deep = true
+ if ($children.length) {
+ $children.forEach(function (childComponent) {
+ // check if the childComponent is in a branch
+ // return otherwise so we dont walk all component branches unnecessarily
+ if (!inMetaInfoBranch(childComponent)) {
+ return
+ }
+
+ result = getComponentOption(options, childComponent, result);
+ });
+ }
+
+ if (metaTemplateKeyName && result.meta) {
+ // apply templates if needed
+ result.meta.forEach(function (metaObject) { return applyTemplate(options, metaObject); });
+
+ // remove meta items with duplicate vmid's
+ result.meta = result.meta.filter(function (metaItem, index, arr) {
+ return (
+ // keep meta item if it doesnt has a vmid
+ !metaItem.hasOwnProperty(tagIDKeyName) ||
+ // or if it's the first item in the array with this vmid
+ index === findIndex(arr, function (item) { return item[tagIDKeyName] === metaItem[tagIDKeyName]; })
+ )
+ });
+ }
+
+ return result
+ }
+
+ /**
+ * 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
+ */
+ function getMetaInfo(options, component, escapeSequences) {
+ if ( options === void 0 ) options = {};
+ if ( escapeSequences === void 0 ) escapeSequences = [];
+
+ // collect & aggregate all metaInfo $options
+ var info = getComponentOption(options, component, defaultInfo);
+
+ // Remove all "template" tags from meta
+
+ // 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.titleTemplate !== '%s') {
+ applyTemplate({ component: component, contentKeyName: 'title' }, info, info.titleTemplate, 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] : [];
+ }
+
+ var escapeOptions = {
+ doEscape: function (value) { return escapeSequences.reduce(function (val, ref) {
+ var v = ref[0];
+ var r = ref[1];
+
+ return val.replace(v, r);
+ }, value); }
+ };
+
+ disableOptionKeys.forEach(function (disableKey, index) {
+ if (index === 0) {
+ ensureIsArray(info, disableKey);
+ } else if (index === 1) {
+ for (var key in info[disableKey]) {
+ ensureIsArray(info[disableKey], key);
+ }
+ }
+
+ escapeOptions[disableKey] = info[disableKey];
+ });
+
+ // begin sanitization
+ info = escape(info, options, escapeOptions);
+
+ return info
+ }
+
+ /**
+ * Updates the document's html tag attributes
+ *
+ * @param {Object} attrs - the new document html attributes
+ * @param {HTMLElement} tag - the HTMLElement tag to update with new attrs
+ */
+ function updateAttribute(ref, attrs, tag) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+
+ var vueMetaAttrString = tag.getAttribute(attribute);
+ var vueMetaAttrs = vueMetaAttrString ? vueMetaAttrString.split(',') : [];
+ var toRemove = toArray(vueMetaAttrs);
+
+ var keepIndexes = [];
+ for (var attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ var value = includes(booleanHtmlAttributes, attr)
+ ? ''
+ : isArray(attrs[attr]) ? attrs[attr].join(' ') : attrs[attr];
+
+ tag.setAttribute(attr, value || '');
+
+ if (!includes(vueMetaAttrs, attr)) {
+ vueMetaAttrs.push(attr);
+ }
+
+ // filter below wont ever check -1
+ keepIndexes.push(toRemove.indexOf(attr));
+ }
+ }
+
+ var removedAttributesCount = toRemove
+ .filter(function (el, index) { return !includes(keepIndexes, index); })
+ .reduce(function (acc, attr) {
+ tag.removeAttribute(attr);
+ return acc + 1
+ }, 0);
+
+ if (vueMetaAttrs.length === removedAttributesCount) {
+ tag.removeAttribute(attribute);
+ } else {
+ tag.setAttribute(attribute, (vueMetaAttrs.sort()).join(','));
+ }
+ }
+
+ /**
+ * Updates the document title
+ *
+ * @param {String} title - the new title of the document
+ */
+ function updateTitle(title) {
+ if ( title === void 0 ) title = document.title;
+
+ document.title = title;
+ }
+
+ /**
+ * Updates meta tags inside and 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)} tags - an array of tag objects or a single object in case of base
+ * @return {Object} - a representation of what tags changed
+ */
+ function updateTag(appId, ref, type, tags, headTag, bodyTag) {
+ if ( ref === void 0 ) ref = {};
+ var attribute = ref.attribute;
+ var tagIDKeyName = ref.tagIDKeyName;
+
+ var oldHeadTags = toArray(headTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"], " + type + "[data-" + tagIDKeyName + "]")));
+ var oldBodyTags = toArray(bodyTag.querySelectorAll((type + "[" + attribute + "=\"" + appId + "\"][data-body=\"true\"], " + type + "[data-" + tagIDKeyName + "][data-body=\"true\"]")));
+ var dataAttributes = [tagIDKeyName, 'body'];
+ var newTags = [];
+
+ 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.filter(function (x) {
+ var k = JSON.stringify(x);
+ var res = !includes(found, k);
+ found.push(k);
+ return res
+ });
+ }
+
+ if (tags.length) {
+ tags.forEach(function (tag) {
+ var newElement = document.createElement(type);
+
+ newElement.setAttribute(attribute, appId);
+
+ var oldTags = tag.body !== true ? oldHeadTags : oldBodyTags;
+
+ for (var attr in tag) {
+ if (tag.hasOwnProperty(attr)) {
+ if (attr === 'innerHTML') {
+ newElement.innerHTML = tag.innerHTML;
+ } else if (attr === 'cssText') {
+ if (newElement.styleSheet) {
+ /* istanbul ignore next */
+ newElement.styleSheet.cssText = tag.cssText;
+ } else {
+ newElement.appendChild(document.createTextNode(tag.cssText));
+ }
+ } else {
+ var _attr = includes(dataAttributes, attr)
+ ? ("data-" + attr)
+ : attr;
+ var value = isUndefined(tag[attr]) || includes(booleanHtmlAttributes, attr) ? '' : tag[attr];
+ newElement.setAttribute(_attr, value);
+ }
+ }
+ }
+
+ // Remove a duplicate tag from domTagstoRemove, so it isn't cleared.
+ var indexToDelete;
+ var hasEqualElement = oldTags.some(function (existingTag, index) {
+ indexToDelete = index;
+ return newElement.isEqualNode(existingTag)
+ });
+
+ if (hasEqualElement && (indexToDelete || indexToDelete === 0)) {
+ oldTags.splice(indexToDelete, 1);
+ } else {
+ newTags.push(newElement);
+ }
+ });
+ }
+
+ var oldTags = oldHeadTags.concat(oldBodyTags);
+ oldTags.forEach(function (tag) { return tag.parentNode.removeChild(tag); });
+ newTags.forEach(function (tag) {
+ if (tag.getAttribute('data-body') === 'true') {
+ bodyTag.appendChild(tag);
+ } else {
+ headTag.appendChild(tag);
+ }
+ });
+
+ return { oldTags: oldTags, newTags: newTags }
+ }
+
+ function getTag(tags, tag) {
+ if (!tags[tag]) {
+ tags[tag] = document.getElementsByTagName(tag)[0];
+ }
+
+ return tags[tag]
+ }
+
+ /**
+ * Performs client-side updates when new meta info is received
+ *
+ * @param {Object} newInfo - the meta info to update to
+ */
+ function updateClientMetaInfo(appId, options, newInfo) {
+ if ( options === void 0 ) options = {};
+
+ var ssrAttribute = options.ssrAttribute;
+
+ // only cache tags for current update
+ var tags = {};
+
+ var htmlTag = getTag(tags, 'html');
+
+ // if this is a server render, then dont update
+ if (appId === 'ssr' && htmlTag.hasAttribute(ssrAttribute)) {
+ // remove the server render attribute so we can update on (next) changes
+ htmlTag.removeAttribute(ssrAttribute);
+ return false
+ }
+
+ // initialize tracked changes
+ var addedTags = {};
+ var removedTags = {};
+
+ for (var type in newInfo) {
+ // ignore these
+ if (includes(metaInfoOptionKeys, type)) {
+ continue
+ }
+
+ if (type === 'title') {
+ // update the title
+ updateTitle(newInfo.title);
+ continue
+ }
+
+ if (includes(metaInfoAttributeKeys, type)) {
+ var tagName = type.substr(0, 4);
+ updateAttribute(options, newInfo[type], getTag(tags, tagName));
+ continue
+ }
+
+ // tags should always be an array, ignore if it isnt
+ if (!isArray(newInfo[type])) {
+ continue
+ }
+
+ var ref = updateTag(
+ appId,
+ options,
+ type,
+ newInfo[type],
+ getTag(tags, 'head'),
+ getTag(tags, 'body')
+ );
+ var oldTags = ref.oldTags;
+ var newTags = ref.newTags;
+
+ if (newTags.length) {
+ addedTags[type] = newTags;
+ removedTags[type] = oldTags;
+ }
+ }
+
+ return { addedTags: addedTags, removedTags: removedTags }
+ }
+
+ 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 metaInfo = getMetaInfo(options, this.$root, clientSequences);
+
+ var appId = this.$root._vueMeta.appId;
+ var tags = updateClientMetaInfo(appId, options, metaInfo);
+ // emit "event" with new info
+ if (tags && isFunction(metaInfo.changed)) {
+ metaInfo.changed(metaInfo, tags.addedTags, tags.removedTags);
+ }
+
+ return { vm: this, metaInfo: metaInfo, tags: tags }
+ }
+ }
+
+ function _$meta(options) {
+ if ( options === void 0 ) options = {};
+
+ var _refresh$1 = _refresh(options);
+ var inject = function () {};
+
+ /**
+ * Returns an injector for server-side rendering.
+ * @this {Object} - the Vue instance (a root component)
+ * @return {Object} - injector
+ */
+ return function $meta() {
+ if (!this.$root._vueMeta) {
+ return {
+ getOptions: showWarningNotSupported,
+ refresh: showWarningNotSupported,
+ inject: showWarningNotSupported,
+ pause: showWarningNotSupported,
+ resume: showWarningNotSupported
+ }
+ }
+
+ return {
+ getOptions: function () { return getOptions(options); },
+ refresh: _refresh$1.bind(this),
+ inject: inject,
+ pause: pause.bind(this),
+ resume: resume.bind(this)
+ }
+ }
+ }
+
+ /**
+ * Plugin install function.
+ * @param {Function} Vue - the Vue constructor.
+ */
+ function install(Vue, options) {
+ if ( options === void 0 ) options = {};
+
+ if (Vue.__vuemeta_installed) {
+ return
+ }
+ Vue.__vuemeta_installed = true;
+
+ options = setOptions(options);
+
+ Vue.prototype.$meta = _$meta(options);
+
+ Vue.mixin(createMixin(Vue, options));
+ }
+
+ // automatic install
+ if (!isUndefined(window) && !isUndefined(window.Vue)) {
+ /* istanbul ignore next */
+ install(window.Vue);
+ }
+
+ var browser = {
+ version: version,
+ install: install,
+ hasMetaInfo: hasMetaInfo
+ };
+
+ return browser;
+
+}));
diff --git a/dist/vue-meta.min.js b/dist/vue-meta.min.js
new file mode 100644
index 0000000..5e8861e
--- /dev/null
+++ b/dist/vue-meta.min.js
@@ -0,0 +1 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).VueMeta=t()}(this,function(){"use strict";var e=null;function t(t,n){t.$root._vueMeta.initialized||!t.$root._vueMeta.initializing&&"watcher"!==n||(t.$root._vueMeta.initialized=null),t.$root._vueMeta.initialized&&!t.$root._vueMeta.paused&&function(t,n){void 0===n&&(n=10);clearTimeout(e),e=setTimeout(function(){t()},n)}(function(){return t.$meta().refresh()})}function n(e){return Array.isArray(e)}function r(e){return void 0===e}function i(e){return"object"==typeof e}function o(e){return"function"==typeof e}function a(e,t){return t&&i(e)?(n(e[t])||(e[t]=[]),e):n(e)?e:[]}function u(e,t,n){a(e,t),e[t].push(n)}function s(e){return void 0===e&&(e=this),e&&(!0===e._vueMeta||i(e._vueMeta))}function f(e){if(!e.$root._vueMeta.navGuards&&e.$root.$router){e.$root._vueMeta.navGuards=!0;var t=e.$root.$router,n=e.$root.$meta();t.beforeEach(function(e,t,r){n.pause(),r()}),t.afterEach(function(){var e=n.resume().metaInfo;e&&e.afterNavigation&&o(e.afterNavigation)&&e.afterNavigation(e)})}}var c=1;var l={title:"",titleChunk:"",titleTemplate:"%s",htmlAttrs:{},bodyAttrs:{},headAttrs:{},base:[],link:[],meta:[],style:[],script:[],noscript:[],__dangerouslyDisableSanitizers:[],__dangerouslyDisableSanitizersByTagID:{}},d={keyName:"metaInfo",attribute:"data-vue-meta",ssrAttribute:"data-vue-meta-server-rendered",tagIDKeyName:"vmid",contentKeyName:"content",metaTemplateKeyName:"template"},v=["titleChunk","titleTemplate","changed","__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],h=["__dangerouslyDisableSanitizers","__dangerouslyDisableSanitizersByTagID"],p=["htmlAttrs","headAttrs","bodyAttrs"],m=["allowfullscreen","amp","async","autofocus","autoplay","checked","compact","controls","declare","default","defaultchecked","defaultmuted","defaultselected","defer","disabled","enabled","formnovalidate","hidden","indeterminate","inert","ismap","itemscope","loop","multiple","muted","nohref","noresize","noshade","novalidate","nowrap","open","pauseonexit","readonly","required","reversed","scoped","seamless","selected","sortable","truespeed","typemustmatch","visible"],y=function(){return console.warn("This vue app/component has no vue-meta configuration")};function g(e){return void 0===e&&(e=!0),this.$root._vueMeta.paused=!0,function(){return b(e)}}function b(e){if(void 0===e&&(e=!0),this.$root._vueMeta.paused=!1,e)return this.$root.$meta().refresh()}function $(e,t,n,i){var a=e.component,u=e.metaTemplateKeyName,s=e.contentKeyName;return r(n)&&(n=t[u],delete t[u]),!!n&&(r(i)&&(i=t[s]),t[s]=o(n)?n.call(a,i):n.replace(/%s/g,i),!0)}function M(e,t){var n=arguments;if(!Array.prototype.findIndex){for(var r=0;r/g,">"],[/"/g,'"'],[/'/g,"'"]];"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;var N=function(e,t){return e(t={exports:{}},t.exports),t.exports}(function(e,t){e.exports=function(){var e=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var n=Object.prototype.toString.call(e);return"[object RegExp]"===n||"[object Date]"===n||function(e){return e.$$typeof===t}(e)}(e)},t="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function n(e,t){return!1!==t.clone&&t.isMergeableObject(e)?o((n=e,Array.isArray(n)?[]:{}),e,t):e;var n}function r(e,t,r){return e.concat(t).map(function(e){return n(e,r)})}function i(e,t,r){var i={};return r.isMergeableObject(e)&&Object.keys(e).forEach(function(t){i[t]=n(e[t],r)}),Object.keys(t).forEach(function(a){r.isMergeableObject(t[a])&&e[a]?i[a]=function(e,t){if(!t.customMerge)return o;var n=t.customMerge(e);return"function"==typeof n?n:o}(a,r)(e[a],t[a],r):i[a]=n(t[a],r)}),i}function o(t,o,a){(a=a||{}).arrayMerge=a.arrayMerge||r,a.isMergeableObject=a.isMergeableObject||e;var u=Array.isArray(o),s=Array.isArray(t),f=u===s;return f?u?a.arrayMerge(t,o,a):i(t,o,a):n(o,a)}return o.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce(function(e,n){return o(e,n,t)},{})},o}()});function w(e,t,n){return void 0===n&&(n={}),t.hasOwnProperty("title")&&void 0===t.title&&delete t.title,p.forEach(function(e){if(t[e])for(var n in t[e])t[e].hasOwnProperty(n)&&void 0===t[e][n]&&delete t[e][n]}),N(e,t,{arrayMerge:function(e,t){return function(e,t,n){var r=e.component,i=e.tagIDKeyName,o=e.metaTemplateKeyName,a=e.contentKeyName,u=[];return t.forEach(function(e,t){if(e[i]){var s=M(n,function(t){return t[i]===e[i]}),f=n[s];if(-1!==s){if(f.hasOwnProperty(a)&&void 0===f[a]||f.hasOwnProperty("innerHTML")&&void 0===f.innerHTML)return u.push(e),void n.splice(s,1);if(null!==f[a]&&null!==f.innerHTML){var c=e[o];c&&(f[o]?f[a]||$({component:r,metaTemplateKeyName:o,contentKeyName:a},f,void 0,e[a]):$({component:r,metaTemplateKeyName:o,contentKeyName:a},f,c))}else n.splice(s,1)}else u.push(e)}else u.push(e)}),u.concat(n)}(n,e,t)}})}function I(e,t,n){void 0===e&&(e={}),void 0===n&&(n={});var a=e.keyName,u=e.metaTemplateKeyName,s=e.tagIDKeyName,f=t.$options,c=t.$children;if(t._inactive)return n;if(f[a]){var l=f[a];if(o(l)&&(l=l.call(t)),!i(l))return n;n=w(n,l,e)}return c.length&&c.forEach(function(t){(function(e){return void 0===e&&(e=this),e&&!r(e._vueMeta)})(t)&&(n=I(e,t,n))}),u&&n.meta&&(n.meta.forEach(function(t){return $(e,t)}),n.meta=n.meta.filter(function(e,t,n){return!e.hasOwnProperty(s)||t===M(n,function(t){return t[s]===e[s]})})),n}function O(e,t,r){void 0===e&&(e={}),void 0===r&&(r=[]);var o=I(e,t,l);o.title&&(o.titleChunk=o.title),o.titleTemplate&&"%s"!==o.titleTemplate&&$({component:t,contentKeyName:"title"},o,o.titleTemplate,o.titleChunk||""),o.base&&(o.base=Object.keys(o.base).length?[o.base]:[]);var u={doEscape:function(e){return r.reduce(function(e,t){var n=t[0],r=t[1];return e.replace(n,r)},e)}};return h.forEach(function(e,t){if(0===t)a(o,e);else if(1===t)for(var n in o[e])a(o[e],n);u[e]=o[e]}),o=function e(t,r,o){var a=r.tagIDKeyName,u=o.doEscape;void 0===u&&(u=function(e){return e});var s={};for(var f in t){var c=t[f];if(T(v,f))s[f]=c;else{var l=h[0];if(o[l]&&T(o[l],f))s[f]=c;else{var d=t[a];d&&(l=h[1],o[l]&&o[l][d]&&T(o[l][d],f))?s[f]=c:"string"==typeof c?s[f]=u(c):n(c)?s[f]=c.map(function(t){return i(t)?e(t,r,o):u(t)}):i(c)?s[f]=e(c,r,o):s[f]=c}}}return s}(o,e,u)}function z(e,t,r){void 0===e&&(e={});var i=e.attribute,o=r.getAttribute(i),a=o?o.split(","):[],u=_(a),s=[];for(var f in t)if(t.hasOwnProperty(f)){var c=T(m,f)?"":n(t[f])?t[f].join(" "):t[f];r.setAttribute(f,c||""),T(a,f)||a.push(f),s.push(u.indexOf(f))}var l=u.filter(function(e,t){return!T(s,t)}).reduce(function(e,t){return r.removeAttribute(t),e+1},0);a.length===l?r.removeAttribute(i):r.setAttribute(i,a.sort().join(","))}function E(e,t,n,i,o,a){void 0===t&&(t={});var u=t.attribute,s=t.tagIDKeyName,f=_(o.querySelectorAll(n+"["+u+'="'+e+'"], '+n+"[data-"+s+"]")),c=_(a.querySelectorAll(n+"["+u+'="'+e+'"][data-body="true"], '+n+"[data-"+s+'][data-body="true"]')),l=[s,"body"],d=[];if(i.length>1){var v=[];i=i.filter(function(e){var t=JSON.stringify(e),n=!T(v,t);return v.push(t),n})}i.length&&i.forEach(function(t){var i=document.createElement(n);i.setAttribute(u,e);var o,a=!0!==t.body?f:c;for(var s in t)if(t.hasOwnProperty(s))if("innerHTML"===s)i.innerHTML=t.innerHTML;else if("cssText"===s)i.styleSheet?i.styleSheet.cssText=t.cssText:i.appendChild(document.createTextNode(t.cssText));else{var v=T(l,s)?"data-"+s:s,h=r(t[s])||T(m,s)?"":t[s];i.setAttribute(v,h)}a.some(function(e,t){return o=t,i.isEqualNode(e)})&&(o||0===o)?a.splice(o,1):d.push(i)});var h=f.concat(c);return h.forEach(function(e){return e.parentNode.removeChild(e)}),d.forEach(function(e){"true"===e.getAttribute("data-body")?a.appendChild(e):o.appendChild(e)}),{oldTags:h,newTags:d}}function S(e,t){return e[t]||(e[t]=document.getElementsByTagName(t)[0]),e[t]}function j(e){return void 0===e&&(e={}),function(){var t=O(e,this.$root,A),r=function(e,t,r){void 0===t&&(t={});var i=t.ssrAttribute,o={},a=S(o,"html");if("ssr"===e&&a.hasAttribute(i))return a.removeAttribute(i),!1;var u,s={},f={};for(var c in r)if(!T(v,c))if("title"!==c){if(T(p,c)){var l=c.substr(0,4);z(t,r[c],S(o,l))}else if(n(r[c])){var d=E(e,t,c,r[c],S(o,"head"),S(o,"body")),h=d.oldTags,m=d.newTags;m.length&&(s[c]=m,f[c]=h)}}else void 0===(u=r.title)&&(u=document.title),document.title=u;return{addedTags:s,removedTags:f}}(this.$root._vueMeta.appId,e,t);return r&&o(t.changed)&&t.changed(t,r.addedTags,r.removedTags),{vm:this,metaInfo:t,tags:r}}}function x(e,n){void 0===n&&(n={}),e.__vuemeta_installed||(e.__vuemeta_installed=!0,n=function(e){for(var t in e=i(e)?e:{},d)e[t]||(e[t]=d[t]);return e}(n),e.prototype.$meta=function(e){void 0===e&&(e={});var t=j(e),n=function(){};return function(){return this.$root._vueMeta?{getOptions:function(){return function(e){var t={};for(var n in e)t[n]=e[n];return t}(e)},refresh:t.bind(this),inject:n,pause:g.bind(this),resume:b.bind(this)}:{getOptions:y,refresh:y,inject:y,pause:y,resume:y}}}(n),e.mixin(function(e,n){var i=["activated","deactivated","beforeMount"];return{beforeCreate:function(){var a=this;if(Object.defineProperty(this,"_hasMetaInfo",{configurable:!0,get:function(){return e.config.devtools&&!this.$root._vueMeta.hasMetaInfoDeprecationWarningShown&&(console.warn("VueMeta DeprecationWarning: _hasMetaInfo has been deprecated and will be removed in a future version. Please use hasMetaInfo(vm) instead"),this.$root._vueMeta.hasMetaInfoDeprecationWarningShown=!0),s(this)}}),!r(this.$options[n.keyName])&&null!==this.$options[n.keyName]){if(this.$root._vueMeta||(this.$root._vueMeta={appId:c},c++),!this._vueMeta){this._vueMeta=!0;for(var l=this.$parent;l&&l!==this.$root;)r(l._vueMeta)&&(l._vueMeta=!1),l=l.$parent}o(this.$options[n.keyName])&&(this.$options.computed||(this.$options.computed={}),this.$options.computed.$metaInfo=this.$options[n.keyName],this.$isServer||u(this.$options,"created",function(){a.$watch("$metaInfo",function(){t(this,"watcher")})})),r(this.$root._vueMeta.initialized)&&(this.$root._vueMeta.initialized=this.$isServer,this.$root._vueMeta.initialized||(u(this.$options,"beforeMount",function(){a.$root.$el&&a.$root.$el.hasAttribute("data-server-rendered")&&(a.$root._vueMeta.appId="ssr")}),u(this.$options,"mounted",function(){a.$root._vueMeta.initialized||(a.$root._vueMeta.initializing=!0,a.$nextTick(function(){var e=this,r=this.$root.$meta().refresh(),i=r.tags,o=r.metaInfo;!1===i&&null===this.$root._vueMeta.initialized&&this.$nextTick(function(){return t(e,"initializing")}),this.$root._vueMeta.initialized=!0,delete this.$root._vueMeta.initializing,!n.refreshOnceOnNavigation&&o.afterNavigation&&f(this)}))}),n.refreshOnceOnNavigation&&f(this))),this.$isServer||(i.forEach(function(e){u(a.$options,e,function(){return t(a,e)})}),u(this.$options,"destroyed",function(){var e=setInterval(function(){a.$el&&null!==a.$el.offsetParent||(clearInterval(e),a.$parent&&t(a,"destroyed"))},50)}))}}}}(e,n)))}return r(window)||r(window.Vue)||x(window.Vue),{version:"2.0.3",install:x,hasMetaInfo:s}});
diff --git a/package.json b/package.json
index 967ce8d..692fb7d 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
"lint": "eslint src test",
"prerelease": "git checkout master && git pull -r",
"release": "yarn lint && yarn test && yarn build && standard-version",
- "postrelease": "yarn build",
+ "postrelease": "yarn build && git add dist/* && git commit --amend",
"test": "yarn test:unit && yarn test:e2e-ssr && yarn test:e2e-browser",
"test:e2e-ssr": "jest test/e2e/ssr",
"test:e2e-browser": "jest test/e2e/browser",