From 3c2d9ce284037db80987cd59e477de780b9ee1f7 Mon Sep 17 00:00:00 2001 From: pimlie Date: Sun, 28 Feb 2021 23:30:04 +0100 Subject: [PATCH] chore(release): 3.0.0-alpha.2 --- CHANGELOG.md | 7 + dist/vue-meta.cjs.js | 231 ++++++++++++++++++--------- dist/vue-meta.cjs.prod.js | 231 ++++++++++++++++++--------- dist/vue-meta.d.ts | 66 ++++++-- dist/vue-meta.esm-browser.js | 261 ++++++++++++++++++++----------- dist/vue-meta.esm-browser.min.js | 4 +- dist/vue-meta.esm-bundler.js | 229 ++++++++++++++++++--------- dist/vue-meta.global.js | 259 +++++++++++++++++++----------- dist/vue-meta.global.min.js | 4 +- package.json | 2 +- 10 files changed, 859 insertions(+), 435 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c51936e..84d132a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [3.0.0-alpha.2](https://github.com/nuxt/vue-meta/compare/v3.0.0-alpha.1...v3.0.0-alpha.2) (2021-02-28) + + +### Features + +* add support for computed metadata ([3e1a0da](https://github.com/nuxt/vue-meta/commit/3e1a0da9e4d744f74702ae11bbe3a1bec0f0a125)) + ## [3.0.0-alpha.1](https://github.com/nuxt/vue-meta/compare/v3.0.0-alpha.0...v3.0.0-alpha.1) (2021-01-31) diff --git a/dist/vue-meta.cjs.js b/dist/vue-meta.cjs.js index 8366b647..9ff1757d 100644 --- a/dist/vue-meta.cjs.js +++ b/dist/vue-meta.cjs.js @@ -1,5 +1,5 @@ /** - * vue-meta v3.0.0-alpha.1 + * vue-meta v3.0.0-alpha.2 * (c) 2021 * - Pim (@pimlie) * - All the amazing contributors @@ -612,7 +612,7 @@ function renderAttributes(context, key, data, config) { } function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) { const slot = slots && slots[slotName]; - if (!slot) { + if (!slot || !isFunction(slot)) { return content; } const slotScopeProps = { @@ -635,9 +635,34 @@ const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === const PolySymbol = (name) => // vm = vue meta hasSymbol - ? Symbol( '[vue-meta]: ' + name ) - : ( '[vue-meta]: ' ) + name; -const metaActiveKey = /*#__PURE__*/ PolySymbol( 'active_meta' ); + ? Symbol('[vue-meta]: ' + name ) + : ('[vue-meta]: ' ) + name; +const metaActiveKey = /*#__PURE__*/ PolySymbol('meta_active' ); + +/** + * Apply the differences between newSource & oldSource to target + */ +function applyDifference(target, newSource, oldSource) { + for (const key in newSource) { + if (!(key in oldSource)) { + target[key] = newSource[key]; + continue; + } + // We dont care about nested objects here , these changes + // should already have been tracked by the MergeProxy + if (isObject(target[key])) { + continue; + } + if (newSource[key] !== oldSource[key]) { + target[key] = newSource[key]; + } + } + for (const key in oldSource) { + if (!(key in newSource)) { + delete target[key]; + } + } +} function getCurrentManager(vm) { if (!vm) { @@ -649,15 +674,22 @@ function getCurrentManager(vm) { return vm.appContext.config.globalProperties.$metaManager; } function useMeta(source, manager) { - const vm = vue.getCurrentInstance(); + const vm = vue.getCurrentInstance() || undefined; if (!manager && vm) { manager = getCurrentManager(vm); } if (!manager) { - // oopsydoopsy throw new Error('No manager or current instance'); } - return manager.addMeta(source, vm || undefined); + if (vue.isProxy(source)) { + vue.watch(source, (newSource, oldSource) => { + // We only care about first level props, second+ level will already be changed by the merge proxy + applyDifference(metaProxy.meta, newSource, oldSource); + }); + source = source.value; + } + const metaProxy = manager.addMeta(source, vm); + return metaProxy; } function useActiveMeta() { return vue.inject(metaActiveKey); @@ -695,83 +727,128 @@ function addVnode(teleports, to, vnodes) { } teleports[to].push(...nodes); } -function createMetaManager(config, resolver) { - const resolve = (options, contexts, active, key, pathSegments) => { - if (isFunction(resolver)) { - return resolver(options, contexts, active, key, pathSegments); +const createMetaManager = (config, resolver) => MetaManager.create(config, resolver); +class MetaManager { + constructor(config, target, resolver) { + this.ssrCleanedUp = false; + this.config = config; + this.target = target; + if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { + this.resolver = resolver; } - return resolver.resolve(options, contexts, active, key, pathSegments); - }; - const { addSource, delSource } = createMergedObject(resolve, active); - // TODO: validate resolver - const manager = { - config, - install(app) { - app.component('Metainfo', Metainfo); - app.config.globalProperties.$metaManager = manager; - app.provide(metaActiveKey, active); - }, - addMeta(metaObj, vm) { - if (!vm) { - vm = vue.getCurrentInstance() || undefined; - } - const resolveContext = { vm }; - if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { - resolver.setup(resolveContext); - } - // TODO: optimize initial compute - const meta = addSource(metaObj, resolveContext, true); - const unmount = () => delSource(meta); - if (vm) { - vue.onUnmounted(unmount); - } - return { - meta, - unmount - }; - }, - render({ slots } = {}) { - const teleports = {}; - for (const key in active) { - const config = this.config[key] || {}; - let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); - if (!renderedNodes) { + } + install(app) { + app.component('Metainfo', Metainfo); + app.config.globalProperties.$metaManager = this; + app.provide(metaActiveKey, active); + } + addMeta(metadata, vm) { + if (!vm) { + vm = vue.getCurrentInstance() || undefined; + } + const metaGuards = ({ + removed: [] + }); + const resolveContext = { vm }; + if (this.resolver) { + this.resolver.setup(resolveContext); + } + // TODO: optimize initial compute (once) + const meta = this.target.addSource(metadata, resolveContext, true); + const onRemoved = (removeGuard) => metaGuards.removed.push(removeGuard); + const unmount = (ignoreGuards) => this.unmount(!!ignoreGuards, meta, metaGuards, vm); + if (vm) { + vue.onUnmounted(unmount); + } + return { + meta, + onRemoved, + unmount + }; + } + unmount(ignoreGuards, meta, metaGuards, vm) { + if (vm) { + const { $el } = vm.proxy; + // Wait for element to be removed from DOM + if ($el && $el.offsetParent) { + let observer = new MutationObserver((records) => { + for (const { removedNodes } of records) { + if (!removedNodes) { + continue; + } + removedNodes.forEach((el) => { + if (el === $el && observer) { + observer.disconnect(); + observer = undefined; + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + }); + } + }); + observer.observe($el.parentNode, { childList: true }); + return; + } + } + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + async reallyUnmount(ignoreGuards, meta, metaGuards) { + this.target.delSource(meta); + if (!ignoreGuards && metaGuards) { + await Promise.all(metaGuards.removed.map(removeGuard => removeGuard())); + } + } + render({ slots } = {}) { + const teleports = {}; + for (const key in active) { + const config = this.config[key] || {}; + let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); + if (!renderedNodes) { + continue; + } + if (!isArray(renderedNodes)) { + renderedNodes = [renderedNodes]; + } + let defaultTo = key !== 'base' && active[key].to; + if (!defaultTo && 'to' in config) { + defaultTo = config.to; + } + if (!defaultTo && 'attributesFor' in config) { + defaultTo = key; + } + for (const { to, vnode } of renderedNodes) { + addVnode(teleports, to || defaultTo || 'head', vnode); + } + } + if (slots) { + for (const slotName in slots) { + const tagName = slotName === 'default' ? 'head' : slotName; + // Only teleport the contents of head/body slots + if (tagName !== 'head' && tagName !== 'body') { continue; } - if (!isArray(renderedNodes)) { - renderedNodes = [renderedNodes]; - } - let defaultTo = key !== 'base' && active[key].to; - if (!defaultTo && 'to' in config) { - defaultTo = config.to; - } - if (!defaultTo && 'attributesFor' in config) { - defaultTo = key; - } - for (const { to, vnode } of renderedNodes) { - addVnode(teleports, to || defaultTo || 'head', vnode); - } - } - if (slots) { - for (const slotName in slots) { - const tagName = slotName === 'default' ? 'head' : slotName; - // Only teleport the contents of head/body slots - if (tagName !== 'head' && tagName !== 'body') { - continue; - } - const slot = slots[slotName]; - if (isFunction(slot)) { - addVnode(teleports, tagName, slot({ metainfo: active })); - } + const slot = slots[slotName]; + if (isFunction(slot)) { + addVnode(teleports, tagName, slot({ metainfo: active })); } } - return Object.keys(teleports).map((to) => { - return vue.h(vue.Teleport, { to }, teleports[to]); - }); } + return Object.keys(teleports).map((to) => { + return vue.h(vue.Teleport, { to }, teleports[to]); + }); + } +} +MetaManager.create = (config, resolver) => { + const resolve = (options, contexts, active, key, pathSegments) => { + if (isFunction(resolver)) { + return resolver(options, contexts, active, key, pathSegments); + } + return resolver.resolve(options, contexts, active, key, pathSegments); }; + const mergedObject = createMergedObject(resolve, active); + // TODO: validate resolver + const manager = new MetaManager(config, mergedObject, resolver); return manager; -} +}; // rollup doesnt like an import as it cant find the export so use require const { renderToString } = require('@vue/server-renderer'); diff --git a/dist/vue-meta.cjs.prod.js b/dist/vue-meta.cjs.prod.js index 2d7c2182..8ba63d35 100644 --- a/dist/vue-meta.cjs.prod.js +++ b/dist/vue-meta.cjs.prod.js @@ -1,5 +1,5 @@ /** - * vue-meta v3.0.0-alpha.1 + * vue-meta v3.0.0-alpha.2 * (c) 2021 * - Pim (@pimlie) * - All the amazing contributors @@ -608,7 +608,7 @@ function renderAttributes(context, key, data, config) { } function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) { const slot = slots && slots[slotName]; - if (!slot) { + if (!slot || !isFunction(slot)) { return content; } const slotScopeProps = { @@ -631,9 +631,34 @@ const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === const PolySymbol = (name) => // vm = vue meta hasSymbol - ? Symbol( name) - : ( '_vm_') + name; -const metaActiveKey = /*#__PURE__*/ PolySymbol( 'am'); + ? Symbol(name) + : ('_vm_') + name; +const metaActiveKey = /*#__PURE__*/ PolySymbol('ma'); + +/** + * Apply the differences between newSource & oldSource to target + */ +function applyDifference(target, newSource, oldSource) { + for (const key in newSource) { + if (!(key in oldSource)) { + target[key] = newSource[key]; + continue; + } + // We dont care about nested objects here , these changes + // should already have been tracked by the MergeProxy + if (isObject(target[key])) { + continue; + } + if (newSource[key] !== oldSource[key]) { + target[key] = newSource[key]; + } + } + for (const key in oldSource) { + if (!(key in newSource)) { + delete target[key]; + } + } +} function getCurrentManager(vm) { if (!vm) { @@ -645,15 +670,22 @@ function getCurrentManager(vm) { return vm.appContext.config.globalProperties.$metaManager; } function useMeta(source, manager) { - const vm = vue.getCurrentInstance(); + const vm = vue.getCurrentInstance() || undefined; if (!manager && vm) { manager = getCurrentManager(vm); } if (!manager) { - // oopsydoopsy throw new Error('No manager or current instance'); } - return manager.addMeta(source, vm || undefined); + if (vue.isProxy(source)) { + vue.watch(source, (newSource, oldSource) => { + // We only care about first level props, second+ level will already be changed by the merge proxy + applyDifference(metaProxy.meta, newSource, oldSource); + }); + source = source.value; + } + const metaProxy = manager.addMeta(source, vm); + return metaProxy; } function useActiveMeta() { return vue.inject(metaActiveKey); @@ -691,83 +723,128 @@ function addVnode(teleports, to, vnodes) { } teleports[to].push(...nodes); } -function createMetaManager(config, resolver) { - const resolve = (options, contexts, active, key, pathSegments) => { - if (isFunction(resolver)) { - return resolver(options, contexts, active, key, pathSegments); +const createMetaManager = (config, resolver) => MetaManager.create(config, resolver); +class MetaManager { + constructor(config, target, resolver) { + this.ssrCleanedUp = false; + this.config = config; + this.target = target; + if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { + this.resolver = resolver; } - return resolver.resolve(options, contexts, active, key, pathSegments); - }; - const { addSource, delSource } = createMergedObject(resolve, active); - // TODO: validate resolver - const manager = { - config, - install(app) { - app.component('Metainfo', Metainfo); - app.config.globalProperties.$metaManager = manager; - app.provide(metaActiveKey, active); - }, - addMeta(metaObj, vm) { - if (!vm) { - vm = vue.getCurrentInstance() || undefined; - } - const resolveContext = { vm }; - if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { - resolver.setup(resolveContext); - } - // TODO: optimize initial compute - const meta = addSource(metaObj, resolveContext, true); - const unmount = () => delSource(meta); - if (vm) { - vue.onUnmounted(unmount); - } - return { - meta, - unmount - }; - }, - render({ slots } = {}) { - const teleports = {}; - for (const key in active) { - const config = this.config[key] || {}; - let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); - if (!renderedNodes) { + } + install(app) { + app.component('Metainfo', Metainfo); + app.config.globalProperties.$metaManager = this; + app.provide(metaActiveKey, active); + } + addMeta(metadata, vm) { + if (!vm) { + vm = vue.getCurrentInstance() || undefined; + } + const metaGuards = ({ + removed: [] + }); + const resolveContext = { vm }; + if (this.resolver) { + this.resolver.setup(resolveContext); + } + // TODO: optimize initial compute (once) + const meta = this.target.addSource(metadata, resolveContext, true); + const onRemoved = (removeGuard) => metaGuards.removed.push(removeGuard); + const unmount = (ignoreGuards) => this.unmount(!!ignoreGuards, meta, metaGuards, vm); + if (vm) { + vue.onUnmounted(unmount); + } + return { + meta, + onRemoved, + unmount + }; + } + unmount(ignoreGuards, meta, metaGuards, vm) { + if (vm) { + const { $el } = vm.proxy; + // Wait for element to be removed from DOM + if ($el && $el.offsetParent) { + let observer = new MutationObserver((records) => { + for (const { removedNodes } of records) { + if (!removedNodes) { + continue; + } + removedNodes.forEach((el) => { + if (el === $el && observer) { + observer.disconnect(); + observer = undefined; + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + }); + } + }); + observer.observe($el.parentNode, { childList: true }); + return; + } + } + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + async reallyUnmount(ignoreGuards, meta, metaGuards) { + this.target.delSource(meta); + if (!ignoreGuards && metaGuards) { + await Promise.all(metaGuards.removed.map(removeGuard => removeGuard())); + } + } + render({ slots } = {}) { + const teleports = {}; + for (const key in active) { + const config = this.config[key] || {}; + let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); + if (!renderedNodes) { + continue; + } + if (!isArray(renderedNodes)) { + renderedNodes = [renderedNodes]; + } + let defaultTo = key !== 'base' && active[key].to; + if (!defaultTo && 'to' in config) { + defaultTo = config.to; + } + if (!defaultTo && 'attributesFor' in config) { + defaultTo = key; + } + for (const { to, vnode } of renderedNodes) { + addVnode(teleports, to || defaultTo || 'head', vnode); + } + } + if (slots) { + for (const slotName in slots) { + const tagName = slotName === 'default' ? 'head' : slotName; + // Only teleport the contents of head/body slots + if (tagName !== 'head' && tagName !== 'body') { continue; } - if (!isArray(renderedNodes)) { - renderedNodes = [renderedNodes]; - } - let defaultTo = key !== 'base' && active[key].to; - if (!defaultTo && 'to' in config) { - defaultTo = config.to; - } - if (!defaultTo && 'attributesFor' in config) { - defaultTo = key; - } - for (const { to, vnode } of renderedNodes) { - addVnode(teleports, to || defaultTo || 'head', vnode); - } - } - if (slots) { - for (const slotName in slots) { - const tagName = slotName === 'default' ? 'head' : slotName; - // Only teleport the contents of head/body slots - if (tagName !== 'head' && tagName !== 'body') { - continue; - } - const slot = slots[slotName]; - if (isFunction(slot)) { - addVnode(teleports, tagName, slot({ metainfo: active })); - } + const slot = slots[slotName]; + if (isFunction(slot)) { + addVnode(teleports, tagName, slot({ metainfo: active })); } } - return Object.keys(teleports).map((to) => { - return vue.h(vue.Teleport, { to }, teleports[to]); - }); } + return Object.keys(teleports).map((to) => { + return vue.h(vue.Teleport, { to }, teleports[to]); + }); + } +} +MetaManager.create = (config, resolver) => { + const resolve = (options, contexts, active, key, pathSegments) => { + if (isFunction(resolver)) { + return resolver(options, contexts, active, key, pathSegments); + } + return resolver.resolve(options, contexts, active, key, pathSegments); }; + const mergedObject = createMergedObject(resolve, active); + // TODO: validate resolver + const manager = new MetaManager(config, mergedObject, resolver); return manager; -} +}; // rollup doesnt like an import as it cant find the export so use require const { renderToString } = require('@vue/server-renderer'); diff --git a/dist/vue-meta.d.ts b/dist/vue-meta.d.ts index 9cc6781f..737382a8 100644 --- a/dist/vue-meta.d.ts +++ b/dist/vue-meta.d.ts @@ -1,13 +1,45 @@ -import { ComponentInternalInstance, App, Slots, VNode } from 'vue'; +import { App, ComponentInternalInstance, Slots, VNode } from 'vue'; import { SSRContext } from '@vue/server-renderer'; +declare type MergeSource = { + [key: string]: any; +}; declare type MergedObjectValue = boolean | number | string | MergedObject | any; declare type MergedObject = { [key: string]: MergedObjectValue; }; declare type PathSegments = Array; declare type ResolveContext = {}; -declare type ResolveMethod = (options: Array, contexts: Array, active: MergedObjectValue, key: string | number | symbol, pathSegments: PathSegments) => MergedObjectValue; +declare type ResolveMethod = (options: Array, contexts: Array, active: MergedObjectValue, key: string | number | symbol, pathSegments: PathSegments) => MergedObjectValue; +declare type MergeContext = { + resolve: ResolveMethod; + active: MergedObject; + sources: Array; +}; +declare type MergedObjectBuilder = { + context: MergeContext; + compute: () => void; + addSource: (source: MergeSource, resolveContext: ResolveContext | undefined, recompute?: Boolean) => any; + delSource: (sourceOrProxy: MergeSource, recompute?: boolean) => boolean; +}; + +declare type createMetaManagerMethod = (config: MetaConfig, resolver: MetaResolver | ResolveMethod) => MetaManager; +declare const createMetaManager: createMetaManagerMethod; +declare class MetaManager { + config: MetaConfig; + target: MergedObjectBuilder; + resolver?: MetaResolverSetup; + ssrCleanedUp: boolean; + constructor(config: MetaConfig, target: MergedObjectBuilder, resolver: MetaResolver | ResolveMethod); + static create: createMetaManagerMethod; + install(app: App): void; + addMeta(metadata: MetaSource, vm?: ComponentInternalInstance): MetaProxy; + private unmount; + private reallyUnmount; + render({ slots }?: { + slots?: Slots; + }): VNode[]; +} declare type MetaConfigSectionKey = 'tag' | 'to' | 'keyAttribute' | 'valueAttribute' | 'nameless' | 'group' | 'namespaced' | 'namespacedAttribute' | 'attributesFor'; interface MetaConfigSectionTag { @@ -40,6 +72,7 @@ declare type MetaTagsConfig = { [key in MetaTagName]: MetaTagConfig; }; +declare type Modify = Omit & R; declare type TODO = any; /** * Proxied meta source for tracking changes and updating the active meta daa @@ -52,12 +85,14 @@ interface MetaSourceProxy extends MergedObject { declare type MetaSource = { [key: string]: TODO; }; +declare type MetaGuardRemoved = () => void | Promise; /** * Return value of the useMeta api */ declare type MetaProxy = { meta: MetaSourceProxy; - unmount: Function | false; + onRemoved: (removeGuard: MetaGuardRemoved) => void; + unmount: (ignoreGuards?: boolean) => void; }; /** * The active/aggregated meta data currently rendered @@ -76,23 +111,21 @@ declare type MetaResolver = { setup?: MetaResolveSetup; resolve: ResolveMethod; }; -/** - * The meta manager - */ -declare type MetaManager = { - readonly config: MetaConfig; - install(app: App): void; - addMeta(source: MetaSource, vm?: ComponentInternalInstance): MetaProxy; - render(ctx?: { - slots?: Slots; - }): Array; -}; +declare type MetaResolverSetup = Modify; /** * @internal */ declare type MetaTeleports = { [key: string]: Array; }; +/** + * @internal + */ +interface MetaGuards { + removed: Array; +} /** * @internal */ @@ -132,6 +165,7 @@ declare type MetaRendered = Array; declare module '@vue/runtime-core' { interface ComponentInternalInstance { $metaManager: MetaManager; + $metaGuards: MetaGuards; } } @@ -152,8 +186,6 @@ declare namespace deepest_d { declare const defaultConfig: MetaConfig; -declare function createMetaManager(config: MetaConfig, resolver: MetaResolver | ResolveMethod): MetaManager; - declare type ResolveOptionReducer = (accumulator: any, context: ResolveContext) => ResolveMethod; declare const resolveOption: (predicament: ResolveOptionReducer) => ResolveMethod; @@ -163,4 +195,4 @@ declare function getCurrentManager(vm?: ComponentInternalInstance): MetaManager declare function useMeta(source: MetaSource, manager?: MetaManager): MetaProxy; declare function useActiveMeta(): MetaActive; -export { MetaActive, MetaConfig, MetaConfigSection, MetaConfigSectionAttribute, MetaConfigSectionGroup, MetaConfigSectionKey, MetaConfigSectionTag, MetaGroupConfig, MetaManager, MetaProxy, MetaRenderContext, MetaRendered, MetaRenderedNode, MetaResolveContext, MetaResolveSetup, MetaResolver, MetaSource, MetaSourceProxy, MetaTagConfig, MetaTagConfigKey, MetaTagName, MetaTagsConfig, MetaTeleports, SlotScopeProperties, TODO, createMetaManager, deepest_d as deepestResolver, defaultConfig, getCurrentManager, renderToStringWithMeta, resolveOption, useActiveMeta, useMeta }; +export { MetaActive, MetaConfig, MetaConfigSection, MetaConfigSectionAttribute, MetaConfigSectionGroup, MetaConfigSectionKey, MetaConfigSectionTag, MetaGroupConfig, MetaGuardRemoved, MetaGuards, MetaProxy, MetaRenderContext, MetaRendered, MetaRenderedNode, MetaResolveContext, MetaResolveSetup, MetaResolver, MetaResolverSetup, MetaSource, MetaSourceProxy, MetaTagConfig, MetaTagConfigKey, MetaTagName, MetaTagsConfig, MetaTeleports, Modify, SlotScopeProperties, TODO, createMetaManager, deepest_d as deepestResolver, defaultConfig, getCurrentManager, renderToStringWithMeta, resolveOption, useActiveMeta, useMeta }; diff --git a/dist/vue-meta.esm-browser.js b/dist/vue-meta.esm-browser.js index 6904d978..aa0f92d5 100644 --- a/dist/vue-meta.esm-browser.js +++ b/dist/vue-meta.esm-browser.js @@ -1,12 +1,12 @@ /** - * vue-meta v3.0.0-alpha.1 + * vue-meta v3.0.0-alpha.2 * (c) 2021 * - Pim (@pimlie) * - All the amazing contributors * @license MIT */ -import { markRaw, h, getCurrentInstance, inject, defineComponent, reactive, onUnmounted, Teleport, Comment } from 'vue'; +import { markRaw, h, getCurrentInstance, isProxy, watch, inject, defineComponent, reactive, onUnmounted, Teleport, Comment } from 'vue'; const resolveOption = predicament => (options, contexts) => { let resolvedIndex = -1; @@ -600,12 +600,12 @@ function renderAttributes(context, key, data, config) { } if (!cachedElements[attributesFor]) { const [el, el2] = Array.from(document.querySelectorAll(attributesFor)); - if ( !el) { + if (!el) { // eslint-disable-next-line no-console console.error('Could not find element for selector', attributesFor, ', won\'t render attributes'); return; } - if ( el2) { + if (el2) { // eslint-disable-next-line no-console console.warn('Found multiple elements for selector', attributesFor); } @@ -629,7 +629,7 @@ function renderAttributes(context, key, data, config) { } function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) { const slot = slots && slots[slotName]; - if (!slot) { + if (!slot || !isFunction(slot)) { return content; } const slotScopeProps = { @@ -652,9 +652,34 @@ const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag === const PolySymbol = (name) => // vm = vue meta hasSymbol - ? Symbol( '[vue-meta]: ' + name ) - : ( '[vue-meta]: ' ) + name; -const metaActiveKey = /*#__PURE__*/ PolySymbol( 'active_meta' ); + ? Symbol('[vue-meta]: ' + name ) + : ('[vue-meta]: ' ) + name; +const metaActiveKey = /*#__PURE__*/ PolySymbol('meta_active' ); + +/** + * Apply the differences between newSource & oldSource to target + */ +function applyDifference(target, newSource, oldSource) { + for (const key in newSource) { + if (!(key in oldSource)) { + target[key] = newSource[key]; + continue; + } + // We dont care about nested objects here , these changes + // should already have been tracked by the MergeProxy + if (isObject(target[key])) { + continue; + } + if (newSource[key] !== oldSource[key]) { + target[key] = newSource[key]; + } + } + for (const key in oldSource) { + if (!(key in newSource)) { + delete target[key]; + } + } +} function getCurrentManager(vm) { if (!vm) { @@ -666,15 +691,22 @@ function getCurrentManager(vm) { return vm.appContext.config.globalProperties.$metaManager; } function useMeta(source, manager) { - const vm = getCurrentInstance(); + const vm = getCurrentInstance() || undefined; if (!manager && vm) { manager = getCurrentManager(vm); } if (!manager) { - // oopsydoopsy throw new Error('No manager or current instance'); } - return manager.addMeta(source, vm || undefined); + if (isProxy(source)) { + watch(source, (newSource, oldSource) => { + // We only care about first level props, second+ level will already be changed by the merge proxy + applyDifference(metaProxy.meta, newSource, oldSource); + }); + source = source.value; + } + const metaProxy = manager.addMeta(source, vm); + return metaProxy; } function useActiveMeta() { return inject(metaActiveKey); @@ -713,96 +745,141 @@ function addVnode(teleports, to, vnodes) { } teleports[to].push(...nodes); } -function createMetaManager(config, resolver) { - let cleanedUpSsr = false; - const resolve = (options, contexts, active, key, pathSegments) => { - if (isFunction(resolver)) { - return resolver(options, contexts, active, key, pathSegments); - } - return resolver.resolve(options, contexts, active, key, pathSegments); - }; - const { addSource, delSource } = createMergedObject(resolve, active); - // TODO: validate resolver - const manager = { - config, - install(app) { - app.component('Metainfo', Metainfo); - app.config.globalProperties.$metaManager = manager; - app.provide(metaActiveKey, active); - }, - addMeta(metaObj, vm) { - if (!vm) { - vm = getCurrentInstance() || undefined; - } - const resolveContext = { vm }; - if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { - resolver.setup(resolveContext); - } - // TODO: optimize initial compute - const meta = addSource(metaObj, resolveContext, true); - const unmount = () => delSource(meta); - if (vm) { - onUnmounted(unmount); - } - return { - meta, - unmount - }; - }, - render({ slots } = {}) { - // cleanup ssr tags if not yet done - if ( !cleanedUpSsr) { - cleanedUpSsr = true; - // Listen for DOM loaded because tags in the body couldnt - // have loaded yet once the manager does it first render - // (preferable there should only be one meta render on hydration) - window.addEventListener('DOMContentLoaded', () => { - const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`); - if (ssrTags && ssrTags.length) { - Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el)); +const createMetaManager = (config, resolver) => MetaManager.create(config, resolver); +class MetaManager { + constructor(config, target, resolver) { + this.ssrCleanedUp = false; + this.config = config; + this.target = target; + if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { + this.resolver = resolver; + } + } + install(app) { + app.component('Metainfo', Metainfo); + app.config.globalProperties.$metaManager = this; + app.provide(metaActiveKey, active); + } + addMeta(metadata, vm) { + if (!vm) { + vm = getCurrentInstance() || undefined; + } + const metaGuards = ({ + removed: [] + }); + const resolveContext = { vm }; + if (this.resolver) { + this.resolver.setup(resolveContext); + } + // TODO: optimize initial compute (once) + const meta = this.target.addSource(metadata, resolveContext, true); + const onRemoved = (removeGuard) => metaGuards.removed.push(removeGuard); + const unmount = (ignoreGuards) => this.unmount(!!ignoreGuards, meta, metaGuards, vm); + if (vm) { + onUnmounted(unmount); + } + return { + meta, + onRemoved, + unmount + }; + } + unmount(ignoreGuards, meta, metaGuards, vm) { + if (vm) { + const { $el } = vm.proxy; + // Wait for element to be removed from DOM + if ($el && $el.offsetParent) { + let observer = new MutationObserver((records) => { + for (const { removedNodes } of records) { + if (!removedNodes) { + continue; + } + removedNodes.forEach((el) => { + if (el === $el && observer) { + observer.disconnect(); + observer = undefined; + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + }); } }); + observer.observe($el.parentNode, { childList: true }); + return; } - const teleports = {}; - for (const key in active) { - const config = this.config[key] || {}; - let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); - if (!renderedNodes) { - continue; - } - if (!isArray(renderedNodes)) { - renderedNodes = [renderedNodes]; - } - let defaultTo = key !== 'base' && active[key].to; - if (!defaultTo && 'to' in config) { - defaultTo = config.to; - } - if (!defaultTo && 'attributesFor' in config) { - defaultTo = key; - } - for (const { to, vnode } of renderedNodes) { - addVnode(teleports, to || defaultTo || 'head', vnode); + } + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + async reallyUnmount(ignoreGuards, meta, metaGuards) { + this.target.delSource(meta); + if (!ignoreGuards && metaGuards) { + await Promise.all(metaGuards.removed.map(removeGuard => removeGuard())); + } + } + render({ slots } = {}) { + // TODO: clean this method + // cleanup ssr tags if not yet done + if (!this.ssrCleanedUp) { + this.ssrCleanedUp = true; + // Listen for DOM loaded because tags in the body couldnt + // have loaded yet once the manager does it first render + // (preferable there should only be one meta render on hydration) + window.addEventListener('DOMContentLoaded', () => { + const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`); + if (ssrTags && ssrTags.length) { + Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el)); } + }); + } + const teleports = {}; + for (const key in active) { + const config = this.config[key] || {}; + let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); + if (!renderedNodes) { + continue; } - if (slots) { - for (const slotName in slots) { - const tagName = slotName === 'default' ? 'head' : slotName; - // Only teleport the contents of head/body slots - if (tagName !== 'head' && tagName !== 'body') { - continue; - } - const slot = slots[slotName]; - if (isFunction(slot)) { - addVnode(teleports, tagName, slot({ metainfo: active })); - } + if (!isArray(renderedNodes)) { + renderedNodes = [renderedNodes]; + } + let defaultTo = key !== 'base' && active[key].to; + if (!defaultTo && 'to' in config) { + defaultTo = config.to; + } + if (!defaultTo && 'attributesFor' in config) { + defaultTo = key; + } + for (const { to, vnode } of renderedNodes) { + addVnode(teleports, to || defaultTo || 'head', vnode); + } + } + if (slots) { + for (const slotName in slots) { + const tagName = slotName === 'default' ? 'head' : slotName; + // Only teleport the contents of head/body slots + if (tagName !== 'head' && tagName !== 'body') { + continue; + } + const slot = slots[slotName]; + if (isFunction(slot)) { + addVnode(teleports, tagName, slot({ metainfo: active })); } } - return Object.keys(teleports).map((to) => { - return h(Teleport, { to }, teleports[to]); - }); } + return Object.keys(teleports).map((to) => { + return h(Teleport, { to }, teleports[to]); + }); + } +} +MetaManager.create = (config, resolver) => { + const resolve = (options, contexts, active, key, pathSegments) => { + if (isFunction(resolver)) { + return resolver(options, contexts, active, key, pathSegments); + } + return resolver.resolve(options, contexts, active, key, pathSegments); }; + const mergedObject = createMergedObject(resolve, active); + // TODO: validate resolver + const manager = new MetaManager(config, mergedObject, resolver); return manager; -} +}; export { createMetaManager, deepest as deepestResolver, defaultConfig, getCurrentManager, resolveOption, useActiveMeta, useMeta }; diff --git a/dist/vue-meta.esm-browser.min.js b/dist/vue-meta.esm-browser.min.js index 1b809291..3081302b 100644 --- a/dist/vue-meta.esm-browser.min.js +++ b/dist/vue-meta.esm-browser.min.js @@ -1,8 +1,8 @@ /** - * vue-meta v3.0.0-alpha.1 + * vue-meta v3.0.0-alpha.2 * (c) 2021 * - Pim (@pimlie) * - All the amazing contributors * @license MIT */ -import{markRaw as t,h as e,getCurrentInstance as o,inject as r,defineComponent as n,reactive as s,onUnmounted as c,Teleport as i,Comment as a}from"vue";const u=t=>(e,o)=>{let r=-1;if(o.reduce(((e,o,n)=>{const s=t(e,o);return s!==e?(r=n,s):e}),void 0),r>-1)return e[r]};const f=u(((t,e)=>{const{depth:o}=e;if(!t||o>t)return t}));var l=Object.freeze({__proto__:null,setup:function(t){let e=0;if(t.vm){let{vm:o}=t;do{o.parent&&(e++,o=o.parent)}while(o&&o.parent&&o!==o.root)}t.depth=e},resolve:f});const p={body:{tag:"script",to:"body"},base:{valueAttribute:"href"},charset:{tag:"meta",nameless:!0,valueAttribute:"charset"},description:{tag:"meta"},og:{group:!0,namespacedAttribute:!0,tag:"meta",keyAttribute:"property"},twitter:{group:!0,namespacedAttribute:!0,tag:"meta"},htmlAttrs:{attributesFor:"html"},headAttrs:{attributesFor:"head"},bodyAttrs:{attributesFor:"body"}},d={title:{attributes:!1},base:{contentAsAttribute:!0,attributes:["href","target"]},meta:{contentAsAttribute:!0,keyAttribute:"name",attributes:["content","name","http-equiv","charset"]},link:{contentAsAttribute:!0,attributes:["href","crossorigin","rel","media","integrity","hreflang","type","referrerpolicy","sizes","imagesrcset","imagesizes","as","color"]},style:{attributes:["media"]},script:{attributes:["src","type","nomodule","async","defer","crossorigin","integrity","referrerpolicy"]},noscript:{attributes:!1}};"production"===process.env.NODE_ENV||Object.freeze({}),"production"===process.env.NODE_ENV||Object.freeze([]);const m=Array.isArray,b=t=>"function"==typeof t,g=t=>"string"==typeof t,y=t=>null!==t&&"object"==typeof t,h=Object.prototype.toString,v=t=>"[object Object]"===h.call(t),A=Symbol("kIsProxy"),S=Symbol("kProxySources"),k=Symbol("kProxyTarget"),N=Symbol("kResolveContext");function j(t){if(m(t))return t.map(j);if(y(t)){const e={};for(const o in t)e[o]="context"===o?t[o]:j(t[o]);return e}return t}const O=(t,e,o)=>{const r=[];for(const n of t)e in n&&(r.push(n[e]),o&&o(n));return r},w=(t,e,o,r=[])=>{if(r.length||(o||(o=t.active),e||(e=t.sources)),!o||!e)return;const n=((t,...e)=>{const o=t?Object.keys(t):[];if(e)for(const t of e)if(t&&y(t))for(const e in t)o.includes(e)||o.push(e);return o})(...e),s=Object.keys(o);for(const t of s)n.includes(t)||delete o[t];for(const s of n){if(v(e[0][s])){o[s]||(o[s]={});const n=[];for(const t of e)s in t&&n.push(t[s]);w(t,n,o[s],[...r,s]);continue}!o[s]&&m(e[0][s])&&(o[s]=[]);const n=[],c=O(e,s,(t=>n.push(t[N])));let i=t.resolve(c,n,o[s],s,r);v(i)&&(i=j(i)),o[s]=i}},x=(e,o,r,n=[])=>{const s=$(e,r,n),c=t(new Proxy(o,s));return!n.length&&e.sources&&e.sources.push(c),c},$=(t,e,o=[])=>({get:(r,n,s)=>{if(n===A)return!0;if(n===S)return t.sources;if(n===k)return r;if(n===N)return e;let c=Reflect.get(r,n,s);if(!y(c))return c;if(!c[A]){const s=[...o,n];c=x(t,c,e,s),r[n]=c}return c},set:(e,r,n)=>{const s=Reflect.set(e,r,n);if(s){const n=m(e);let c,i=!1,{sources:a,active:u}=t,f=0;for(const t of o){if(a=O(a,t),n&&f===o.length-1){c=t;break}m(u)&&(i=!0),u=u[t],f++}if(i)return w(t),s;let l,p=[];n?(l=a,p=a.map((t=>t[N]))):l=O(a,r,(t=>p.push(t[N])));let d=t.resolve(l,p,u,r,o);v(d)&&(d=j(d)),n&&c?u[c]=d:u[r]=d}return s},deleteProperty:(e,r)=>{const n=Reflect.deleteProperty(e,r);if(n){const n=m(e);let s,c=t.sources,i=t.active,a=0;for(const t of o){if(c=c.map((e=>e[t])),n&&a===o.length-1){s=t;break}i=i[t],a++}if(c.some((t=>r in t))){let e,a=[];n?(e=c,a=c.map((t=>t[N]))):e=O(c,r,(t=>a.push(t[N])));let u=t.resolve(e,a,i,r,o);v(u)&&(u=j(u)),n&&s?i[s]=u:i[r]=u}else delete i[r]}return n}}),E={};function M(t,e,o,r){return"attributesFor"in r?function(t,e,o,r){const{attributesFor:n}=r;if(!n)return;if(!E[n]){const[t,e]=Array.from(document.querySelectorAll(n));if(!t)return void console.error("Could not find element for selector",n,", won't render attributes");e&&console.warn("Found multiple elements for selector",n),E[n]={el:t,attrs:[]}}const{el:s,attrs:c}=E[n];for(const r in o){const n=F(t,`${e}(${r})`,o[r],o);s.setAttribute(r,n||""),c.includes(r)||c.push(r)}const i=c.filter((t=>!o[t]));for(const t of i)s.removeAttribute(t)}(t,e,o,r):"group"in r?function(t,e,o,r){if(m(o))return console.warn("Specifying an array for group properties isnt supported"),[];return Object.keys(o).map((n=>{const s={group:e,data:o};if(r.namespaced)s.tagNamespace=!0===r.namespaced?e:r.namespaced;else if(r.namespacedAttribute){const t=!0===r.namespacedAttribute?e:r.namespacedAttribute;s.fullName=`${t}:${n}`,s.slotName=`${t}(${n})`}return P(t,e,o[n],r,s)})).flat()}(t,e,o,r):P(t,e,o,r)}function P(t,o,r,n={},s){const c=["content","json","rawContent"],i=t=>function(t,e){for(const o of t){const t=d[o];if(o&&t)return t[e]}}([a,n.tag],t);if(m(r))return r.map((e=>P(t,o,e,n,s))).flat();const{tag:a=n.tag||o}=r;let u="",f=!1,l=!1;if(g(r))u=r;else if(r.children&&m(r.children))f=!0,u=r.children.map((e=>{const r=P(t,o,e,n,s);return m(r)?r.map((({vnode:t})=>t)):r.vnode}));else{let t=0;for(const e of c){if(!u&&r[e]){u=1===t?JSON.stringify(r[e]):r[e],l=t>1;break}t++}}const p=s&&s.fullName||o,b=s&&s.slotName||o;let{attrs:y}=r;if(y||"object"!=typeof r)y||(y={});else{y={...r},delete y.tag,delete y.children,delete y.to;for(const t of c)delete y[t]}if(f)u=F(t,b,u,r);else{const e=!!i("contentAsAttribute");let{valueAttribute:o}=n;if(!o&&e){const[t]=i("attributes");o=g(e)?e:t}if(o){const{nameless:e,keyAttribute:r}=n;e||r&&(y[r]=p),y[o]=F(t,b,y[o]||u,s),u=""}else u=F(t,b,u,r)}const h=s&&s.tagNamespace?`${s.tagNamespace}:${a}`:a;l&&u&&(y.innerHTML=u);const v=e(h,y,u||void 0);return{to:r.to,vnode:v}}function F({metainfo:t,slots:e},o,r,n){const s=e&&e[o];if(!s)return r;const c={content:r,metainfo:t};if(n&&n.group){const{group:t,data:e}=n;c[t]=e}const i=s(c);if(i&&i.length){const{children:t}=i[0];return t?t.toString():""}return r}const _="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag,C=(t=>_?Symbol("[vue-meta]: "+t):"[vue-meta]: "+t)("active_meta");function z(t){if(t||(t=o()||void 0),t)return t.appContext.config.globalProperties.$metaManager}function R(t,e){const r=o();if(!e&&r&&(e=z(r)),!e)throw new Error("No manager or current instance");return e.addMeta(t,r||void 0)}function q(){return r(C)}const D=n({name:"Metainfo",inheritAttrs:!1,setup:(t,{slots:e})=>()=>{const t=z();if(t)return t.render({slots:e})}}),L=s({});function T(t,e,o){const r=m(o)?o:[o];r.forEach(((t,e)=>{t.type===a&&r.splice(e,1)})),t[e]||(t[e]=[]),t[e].push(...r)}function I(t,r){let n=!1;const{addSource:s,delSource:a}=((t,e={})=>{const o=[];e||(e={});const r={active:e,resolve:t,sources:o},n=()=>w(r);return{context:r,compute:n,addSource:(t,e,o=!1)=>{const s=x(r,t,e||{});return o&&n(),s},delSource:(t,e=!0)=>{const r=o.findIndex((e=>e===t||e[k]===t));return r>-1&&(o.splice(r,1),e&&n(),!0)}}})(((t,e,o,n,s)=>b(r)?r(t,e,o,n,s):r.resolve(t,e,o,n,s)),L),u={config:t,install(t){t.component("Metainfo",D),t.config.globalProperties.$metaManager=u,t.provide(C,L)},addMeta(t,e){e||(e=o()||void 0);const n={vm:e};r&&"setup"in r&&b(r.setup)&&r.setup(n);const i=s(t,n,!0),u=()=>a(i);return e&&c(u),{meta:i,unmount:u}},render({slots:t}={}){n||(n=!0,window.addEventListener("DOMContentLoaded",(()=>{const t=document.querySelectorAll("[data-vm-ssr]");t&&t.length&&Array.from(t).forEach((t=>t.parentNode&&t.parentNode.removeChild(t)))})));const o={};for(const e in L){const r=this.config[e]||{};let n=M({metainfo:L,slots:t},e,L[e],r);if(!n)continue;m(n)||(n=[n]);let s="base"!==e&&L[e].to;!s&&"to"in r&&(s=r.to),!s&&"attributesFor"in r&&(s=e);for(const{to:t,vnode:e}of n)T(o,t||s||"head",e)}if(t)for(const e in t){const r="default"===e?"head":e;if("head"!==r&&"body"!==r)continue;const n=t[e];b(n)&&T(o,r,n({metainfo:L}))}return Object.keys(o).map((t=>e(i,{to:t},o[t])))}};return u}export{I as createMetaManager,l as deepestResolver,p as defaultConfig,z as getCurrentManager,u as resolveOption,q as useActiveMeta,R as useMeta}; +import{markRaw as t,h as e,getCurrentInstance as o,isProxy as r,watch as n,inject as s,defineComponent as i,reactive as c,onUnmounted as a,Teleport as u,Comment as l}from"vue";const f=t=>(e,o)=>{let r=-1;if(o.reduce(((e,o,n)=>{const s=t(e,o);return s!==e?(r=n,s):e}),void 0),r>-1)return e[r]};const d=f(((t,e)=>{const{depth:o}=e;if(!t||o>t)return t}));var p=Object.freeze({__proto__:null,setup:function(t){let e=0;if(t.vm){let{vm:o}=t;do{o.parent&&(e++,o=o.parent)}while(o&&o.parent&&o!==o.root)}t.depth=e},resolve:d});const m={body:{tag:"script",to:"body"},base:{valueAttribute:"href"},charset:{tag:"meta",nameless:!0,valueAttribute:"charset"},description:{tag:"meta"},og:{group:!0,namespacedAttribute:!0,tag:"meta",keyAttribute:"property"},twitter:{group:!0,namespacedAttribute:!0,tag:"meta"},htmlAttrs:{attributesFor:"html"},headAttrs:{attributesFor:"head"},bodyAttrs:{attributesFor:"body"}},b={title:{attributes:!1},base:{contentAsAttribute:!0,attributes:["href","target"]},meta:{contentAsAttribute:!0,keyAttribute:"name",attributes:["content","name","http-equiv","charset"]},link:{contentAsAttribute:!0,attributes:["href","crossorigin","rel","media","integrity","hreflang","type","referrerpolicy","sizes","imagesrcset","imagesizes","as","color"]},style:{attributes:["media"]},script:{attributes:["src","type","nomodule","async","defer","crossorigin","integrity","referrerpolicy"]},noscript:{attributes:!1}};"production"===process.env.NODE_ENV||Object.freeze({}),"production"===process.env.NODE_ENV||Object.freeze([]);const h=Array.isArray,y=t=>"function"==typeof t,g=t=>"string"==typeof t,v=t=>null!==t&&"object"==typeof t,A=Object.prototype.toString,S=t=>"[object Object]"===A.call(t),N=Symbol("kIsProxy"),k=Symbol("kProxySources"),w=Symbol("kProxyTarget"),O=Symbol("kResolveContext");function j(t){if(h(t))return t.map(j);if(v(t)){const e={};for(const o in t)e[o]="context"===o?t[o]:j(t[o]);return e}return t}const x=(t,e,o)=>{const r=[];for(const n of t)e in n&&(r.push(n[e]),o&&o(n));return r},$=(t,e,o,r=[])=>{if(r.length||(o||(o=t.active),e||(e=t.sources)),!o||!e)return;const n=((t,...e)=>{const o=t?Object.keys(t):[];if(e)for(const t of e)if(t&&v(t))for(const e in t)o.includes(e)||o.push(e);return o})(...e),s=Object.keys(o);for(const t of s)n.includes(t)||delete o[t];for(const s of n){if(S(e[0][s])){o[s]||(o[s]={});const n=[];for(const t of e)s in t&&n.push(t[s]);$(t,n,o[s],[...r,s]);continue}!o[s]&&h(e[0][s])&&(o[s]=[]);const n=[],i=x(e,s,(t=>n.push(t[O])));let c=t.resolve(i,n,o[s],s,r);S(c)&&(c=j(c)),o[s]=c}},P=(e,o,r,n=[])=>{const s=C(e,r,n),i=t(new Proxy(o,s));return!n.length&&e.sources&&e.sources.push(i),i},C=(t,e,o=[])=>({get:(r,n,s)=>{if(n===N)return!0;if(n===k)return t.sources;if(n===w)return r;if(n===O)return e;let i=Reflect.get(r,n,s);if(!v(i))return i;if(!i[N]){const s=[...o,n];i=P(t,i,e,s),r[n]=i}return i},set:(e,r,n)=>{const s=Reflect.set(e,r,n);if(s){const n=h(e);let i,c=!1,{sources:a,active:u}=t,l=0;for(const t of o){if(a=x(a,t),n&&l===o.length-1){i=t;break}h(u)&&(c=!0),u=u[t],l++}if(c)return $(t),s;let f,d=[];n?(f=a,d=a.map((t=>t[O]))):f=x(a,r,(t=>d.push(t[O])));let p=t.resolve(f,d,u,r,o);S(p)&&(p=j(p)),n&&i?u[i]=p:u[r]=p}return s},deleteProperty:(e,r)=>{const n=Reflect.deleteProperty(e,r);if(n){const n=h(e);let s,i=t.sources,c=t.active,a=0;for(const t of o){if(i=i.map((e=>e[t])),n&&a===o.length-1){s=t;break}c=c[t],a++}if(i.some((t=>r in t))){let e,a=[];n?(e=i,a=i.map((t=>t[O]))):e=x(i,r,(t=>a.push(t[O])));let u=t.resolve(e,a,c,r,o);S(u)&&(u=j(u)),n&&s?c[s]=u:c[r]=u}else delete c[r]}return n}}),E={};function M(t,e,o,r){return"attributesFor"in r?function(t,e,o,r){const{attributesFor:n}=r;if(!n)return;if(!E[n]){const[t,e]=Array.from(document.querySelectorAll(n));if(!t)return void console.error("Could not find element for selector",n,", won't render attributes");e&&console.warn("Found multiple elements for selector",n),E[n]={el:t,attrs:[]}}const{el:s,attrs:i}=E[n];for(const r in o){const n=_(t,`${e}(${r})`,o[r],o);s.setAttribute(r,n||""),i.includes(r)||i.push(r)}const c=i.filter((t=>!o[t]));for(const t of c)s.removeAttribute(t)}(t,e,o,r):"group"in r?function(t,e,o,r){if(h(o))return console.warn("Specifying an array for group properties isnt supported"),[];return Object.keys(o).map((n=>{const s={group:e,data:o};if(r.namespaced)s.tagNamespace=!0===r.namespaced?e:r.namespaced;else if(r.namespacedAttribute){const t=!0===r.namespacedAttribute?e:r.namespacedAttribute;s.fullName=`${t}:${n}`,s.slotName=`${t}(${n})`}return F(t,e,o[n],r,s)})).flat()}(t,e,o,r):F(t,e,o,r)}function F(t,o,r,n={},s){const i=["content","json","rawContent"],c=t=>function(t,e){for(const o of t){const t=b[o];if(o&&t)return t[e]}}([a,n.tag],t);if(h(r))return r.map((e=>F(t,o,e,n,s))).flat();const{tag:a=n.tag||o}=r;let u="",l=!1,f=!1;if(g(r))u=r;else if(r.children&&h(r.children))l=!0,u=r.children.map((e=>{const r=F(t,o,e,n,s);return h(r)?r.map((({vnode:t})=>t)):r.vnode}));else{let t=0;for(const e of i){if(!u&&r[e]){u=1===t?JSON.stringify(r[e]):r[e],f=t>1;break}t++}}const d=s&&s.fullName||o,p=s&&s.slotName||o;let{attrs:m}=r;if(m||"object"!=typeof r)m||(m={});else{m={...r},delete m.tag,delete m.children,delete m.to;for(const t of i)delete m[t]}if(l)u=_(t,p,u,r);else{const e=!!c("contentAsAttribute");let{valueAttribute:o}=n;if(!o&&e){const[t]=c("attributes");o=g(e)?e:t}if(o){const{nameless:e,keyAttribute:r}=n;e||r&&(m[r]=d),m[o]=_(t,p,m[o]||u,s),u=""}else u=_(t,p,u,r)}const y=s&&s.tagNamespace?`${s.tagNamespace}:${a}`:a;f&&u&&(m.innerHTML=u);const v=e(y,m,u||void 0);return{to:r.to,vnode:v}}function _({metainfo:t,slots:e},o,r,n){const s=e&&e[o];if(!s||!y(s))return r;const i={content:r,metainfo:t};if(n&&n.group){const{group:t,data:e}=n;i[t]=e}const c=s(i);if(c&&c.length){const{children:t}=c[0];return t?t.toString():""}return r}const R="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag,U=(t=>R?Symbol("[vue-meta]: "+t):"[vue-meta]: "+t)("meta_active");function z(t){if(t||(t=o()||void 0),t)return t.appContext.config.globalProperties.$metaManager}function L(t,e){const s=o()||void 0;if(!e&&s&&(e=z(s)),!e)throw new Error("No manager or current instance");r(t)&&(n(t,((t,e)=>{!function(t,e,o){for(const r in e)r in o?v(t[r])||e[r]!==o[r]&&(t[r]=e[r]):t[r]=e[r];for(const r in o)r in e||delete t[r]}(i.meta,t,e)})),t=t.value);const i=e.addMeta(t,s);return i}function q(){return s(U)}const D=i({name:"Metainfo",inheritAttrs:!1,setup:(t,{slots:e})=>()=>{const t=z();if(t)return t.render({slots:e})}}),T=c({});function I(t,e,o){const r=h(o)?o:[o];r.forEach(((t,e)=>{t.type===l&&r.splice(e,1)})),t[e]||(t[e]=[]),t[e].push(...r)}const V=(t,e)=>H.create(t,e);class H{constructor(t,e,o){this.ssrCleanedUp=!1,this.config=t,this.target=e,o&&"setup"in o&&y(o.setup)&&(this.resolver=o)}install(t){t.component("Metainfo",D),t.config.globalProperties.$metaManager=this,t.provide(U,T)}addMeta(t,e){e||(e=o()||void 0);const r={removed:[]},n={vm:e};this.resolver&&this.resolver.setup(n);const s=this.target.addSource(t,n,!0),i=t=>this.unmount(!!t,s,r,e);return e&&a(i),{meta:s,onRemoved:t=>r.removed.push(t),unmount:i}}unmount(t,e,o,r){if(r){const{$el:n}=r.proxy;if(n&&n.offsetParent){let r=new MutationObserver((s=>{for(const{removedNodes:i}of s)i&&i.forEach((s=>{s===n&&r&&(r.disconnect(),r=void 0,this.reallyUnmount(t,e,o))}))}));return void r.observe(n.parentNode,{childList:!0})}}this.reallyUnmount(t,e,o)}async reallyUnmount(t,e,o){this.target.delSource(e),!t&&o&&await Promise.all(o.removed.map((t=>t())))}render({slots:t}={}){this.ssrCleanedUp||(this.ssrCleanedUp=!0,window.addEventListener("DOMContentLoaded",(()=>{const t=document.querySelectorAll("[data-vm-ssr]");t&&t.length&&Array.from(t).forEach((t=>t.parentNode&&t.parentNode.removeChild(t)))})));const o={};for(const e in T){const r=this.config[e]||{};let n=M({metainfo:T,slots:t},e,T[e],r);if(!n)continue;h(n)||(n=[n]);let s="base"!==e&&T[e].to;!s&&"to"in r&&(s=r.to),!s&&"attributesFor"in r&&(s=e);for(const{to:t,vnode:e}of n)I(o,t||s||"head",e)}if(t)for(const e in t){const r="default"===e?"head":e;if("head"!==r&&"body"!==r)continue;const n=t[e];y(n)&&I(o,r,n({metainfo:T}))}return Object.keys(o).map((t=>e(u,{to:t},o[t])))}}H.create=(t,e)=>{const o=((t,e={})=>{const o=[];e||(e={});const r={active:e,resolve:t,sources:o},n=()=>$(r);return{context:r,compute:n,addSource:(t,e,o=!1)=>{const s=P(r,t,e||{});return o&&n(),s},delSource:(t,e=!0)=>{const r=o.findIndex((e=>e===t||e[w]===t));return r>-1&&(o.splice(r,1),e&&n(),!0)}}})(((t,o,r,n,s)=>y(e)?e(t,o,r,n,s):e.resolve(t,o,r,n,s)),T);return new H(t,o,e)};export{V as createMetaManager,p as deepestResolver,m as defaultConfig,z as getCurrentManager,f as resolveOption,q as useActiveMeta,L as useMeta}; diff --git a/dist/vue-meta.esm-bundler.js b/dist/vue-meta.esm-bundler.js index c76404f8..2777647e 100644 --- a/dist/vue-meta.esm-bundler.js +++ b/dist/vue-meta.esm-bundler.js @@ -1,12 +1,12 @@ /** - * vue-meta v3.0.0-alpha.1 + * vue-meta v3.0.0-alpha.2 * (c) 2021 * - Pim (@pimlie) * - All the amazing contributors * @license MIT */ -import { markRaw, h, getCurrentInstance, inject, defineComponent, reactive, onUnmounted, Teleport } from 'vue'; +import { markRaw, h, getCurrentInstance, isProxy, watch, inject, defineComponent, reactive, onUnmounted, Teleport } from 'vue'; const resolveOption = predicament => (options, contexts) => { let resolvedIndex = -1; @@ -608,7 +608,7 @@ function renderAttributes(context, key, data, config) { } function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) { const slot = slots && slots[slotName]; - if (!slot) { + if (!slot || !isFunction(slot)) { return content; } const slotScopeProps = { @@ -633,7 +633,32 @@ const PolySymbol = (name) => hasSymbol ? Symbol((process.env.NODE_ENV !== 'production') ? '[vue-meta]: ' + name : name) : ((process.env.NODE_ENV !== 'production') ? '[vue-meta]: ' : '_vm_') + name; -const metaActiveKey = /*#__PURE__*/ PolySymbol((process.env.NODE_ENV !== 'production') ? 'active_meta' : 'am'); +const metaActiveKey = /*#__PURE__*/ PolySymbol((process.env.NODE_ENV !== 'production') ? 'meta_active' : 'ma'); + +/** + * Apply the differences between newSource & oldSource to target + */ +function applyDifference(target, newSource, oldSource) { + for (const key in newSource) { + if (!(key in oldSource)) { + target[key] = newSource[key]; + continue; + } + // We dont care about nested objects here , these changes + // should already have been tracked by the MergeProxy + if (isObject(target[key])) { + continue; + } + if (newSource[key] !== oldSource[key]) { + target[key] = newSource[key]; + } + } + for (const key in oldSource) { + if (!(key in newSource)) { + delete target[key]; + } + } +} function getCurrentManager(vm) { if (!vm) { @@ -645,15 +670,22 @@ function getCurrentManager(vm) { return vm.appContext.config.globalProperties.$metaManager; } function useMeta(source, manager) { - const vm = getCurrentInstance(); + const vm = getCurrentInstance() || undefined; if (!manager && vm) { manager = getCurrentManager(vm); } if (!manager) { - // oopsydoopsy throw new Error('No manager or current instance'); } - return manager.addMeta(source, vm || undefined); + if (isProxy(source)) { + watch(source, (newSource, oldSource) => { + // We only care about first level props, second+ level will already be changed by the merge proxy + applyDifference(metaProxy.meta, newSource, oldSource); + }); + source = source.value; + } + const metaProxy = manager.addMeta(source, vm); + return metaProxy; } function useActiveMeta() { return inject(metaActiveKey); @@ -691,83 +723,128 @@ function addVnode(teleports, to, vnodes) { } teleports[to].push(...nodes); } -function createMetaManager(config, resolver) { - const resolve = (options, contexts, active, key, pathSegments) => { - if (isFunction(resolver)) { - return resolver(options, contexts, active, key, pathSegments); +const createMetaManager = (config, resolver) => MetaManager.create(config, resolver); +class MetaManager { + constructor(config, target, resolver) { + this.ssrCleanedUp = false; + this.config = config; + this.target = target; + if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { + this.resolver = resolver; } - return resolver.resolve(options, contexts, active, key, pathSegments); - }; - const { addSource, delSource } = createMergedObject(resolve, active); - // TODO: validate resolver - const manager = { - config, - install(app) { - app.component('Metainfo', Metainfo); - app.config.globalProperties.$metaManager = manager; - app.provide(metaActiveKey, active); - }, - addMeta(metaObj, vm) { - if (!vm) { - vm = getCurrentInstance() || undefined; - } - const resolveContext = { vm }; - if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { - resolver.setup(resolveContext); - } - // TODO: optimize initial compute - const meta = addSource(metaObj, resolveContext, true); - const unmount = () => delSource(meta); - if (vm) { - onUnmounted(unmount); - } - return { - meta, - unmount - }; - }, - render({ slots } = {}) { - const teleports = {}; - for (const key in active) { - const config = this.config[key] || {}; - let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); - if (!renderedNodes) { + } + install(app) { + app.component('Metainfo', Metainfo); + app.config.globalProperties.$metaManager = this; + app.provide(metaActiveKey, active); + } + addMeta(metadata, vm) { + if (!vm) { + vm = getCurrentInstance() || undefined; + } + const metaGuards = ({ + removed: [] + }); + const resolveContext = { vm }; + if (this.resolver) { + this.resolver.setup(resolveContext); + } + // TODO: optimize initial compute (once) + const meta = this.target.addSource(metadata, resolveContext, true); + const onRemoved = (removeGuard) => metaGuards.removed.push(removeGuard); + const unmount = (ignoreGuards) => this.unmount(!!ignoreGuards, meta, metaGuards, vm); + if (vm) { + onUnmounted(unmount); + } + return { + meta, + onRemoved, + unmount + }; + } + unmount(ignoreGuards, meta, metaGuards, vm) { + if (vm) { + const { $el } = vm.proxy; + // Wait for element to be removed from DOM + if ($el && $el.offsetParent) { + let observer = new MutationObserver((records) => { + for (const { removedNodes } of records) { + if (!removedNodes) { + continue; + } + removedNodes.forEach((el) => { + if (el === $el && observer) { + observer.disconnect(); + observer = undefined; + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + }); + } + }); + observer.observe($el.parentNode, { childList: true }); + return; + } + } + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + async reallyUnmount(ignoreGuards, meta, metaGuards) { + this.target.delSource(meta); + if (!ignoreGuards && metaGuards) { + await Promise.all(metaGuards.removed.map(removeGuard => removeGuard())); + } + } + render({ slots } = {}) { + const teleports = {}; + for (const key in active) { + const config = this.config[key] || {}; + let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); + if (!renderedNodes) { + continue; + } + if (!isArray(renderedNodes)) { + renderedNodes = [renderedNodes]; + } + let defaultTo = key !== 'base' && active[key].to; + if (!defaultTo && 'to' in config) { + defaultTo = config.to; + } + if (!defaultTo && 'attributesFor' in config) { + defaultTo = key; + } + for (const { to, vnode } of renderedNodes) { + addVnode(teleports, to || defaultTo || 'head', vnode); + } + } + if (slots) { + for (const slotName in slots) { + const tagName = slotName === 'default' ? 'head' : slotName; + // Only teleport the contents of head/body slots + if (tagName !== 'head' && tagName !== 'body') { continue; } - if (!isArray(renderedNodes)) { - renderedNodes = [renderedNodes]; - } - let defaultTo = key !== 'base' && active[key].to; - if (!defaultTo && 'to' in config) { - defaultTo = config.to; - } - if (!defaultTo && 'attributesFor' in config) { - defaultTo = key; - } - for (const { to, vnode } of renderedNodes) { - addVnode(teleports, to || defaultTo || 'head', vnode); - } - } - if (slots) { - for (const slotName in slots) { - const tagName = slotName === 'default' ? 'head' : slotName; - // Only teleport the contents of head/body slots - if (tagName !== 'head' && tagName !== 'body') { - continue; - } - const slot = slots[slotName]; - if (isFunction(slot)) { - addVnode(teleports, tagName, slot({ metainfo: active })); - } + const slot = slots[slotName]; + if (isFunction(slot)) { + addVnode(teleports, tagName, slot({ metainfo: active })); } } - return Object.keys(teleports).map((to) => { - return h(Teleport, { to }, teleports[to]); - }); } + return Object.keys(teleports).map((to) => { + return h(Teleport, { to }, teleports[to]); + }); + } +} +MetaManager.create = (config, resolver) => { + const resolve = (options, contexts, active, key, pathSegments) => { + if (isFunction(resolver)) { + return resolver(options, contexts, active, key, pathSegments); + } + return resolver.resolve(options, contexts, active, key, pathSegments); }; + const mergedObject = createMergedObject(resolve, active); + // TODO: validate resolver + const manager = new MetaManager(config, mergedObject, resolver); return manager; -} +}; // rollup doesnt like an import as it cant find the export so use require const { renderToString } = require('@vue/server-renderer'); diff --git a/dist/vue-meta.global.js b/dist/vue-meta.global.js index 74512f45..3ce1add4 100644 --- a/dist/vue-meta.global.js +++ b/dist/vue-meta.global.js @@ -1,5 +1,5 @@ /** - * vue-meta v3.0.0-alpha.1 + * vue-meta v3.0.0-alpha.2 * (c) 2021 * - Pim (@pimlie) * - All the amazing contributors @@ -601,12 +601,12 @@ var VueMeta = (function (exports, vue) { } if (!cachedElements[attributesFor]) { const [el, el2] = Array.from(document.querySelectorAll(attributesFor)); - if ( !el) { + if (!el) { // eslint-disable-next-line no-console console.error('Could not find element for selector', attributesFor, ', won\'t render attributes'); return; } - if ( el2) { + if (el2) { // eslint-disable-next-line no-console console.warn('Found multiple elements for selector', attributesFor); } @@ -630,7 +630,7 @@ var VueMeta = (function (exports, vue) { } function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) { const slot = slots && slots[slotName]; - if (!slot) { + if (!slot || !isFunction(slot)) { return content; } const slotScopeProps = { @@ -653,9 +653,34 @@ var VueMeta = (function (exports, vue) { const PolySymbol = (name) => // vm = vue meta hasSymbol - ? Symbol( '[vue-meta]: ' + name ) - : ( '[vue-meta]: ' ) + name; - const metaActiveKey = /*#__PURE__*/ PolySymbol( 'active_meta' ); + ? Symbol('[vue-meta]: ' + name ) + : ('[vue-meta]: ' ) + name; + const metaActiveKey = /*#__PURE__*/ PolySymbol('meta_active' ); + + /** + * Apply the differences between newSource & oldSource to target + */ + function applyDifference(target, newSource, oldSource) { + for (const key in newSource) { + if (!(key in oldSource)) { + target[key] = newSource[key]; + continue; + } + // We dont care about nested objects here , these changes + // should already have been tracked by the MergeProxy + if (isObject(target[key])) { + continue; + } + if (newSource[key] !== oldSource[key]) { + target[key] = newSource[key]; + } + } + for (const key in oldSource) { + if (!(key in newSource)) { + delete target[key]; + } + } + } function getCurrentManager(vm) { if (!vm) { @@ -667,15 +692,22 @@ var VueMeta = (function (exports, vue) { return vm.appContext.config.globalProperties.$metaManager; } function useMeta(source, manager) { - const vm = vue.getCurrentInstance(); + const vm = vue.getCurrentInstance() || undefined; if (!manager && vm) { manager = getCurrentManager(vm); } if (!manager) { - // oopsydoopsy throw new Error('No manager or current instance'); } - return manager.addMeta(source, vm || undefined); + if (vue.isProxy(source)) { + vue.watch(source, (newSource, oldSource) => { + // We only care about first level props, second+ level will already be changed by the merge proxy + applyDifference(metaProxy.meta, newSource, oldSource); + }); + source = source.value; + } + const metaProxy = manager.addMeta(source, vm); + return metaProxy; } function useActiveMeta() { return vue.inject(metaActiveKey); @@ -714,97 +746,142 @@ var VueMeta = (function (exports, vue) { } teleports[to].push(...nodes); } - function createMetaManager(config, resolver) { - let cleanedUpSsr = false; - const resolve = (options, contexts, active, key, pathSegments) => { - if (isFunction(resolver)) { - return resolver(options, contexts, active, key, pathSegments); - } - return resolver.resolve(options, contexts, active, key, pathSegments); - }; - const { addSource, delSource } = createMergedObject(resolve, active); - // TODO: validate resolver - const manager = { - config, - install(app) { - app.component('Metainfo', Metainfo); - app.config.globalProperties.$metaManager = manager; - app.provide(metaActiveKey, active); - }, - addMeta(metaObj, vm) { - if (!vm) { - vm = vue.getCurrentInstance() || undefined; - } - const resolveContext = { vm }; - if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { - resolver.setup(resolveContext); - } - // TODO: optimize initial compute - const meta = addSource(metaObj, resolveContext, true); - const unmount = () => delSource(meta); - if (vm) { - vue.onUnmounted(unmount); - } - return { - meta, - unmount - }; - }, - render({ slots } = {}) { - // cleanup ssr tags if not yet done - if ( !cleanedUpSsr) { - cleanedUpSsr = true; - // Listen for DOM loaded because tags in the body couldnt - // have loaded yet once the manager does it first render - // (preferable there should only be one meta render on hydration) - window.addEventListener('DOMContentLoaded', () => { - const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`); - if (ssrTags && ssrTags.length) { - Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el)); + const createMetaManager = (config, resolver) => MetaManager.create(config, resolver); + class MetaManager { + constructor(config, target, resolver) { + this.ssrCleanedUp = false; + this.config = config; + this.target = target; + if (resolver && 'setup' in resolver && isFunction(resolver.setup)) { + this.resolver = resolver; + } + } + install(app) { + app.component('Metainfo', Metainfo); + app.config.globalProperties.$metaManager = this; + app.provide(metaActiveKey, active); + } + addMeta(metadata, vm) { + if (!vm) { + vm = vue.getCurrentInstance() || undefined; + } + const metaGuards = ({ + removed: [] + }); + const resolveContext = { vm }; + if (this.resolver) { + this.resolver.setup(resolveContext); + } + // TODO: optimize initial compute (once) + const meta = this.target.addSource(metadata, resolveContext, true); + const onRemoved = (removeGuard) => metaGuards.removed.push(removeGuard); + const unmount = (ignoreGuards) => this.unmount(!!ignoreGuards, meta, metaGuards, vm); + if (vm) { + vue.onUnmounted(unmount); + } + return { + meta, + onRemoved, + unmount + }; + } + unmount(ignoreGuards, meta, metaGuards, vm) { + if (vm) { + const { $el } = vm.proxy; + // Wait for element to be removed from DOM + if ($el && $el.offsetParent) { + let observer = new MutationObserver((records) => { + for (const { removedNodes } of records) { + if (!removedNodes) { + continue; + } + removedNodes.forEach((el) => { + if (el === $el && observer) { + observer.disconnect(); + observer = undefined; + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + }); } }); + observer.observe($el.parentNode, { childList: true }); + return; } - const teleports = {}; - for (const key in active) { - const config = this.config[key] || {}; - let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); - if (!renderedNodes) { - continue; - } - if (!isArray(renderedNodes)) { - renderedNodes = [renderedNodes]; - } - let defaultTo = key !== 'base' && active[key].to; - if (!defaultTo && 'to' in config) { - defaultTo = config.to; - } - if (!defaultTo && 'attributesFor' in config) { - defaultTo = key; - } - for (const { to, vnode } of renderedNodes) { - addVnode(teleports, to || defaultTo || 'head', vnode); + } + this.reallyUnmount(ignoreGuards, meta, metaGuards); + } + async reallyUnmount(ignoreGuards, meta, metaGuards) { + this.target.delSource(meta); + if (!ignoreGuards && metaGuards) { + await Promise.all(metaGuards.removed.map(removeGuard => removeGuard())); + } + } + render({ slots } = {}) { + // TODO: clean this method + // cleanup ssr tags if not yet done + if (!this.ssrCleanedUp) { + this.ssrCleanedUp = true; + // Listen for DOM loaded because tags in the body couldnt + // have loaded yet once the manager does it first render + // (preferable there should only be one meta render on hydration) + window.addEventListener('DOMContentLoaded', () => { + const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`); + if (ssrTags && ssrTags.length) { + Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el)); } + }); + } + const teleports = {}; + for (const key in active) { + const config = this.config[key] || {}; + let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config); + if (!renderedNodes) { + continue; } - if (slots) { - for (const slotName in slots) { - const tagName = slotName === 'default' ? 'head' : slotName; - // Only teleport the contents of head/body slots - if (tagName !== 'head' && tagName !== 'body') { - continue; - } - const slot = slots[slotName]; - if (isFunction(slot)) { - addVnode(teleports, tagName, slot({ metainfo: active })); - } + if (!isArray(renderedNodes)) { + renderedNodes = [renderedNodes]; + } + let defaultTo = key !== 'base' && active[key].to; + if (!defaultTo && 'to' in config) { + defaultTo = config.to; + } + if (!defaultTo && 'attributesFor' in config) { + defaultTo = key; + } + for (const { to, vnode } of renderedNodes) { + addVnode(teleports, to || defaultTo || 'head', vnode); + } + } + if (slots) { + for (const slotName in slots) { + const tagName = slotName === 'default' ? 'head' : slotName; + // Only teleport the contents of head/body slots + if (tagName !== 'head' && tagName !== 'body') { + continue; + } + const slot = slots[slotName]; + if (isFunction(slot)) { + addVnode(teleports, tagName, slot({ metainfo: active })); } } - return Object.keys(teleports).map((to) => { - return vue.h(vue.Teleport, { to }, teleports[to]); - }); } + return Object.keys(teleports).map((to) => { + return vue.h(vue.Teleport, { to }, teleports[to]); + }); + } + } + MetaManager.create = (config, resolver) => { + const resolve = (options, contexts, active, key, pathSegments) => { + if (isFunction(resolver)) { + return resolver(options, contexts, active, key, pathSegments); + } + return resolver.resolve(options, contexts, active, key, pathSegments); }; + const mergedObject = createMergedObject(resolve, active); + // TODO: validate resolver + const manager = new MetaManager(config, mergedObject, resolver); return manager; - } + }; exports.createMetaManager = createMetaManager; exports.deepestResolver = deepest; diff --git a/dist/vue-meta.global.min.js b/dist/vue-meta.global.min.js index dc6effab..7ebdf726 100644 --- a/dist/vue-meta.global.min.js +++ b/dist/vue-meta.global.min.js @@ -1,8 +1,8 @@ /** - * vue-meta v3.0.0-alpha.1 + * vue-meta v3.0.0-alpha.2 * (c) 2021 * - Pim (@pimlie) * - All the amazing contributors * @license MIT */ -var VueMeta=function(t,e){"use strict";const r=t=>(e,r)=>{let n=-1;if(r.reduce(((e,r,o)=>{const s=t(e,r);return s!==e?(n=o,s):e}),void 0),n>-1)return e[n]};const n=r(((t,e)=>{const{depth:r}=e;if(!t||r>t)return t}));var o=Object.freeze({__proto__:null,setup:function(t){let e=0;if(t.vm){let{vm:r}=t;do{r.parent&&(e++,r=r.parent)}while(r&&r.parent&&r!==r.root)}t.depth=e},resolve:n});const s={title:{attributes:!1},base:{contentAsAttribute:!0,attributes:["href","target"]},meta:{contentAsAttribute:!0,keyAttribute:"name",attributes:["content","name","http-equiv","charset"]},link:{contentAsAttribute:!0,attributes:["href","crossorigin","rel","media","integrity","hreflang","type","referrerpolicy","sizes","imagesrcset","imagesizes","as","color"]},style:{attributes:["media"]},script:{attributes:["src","type","nomodule","async","defer","crossorigin","integrity","referrerpolicy"]},noscript:{attributes:!1}};"production"===process.env.NODE_ENV||Object.freeze({}),"production"===process.env.NODE_ENV||Object.freeze([]);const c=Array.isArray,i=t=>"function"==typeof t,a=t=>"string"==typeof t,u=t=>null!==t&&"object"==typeof t,f=Object.prototype.toString,l=t=>"[object Object]"===f.call(t),d=Symbol("kIsProxy"),p=Symbol("kProxySources"),m=Symbol("kProxyTarget"),b=Symbol("kResolveContext");function g(t){if(c(t))return t.map(g);if(u(t)){const e={};for(const r in t)e[r]="context"===r?t[r]:g(t[r]);return e}return t}const y=(t,e,r)=>{const n=[];for(const o of t)e in o&&(n.push(o[e]),r&&r(o));return n},h=(t,e,r,n=[])=>{if(n.length||(r||(r=t.active),e||(e=t.sources)),!r||!e)return;const o=((t,...e)=>{const r=t?Object.keys(t):[];if(e)for(const t of e)if(t&&u(t))for(const e in t)r.includes(e)||r.push(e);return r})(...e),s=Object.keys(r);for(const t of s)o.includes(t)||delete r[t];for(const s of o){if(l(e[0][s])){r[s]||(r[s]={});const o=[];for(const t of e)s in t&&o.push(t[s]);h(t,o,r[s],[...n,s]);continue}!r[s]&&c(e[0][s])&&(r[s]=[]);const o=[],i=y(e,s,(t=>o.push(t[b])));let a=t.resolve(i,o,r[s],s,n);l(a)&&(a=g(a)),r[s]=a}},v=(t,r,n,o=[])=>{const s=A(t,n,o),c=e.markRaw(new Proxy(r,s));return!o.length&&t.sources&&t.sources.push(c),c},A=(t,e,r=[])=>({get:(n,o,s)=>{if(o===d)return!0;if(o===p)return t.sources;if(o===m)return n;if(o===b)return e;let c=Reflect.get(n,o,s);if(!u(c))return c;if(!c[d]){const s=[...r,o];c=v(t,c,e,s),n[o]=c}return c},set:(e,n,o)=>{const s=Reflect.set(e,n,o);if(s){const o=c(e);let i,a=!1,{sources:u,active:f}=t,d=0;for(const t of r){if(u=y(u,t),o&&d===r.length-1){i=t;break}c(f)&&(a=!0),f=f[t],d++}if(a)return h(t),s;let p,m=[];o?(p=u,m=u.map((t=>t[b]))):p=y(u,n,(t=>m.push(t[b])));let v=t.resolve(p,m,f,n,r);l(v)&&(v=g(v)),o&&i?f[i]=v:f[n]=v}return s},deleteProperty:(e,n)=>{const o=Reflect.deleteProperty(e,n);if(o){const o=c(e);let s,i=t.sources,a=t.active,u=0;for(const t of r){if(i=i.map((e=>e[t])),o&&u===r.length-1){s=t;break}a=a[t],u++}if(i.some((t=>n in t))){let e,c=[];o?(e=i,c=i.map((t=>t[b]))):e=y(i,n,(t=>c.push(t[b])));let u=t.resolve(e,c,a,n,r);l(u)&&(u=g(u)),o&&s?a[s]=u:a[n]=u}else delete a[n]}return o}}),S={};function k(t,e,r,n){return"attributesFor"in n?function(t,e,r,n){const{attributesFor:o}=n;if(!o)return;if(!S[o]){const[t,e]=Array.from(document.querySelectorAll(o));if(!t)return void console.error("Could not find element for selector",o,", won't render attributes");e&&console.warn("Found multiple elements for selector",o),S[o]={el:t,attrs:[]}}const{el:s,attrs:c}=S[o];for(const n in r){const o=M(t,`${e}(${n})`,r[n],r);s.setAttribute(n,o||""),c.includes(n)||c.push(n)}const i=c.filter((t=>!r[t]));for(const t of i)s.removeAttribute(t)}(t,e,r,n):"group"in n?function(t,e,r,n){if(c(r))return console.warn("Specifying an array for group properties isnt supported"),[];return Object.keys(r).map((o=>{const s={group:e,data:r};if(n.namespaced)s.tagNamespace=!0===n.namespaced?e:n.namespaced;else if(n.namespacedAttribute){const t=!0===n.namespacedAttribute?e:n.namespacedAttribute;s.fullName=`${t}:${o}`,s.slotName=`${t}(${o})`}return j(t,e,r[o],n,s)})).flat()}(t,e,r,n):j(t,e,r,n)}function j(t,r,n,o={},i){const u=["content","json","rawContent"],f=t=>function(t,e){for(const r of t){const t=s[r];if(r&&t)return t[e]}}([l,o.tag],t);if(c(n))return n.map((e=>j(t,r,e,o,i))).flat();const{tag:l=o.tag||r}=n;let d="",p=!1,m=!1;if(a(n))d=n;else if(n.children&&c(n.children))p=!0,d=n.children.map((e=>{const n=j(t,r,e,o,i);return c(n)?n.map((({vnode:t})=>t)):n.vnode}));else{let t=0;for(const e of u){if(!d&&n[e]){d=1===t?JSON.stringify(n[e]):n[e],m=t>1;break}t++}}const b=i&&i.fullName||r,g=i&&i.slotName||r;let{attrs:y}=n;if(y||"object"!=typeof n)y||(y={});else{y={...n},delete y.tag,delete y.children,delete y.to;for(const t of u)delete y[t]}if(p)d=M(t,g,d,n);else{const e=!!f("contentAsAttribute");let{valueAttribute:r}=o;if(!r&&e){const[t]=f("attributes");r=a(e)?e:t}if(r){const{nameless:e,keyAttribute:n}=o;e||n&&(y[n]=b),y[r]=M(t,g,y[r]||d,i),d=""}else d=M(t,g,d,n)}const h=i&&i.tagNamespace?`${i.tagNamespace}:${l}`:l;m&&d&&(y.innerHTML=d);const v=e.h(h,y,d||void 0);return{to:n.to,vnode:v}}function M({metainfo:t,slots:e},r,n,o){const s=e&&e[r];if(!s)return n;const c={content:n,metainfo:t};if(o&&o.group){const{group:t,data:e}=o;c[t]=e}const i=s(c);if(i&&i.length){const{children:t}=i[0];return t?t.toString():""}return n}const N="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag,O=(t=>N?Symbol("[vue-meta]: "+t):"[vue-meta]: "+t)("active_meta");function C(t){if(t||(t=e.getCurrentInstance()||void 0),t)return t.appContext.config.globalProperties.$metaManager}const w=e.defineComponent({name:"Metainfo",inheritAttrs:!1,setup:(t,{slots:e})=>()=>{const t=C();if(t)return t.render({slots:e})}}),$=e.reactive({});function x(t,r,n){const o=c(n)?n:[n];o.forEach(((t,r)=>{t.type===e.Comment&&o.splice(r,1)})),t[r]||(t[r]=[]),t[r].push(...o)}return t.createMetaManager=function(t,r){let n=!1;const{addSource:o,delSource:s}=((t,e={})=>{const r=[];e||(e={});const n={active:e,resolve:t,sources:r},o=()=>h(n);return{context:n,compute:o,addSource:(t,e,r=!1)=>{const s=v(n,t,e||{});return r&&o(),s},delSource:(t,e=!0)=>{const n=r.findIndex((e=>e===t||e[m]===t));return n>-1&&(r.splice(n,1),e&&o(),!0)}}})(((t,e,n,o,s)=>i(r)?r(t,e,n,o,s):r.resolve(t,e,n,o,s)),$),a={config:t,install(t){t.component("Metainfo",w),t.config.globalProperties.$metaManager=a,t.provide(O,$)},addMeta(t,n){n||(n=e.getCurrentInstance()||void 0);const c={vm:n};r&&"setup"in r&&i(r.setup)&&r.setup(c);const a=o(t,c,!0),u=()=>s(a);return n&&e.onUnmounted(u),{meta:a,unmount:u}},render({slots:t}={}){n||(n=!0,window.addEventListener("DOMContentLoaded",(()=>{const t=document.querySelectorAll("[data-vm-ssr]");t&&t.length&&Array.from(t).forEach((t=>t.parentNode&&t.parentNode.removeChild(t)))})));const r={};for(const e in $){const n=this.config[e]||{};let o=k({metainfo:$,slots:t},e,$[e],n);if(!o)continue;c(o)||(o=[o]);let s="base"!==e&&$[e].to;!s&&"to"in n&&(s=n.to),!s&&"attributesFor"in n&&(s=e);for(const{to:t,vnode:e}of o)x(r,t||s||"head",e)}if(t)for(const e in t){const n="default"===e?"head":e;if("head"!==n&&"body"!==n)continue;const o=t[e];i(o)&&x(r,n,o({metainfo:$}))}return Object.keys(r).map((t=>e.h(e.Teleport,{to:t},r[t])))}};return a},t.deepestResolver=o,t.defaultConfig={body:{tag:"script",to:"body"},base:{valueAttribute:"href"},charset:{tag:"meta",nameless:!0,valueAttribute:"charset"},description:{tag:"meta"},og:{group:!0,namespacedAttribute:!0,tag:"meta",keyAttribute:"property"},twitter:{group:!0,namespacedAttribute:!0,tag:"meta"},htmlAttrs:{attributesFor:"html"},headAttrs:{attributesFor:"head"},bodyAttrs:{attributesFor:"body"}},t.getCurrentManager=C,t.resolveOption=r,t.useActiveMeta=function(){return e.inject(O)},t.useMeta=function(t,r){const n=e.getCurrentInstance();if(!r&&n&&(r=C(n)),!r)throw new Error("No manager or current instance");return r.addMeta(t,n||void 0)},Object.defineProperty(t,"__esModule",{value:!0}),t}({},Vue); +var VueMeta=function(t,e){"use strict";const r=t=>(e,r)=>{let o=-1;if(r.reduce(((e,r,n)=>{const s=t(e,r);return s!==e?(o=n,s):e}),void 0),o>-1)return e[o]};const o=r(((t,e)=>{const{depth:r}=e;if(!t||r>t)return t}));var n=Object.freeze({__proto__:null,setup:function(t){let e=0;if(t.vm){let{vm:r}=t;do{r.parent&&(e++,r=r.parent)}while(r&&r.parent&&r!==r.root)}t.depth=e},resolve:o});const s={title:{attributes:!1},base:{contentAsAttribute:!0,attributes:["href","target"]},meta:{contentAsAttribute:!0,keyAttribute:"name",attributes:["content","name","http-equiv","charset"]},link:{contentAsAttribute:!0,attributes:["href","crossorigin","rel","media","integrity","hreflang","type","referrerpolicy","sizes","imagesrcset","imagesizes","as","color"]},style:{attributes:["media"]},script:{attributes:["src","type","nomodule","async","defer","crossorigin","integrity","referrerpolicy"]},noscript:{attributes:!1}};"production"===process.env.NODE_ENV||Object.freeze({}),"production"===process.env.NODE_ENV||Object.freeze([]);const i=Array.isArray,c=t=>"function"==typeof t,a=t=>"string"==typeof t,u=t=>null!==t&&"object"==typeof t,l=Object.prototype.toString,f=t=>"[object Object]"===l.call(t),d=Symbol("kIsProxy"),p=Symbol("kProxySources"),m=Symbol("kProxyTarget"),b=Symbol("kResolveContext");function h(t){if(i(t))return t.map(h);if(u(t)){const e={};for(const r in t)e[r]="context"===r?t[r]:h(t[r]);return e}return t}const g=(t,e,r)=>{const o=[];for(const n of t)e in n&&(o.push(n[e]),r&&r(n));return o},y=(t,e,r,o=[])=>{if(o.length||(r||(r=t.active),e||(e=t.sources)),!r||!e)return;const n=((t,...e)=>{const r=t?Object.keys(t):[];if(e)for(const t of e)if(t&&u(t))for(const e in t)r.includes(e)||r.push(e);return r})(...e),s=Object.keys(r);for(const t of s)n.includes(t)||delete r[t];for(const s of n){if(f(e[0][s])){r[s]||(r[s]={});const n=[];for(const t of e)s in t&&n.push(t[s]);y(t,n,r[s],[...o,s]);continue}!r[s]&&i(e[0][s])&&(r[s]=[]);const n=[],c=g(e,s,(t=>n.push(t[b])));let a=t.resolve(c,n,r[s],s,o);f(a)&&(a=h(a)),r[s]=a}},v=(t,r,o,n=[])=>{const s=A(t,o,n),i=e.markRaw(new Proxy(r,s));return!n.length&&t.sources&&t.sources.push(i),i},A=(t,e,r=[])=>({get:(o,n,s)=>{if(n===d)return!0;if(n===p)return t.sources;if(n===m)return o;if(n===b)return e;let i=Reflect.get(o,n,s);if(!u(i))return i;if(!i[d]){const s=[...r,n];i=v(t,i,e,s),o[n]=i}return i},set:(e,o,n)=>{const s=Reflect.set(e,o,n);if(s){const n=i(e);let c,a=!1,{sources:u,active:l}=t,d=0;for(const t of r){if(u=g(u,t),n&&d===r.length-1){c=t;break}i(l)&&(a=!0),l=l[t],d++}if(a)return y(t),s;let p,m=[];n?(p=u,m=u.map((t=>t[b]))):p=g(u,o,(t=>m.push(t[b])));let v=t.resolve(p,m,l,o,r);f(v)&&(v=h(v)),n&&c?l[c]=v:l[o]=v}return s},deleteProperty:(e,o)=>{const n=Reflect.deleteProperty(e,o);if(n){const n=i(e);let s,c=t.sources,a=t.active,u=0;for(const t of r){if(c=c.map((e=>e[t])),n&&u===r.length-1){s=t;break}a=a[t],u++}if(c.some((t=>o in t))){let e,i=[];n?(e=c,i=c.map((t=>t[b]))):e=g(c,o,(t=>i.push(t[b])));let u=t.resolve(e,i,a,o,r);f(u)&&(u=h(u)),n&&s?a[s]=u:a[o]=u}else delete a[o]}return n}}),S={};function N(t,e,r,o){return"attributesFor"in o?function(t,e,r,o){const{attributesFor:n}=o;if(!n)return;if(!S[n]){const[t,e]=Array.from(document.querySelectorAll(n));if(!t)return void console.error("Could not find element for selector",n,", won't render attributes");e&&console.warn("Found multiple elements for selector",n),S[n]={el:t,attrs:[]}}const{el:s,attrs:i}=S[n];for(const o in r){const n=w(t,`${e}(${o})`,r[o],r);s.setAttribute(o,n||""),i.includes(o)||i.push(o)}const c=i.filter((t=>!r[t]));for(const t of c)s.removeAttribute(t)}(t,e,r,o):"group"in o?function(t,e,r,o){if(i(r))return console.warn("Specifying an array for group properties isnt supported"),[];return Object.keys(r).map((n=>{const s={group:e,data:r};if(o.namespaced)s.tagNamespace=!0===o.namespaced?e:o.namespaced;else if(o.namespacedAttribute){const t=!0===o.namespacedAttribute?e:o.namespacedAttribute;s.fullName=`${t}:${n}`,s.slotName=`${t}(${n})`}return k(t,e,r[n],o,s)})).flat()}(t,e,r,o):k(t,e,r,o)}function k(t,r,o,n={},c){const u=["content","json","rawContent"],l=t=>function(t,e){for(const r of t){const t=s[r];if(r&&t)return t[e]}}([f,n.tag],t);if(i(o))return o.map((e=>k(t,r,e,n,c))).flat();const{tag:f=n.tag||r}=o;let d="",p=!1,m=!1;if(a(o))d=o;else if(o.children&&i(o.children))p=!0,d=o.children.map((e=>{const o=k(t,r,e,n,c);return i(o)?o.map((({vnode:t})=>t)):o.vnode}));else{let t=0;for(const e of u){if(!d&&o[e]){d=1===t?JSON.stringify(o[e]):o[e],m=t>1;break}t++}}const b=c&&c.fullName||r,h=c&&c.slotName||r;let{attrs:g}=o;if(g||"object"!=typeof o)g||(g={});else{g={...o},delete g.tag,delete g.children,delete g.to;for(const t of u)delete g[t]}if(p)d=w(t,h,d,o);else{const e=!!l("contentAsAttribute");let{valueAttribute:r}=n;if(!r&&e){const[t]=l("attributes");r=a(e)?e:t}if(r){const{nameless:e,keyAttribute:o}=n;e||o&&(g[o]=b),g[r]=w(t,h,g[r]||d,c),d=""}else d=w(t,h,d,o)}const y=c&&c.tagNamespace?`${c.tagNamespace}:${f}`:f;m&&d&&(g.innerHTML=d);const v=e.h(y,g,d||void 0);return{to:o.to,vnode:v}}function w({metainfo:t,slots:e},r,o,n){const s=e&&e[r];if(!s||!c(s))return o;const i={content:o,metainfo:t};if(n&&n.group){const{group:t,data:e}=n;i[t]=e}const a=s(i);if(a&&a.length){const{children:t}=a[0];return t?t.toString():""}return o}const C="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag,M=(t=>C?Symbol("[vue-meta]: "+t):"[vue-meta]: "+t)("meta_active");function O(t){if(t||(t=e.getCurrentInstance()||void 0),t)return t.appContext.config.globalProperties.$metaManager}const j=e.defineComponent({name:"Metainfo",inheritAttrs:!1,setup:(t,{slots:e})=>()=>{const t=O();if(t)return t.render({slots:e})}}),P=e.reactive({});function x(t,r,o){const n=i(o)?o:[o];n.forEach(((t,r)=>{t.type===e.Comment&&n.splice(r,1)})),t[r]||(t[r]=[]),t[r].push(...n)}class ${constructor(t,e,r){this.ssrCleanedUp=!1,this.config=t,this.target=e,r&&"setup"in r&&c(r.setup)&&(this.resolver=r)}install(t){t.component("Metainfo",j),t.config.globalProperties.$metaManager=this,t.provide(M,P)}addMeta(t,r){r||(r=e.getCurrentInstance()||void 0);const o={removed:[]},n={vm:r};this.resolver&&this.resolver.setup(n);const s=this.target.addSource(t,n,!0),i=t=>this.unmount(!!t,s,o,r);return r&&e.onUnmounted(i),{meta:s,onRemoved:t=>o.removed.push(t),unmount:i}}unmount(t,e,r,o){if(o){const{$el:n}=o.proxy;if(n&&n.offsetParent){let o=new MutationObserver((s=>{for(const{removedNodes:i}of s)i&&i.forEach((s=>{s===n&&o&&(o.disconnect(),o=void 0,this.reallyUnmount(t,e,r))}))}));return void o.observe(n.parentNode,{childList:!0})}}this.reallyUnmount(t,e,r)}async reallyUnmount(t,e,r){this.target.delSource(e),!t&&r&&await Promise.all(r.removed.map((t=>t())))}render({slots:t}={}){this.ssrCleanedUp||(this.ssrCleanedUp=!0,window.addEventListener("DOMContentLoaded",(()=>{const t=document.querySelectorAll("[data-vm-ssr]");t&&t.length&&Array.from(t).forEach((t=>t.parentNode&&t.parentNode.removeChild(t)))})));const r={};for(const e in P){const o=this.config[e]||{};let n=N({metainfo:P,slots:t},e,P[e],o);if(!n)continue;i(n)||(n=[n]);let s="base"!==e&&P[e].to;!s&&"to"in o&&(s=o.to),!s&&"attributesFor"in o&&(s=e);for(const{to:t,vnode:e}of n)x(r,t||s||"head",e)}if(t)for(const e in t){const o="default"===e?"head":e;if("head"!==o&&"body"!==o)continue;const n=t[e];c(n)&&x(r,o,n({metainfo:P}))}return Object.keys(r).map((t=>e.h(e.Teleport,{to:t},r[t])))}}return $.create=(t,e)=>{const r=((t,e={})=>{const r=[];e||(e={});const o={active:e,resolve:t,sources:r},n=()=>y(o);return{context:o,compute:n,addSource:(t,e,r=!1)=>{const s=v(o,t,e||{});return r&&n(),s},delSource:(t,e=!0)=>{const o=r.findIndex((e=>e===t||e[m]===t));return o>-1&&(r.splice(o,1),e&&n(),!0)}}})(((t,r,o,n,s)=>c(e)?e(t,r,o,n,s):e.resolve(t,r,o,n,s)),P);return new $(t,r,e)},t.createMetaManager=(t,e)=>$.create(t,e),t.deepestResolver=n,t.defaultConfig={body:{tag:"script",to:"body"},base:{valueAttribute:"href"},charset:{tag:"meta",nameless:!0,valueAttribute:"charset"},description:{tag:"meta"},og:{group:!0,namespacedAttribute:!0,tag:"meta",keyAttribute:"property"},twitter:{group:!0,namespacedAttribute:!0,tag:"meta"},htmlAttrs:{attributesFor:"html"},headAttrs:{attributesFor:"head"},bodyAttrs:{attributesFor:"body"}},t.getCurrentManager=O,t.resolveOption=r,t.useActiveMeta=function(){return e.inject(M)},t.useMeta=function(t,r){const o=e.getCurrentInstance()||void 0;if(!r&&o&&(r=O(o)),!r)throw new Error("No manager or current instance");e.isProxy(t)&&(e.watch(t,((t,e)=>{!function(t,e,r){for(const o in e)o in r?u(t[o])||e[o]!==r[o]&&(t[o]=e[o]):t[o]=e[o];for(const o in r)o in e||delete t[o]}(n.meta,t,e)})),t=t.value);const n=r.addMeta(t,o);return n},Object.defineProperty(t,"__esModule",{value:!0}),t}({},Vue); diff --git a/package.json b/package.json index 76137c23..422d9846 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-meta", - "version": "3.0.0-alpha.1", + "version": "3.0.0-alpha.2", "description": "Manage HTML metadata in Vue.js components with SSR support", "main": "dist/vue-meta.cjs.js", "unpkg": "dist/vue-meta.global.js",