mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-16 17:10:34 +03:00
18fd23d3c0
This shouldnt be necessary anymore because we force initialization once on mounted/nextTick. Using beforeMount is also inherently less optimal because you are unlikely to benefit from walking the component tree as beforeMount is called before all child components are loaded. So using beforeMount resulted probably that for every component which uses metaInfo a refresh was called on load. A possible caveat that may exists due to removing beforeMount in favor of a single refresh is that it takes longer for your metaInfo to be updated if you have a lot of components on your page, not sure if this will be a problem in real world scenarios because if this is a problem you should probably be using ssr anyway. Also the v1 docs state that using beforeMount also results in a single update (although in practice it could be more then one)
136 lines
5.3 KiB
JavaScript
136 lines
5.3 KiB
JavaScript
import triggerUpdate from '../client/triggerUpdate'
|
|
import hasMetaInfo from './hasMetaInfo'
|
|
import { isUndefined, isFunction } from './typeof'
|
|
import { ensuredPush } from './ensure'
|
|
|
|
export default function createMixin(Vue, options) {
|
|
// for which Vue lifecycle hooks should the metaInfo be refreshed
|
|
const updateOnLifecycleHook = ['activated', 'deactivated']
|
|
|
|
// watch for client side component updates
|
|
return {
|
|
beforeCreate() {
|
|
Object.defineProperty(this, '_hasMetaInfo', {
|
|
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 import hasMetaInfo and 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 = {}
|
|
}
|
|
|
|
// 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 (isUndefined(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) {
|
|
const $rootMeta = this.$root.$meta()
|
|
|
|
ensuredPush(this.$options, 'mounted', () => {
|
|
if (!this.$root._vueMeta.initialized) {
|
|
// refresh meta in nextTick so all child components have loaded
|
|
this.$nextTick(function () {
|
|
$rootMeta.refresh()
|
|
this.$root._vueMeta.initialized = true
|
|
})
|
|
}
|
|
})
|
|
|
|
// add vue-router navigation guard to prevent multiple updates during navigation
|
|
// only usefull on the client side
|
|
if (options.refreshOnceOnNavigation && this.$root.$router) {
|
|
const $router = this.$root.$router
|
|
$router.beforeEach((to, from, next) => {
|
|
$rootMeta.pause()
|
|
next()
|
|
})
|
|
|
|
$router.afterEach(() => {
|
|
const { vm, metaInfo } = $rootMeta.resume()
|
|
if (metaInfo && metaInfo.afterNavigation && isFunction(metaInfo.afterNavigation)) {
|
|
metaInfo.afterNavigation.call(vm, metaInfo)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|