mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-07 17:52:24 +03:00
chore(release): 3.0.0-alpha.2
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
Vendored
+154
-77
@@ -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 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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
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 { addSource, delSource } = createMergedObject(resolve, active);
|
||||
const mergedObject = 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) {
|
||||
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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return vue.h(vue.Teleport, { to }, teleports[to]);
|
||||
});
|
||||
}
|
||||
};
|
||||
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');
|
||||
|
||||
Vendored
+154
-77
@@ -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 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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
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 { addSource, delSource } = createMergedObject(resolve, active);
|
||||
const mergedObject = 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) {
|
||||
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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return vue.h(vue.Teleport, { to }, teleports[to]);
|
||||
});
|
||||
}
|
||||
};
|
||||
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');
|
||||
|
||||
Vendored
+49
-17
@@ -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<string>;
|
||||
declare type ResolveContext = {};
|
||||
declare type ResolveMethod = (options: Array<any>, contexts: Array<ResolveContext>, active: MergedObjectValue, key: string | number | symbol, pathSegments: PathSegments) => MergedObjectValue;
|
||||
declare type ResolveMethod = (options: Array<any>, contexts: Array<ResolveContext>, active: MergedObjectValue, key: string | number | symbol, pathSegments: PathSegments) => MergedObjectValue;
|
||||
declare type MergeContext = {
|
||||
resolve: ResolveMethod;
|
||||
active: MergedObject;
|
||||
sources: Array<MergeSource>;
|
||||
};
|
||||
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<T, R> = Omit<T, keyof R> & 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<void>;
|
||||
/**
|
||||
* 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<VNode>;
|
||||
};
|
||||
declare type MetaResolverSetup = Modify<MetaResolver, {
|
||||
setup: MetaResolveSetup;
|
||||
}>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
declare type MetaTeleports = {
|
||||
[key: string]: Array<VNode>;
|
||||
};
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface MetaGuards {
|
||||
removed: Array<MetaGuardRemoved>;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -132,6 +165,7 @@ declare type MetaRendered = Array<MetaRenderedNode>;
|
||||
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 };
|
||||
|
||||
Vendored
+171
-94
@@ -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 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;
|
||||
}
|
||||
}
|
||||
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 (!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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
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 { addSource, delSource } = createMergedObject(resolve, active);
|
||||
const mergedObject = 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 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;
|
||||
}
|
||||
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]);
|
||||
});
|
||||
}
|
||||
};
|
||||
const manager = new MetaManager(config, mergedObject, resolver);
|
||||
return manager;
|
||||
}
|
||||
};
|
||||
|
||||
export { createMetaManager, deepest as deepestResolver, defaultConfig, getCurrentManager, resolveOption, useActiveMeta, useMeta };
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+153
-76
@@ -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 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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
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 { addSource, delSource } = createMergedObject(resolve, active);
|
||||
const mergedObject = 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) {
|
||||
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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return h(Teleport, { to }, teleports[to]);
|
||||
});
|
||||
}
|
||||
};
|
||||
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');
|
||||
|
||||
Vendored
+170
-93
@@ -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 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;
|
||||
}
|
||||
}
|
||||
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 (!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]);
|
||||
});
|
||||
}
|
||||
}
|
||||
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 { addSource, delSource } = createMergedObject(resolve, active);
|
||||
const mergedObject = 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 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;
|
||||
}
|
||||
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]);
|
||||
});
|
||||
}
|
||||
};
|
||||
const manager = new MetaManager(config, mergedObject, resolver);
|
||||
return manager;
|
||||
}
|
||||
};
|
||||
|
||||
exports.createMetaManager = createMetaManager;
|
||||
exports.deepestResolver = deepest;
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+1
-1
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user