2
0
mirror of https://github.com/tenrok/vue-meta.git synced 2026-06-16 17:10:34 +03:00
Files
vue-meta/src/shared/mixin.js
T
pimlie 18fd23d3c0 refactor: remove beforeMount hook
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)
2019-03-12 10:03:46 +01:00

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)
})
}
}
}
}
}