diff --git a/examples/next/Metainfo.vue b/examples/next/Metainfo.vue index fde5855..13c0dd5 100644 --- a/examples/next/Metainfo.vue +++ b/examples/next/Metainfo.vue @@ -10,17 +10,12 @@ export default { } }, setup() { - const mapping = inject('__vueMetaConfig') - - return { - mapping - } }, render() { const targets = {} for (const key in this.metainfo) { - const config = this.mapping[key] || {} + const config = this.$metaInfo.config[key] || {} const vnodes = renderMeta(this, key, this.metainfo[key], config) let target = (key !== 'base' && this.metainfo[key].target) || config.target || 'head' diff --git a/examples/next/index.js b/examples/next/index.js index dc55f6f..96373ea 100644 --- a/examples/next/index.js +++ b/examples/next/index.js @@ -1,9 +1,12 @@ -import { markRaw, reactive, onMounted, customRef, getCurrentInstance } from 'vue' -import { def, hasOwn, isObject, isArray, isPlainObject } from '@vue/shared' +import { markRaw, reactive, onUnmounted, getCurrentInstance } from 'vue' +import { hasOwn, isObject, isArray, isPlainObject } from '@vue/shared' import { defaultMapping } from './config' let appId = 0 +const shadow = markRaw({}) +const metainfo = reactive({}) + export function createMeta ({ resolver, config }) { const id = Symbol(`vueMeta[${appId++}]`) @@ -12,116 +15,142 @@ export function createMeta ({ resolver, config }) { const $metaInfo = { id, resolver, - shadow: markRaw({}), - metainfo: reactive({}) + config: { + ...defaultMapping, + ...config + } } app.config.globalProperties.$metaInfo = $metaInfo - app.provide('metainfo', $metaInfo.metainfo) - app.provide('__vueMetaConfig', { - id, - ...defaultMapping, - ...config - }) - - app.config.globalProperties.$meta = this + app.provide('metainfo', metainfo) } } } -export function useMeta (obj) { - const vm = getCurrentInstance() +export function useMeta (obj, context) { + // set empty object to remove everything + const unmount = () => setMetainfoByObject(context, {}, shadow, metainfo) - const { shadow, metainfo } = vm.ctx.$metaInfo - addMetainfoRecursive(obj, vm, shadow, metainfo) + if (!context) { + context = getCurrentInstance() - return createProxy(obj, createHandler(vm)) + onUnmounted(unmount) + } + + if (!context) { + context = {} + } + + setMetainfoByObject(context, obj, shadow, metainfo) + + const meta = createProxy(obj, createHandler(context)) + + return { + meta, + unmount + } } function createProxy (obj, handler) { - return new Proxy(obj, handler) + return markRaw(new Proxy(obj, handler)) } -function createHandler (vm, keyPath = []) { +function createHandler (context, keyPath = []) { return { get (target, prop) { const value = target[prop] - if (isObject(value)) { - if (!value.__vm_proxy) { - const newKeyPath = [...keyPath, prop] - - const handler = createHandler(vm, newKeyPath) - value.__vm_proxy = createProxy(value, handler) - } - - return value.__vm_proxy + if (!isObject(value)) { + return value } - return value + if (!value.__vm_proxy) { + const newKeyPath = [...keyPath, prop] + + const handler = createHandler(context, newKeyPath) + value.__vm_proxy = createProxy(value, handler) + } + + return value.__vm_proxy }, set (target, prop, value) { - updateMetainfo(keyPath, vm, prop, value) + updateMetainfo(keyPath, context, prop, value) return true } } } -function addMetainfoRecursive (obj, vm, shadowParent, liveParent) { - for (const key in obj) { - if (isPlainObject(obj[key])) { - if (!shadowParent[key]) { - shadowParent[key] = {} - liveParent[key] = {} - } +function setMetainfo (context, key, value, shadowParent, liveParent, keyTree = []) { + if (isPlainObject(value)) { + if (!shadowParent[key]) { + shadowParent[key] = {} + liveParent[key] = {} + } - addMetainfoRecursive(obj[key], vm, shadowParent[key], liveParent[key]) + return setMetainfoByObject(context, value, shadowParent[key], liveParent[key], keyTree) + } + + let idx = -1 + if (!hasOwn(shadowParent, key)) { + shadowParent[key] = [] + } else { + idx = shadowParent[key].findIndex(({ context: $context }) => $context === context) + } + + if (idx > -1 && value === undefined) { + shadowParent[key].splice(idx, 1) + } else if (idx > -1) { + shadowParent[key][idx].value = value + } else if (value) { + shadowParent[key].push({ context, value }) + } + + resolveActiveMetainfo(context, key, keyTree, shadowParent, liveParent) +} + +function setMetainfoByObject (context, obj, shadowParent, liveParent, keyTree = []) { + for (const key in shadowParent) { + if (hasOwn(obj, key)) { continue } - if (!shadowParent[key]) { - shadowParent[key] = [] + if (isPlainObject(shadowParent[key])) { + setMetainfoByObject(context, {}, shadowParent[key], liveParent[key], [...keyTree, key]) + continue } - shadowParent[key].push({ vm, value: obj[key] }) + setMetainfo(context, key, undefined, shadowParent, liveParent, [...keyTree, key]) + } - setLive(vm, key, shadowParent, liveParent) + for (const key in obj) { + setMetainfo(context, key, obj[key], shadowParent, liveParent, [...keyTree, key]) } } -function updateMetainfo (keyPath, vm, key, value) { - let { shadow: shadowParent, metainfo: liveParent } = vm.ctx.$metaInfo +function updateMetainfo (keyPath, context, key, value) { + let shadowParent = shadow + let liveParent = metainfo for (const _key of keyPath) { shadowParent = shadowParent[_key] liveParent = liveParent[_key] } - if (isPlainObject(value)) { - if (!shadowParent[key]) { - shadowParent[key] = {} - liveParent[key] = {} - } - // TODO: fix this shit - addMetainfoRecursive(value, vm, shadowParent[key], liveParent[key]) - return - } - - const idx = shadowParent[key].findIndex(({ vm: _vm }) => _vm === vm) - shadowParent[key][idx].value = value - - setLive(vm, key, shadowParent, liveParent) + setMetainfo(context, key, value, shadowParent, liveParent) } -function setLive (vm, key, shadowParent, liveParent) { +function resolveActiveMetainfo (context, key, keyTree, shadowParent, liveParent) { let value + if (shadowParent[key].length > 1) { - value = vm.ctx.$metaInfo.resolver(shadowParent[key]) - } else { + value = context.ctx.$metaInfo.resolver(key, shadowParent[key], liveParent[key]) + } else if (shadowParent[key].length) { value = shadowParent[key][0].value } - if (!hasOwn(liveParent, key) || liveParent[key] !== value) { + if (value === undefined) { + delete liveParent[key] + } else if (!hasOwn(liveParent, key) || liveParent[key] !== value) { liveParent[key] = value } } diff --git a/examples/vue-router/app.js b/examples/vue-router/app.js index 8931311..3b1ec35 100644 --- a/examples/vue-router/app.js +++ b/examples/vue-router/app.js @@ -1,8 +1,8 @@ -import { createApp, defineComponent, reactive, inject, markRaw, toRefs, h, customRef, watch, watchEffect } from 'vue' +import { createApp, defineComponent, reactive, inject, toRefs, h, watch } from 'vue' import { createRouter, createWebHistory } from 'vue-router' import Metainfo from '../next/Metainfo.vue' import { createMeta, useMeta } from '../next' -import About from './about.vue' +// import About from './about.vue' const metaUpdated = 'no' @@ -16,25 +16,24 @@ const ChildComponent = defineComponent({
Has metaInfo been updated due to navigation? {{ metaUpdated }}
`, - created () { - // console.log(this) - }, - setup () { + setup (props) { const state = reactive({ date: null, metaUpdated }) - const metainfo = useMeta({ + const title = props.page[0].toUpperCase() + props.page.slice(1) + + useMeta({ charset: 'utf16', - description: 'Description 2', + title, + description: 'Description ' + props.page, og: { - title: 'Og Title 2' + title: 'Og Title ' + props.page } }) return { - metainfo, ...toRefs(state) } } @@ -51,7 +50,7 @@ function view (page) { const App = { setup () { - /* const metainfo = useMeta({ + const { meta } = useMeta({ base: { href: '/vue-router', target: '_blank' }, charset: 'utf8', title: 'My Title', @@ -83,7 +82,7 @@ const App = { script: [ '', { src: 'body-script2.js', target: 'body' }, { src: 'body-script3.js', target: '#put-it-here' } ], @@ -102,21 +101,12 @@ const App = { } }) - setTimeout(() => (metainfo.title = 'My Updated Title'), 2000) */ - - const meta = useMeta({ - charset: 'utf8', - title: 'Title 1', - og: { - title: 'Og Title 1' - } - }) - - setTimeout(() => (meta.charset = 'utf17'), 2000) - setTimeout(() => (meta.og = { title: 'Updated Og Title 1' }), 3000) // TODO: fix + setTimeout(() => (meta.title = 'My Updated Title'), 2000) const metainfo = inject('metainfo') + window.$metainfo = metainfo + watch(metainfo, (newValue, oldValue) => { console.log('UPDATE', newValue) }) @@ -137,7 +127,7 @@ const App = {