mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-23 09:10:34 +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.
|
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)
|
## [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
|
* (c) 2021
|
||||||
* - Pim (@pimlie)
|
* - Pim (@pimlie)
|
||||||
* - All the amazing contributors
|
* - All the amazing contributors
|
||||||
@@ -612,7 +612,7 @@ function renderAttributes(context, key, data, config) {
|
|||||||
}
|
}
|
||||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||||
const slot = slots && slots[slotName];
|
const slot = slots && slots[slotName];
|
||||||
if (!slot) {
|
if (!slot || !isFunction(slot)) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
const slotScopeProps = {
|
const slotScopeProps = {
|
||||||
@@ -635,9 +635,34 @@ const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag ===
|
|||||||
const PolySymbol = (name) =>
|
const PolySymbol = (name) =>
|
||||||
// vm = vue meta
|
// vm = vue meta
|
||||||
hasSymbol
|
hasSymbol
|
||||||
? Symbol( '[vue-meta]: ' + name )
|
? Symbol('[vue-meta]: ' + name )
|
||||||
: ( '[vue-meta]: ' ) + name;
|
: ('[vue-meta]: ' ) + name;
|
||||||
const metaActiveKey = /*#__PURE__*/ PolySymbol( 'active_meta' );
|
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) {
|
function getCurrentManager(vm) {
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
@@ -649,15 +674,22 @@ function getCurrentManager(vm) {
|
|||||||
return vm.appContext.config.globalProperties.$metaManager;
|
return vm.appContext.config.globalProperties.$metaManager;
|
||||||
}
|
}
|
||||||
function useMeta(source, manager) {
|
function useMeta(source, manager) {
|
||||||
const vm = vue.getCurrentInstance();
|
const vm = vue.getCurrentInstance() || undefined;
|
||||||
if (!manager && vm) {
|
if (!manager && vm) {
|
||||||
manager = getCurrentManager(vm);
|
manager = getCurrentManager(vm);
|
||||||
}
|
}
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
// oopsydoopsy
|
|
||||||
throw new Error('No manager or current instance');
|
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() {
|
function useActiveMeta() {
|
||||||
return vue.inject(metaActiveKey);
|
return vue.inject(metaActiveKey);
|
||||||
@@ -695,83 +727,128 @@ function addVnode(teleports, to, vnodes) {
|
|||||||
}
|
}
|
||||||
teleports[to].push(...nodes);
|
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) => {
|
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||||
if (isFunction(resolver)) {
|
if (isFunction(resolver)) {
|
||||||
return resolver(options, contexts, active, key, pathSegments);
|
return resolver(options, contexts, active, key, pathSegments);
|
||||||
}
|
}
|
||||||
return resolver.resolve(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
|
// TODO: validate resolver
|
||||||
const manager = {
|
const manager = new MetaManager(config, mergedObject, resolver);
|
||||||
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]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return manager;
|
return manager;
|
||||||
}
|
};
|
||||||
|
|
||||||
// rollup doesnt like an import as it cant find the export so use require
|
// rollup doesnt like an import as it cant find the export so use require
|
||||||
const { renderToString } = require('@vue/server-renderer');
|
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
|
* (c) 2021
|
||||||
* - Pim (@pimlie)
|
* - Pim (@pimlie)
|
||||||
* - All the amazing contributors
|
* - All the amazing contributors
|
||||||
@@ -608,7 +608,7 @@ function renderAttributes(context, key, data, config) {
|
|||||||
}
|
}
|
||||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||||
const slot = slots && slots[slotName];
|
const slot = slots && slots[slotName];
|
||||||
if (!slot) {
|
if (!slot || !isFunction(slot)) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
const slotScopeProps = {
|
const slotScopeProps = {
|
||||||
@@ -631,9 +631,34 @@ const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag ===
|
|||||||
const PolySymbol = (name) =>
|
const PolySymbol = (name) =>
|
||||||
// vm = vue meta
|
// vm = vue meta
|
||||||
hasSymbol
|
hasSymbol
|
||||||
? Symbol( name)
|
? Symbol(name)
|
||||||
: ( '_vm_') + name;
|
: ('_vm_') + name;
|
||||||
const metaActiveKey = /*#__PURE__*/ PolySymbol( 'am');
|
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) {
|
function getCurrentManager(vm) {
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
@@ -645,15 +670,22 @@ function getCurrentManager(vm) {
|
|||||||
return vm.appContext.config.globalProperties.$metaManager;
|
return vm.appContext.config.globalProperties.$metaManager;
|
||||||
}
|
}
|
||||||
function useMeta(source, manager) {
|
function useMeta(source, manager) {
|
||||||
const vm = vue.getCurrentInstance();
|
const vm = vue.getCurrentInstance() || undefined;
|
||||||
if (!manager && vm) {
|
if (!manager && vm) {
|
||||||
manager = getCurrentManager(vm);
|
manager = getCurrentManager(vm);
|
||||||
}
|
}
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
// oopsydoopsy
|
|
||||||
throw new Error('No manager or current instance');
|
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() {
|
function useActiveMeta() {
|
||||||
return vue.inject(metaActiveKey);
|
return vue.inject(metaActiveKey);
|
||||||
@@ -691,83 +723,128 @@ function addVnode(teleports, to, vnodes) {
|
|||||||
}
|
}
|
||||||
teleports[to].push(...nodes);
|
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) => {
|
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||||
if (isFunction(resolver)) {
|
if (isFunction(resolver)) {
|
||||||
return resolver(options, contexts, active, key, pathSegments);
|
return resolver(options, contexts, active, key, pathSegments);
|
||||||
}
|
}
|
||||||
return resolver.resolve(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
|
// TODO: validate resolver
|
||||||
const manager = {
|
const manager = new MetaManager(config, mergedObject, resolver);
|
||||||
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]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return manager;
|
return manager;
|
||||||
}
|
};
|
||||||
|
|
||||||
// rollup doesnt like an import as it cant find the export so use require
|
// rollup doesnt like an import as it cant find the export so use require
|
||||||
const { renderToString } = require('@vue/server-renderer');
|
const { renderToString } = require('@vue/server-renderer');
|
||||||
|
|||||||
Vendored
+48
-16
@@ -1,6 +1,9 @@
|
|||||||
import { ComponentInternalInstance, App, Slots, VNode } from 'vue';
|
import { App, ComponentInternalInstance, Slots, VNode } from 'vue';
|
||||||
import { SSRContext } from '@vue/server-renderer';
|
import { SSRContext } from '@vue/server-renderer';
|
||||||
|
|
||||||
|
declare type MergeSource = {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
declare type MergedObjectValue = boolean | number | string | MergedObject | any;
|
declare type MergedObjectValue = boolean | number | string | MergedObject | any;
|
||||||
declare type MergedObject = {
|
declare type MergedObject = {
|
||||||
[key: string]: MergedObjectValue;
|
[key: string]: MergedObjectValue;
|
||||||
@@ -8,6 +11,35 @@ declare type MergedObject = {
|
|||||||
declare type PathSegments = Array<string>;
|
declare type PathSegments = Array<string>;
|
||||||
declare type ResolveContext = {};
|
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';
|
declare type MetaConfigSectionKey = 'tag' | 'to' | 'keyAttribute' | 'valueAttribute' | 'nameless' | 'group' | 'namespaced' | 'namespacedAttribute' | 'attributesFor';
|
||||||
interface MetaConfigSectionTag {
|
interface MetaConfigSectionTag {
|
||||||
@@ -40,6 +72,7 @@ declare type MetaTagsConfig = {
|
|||||||
[key in MetaTagName]: MetaTagConfig;
|
[key in MetaTagName]: MetaTagConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare type Modify<T, R> = Omit<T, keyof R> & R;
|
||||||
declare type TODO = any;
|
declare type TODO = any;
|
||||||
/**
|
/**
|
||||||
* Proxied meta source for tracking changes and updating the active meta daa
|
* Proxied meta source for tracking changes and updating the active meta daa
|
||||||
@@ -52,12 +85,14 @@ interface MetaSourceProxy extends MergedObject {
|
|||||||
declare type MetaSource = {
|
declare type MetaSource = {
|
||||||
[key: string]: TODO;
|
[key: string]: TODO;
|
||||||
};
|
};
|
||||||
|
declare type MetaGuardRemoved = () => void | Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Return value of the useMeta api
|
* Return value of the useMeta api
|
||||||
*/
|
*/
|
||||||
declare type MetaProxy = {
|
declare type MetaProxy = {
|
||||||
meta: MetaSourceProxy;
|
meta: MetaSourceProxy;
|
||||||
unmount: Function | false;
|
onRemoved: (removeGuard: MetaGuardRemoved) => void;
|
||||||
|
unmount: (ignoreGuards?: boolean) => void;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* The active/aggregated meta data currently rendered
|
* The active/aggregated meta data currently rendered
|
||||||
@@ -76,23 +111,21 @@ declare type MetaResolver = {
|
|||||||
setup?: MetaResolveSetup;
|
setup?: MetaResolveSetup;
|
||||||
resolve: ResolveMethod;
|
resolve: ResolveMethod;
|
||||||
};
|
};
|
||||||
/**
|
declare type MetaResolverSetup = Modify<MetaResolver, {
|
||||||
* The meta manager
|
setup: MetaResolveSetup;
|
||||||
*/
|
}>;
|
||||||
declare type MetaManager = {
|
|
||||||
readonly config: MetaConfig;
|
|
||||||
install(app: App): void;
|
|
||||||
addMeta(source: MetaSource, vm?: ComponentInternalInstance): MetaProxy;
|
|
||||||
render(ctx?: {
|
|
||||||
slots?: Slots;
|
|
||||||
}): Array<VNode>;
|
|
||||||
};
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
declare type MetaTeleports = {
|
declare type MetaTeleports = {
|
||||||
[key: string]: Array<VNode>;
|
[key: string]: Array<VNode>;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
interface MetaGuards {
|
||||||
|
removed: Array<MetaGuardRemoved>;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@@ -132,6 +165,7 @@ declare type MetaRendered = Array<MetaRenderedNode>;
|
|||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
interface ComponentInternalInstance {
|
interface ComponentInternalInstance {
|
||||||
$metaManager: MetaManager;
|
$metaManager: MetaManager;
|
||||||
|
$metaGuards: MetaGuards;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,8 +186,6 @@ declare namespace deepest_d {
|
|||||||
|
|
||||||
declare const defaultConfig: MetaConfig;
|
declare const defaultConfig: MetaConfig;
|
||||||
|
|
||||||
declare function createMetaManager(config: MetaConfig, resolver: MetaResolver | ResolveMethod): MetaManager;
|
|
||||||
|
|
||||||
declare type ResolveOptionReducer = (accumulator: any, context: ResolveContext) => ResolveMethod;
|
declare type ResolveOptionReducer = (accumulator: any, context: ResolveContext) => ResolveMethod;
|
||||||
declare const resolveOption: (predicament: ResolveOptionReducer) => 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 useMeta(source: MetaSource, manager?: MetaManager): MetaProxy;
|
||||||
declare function useActiveMeta(): MetaActive;
|
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
|
* (c) 2021
|
||||||
* - Pim (@pimlie)
|
* - Pim (@pimlie)
|
||||||
* - All the amazing contributors
|
* - All the amazing contributors
|
||||||
* @license MIT
|
* @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) => {
|
const resolveOption = predicament => (options, contexts) => {
|
||||||
let resolvedIndex = -1;
|
let resolvedIndex = -1;
|
||||||
@@ -600,12 +600,12 @@ function renderAttributes(context, key, data, config) {
|
|||||||
}
|
}
|
||||||
if (!cachedElements[attributesFor]) {
|
if (!cachedElements[attributesFor]) {
|
||||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||||
if ( !el) {
|
if (!el) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error('Could not find element for selector', attributesFor, ', won\'t render attributes');
|
console.error('Could not find element for selector', attributesFor, ', won\'t render attributes');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( el2) {
|
if (el2) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn('Found multiple elements for selector', attributesFor);
|
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) {
|
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||||
const slot = slots && slots[slotName];
|
const slot = slots && slots[slotName];
|
||||||
if (!slot) {
|
if (!slot || !isFunction(slot)) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
const slotScopeProps = {
|
const slotScopeProps = {
|
||||||
@@ -652,9 +652,34 @@ const hasSymbol = typeof Symbol === 'function' && typeof Symbol.toStringTag ===
|
|||||||
const PolySymbol = (name) =>
|
const PolySymbol = (name) =>
|
||||||
// vm = vue meta
|
// vm = vue meta
|
||||||
hasSymbol
|
hasSymbol
|
||||||
? Symbol( '[vue-meta]: ' + name )
|
? Symbol('[vue-meta]: ' + name )
|
||||||
: ( '[vue-meta]: ' ) + name;
|
: ('[vue-meta]: ' ) + name;
|
||||||
const metaActiveKey = /*#__PURE__*/ PolySymbol( 'active_meta' );
|
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) {
|
function getCurrentManager(vm) {
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
@@ -666,15 +691,22 @@ function getCurrentManager(vm) {
|
|||||||
return vm.appContext.config.globalProperties.$metaManager;
|
return vm.appContext.config.globalProperties.$metaManager;
|
||||||
}
|
}
|
||||||
function useMeta(source, manager) {
|
function useMeta(source, manager) {
|
||||||
const vm = getCurrentInstance();
|
const vm = getCurrentInstance() || undefined;
|
||||||
if (!manager && vm) {
|
if (!manager && vm) {
|
||||||
manager = getCurrentManager(vm);
|
manager = getCurrentManager(vm);
|
||||||
}
|
}
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
// oopsydoopsy
|
|
||||||
throw new Error('No manager or current instance');
|
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() {
|
function useActiveMeta() {
|
||||||
return inject(metaActiveKey);
|
return inject(metaActiveKey);
|
||||||
@@ -713,96 +745,141 @@ function addVnode(teleports, to, vnodes) {
|
|||||||
}
|
}
|
||||||
teleports[to].push(...nodes);
|
teleports[to].push(...nodes);
|
||||||
}
|
}
|
||||||
function createMetaManager(config, resolver) {
|
const createMetaManager = (config, resolver) => MetaManager.create(config, resolver);
|
||||||
let cleanedUpSsr = false;
|
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) => {
|
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||||
if (isFunction(resolver)) {
|
if (isFunction(resolver)) {
|
||||||
return resolver(options, contexts, active, key, pathSegments);
|
return resolver(options, contexts, active, key, pathSegments);
|
||||||
}
|
}
|
||||||
return resolver.resolve(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
|
// TODO: validate resolver
|
||||||
const manager = {
|
const manager = new MetaManager(config, mergedObject, resolver);
|
||||||
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]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return manager;
|
return manager;
|
||||||
}
|
};
|
||||||
|
|
||||||
export { createMetaManager, deepest as deepestResolver, defaultConfig, getCurrentManager, resolveOption, useActiveMeta, useMeta };
|
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
|
* (c) 2021
|
||||||
* - Pim (@pimlie)
|
* - Pim (@pimlie)
|
||||||
* - All the amazing contributors
|
* - All the amazing contributors
|
||||||
* @license MIT
|
* @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) => {
|
const resolveOption = predicament => (options, contexts) => {
|
||||||
let resolvedIndex = -1;
|
let resolvedIndex = -1;
|
||||||
@@ -608,7 +608,7 @@ function renderAttributes(context, key, data, config) {
|
|||||||
}
|
}
|
||||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||||
const slot = slots && slots[slotName];
|
const slot = slots && slots[slotName];
|
||||||
if (!slot) {
|
if (!slot || !isFunction(slot)) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
const slotScopeProps = {
|
const slotScopeProps = {
|
||||||
@@ -633,7 +633,32 @@ const PolySymbol = (name) =>
|
|||||||
hasSymbol
|
hasSymbol
|
||||||
? Symbol((process.env.NODE_ENV !== 'production') ? '[vue-meta]: ' + name : name)
|
? Symbol((process.env.NODE_ENV !== 'production') ? '[vue-meta]: ' + name : name)
|
||||||
: ((process.env.NODE_ENV !== 'production') ? '[vue-meta]: ' : '_vm_') + 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) {
|
function getCurrentManager(vm) {
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
@@ -645,15 +670,22 @@ function getCurrentManager(vm) {
|
|||||||
return vm.appContext.config.globalProperties.$metaManager;
|
return vm.appContext.config.globalProperties.$metaManager;
|
||||||
}
|
}
|
||||||
function useMeta(source, manager) {
|
function useMeta(source, manager) {
|
||||||
const vm = getCurrentInstance();
|
const vm = getCurrentInstance() || undefined;
|
||||||
if (!manager && vm) {
|
if (!manager && vm) {
|
||||||
manager = getCurrentManager(vm);
|
manager = getCurrentManager(vm);
|
||||||
}
|
}
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
// oopsydoopsy
|
|
||||||
throw new Error('No manager or current instance');
|
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() {
|
function useActiveMeta() {
|
||||||
return inject(metaActiveKey);
|
return inject(metaActiveKey);
|
||||||
@@ -691,83 +723,128 @@ function addVnode(teleports, to, vnodes) {
|
|||||||
}
|
}
|
||||||
teleports[to].push(...nodes);
|
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) => {
|
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||||
if (isFunction(resolver)) {
|
if (isFunction(resolver)) {
|
||||||
return resolver(options, contexts, active, key, pathSegments);
|
return resolver(options, contexts, active, key, pathSegments);
|
||||||
}
|
}
|
||||||
return resolver.resolve(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
|
// TODO: validate resolver
|
||||||
const manager = {
|
const manager = new MetaManager(config, mergedObject, resolver);
|
||||||
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]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return manager;
|
return manager;
|
||||||
}
|
};
|
||||||
|
|
||||||
// rollup doesnt like an import as it cant find the export so use require
|
// rollup doesnt like an import as it cant find the export so use require
|
||||||
const { renderToString } = require('@vue/server-renderer');
|
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
|
* (c) 2021
|
||||||
* - Pim (@pimlie)
|
* - Pim (@pimlie)
|
||||||
* - All the amazing contributors
|
* - All the amazing contributors
|
||||||
@@ -601,12 +601,12 @@ var VueMeta = (function (exports, vue) {
|
|||||||
}
|
}
|
||||||
if (!cachedElements[attributesFor]) {
|
if (!cachedElements[attributesFor]) {
|
||||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||||
if ( !el) {
|
if (!el) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error('Could not find element for selector', attributesFor, ', won\'t render attributes');
|
console.error('Could not find element for selector', attributesFor, ', won\'t render attributes');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( el2) {
|
if (el2) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn('Found multiple elements for selector', attributesFor);
|
console.warn('Found multiple elements for selector', attributesFor);
|
||||||
}
|
}
|
||||||
@@ -630,7 +630,7 @@ var VueMeta = (function (exports, vue) {
|
|||||||
}
|
}
|
||||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||||
const slot = slots && slots[slotName];
|
const slot = slots && slots[slotName];
|
||||||
if (!slot) {
|
if (!slot || !isFunction(slot)) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
const slotScopeProps = {
|
const slotScopeProps = {
|
||||||
@@ -653,9 +653,34 @@ var VueMeta = (function (exports, vue) {
|
|||||||
const PolySymbol = (name) =>
|
const PolySymbol = (name) =>
|
||||||
// vm = vue meta
|
// vm = vue meta
|
||||||
hasSymbol
|
hasSymbol
|
||||||
? Symbol( '[vue-meta]: ' + name )
|
? Symbol('[vue-meta]: ' + name )
|
||||||
: ( '[vue-meta]: ' ) + name;
|
: ('[vue-meta]: ' ) + name;
|
||||||
const metaActiveKey = /*#__PURE__*/ PolySymbol( 'active_meta' );
|
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) {
|
function getCurrentManager(vm) {
|
||||||
if (!vm) {
|
if (!vm) {
|
||||||
@@ -667,15 +692,22 @@ var VueMeta = (function (exports, vue) {
|
|||||||
return vm.appContext.config.globalProperties.$metaManager;
|
return vm.appContext.config.globalProperties.$metaManager;
|
||||||
}
|
}
|
||||||
function useMeta(source, manager) {
|
function useMeta(source, manager) {
|
||||||
const vm = vue.getCurrentInstance();
|
const vm = vue.getCurrentInstance() || undefined;
|
||||||
if (!manager && vm) {
|
if (!manager && vm) {
|
||||||
manager = getCurrentManager(vm);
|
manager = getCurrentManager(vm);
|
||||||
}
|
}
|
||||||
if (!manager) {
|
if (!manager) {
|
||||||
// oopsydoopsy
|
|
||||||
throw new Error('No manager or current instance');
|
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() {
|
function useActiveMeta() {
|
||||||
return vue.inject(metaActiveKey);
|
return vue.inject(metaActiveKey);
|
||||||
@@ -714,97 +746,142 @@ var VueMeta = (function (exports, vue) {
|
|||||||
}
|
}
|
||||||
teleports[to].push(...nodes);
|
teleports[to].push(...nodes);
|
||||||
}
|
}
|
||||||
function createMetaManager(config, resolver) {
|
const createMetaManager = (config, resolver) => MetaManager.create(config, resolver);
|
||||||
let cleanedUpSsr = false;
|
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) => {
|
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||||
if (isFunction(resolver)) {
|
if (isFunction(resolver)) {
|
||||||
return resolver(options, contexts, active, key, pathSegments);
|
return resolver(options, contexts, active, key, pathSegments);
|
||||||
}
|
}
|
||||||
return resolver.resolve(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
|
// TODO: validate resolver
|
||||||
const manager = {
|
const manager = new MetaManager(config, mergedObject, resolver);
|
||||||
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]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return manager;
|
return manager;
|
||||||
}
|
};
|
||||||
|
|
||||||
exports.createMetaManager = createMetaManager;
|
exports.createMetaManager = createMetaManager;
|
||||||
exports.deepestResolver = deepest;
|
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",
|
"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",
|
"description": "Manage HTML metadata in Vue.js components with SSR support",
|
||||||
"main": "dist/vue-meta.cjs.js",
|
"main": "dist/vue-meta.cjs.js",
|
||||||
"unpkg": "dist/vue-meta.global.js",
|
"unpkg": "dist/vue-meta.global.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user