mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-05-17 05:09:38 +03:00
chore(release): 3.0.0-alpha.6
This commit is contained in:
@@ -2,6 +2,22 @@
|
||||
|
||||
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.6](https://github.com/nuxt/vue-meta/compare/v3.0.0-alpha.5...v3.0.0-alpha.6) (2021-05-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for recomputing nested paths ([8c0fb63](https://github.com/nuxt/vue-meta/commit/8c0fb63f123151395d0c7afcffe2af1869a71e41))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* also delete previous values if computed value was faly ([449bb20](https://github.com/nuxt/vue-meta/commit/449bb20e6f42d0ddb837c2ba8e7d8197e887f205))
|
||||
* better ssr support ([1d84787](https://github.com/nuxt/vue-meta/commit/1d847870e949ea7dac710153361185cc75ed704a))
|
||||
* make types of deepest resolver compatible ([d8651be](https://github.com/nuxt/vue-meta/commit/d8651be35bd288f5e6016121b6ee384ba190afd9))
|
||||
* recompute all props when assigning an object to a proxy key ([cae8e35](https://github.com/nuxt/vue-meta/commit/cae8e35340d17b72c6da4283d0043274c5dc53d3))
|
||||
* rollup config, esm-bundler builds are also browser builds ([d7be9a4](https://github.com/nuxt/vue-meta/commit/d7be9a43e517910357e6232e0572a05b78e5ab5d))
|
||||
|
||||
## [3.0.0-alpha.5](https://github.com/nuxt/vue-meta/compare/v3.0.0-alpha.4...v3.0.0-alpha.5) (2021-05-03)
|
||||
|
||||
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import { App } from 'vue';
|
||||
import { SSRContext } from '@vue/server-renderer';
|
||||
|
||||
declare function renderMetaToString(app: App, ctx?: SSRContext): Promise<SSRContext>;
|
||||
|
||||
export { renderMetaToString };
|
||||
Vendored
+120
-36
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.5
|
||||
* vue-meta v3.0.0-alpha.6
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
@@ -27,7 +27,7 @@ const resolveOption = (predicament, initialValue) => (options, contexts) => {
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
const setup = (context) => {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
@@ -39,7 +39,7 @@ function setup(context) {
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
};
|
||||
const resolve = resolveOption((currentValue, context) => {
|
||||
const { depth } = context;
|
||||
if (!currentValue || depth > currentValue) {
|
||||
@@ -203,7 +203,7 @@ function clone(v) {
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
if (row && key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
@@ -230,13 +230,23 @@ const allKeys = (source, ...sources) => {
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
const recompute = (context, path = [], target, sources) => {
|
||||
const setTargetAndSources = !target && !sources;
|
||||
if (setTargetAndSources) {
|
||||
({ active: target, sources } = context);
|
||||
if (path.length) {
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const seg = path[i];
|
||||
if (!target || !target[seg]) {
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`recompute: segment ${seg} not found on target`, path, target);
|
||||
}
|
||||
return;
|
||||
}
|
||||
target = target[seg];
|
||||
sources = sources.map(source => source[seg]).filter(Boolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
@@ -253,7 +263,15 @@ const recompute = (context, sources, target, path = []) => {
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
// @ts-ignore
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
let isObject = false;
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
const source = sources[i];
|
||||
if (source && key in source && source[key] !== undefined) {
|
||||
isObject = isPlainObject(source[key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isObject) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
@@ -264,7 +282,7 @@ const recompute = (context, sources, target, path = []) => {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
recompute(context, [...path, key], target[key], keySources);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
@@ -278,7 +296,6 @@ const recompute = (context, sources, target, path = []) => {
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
@@ -309,6 +326,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
// Also return a merge proxy for nested objects
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
@@ -346,6 +364,12 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
else if (isPlainObject(value)) {
|
||||
// if an object was assigned to this key make sure to recompute all
|
||||
// of its individual properies
|
||||
recompute(context, pathSegments);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -357,13 +381,13 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// the sourceProxies and not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -376,7 +400,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
@@ -385,7 +409,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
// @ts-ignore
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
proxies = proxies.map(proxy => proxy && proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
@@ -395,7 +419,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
if (proxies.some(proxy => proxy && (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -409,7 +433,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -457,6 +481,7 @@ const createMergedObject = (resolve, active) => {
|
||||
};
|
||||
};
|
||||
|
||||
const cachedElements = {};
|
||||
function renderMeta(context, key, data, config) {
|
||||
// console.info('renderMeta', key, data, config)
|
||||
if ('attributesFor' in config) {
|
||||
@@ -585,7 +610,7 @@ function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
// console.log(data, attributes, config)
|
||||
if (isRaw && content) {
|
||||
attributes.innerHTML = content;
|
||||
}
|
||||
@@ -599,10 +624,10 @@ function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
function renderAttributes(context, key, data, config) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
if (!attributesFor) {
|
||||
if (!attributesFor || !data) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
if (context.isSSR) {
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
@@ -610,6 +635,37 @@ function renderAttributes(context, key, data, config) {
|
||||
vnode: vue.h(`ssr-${attributesFor}`, data)
|
||||
};
|
||||
}
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||
if (!el) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Could not find element for selector', attributesFor, ', won\'t render attributes');
|
||||
return;
|
||||
}
|
||||
if (el2) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Found multiple elements for selector', attributesFor);
|
||||
}
|
||||
cachedElements[attributesFor] = {
|
||||
el,
|
||||
attrs: []
|
||||
};
|
||||
}
|
||||
const { el, attrs } = cachedElements[attributesFor];
|
||||
for (const attr in data) {
|
||||
let content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
if (isArray(content)) {
|
||||
content = content.join(',');
|
||||
}
|
||||
el.setAttribute(attr, content || '');
|
||||
if (!attrs.includes(attr)) {
|
||||
attrs.push(attr);
|
||||
}
|
||||
}
|
||||
const attrsToRemove = attrs.filter(attr => !data[attr]);
|
||||
for (const attr of attrsToRemove) {
|
||||
el.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||
const slot = slots && slots[slotName];
|
||||
@@ -658,7 +714,7 @@ function applyDifference(target, newSource, oldSource) {
|
||||
}
|
||||
}
|
||||
for (const key in oldSource) {
|
||||
if (!(key in newSource)) {
|
||||
if (!newSource || !(key in newSource)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
@@ -711,9 +767,18 @@ const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = vue.reactive({});
|
||||
function addVnode(teleports, to, vnodes) {
|
||||
function addVnode(isSSR, teleports, to, vnodes) {
|
||||
const nodes = (isArray(vnodes) ? vnodes : [vnodes]);
|
||||
if (!to.endsWith('Attrs')) {
|
||||
if (!isSSR) {
|
||||
// Comments shouldnt have any use on the client as they are not reactive anyway
|
||||
nodes.forEach((vnode, idx) => {
|
||||
if (vnode.type === vue.Comment) {
|
||||
nodes.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
// only add ssrAttribute's for real meta tags
|
||||
}
|
||||
else if (!to.endsWith('Attrs')) {
|
||||
nodes.forEach((vnode) => {
|
||||
if (!vnode.props) {
|
||||
vnode.props = {};
|
||||
@@ -726,10 +791,12 @@ function addVnode(teleports, to, vnodes) {
|
||||
}
|
||||
teleports[to].push(...nodes);
|
||||
}
|
||||
const createMetaManager = (config, resolver) => MetaManager.create(config || defaultConfig, resolver || defaultResolver);
|
||||
const createMetaManager = (isSSR = false, config, resolver) => MetaManager.create(isSSR, config || defaultConfig, resolver || defaultResolver);
|
||||
class MetaManager {
|
||||
constructor(config, target, resolver) {
|
||||
constructor(isSSR, config, target, resolver) {
|
||||
this.isSSR = false;
|
||||
this.ssrCleanedUp = false;
|
||||
this.isSSR = isSSR;
|
||||
this.config = config;
|
||||
this.target = target;
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
@@ -749,8 +816,9 @@ class MetaManager {
|
||||
removed: []
|
||||
});
|
||||
const resolveContext = { vm };
|
||||
if (this.resolver) {
|
||||
this.resolver.setup(resolveContext);
|
||||
const { resolver } = this;
|
||||
if (resolver && resolver.setup) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute (once)
|
||||
const meta = this.target.addSource(metadata, resolveContext, true);
|
||||
@@ -797,10 +865,25 @@ class MetaManager {
|
||||
}
|
||||
}
|
||||
render({ slots } = {}) {
|
||||
// TODO: clean this method
|
||||
const { isSSR } = this;
|
||||
// cleanup ssr tags if not yet done
|
||||
if (!isSSR && !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) {
|
||||
ssrTags.forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
}
|
||||
}, { once: true });
|
||||
}
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
let renderedNodes = renderMeta({ isSSR, metainfo: active, slots }, key, active[key], config);
|
||||
if (!renderedNodes) {
|
||||
continue;
|
||||
}
|
||||
@@ -815,7 +898,7 @@ class MetaManager {
|
||||
defaultTo = key;
|
||||
}
|
||||
for (const { to, vnode } of renderedNodes) {
|
||||
addVnode(teleports, to || defaultTo || 'head', vnode);
|
||||
addVnode(this.isSSR, teleports, to || defaultTo || 'head', vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
@@ -827,16 +910,17 @@ class MetaManager {
|
||||
}
|
||||
const slot = slots[slotName];
|
||||
if (isFunction(slot)) {
|
||||
addVnode(teleports, tagName, slot({ metainfo: active }));
|
||||
addVnode(this.isSSR, teleports, tagName, slot({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return vue.h(vue.Teleport, { to }, teleports[to]);
|
||||
const teleport = teleports[to];
|
||||
return vue.h(vue.Teleport, { to }, teleport);
|
||||
});
|
||||
}
|
||||
}
|
||||
MetaManager.create = (config, resolver) => {
|
||||
MetaManager.create = (isSSR, config, resolver) => {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
@@ -845,7 +929,7 @@ MetaManager.create = (config, resolver) => {
|
||||
};
|
||||
const mergedObject = createMergedObject(resolve, active);
|
||||
// TODO: validate resolver
|
||||
const manager = new MetaManager(config, mergedObject, resolver);
|
||||
const manager = new MetaManager(isSSR, config, mergedObject, resolver);
|
||||
return manager;
|
||||
};
|
||||
|
||||
|
||||
Vendored
+107
-36
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.5
|
||||
* vue-meta v3.0.0-alpha.6
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
@@ -27,7 +27,7 @@ const resolveOption = (predicament, initialValue) => (options, contexts) => {
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
const setup = (context) => {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
@@ -39,7 +39,7 @@ function setup(context) {
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
};
|
||||
const resolve = resolveOption((currentValue, context) => {
|
||||
const { depth } = context;
|
||||
if (!currentValue || depth > currentValue) {
|
||||
@@ -200,7 +200,7 @@ function clone(v) {
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
if (row && key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
@@ -227,13 +227,19 @@ const allKeys = (source, ...sources) => {
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
const recompute = (context, path = [], target, sources) => {
|
||||
const setTargetAndSources = !target && !sources;
|
||||
if (setTargetAndSources) {
|
||||
({ active: target, sources } = context);
|
||||
if (path.length) {
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const seg = path[i];
|
||||
if (!target || !target[seg]) {
|
||||
return;
|
||||
}
|
||||
target = target[seg];
|
||||
sources = sources.map(source => source[seg]).filter(Boolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
@@ -250,7 +256,15 @@ const recompute = (context, sources, target, path = []) => {
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
// @ts-ignore
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
let isObject = false;
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
const source = sources[i];
|
||||
if (source && key in source && source[key] !== undefined) {
|
||||
isObject = isPlainObject(source[key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isObject) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
@@ -261,7 +275,7 @@ const recompute = (context, sources, target, path = []) => {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
recompute(context, [...path, key], target[key], keySources);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
@@ -275,7 +289,6 @@ const recompute = (context, sources, target, path = []) => {
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
@@ -306,6 +319,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
// Also return a merge proxy for nested objects
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
@@ -343,6 +357,12 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
else if (isPlainObject(value)) {
|
||||
// if an object was assigned to this key make sure to recompute all
|
||||
// of its individual properies
|
||||
recompute(context, pathSegments);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -354,13 +374,13 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// the sourceProxies and not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -373,7 +393,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
@@ -382,7 +402,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
// @ts-ignore
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
proxies = proxies.map(proxy => proxy && proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
@@ -392,7 +412,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
if (proxies.some(proxy => proxy && (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -406,7 +426,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -454,6 +474,7 @@ const createMergedObject = (resolve, active) => {
|
||||
};
|
||||
};
|
||||
|
||||
const cachedElements = {};
|
||||
function renderMeta(context, key, data, config) {
|
||||
// console.info('renderMeta', key, data, config)
|
||||
if ('attributesFor' in config) {
|
||||
@@ -578,7 +599,7 @@ function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
// console.log(data, attributes, config)
|
||||
if (isRaw && content) {
|
||||
attributes.innerHTML = content;
|
||||
}
|
||||
@@ -592,10 +613,10 @@ function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
function renderAttributes(context, key, data, config) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
if (!attributesFor) {
|
||||
if (!attributesFor || !data) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
if (context.isSSR) {
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
@@ -603,6 +624,28 @@ function renderAttributes(context, key, data, config) {
|
||||
vnode: vue.h(`ssr-${attributesFor}`, data)
|
||||
};
|
||||
}
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||
cachedElements[attributesFor] = {
|
||||
el,
|
||||
attrs: []
|
||||
};
|
||||
}
|
||||
const { el, attrs } = cachedElements[attributesFor];
|
||||
for (const attr in data) {
|
||||
let content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
if (isArray(content)) {
|
||||
content = content.join(',');
|
||||
}
|
||||
el.setAttribute(attr, content || '');
|
||||
if (!attrs.includes(attr)) {
|
||||
attrs.push(attr);
|
||||
}
|
||||
}
|
||||
const attrsToRemove = attrs.filter(attr => !data[attr]);
|
||||
for (const attr of attrsToRemove) {
|
||||
el.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||
const slot = slots && slots[slotName];
|
||||
@@ -651,7 +694,7 @@ function applyDifference(target, newSource, oldSource) {
|
||||
}
|
||||
}
|
||||
for (const key in oldSource) {
|
||||
if (!(key in newSource)) {
|
||||
if (!newSource || !(key in newSource)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
@@ -704,9 +747,18 @@ const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = vue.reactive({});
|
||||
function addVnode(teleports, to, vnodes) {
|
||||
function addVnode(isSSR, teleports, to, vnodes) {
|
||||
const nodes = (isArray(vnodes) ? vnodes : [vnodes]);
|
||||
if (!to.endsWith('Attrs')) {
|
||||
if (!isSSR) {
|
||||
// Comments shouldnt have any use on the client as they are not reactive anyway
|
||||
nodes.forEach((vnode, idx) => {
|
||||
if (vnode.type === vue.Comment) {
|
||||
nodes.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
// only add ssrAttribute's for real meta tags
|
||||
}
|
||||
else if (!to.endsWith('Attrs')) {
|
||||
nodes.forEach((vnode) => {
|
||||
if (!vnode.props) {
|
||||
vnode.props = {};
|
||||
@@ -719,10 +771,12 @@ function addVnode(teleports, to, vnodes) {
|
||||
}
|
||||
teleports[to].push(...nodes);
|
||||
}
|
||||
const createMetaManager = (config, resolver) => MetaManager.create(config || defaultConfig, resolver || defaultResolver);
|
||||
const createMetaManager = (isSSR = false, config, resolver) => MetaManager.create(isSSR, config || defaultConfig, resolver || defaultResolver);
|
||||
class MetaManager {
|
||||
constructor(config, target, resolver) {
|
||||
constructor(isSSR, config, target, resolver) {
|
||||
this.isSSR = false;
|
||||
this.ssrCleanedUp = false;
|
||||
this.isSSR = isSSR;
|
||||
this.config = config;
|
||||
this.target = target;
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
@@ -742,8 +796,9 @@ class MetaManager {
|
||||
removed: []
|
||||
});
|
||||
const resolveContext = { vm };
|
||||
if (this.resolver) {
|
||||
this.resolver.setup(resolveContext);
|
||||
const { resolver } = this;
|
||||
if (resolver && resolver.setup) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute (once)
|
||||
const meta = this.target.addSource(metadata, resolveContext, true);
|
||||
@@ -790,10 +845,25 @@ class MetaManager {
|
||||
}
|
||||
}
|
||||
render({ slots } = {}) {
|
||||
// TODO: clean this method
|
||||
const { isSSR } = this;
|
||||
// cleanup ssr tags if not yet done
|
||||
if (!isSSR && !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) {
|
||||
ssrTags.forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
}
|
||||
}, { once: true });
|
||||
}
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
let renderedNodes = renderMeta({ isSSR, metainfo: active, slots }, key, active[key], config);
|
||||
if (!renderedNodes) {
|
||||
continue;
|
||||
}
|
||||
@@ -808,7 +878,7 @@ class MetaManager {
|
||||
defaultTo = key;
|
||||
}
|
||||
for (const { to, vnode } of renderedNodes) {
|
||||
addVnode(teleports, to || defaultTo || 'head', vnode);
|
||||
addVnode(this.isSSR, teleports, to || defaultTo || 'head', vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
@@ -820,16 +890,17 @@ class MetaManager {
|
||||
}
|
||||
const slot = slots[slotName];
|
||||
if (isFunction(slot)) {
|
||||
addVnode(teleports, tagName, slot({ metainfo: active }));
|
||||
addVnode(this.isSSR, teleports, tagName, slot({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return vue.h(vue.Teleport, { to }, teleports[to]);
|
||||
const teleport = teleports[to];
|
||||
return vue.h(vue.Teleport, { to }, teleport);
|
||||
});
|
||||
}
|
||||
}
|
||||
MetaManager.create = (config, resolver) => {
|
||||
MetaManager.create = (isSSR, config, resolver) => {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
@@ -838,7 +909,7 @@ MetaManager.create = (config, resolver) => {
|
||||
};
|
||||
const mergedObject = createMergedObject(resolve, active);
|
||||
// TODO: validate resolver
|
||||
const manager = new MetaManager(config, mergedObject, resolver);
|
||||
const manager = new MetaManager(isSSR, config, mergedObject, resolver);
|
||||
return manager;
|
||||
};
|
||||
|
||||
|
||||
Vendored
+22
-15
@@ -1,3 +1,13 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.6
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/// <reference path="ssr.d.ts" />
|
||||
|
||||
import { App, ComponentInternalInstance, Slots, VNode } from 'vue';
|
||||
|
||||
declare const IS_PROXY: unique symbol;
|
||||
@@ -35,15 +45,16 @@ declare type MergedObjectBuilder<T> = {
|
||||
delSource: (sourceOrProxy: T | MergeSource<T>, recompute?: boolean) => boolean;
|
||||
};
|
||||
|
||||
declare type createMetaManagerMethod = (config: MetaConfig, resolver: MetaResolver | ResolveMethod) => MetaManager;
|
||||
declare const createMetaManager: (config?: MetaConfig | undefined, resolver?: MetaResolver | undefined) => MetaManager;
|
||||
declare type CreateMetaManagerMethod = (isSSR: boolean, config: MetaConfig, resolver: MetaResolver | ResolveMethod) => MetaManager;
|
||||
declare const createMetaManager: (isSSR?: boolean, config?: MetaConfig | undefined, resolver?: MetaResolver | undefined) => MetaManager;
|
||||
declare class MetaManager {
|
||||
isSSR: boolean;
|
||||
config: MetaConfig;
|
||||
target: MergedObjectBuilder<MetaSource>;
|
||||
resolver?: MetaResolverSetup;
|
||||
resolver?: MetaResolver;
|
||||
ssrCleanedUp: boolean;
|
||||
constructor(config: MetaConfig, target: MergedObjectBuilder<MetaSource>, resolver: MetaResolver | ResolveMethod);
|
||||
static create: createMetaManagerMethod;
|
||||
constructor(isSSR: boolean, config: MetaConfig, target: MergedObjectBuilder<MetaSource>, resolver: MetaResolver | ResolveMethod);
|
||||
static create: CreateMetaManagerMethod;
|
||||
install(app: App): void;
|
||||
addMeta(metadata: MetaSource, vm?: ComponentInternalInstance): MetaProxy;
|
||||
private unmount;
|
||||
@@ -116,16 +127,14 @@ interface MetaActive {
|
||||
* Context passed to the meta resolvers
|
||||
*/
|
||||
declare type MetaResolveContext = ResolveContext & {
|
||||
vm: ComponentInternalInstance | undefined;
|
||||
vm?: ComponentInternalInstance;
|
||||
};
|
||||
declare type MetaResolveSetup = (context: MetaResolveContext) => void;
|
||||
declare type MetaResolver = {
|
||||
setup?: MetaResolveSetup;
|
||||
resolve: ResolveMethod;
|
||||
};
|
||||
declare type MetaResolverSetup = Modify<MetaResolver, {
|
||||
setup: MetaResolveSetup;
|
||||
}>;
|
||||
declare type MetaResolverSetup = Required<MetaResolver>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -142,8 +151,9 @@ interface MetaGuards {
|
||||
* @internal
|
||||
*/
|
||||
interface MetaRenderContext {
|
||||
slots?: Slots;
|
||||
isSSR: boolean;
|
||||
metainfo: MetaActive;
|
||||
slots?: Slots;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
@@ -181,11 +191,8 @@ declare module '@vue/runtime-core' {
|
||||
}
|
||||
}
|
||||
|
||||
declare type MergeResolveContextDeepest = MetaResolveContext & {
|
||||
depth: number;
|
||||
};
|
||||
declare function setup(context: MergeResolveContextDeepest): void;
|
||||
declare const resolve: ResolveMethod<any, MergeResolveContextDeepest>;
|
||||
declare const setup: MetaResolveSetup;
|
||||
declare const resolve: ResolveMethod<any, ResolveContext>;
|
||||
|
||||
declare const deepest_d_setup: typeof setup;
|
||||
declare const deepest_d_resolve: typeof resolve;
|
||||
|
||||
Vendored
+87
-39
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.5
|
||||
* vue-meta v3.0.0-alpha.6
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
@@ -23,7 +23,7 @@ const resolveOption = (predicament, initialValue) => (options, contexts) => {
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
const setup = (context) => {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
@@ -35,7 +35,7 @@ function setup(context) {
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
};
|
||||
const resolve = resolveOption((currentValue, context) => {
|
||||
const { depth } = context;
|
||||
if (!currentValue || depth > currentValue) {
|
||||
@@ -199,7 +199,7 @@ function clone(v) {
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
if (row && key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
@@ -226,13 +226,23 @@ const allKeys = (source, ...sources) => {
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
const recompute = (context, path = [], target, sources) => {
|
||||
const setTargetAndSources = !target && !sources;
|
||||
if (setTargetAndSources) {
|
||||
({ active: target, sources } = context);
|
||||
if (path.length) {
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const seg = path[i];
|
||||
if (!target || !target[seg]) {
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`recompute: segment ${seg} not found on target`, path, target);
|
||||
}
|
||||
return;
|
||||
}
|
||||
target = target[seg];
|
||||
sources = sources.map(source => source[seg]).filter(Boolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
@@ -249,7 +259,15 @@ const recompute = (context, sources, target, path = []) => {
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
// @ts-ignore
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
let isObject = false;
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
const source = sources[i];
|
||||
if (source && key in source && source[key] !== undefined) {
|
||||
isObject = isPlainObject(source[key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isObject) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
@@ -260,7 +278,7 @@ const recompute = (context, sources, target, path = []) => {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
recompute(context, [...path, key], target[key], keySources);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
@@ -274,7 +292,6 @@ const recompute = (context, sources, target, path = []) => {
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
@@ -305,6 +322,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
// Also return a merge proxy for nested objects
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
@@ -342,6 +360,12 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
else if (isPlainObject(value)) {
|
||||
// if an object was assigned to this key make sure to recompute all
|
||||
// of its individual properies
|
||||
recompute(context, pathSegments);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -353,13 +377,13 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// the sourceProxies and not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -372,7 +396,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
@@ -381,7 +405,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
// @ts-ignore
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
proxies = proxies.map(proxy => proxy && proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
@@ -391,7 +415,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
if (proxies.some(proxy => proxy && (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -405,7 +429,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -582,7 +606,7 @@ function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
// console.log(data, attributes, config)
|
||||
if (isRaw && content) {
|
||||
attributes.innerHTML = content;
|
||||
}
|
||||
@@ -596,9 +620,17 @@ function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
function renderAttributes(context, key, data, config) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
if (!attributesFor) {
|
||||
if (!attributesFor || !data) {
|
||||
return;
|
||||
}
|
||||
if (context.isSSR) {
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
to: '',
|
||||
vnode: h(`ssr-${attributesFor}`, data)
|
||||
};
|
||||
}
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||
if (!el) {
|
||||
@@ -617,7 +649,10 @@ function renderAttributes(context, key, data, config) {
|
||||
}
|
||||
const { el, attrs } = cachedElements[attributesFor];
|
||||
for (const attr in data) {
|
||||
const content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
let content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
if (isArray(content)) {
|
||||
content = content.join(',');
|
||||
}
|
||||
el.setAttribute(attr, content || '');
|
||||
if (!attrs.includes(attr)) {
|
||||
attrs.push(attr);
|
||||
@@ -675,7 +710,7 @@ function applyDifference(target, newSource, oldSource) {
|
||||
}
|
||||
}
|
||||
for (const key in oldSource) {
|
||||
if (!(key in newSource)) {
|
||||
if (!newSource || !(key in newSource)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
@@ -728,9 +763,9 @@ const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = reactive({});
|
||||
function addVnode(teleports, to, vnodes) {
|
||||
function addVnode(isSSR, teleports, to, vnodes) {
|
||||
const nodes = (isArray(vnodes) ? vnodes : [vnodes]);
|
||||
{
|
||||
if (!isSSR) {
|
||||
// Comments shouldnt have any use on the client as they are not reactive anyway
|
||||
nodes.forEach((vnode, idx) => {
|
||||
if (vnode.type === Comment) {
|
||||
@@ -739,15 +774,25 @@ function addVnode(teleports, to, vnodes) {
|
||||
});
|
||||
// only add ssrAttribute's for real meta tags
|
||||
}
|
||||
else if (!to.endsWith('Attrs')) {
|
||||
nodes.forEach((vnode) => {
|
||||
if (!vnode.props) {
|
||||
vnode.props = {};
|
||||
}
|
||||
vnode.props[ssrAttribute] = true;
|
||||
});
|
||||
}
|
||||
if (!teleports[to]) {
|
||||
teleports[to] = [];
|
||||
}
|
||||
teleports[to].push(...nodes);
|
||||
}
|
||||
const createMetaManager = (config, resolver) => MetaManager.create(config || defaultConfig, resolver || defaultResolver);
|
||||
const createMetaManager = (isSSR = false, config, resolver) => MetaManager.create(isSSR, config || defaultConfig, resolver || defaultResolver);
|
||||
class MetaManager {
|
||||
constructor(config, target, resolver) {
|
||||
constructor(isSSR, config, target, resolver) {
|
||||
this.isSSR = false;
|
||||
this.ssrCleanedUp = false;
|
||||
this.isSSR = isSSR;
|
||||
this.config = config;
|
||||
this.target = target;
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
@@ -767,8 +812,9 @@ class MetaManager {
|
||||
removed: []
|
||||
});
|
||||
const resolveContext = { vm };
|
||||
if (this.resolver) {
|
||||
this.resolver.setup(resolveContext);
|
||||
const { resolver } = this;
|
||||
if (resolver && resolver.setup) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute (once)
|
||||
const meta = this.target.addSource(metadata, resolveContext, true);
|
||||
@@ -816,8 +862,9 @@ class MetaManager {
|
||||
}
|
||||
render({ slots } = {}) {
|
||||
// TODO: clean this method
|
||||
const { isSSR } = this;
|
||||
// cleanup ssr tags if not yet done
|
||||
if (!this.ssrCleanedUp) {
|
||||
if (!isSSR && !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
|
||||
@@ -825,14 +872,14 @@ class MetaManager {
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`);
|
||||
if (ssrTags && ssrTags.length) {
|
||||
Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
ssrTags.forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
}
|
||||
});
|
||||
}, { once: true });
|
||||
}
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
let renderedNodes = renderMeta({ isSSR, metainfo: active, slots }, key, active[key], config);
|
||||
if (!renderedNodes) {
|
||||
continue;
|
||||
}
|
||||
@@ -847,7 +894,7 @@ class MetaManager {
|
||||
defaultTo = key;
|
||||
}
|
||||
for (const { to, vnode } of renderedNodes) {
|
||||
addVnode(teleports, to || defaultTo || 'head', vnode);
|
||||
addVnode(this.isSSR, teleports, to || defaultTo || 'head', vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
@@ -859,16 +906,17 @@ class MetaManager {
|
||||
}
|
||||
const slot = slots[slotName];
|
||||
if (isFunction(slot)) {
|
||||
addVnode(teleports, tagName, slot({ metainfo: active }));
|
||||
addVnode(this.isSSR, teleports, tagName, slot({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return h(Teleport, { to }, teleports[to]);
|
||||
const teleport = teleports[to];
|
||||
return h(Teleport, { to }, teleport);
|
||||
});
|
||||
}
|
||||
}
|
||||
MetaManager.create = (config, resolver) => {
|
||||
MetaManager.create = (isSSR, config, resolver) => {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
@@ -877,7 +925,7 @@ MetaManager.create = (config, resolver) => {
|
||||
};
|
||||
const mergedObject = createMergedObject(resolve, active);
|
||||
// TODO: validate resolver
|
||||
const manager = new MetaManager(config, mergedObject, resolver);
|
||||
const manager = new MetaManager(isSSR, config, mergedObject, resolver);
|
||||
return manager;
|
||||
};
|
||||
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+121
-37
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.5
|
||||
* vue-meta v3.0.0-alpha.6
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { markRaw, h, getCurrentInstance, isProxy, watch, inject, defineComponent, reactive, onUnmounted, Teleport } from 'vue';
|
||||
import { markRaw, h, getCurrentInstance, isProxy, watch, inject, defineComponent, reactive, onUnmounted, Teleport, Comment } from 'vue';
|
||||
|
||||
const resolveOption = (predicament, initialValue) => (options, contexts) => {
|
||||
let resolvedIndex = -1;
|
||||
@@ -23,7 +23,7 @@ const resolveOption = (predicament, initialValue) => (options, contexts) => {
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
const setup = (context) => {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
@@ -35,7 +35,7 @@ function setup(context) {
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
};
|
||||
const resolve = resolveOption((currentValue, context) => {
|
||||
const { depth } = context;
|
||||
if (!currentValue || depth > currentValue) {
|
||||
@@ -199,7 +199,7 @@ function clone(v) {
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
if (row && key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
@@ -226,13 +226,23 @@ const allKeys = (source, ...sources) => {
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
const recompute = (context, path = [], target, sources) => {
|
||||
const setTargetAndSources = !target && !sources;
|
||||
if (setTargetAndSources) {
|
||||
({ active: target, sources } = context);
|
||||
if (path.length) {
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const seg = path[i];
|
||||
if (!target || !target[seg]) {
|
||||
if (("development" !== 'production')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`recompute: segment ${seg} not found on target`, path, target);
|
||||
}
|
||||
return;
|
||||
}
|
||||
target = target[seg];
|
||||
sources = sources.map(source => source[seg]).filter(Boolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
@@ -249,7 +259,15 @@ const recompute = (context, sources, target, path = []) => {
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
// @ts-ignore
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
let isObject = false;
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
const source = sources[i];
|
||||
if (source && key in source && source[key] !== undefined) {
|
||||
isObject = isPlainObject(source[key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isObject) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
@@ -260,7 +278,7 @@ const recompute = (context, sources, target, path = []) => {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
recompute(context, [...path, key], target[key], keySources);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
@@ -274,7 +292,6 @@ const recompute = (context, sources, target, path = []) => {
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
@@ -305,6 +322,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
// Also return a merge proxy for nested objects
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
@@ -342,6 +360,12 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
else if (isPlainObject(value)) {
|
||||
// if an object was assigned to this key make sure to recompute all
|
||||
// of its individual properies
|
||||
recompute(context, pathSegments);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -353,13 +377,13 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// the sourceProxies and not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -372,7 +396,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
@@ -381,7 +405,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
// @ts-ignore
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
proxies = proxies.map(proxy => proxy && proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
@@ -391,7 +415,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
if (proxies.some(proxy => proxy && (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -405,7 +429,7 @@ const createHandler = (context, resolveContext, pathSegments = []) => ({
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -453,6 +477,7 @@ const createMergedObject = (resolve, active) => {
|
||||
};
|
||||
};
|
||||
|
||||
const cachedElements = {};
|
||||
function renderMeta(context, key, data, config) {
|
||||
// console.info('renderMeta', key, data, config)
|
||||
if ('attributesFor' in config) {
|
||||
@@ -581,7 +606,7 @@ function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
// console.log(data, attributes, config)
|
||||
if (isRaw && content) {
|
||||
attributes.innerHTML = content;
|
||||
}
|
||||
@@ -595,10 +620,10 @@ function renderTag(context, key, data, config = {}, groupConfig) {
|
||||
function renderAttributes(context, key, data, config) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
if (!attributesFor) {
|
||||
if (!attributesFor || !data) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
if (context.isSSR) {
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
@@ -606,6 +631,37 @@ function renderAttributes(context, key, data, config) {
|
||||
vnode: h(`ssr-${attributesFor}`, data)
|
||||
};
|
||||
}
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||
if (("development" !== 'production') && !el) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Could not find element for selector', attributesFor, ', won\'t render attributes');
|
||||
return;
|
||||
}
|
||||
if (("development" !== 'production') && el2) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('Found multiple elements for selector', attributesFor);
|
||||
}
|
||||
cachedElements[attributesFor] = {
|
||||
el,
|
||||
attrs: []
|
||||
};
|
||||
}
|
||||
const { el, attrs } = cachedElements[attributesFor];
|
||||
for (const attr in data) {
|
||||
let content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
if (isArray(content)) {
|
||||
content = content.join(',');
|
||||
}
|
||||
el.setAttribute(attr, content || '');
|
||||
if (!attrs.includes(attr)) {
|
||||
attrs.push(attr);
|
||||
}
|
||||
}
|
||||
const attrsToRemove = attrs.filter(attr => !data[attr]);
|
||||
for (const attr of attrsToRemove) {
|
||||
el.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
function getSlotContent({ metainfo, slots }, slotName, content, groupConfig) {
|
||||
const slot = slots && slots[slotName];
|
||||
@@ -654,7 +710,7 @@ function applyDifference(target, newSource, oldSource) {
|
||||
}
|
||||
}
|
||||
for (const key in oldSource) {
|
||||
if (!(key in newSource)) {
|
||||
if (!newSource || !(key in newSource)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
@@ -707,9 +763,18 @@ const Metainfo = MetainfoImpl;
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = reactive({});
|
||||
function addVnode(teleports, to, vnodes) {
|
||||
function addVnode(isSSR, teleports, to, vnodes) {
|
||||
const nodes = (isArray(vnodes) ? vnodes : [vnodes]);
|
||||
if (!to.endsWith('Attrs')) {
|
||||
if (!isSSR) {
|
||||
// Comments shouldnt have any use on the client as they are not reactive anyway
|
||||
nodes.forEach((vnode, idx) => {
|
||||
if (vnode.type === Comment) {
|
||||
nodes.splice(idx, 1);
|
||||
}
|
||||
});
|
||||
// only add ssrAttribute's for real meta tags
|
||||
}
|
||||
else if (!to.endsWith('Attrs')) {
|
||||
nodes.forEach((vnode) => {
|
||||
if (!vnode.props) {
|
||||
vnode.props = {};
|
||||
@@ -722,10 +787,12 @@ function addVnode(teleports, to, vnodes) {
|
||||
}
|
||||
teleports[to].push(...nodes);
|
||||
}
|
||||
const createMetaManager = (config, resolver) => MetaManager.create(config || defaultConfig, resolver || defaultResolver);
|
||||
const createMetaManager = (isSSR = false, config, resolver) => MetaManager.create(isSSR, config || defaultConfig, resolver || defaultResolver);
|
||||
class MetaManager {
|
||||
constructor(config, target, resolver) {
|
||||
constructor(isSSR, config, target, resolver) {
|
||||
this.isSSR = false;
|
||||
this.ssrCleanedUp = false;
|
||||
this.isSSR = isSSR;
|
||||
this.config = config;
|
||||
this.target = target;
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
@@ -745,8 +812,9 @@ class MetaManager {
|
||||
removed: []
|
||||
});
|
||||
const resolveContext = { vm };
|
||||
if (this.resolver) {
|
||||
this.resolver.setup(resolveContext);
|
||||
const { resolver } = this;
|
||||
if (resolver && resolver.setup) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute (once)
|
||||
const meta = this.target.addSource(metadata, resolveContext, true);
|
||||
@@ -793,10 +861,25 @@ class MetaManager {
|
||||
}
|
||||
}
|
||||
render({ slots } = {}) {
|
||||
// TODO: clean this method
|
||||
const { isSSR } = this;
|
||||
// cleanup ssr tags if not yet done
|
||||
if (!isSSR && !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) {
|
||||
ssrTags.forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
}
|
||||
}, { once: true });
|
||||
}
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
let renderedNodes = renderMeta({ isSSR, metainfo: active, slots }, key, active[key], config);
|
||||
if (!renderedNodes) {
|
||||
continue;
|
||||
}
|
||||
@@ -811,7 +894,7 @@ class MetaManager {
|
||||
defaultTo = key;
|
||||
}
|
||||
for (const { to, vnode } of renderedNodes) {
|
||||
addVnode(teleports, to || defaultTo || 'head', vnode);
|
||||
addVnode(this.isSSR, teleports, to || defaultTo || 'head', vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
@@ -823,16 +906,17 @@ class MetaManager {
|
||||
}
|
||||
const slot = slots[slotName];
|
||||
if (isFunction(slot)) {
|
||||
addVnode(teleports, tagName, slot({ metainfo: active }));
|
||||
addVnode(this.isSSR, teleports, tagName, slot({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return h(Teleport, { to }, teleports[to]);
|
||||
const teleport = teleports[to];
|
||||
return h(Teleport, { to }, teleport);
|
||||
});
|
||||
}
|
||||
}
|
||||
MetaManager.create = (config, resolver) => {
|
||||
MetaManager.create = (isSSR, config, resolver) => {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
@@ -841,7 +925,7 @@ MetaManager.create = (config, resolver) => {
|
||||
};
|
||||
const mergedObject = createMergedObject(resolve, active);
|
||||
// TODO: validate resolver
|
||||
const manager = new MetaManager(config, mergedObject, resolver);
|
||||
const manager = new MetaManager(isSSR, config, mergedObject, resolver);
|
||||
return manager;
|
||||
};
|
||||
|
||||
|
||||
Vendored
+87
-39
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* vue-meta v3.0.0-alpha.5
|
||||
* vue-meta v3.0.0-alpha.6
|
||||
* (c) 2021
|
||||
* - Pim (@pimlie)
|
||||
* - All the amazing contributors
|
||||
@@ -24,7 +24,7 @@ var VueMeta = (function (exports, vue) {
|
||||
}
|
||||
};
|
||||
|
||||
function setup(context) {
|
||||
const setup = (context) => {
|
||||
let depth = 0;
|
||||
if (context.vm) {
|
||||
let { vm } = context;
|
||||
@@ -36,7 +36,7 @@ var VueMeta = (function (exports, vue) {
|
||||
} while (vm && vm.parent && vm !== vm.root);
|
||||
}
|
||||
context.depth = depth;
|
||||
}
|
||||
};
|
||||
const resolve = resolveOption((currentValue, context) => {
|
||||
const { depth } = context;
|
||||
if (!currentValue || depth > currentValue) {
|
||||
@@ -200,7 +200,7 @@ var VueMeta = (function (exports, vue) {
|
||||
const pluck = (collection, key, callback) => {
|
||||
const plucked = [];
|
||||
for (const row of collection) {
|
||||
if (key in row) {
|
||||
if (row && key in row) {
|
||||
plucked.push(row[key]);
|
||||
if (callback) {
|
||||
callback(row);
|
||||
@@ -227,13 +227,23 @@ var VueMeta = (function (exports, vue) {
|
||||
// TODO: add check for consistent types for each key (dev only)
|
||||
return keys;
|
||||
};
|
||||
const recompute = (context, sources, target, path = []) => {
|
||||
if (!path.length) {
|
||||
if (!target) {
|
||||
target = context.active;
|
||||
}
|
||||
if (!sources) {
|
||||
sources = context.sources;
|
||||
const recompute = (context, path = [], target, sources) => {
|
||||
const setTargetAndSources = !target && !sources;
|
||||
if (setTargetAndSources) {
|
||||
({ active: target, sources } = context);
|
||||
if (path.length) {
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const seg = path[i];
|
||||
if (!target || !target[seg]) {
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`recompute: segment ${seg} not found on target`, path, target);
|
||||
}
|
||||
return;
|
||||
}
|
||||
target = target[seg];
|
||||
sources = sources.map(source => source[seg]).filter(Boolean);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!target || !sources) {
|
||||
@@ -250,7 +260,15 @@ var VueMeta = (function (exports, vue) {
|
||||
for (const key of keys) {
|
||||
// This assumes consistent types usages for keys across sources
|
||||
// @ts-ignore
|
||||
if (isPlainObject(sources[0][key])) {
|
||||
let isObject = false;
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
const source = sources[i];
|
||||
if (source && key in source && source[key] !== undefined) {
|
||||
isObject = isPlainObject(source[key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isObject) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
@@ -261,7 +279,7 @@ var VueMeta = (function (exports, vue) {
|
||||
keySources.push(source[key]);
|
||||
}
|
||||
}
|
||||
recompute(context, keySources, target[key], [...path, key]);
|
||||
recompute(context, [...path, key], target[key], keySources);
|
||||
continue;
|
||||
}
|
||||
// Ensure the target is an array if source is an array and target is empty
|
||||
@@ -275,7 +293,6 @@ var VueMeta = (function (exports, vue) {
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('RESOLVED', key, resolved, 'was', target[key])
|
||||
target[key] = resolved;
|
||||
}
|
||||
};
|
||||
@@ -306,6 +323,7 @@ var VueMeta = (function (exports, vue) {
|
||||
if (!isObject(value)) {
|
||||
return value;
|
||||
}
|
||||
// Also return a merge proxy for nested objects
|
||||
if (!value[IS_PROXY]) {
|
||||
const keyPath = [...pathSegments, key];
|
||||
value = createProxy(context, value, resolveContext, keyPath);
|
||||
@@ -343,6 +361,12 @@ var VueMeta = (function (exports, vue) {
|
||||
recompute(context);
|
||||
return success;
|
||||
}
|
||||
else if (isPlainObject(value)) {
|
||||
// if an object was assigned to this key make sure to recompute all
|
||||
// of its individual properies
|
||||
recompute(context, pathSegments);
|
||||
return success;
|
||||
}
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -354,13 +378,13 @@ var VueMeta = (function (exports, vue) {
|
||||
}
|
||||
let resolved = context.resolve(keySources, keyContexts, active, key, pathSegments);
|
||||
// Ensure to clone if value is an object, cause sources is an array of
|
||||
// the sourceProxies not the sources so we could trigger an endless loop when
|
||||
// the sourceProxies and not the sources so we could trigger an endless loop when
|
||||
// updating a prop on an obj as the prop on the active object refers to
|
||||
// a prop on a proxy
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
// console.log('SET VALUE', isArrayItem, key, '\nresolved:\n', resolved, '\nsources:\n', context.sources, '\nactive:\n', active, Object.keys(active))
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -373,7 +397,7 @@ var VueMeta = (function (exports, vue) {
|
||||
},
|
||||
deleteProperty: (target, key) => {
|
||||
const success = Reflect.deleteProperty(target, key);
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
// console.warn('PROXY DELETE\nkey:', key, '\npath:', pathSegments, '\nparent:', isArray(target), target)
|
||||
if (success) {
|
||||
const isArrayItem = isArray(target);
|
||||
let activeSegmentKey;
|
||||
@@ -382,7 +406,7 @@ var VueMeta = (function (exports, vue) {
|
||||
let index = 0;
|
||||
for (const segment of pathSegments) {
|
||||
// @ts-ignore
|
||||
proxies = proxies.map(proxy => proxy[segment]);
|
||||
proxies = proxies.map(proxy => proxy && proxy[segment]);
|
||||
if (isArrayItem && index === pathSegments.length - 1) {
|
||||
activeSegmentKey = segment;
|
||||
break;
|
||||
@@ -392,7 +416,7 @@ var VueMeta = (function (exports, vue) {
|
||||
}
|
||||
// Check if the key still exists in one of the sourceProxies,
|
||||
// if so resolve the new value, if not remove the key
|
||||
if (proxies.some(proxy => (key in proxy))) {
|
||||
if (proxies.some(proxy => proxy && (key in proxy))) {
|
||||
let keyContexts = [];
|
||||
let keySources;
|
||||
if (isArrayItem) {
|
||||
@@ -406,7 +430,7 @@ var VueMeta = (function (exports, vue) {
|
||||
if (isPlainObject(resolved)) {
|
||||
resolved = clone(resolved);
|
||||
}
|
||||
// console.log('SET VALUE', resolved)
|
||||
// console.log('SET VALUE', resolved)
|
||||
if (isArrayItem && activeSegmentKey) {
|
||||
active[activeSegmentKey] = resolved;
|
||||
}
|
||||
@@ -583,7 +607,7 @@ var VueMeta = (function (exports, vue) {
|
||||
// console.info('FINAL TAG', finalTag)
|
||||
// console.log(' ATTRIBUTES', attributes)
|
||||
// console.log(' CONTENT', content)
|
||||
// // console.log(data, attributes, config)
|
||||
// console.log(data, attributes, config)
|
||||
if (isRaw && content) {
|
||||
attributes.innerHTML = content;
|
||||
}
|
||||
@@ -597,9 +621,17 @@ var VueMeta = (function (exports, vue) {
|
||||
function renderAttributes(context, key, data, config) {
|
||||
// console.info('renderAttributes', key, data, config)
|
||||
const { attributesFor } = config;
|
||||
if (!attributesFor) {
|
||||
if (!attributesFor || !data) {
|
||||
return;
|
||||
}
|
||||
if (context.isSSR) {
|
||||
// render attributes in a placeholder vnode so Vue
|
||||
// will render the string for us
|
||||
return {
|
||||
to: '',
|
||||
vnode: vue.h(`ssr-${attributesFor}`, data)
|
||||
};
|
||||
}
|
||||
if (!cachedElements[attributesFor]) {
|
||||
const [el, el2] = Array.from(document.querySelectorAll(attributesFor));
|
||||
if (!el) {
|
||||
@@ -618,7 +650,10 @@ var VueMeta = (function (exports, vue) {
|
||||
}
|
||||
const { el, attrs } = cachedElements[attributesFor];
|
||||
for (const attr in data) {
|
||||
const content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
let content = getSlotContent(context, `${key}(${attr})`, data[attr], data);
|
||||
if (isArray(content)) {
|
||||
content = content.join(',');
|
||||
}
|
||||
el.setAttribute(attr, content || '');
|
||||
if (!attrs.includes(attr)) {
|
||||
attrs.push(attr);
|
||||
@@ -676,7 +711,7 @@ var VueMeta = (function (exports, vue) {
|
||||
}
|
||||
}
|
||||
for (const key in oldSource) {
|
||||
if (!(key in newSource)) {
|
||||
if (!newSource || !(key in newSource)) {
|
||||
delete target[key];
|
||||
}
|
||||
}
|
||||
@@ -729,9 +764,9 @@ var VueMeta = (function (exports, vue) {
|
||||
|
||||
const ssrAttribute = 'data-vm-ssr';
|
||||
const active = vue.reactive({});
|
||||
function addVnode(teleports, to, vnodes) {
|
||||
function addVnode(isSSR, teleports, to, vnodes) {
|
||||
const nodes = (isArray(vnodes) ? vnodes : [vnodes]);
|
||||
{
|
||||
if (!isSSR) {
|
||||
// Comments shouldnt have any use on the client as they are not reactive anyway
|
||||
nodes.forEach((vnode, idx) => {
|
||||
if (vnode.type === vue.Comment) {
|
||||
@@ -740,15 +775,25 @@ var VueMeta = (function (exports, vue) {
|
||||
});
|
||||
// only add ssrAttribute's for real meta tags
|
||||
}
|
||||
else if (!to.endsWith('Attrs')) {
|
||||
nodes.forEach((vnode) => {
|
||||
if (!vnode.props) {
|
||||
vnode.props = {};
|
||||
}
|
||||
vnode.props[ssrAttribute] = true;
|
||||
});
|
||||
}
|
||||
if (!teleports[to]) {
|
||||
teleports[to] = [];
|
||||
}
|
||||
teleports[to].push(...nodes);
|
||||
}
|
||||
const createMetaManager = (config, resolver) => MetaManager.create(config || defaultConfig, resolver || defaultResolver);
|
||||
const createMetaManager = (isSSR = false, config, resolver) => MetaManager.create(isSSR, config || defaultConfig, resolver || defaultResolver);
|
||||
class MetaManager {
|
||||
constructor(config, target, resolver) {
|
||||
constructor(isSSR, config, target, resolver) {
|
||||
this.isSSR = false;
|
||||
this.ssrCleanedUp = false;
|
||||
this.isSSR = isSSR;
|
||||
this.config = config;
|
||||
this.target = target;
|
||||
if (resolver && 'setup' in resolver && isFunction(resolver.setup)) {
|
||||
@@ -768,8 +813,9 @@ var VueMeta = (function (exports, vue) {
|
||||
removed: []
|
||||
});
|
||||
const resolveContext = { vm };
|
||||
if (this.resolver) {
|
||||
this.resolver.setup(resolveContext);
|
||||
const { resolver } = this;
|
||||
if (resolver && resolver.setup) {
|
||||
resolver.setup(resolveContext);
|
||||
}
|
||||
// TODO: optimize initial compute (once)
|
||||
const meta = this.target.addSource(metadata, resolveContext, true);
|
||||
@@ -817,8 +863,9 @@ var VueMeta = (function (exports, vue) {
|
||||
}
|
||||
render({ slots } = {}) {
|
||||
// TODO: clean this method
|
||||
const { isSSR } = this;
|
||||
// cleanup ssr tags if not yet done
|
||||
if (!this.ssrCleanedUp) {
|
||||
if (!isSSR && !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
|
||||
@@ -826,14 +873,14 @@ var VueMeta = (function (exports, vue) {
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const ssrTags = document.querySelectorAll(`[${ssrAttribute}]`);
|
||||
if (ssrTags && ssrTags.length) {
|
||||
Array.from(ssrTags).forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
ssrTags.forEach(el => el.parentNode && el.parentNode.removeChild(el));
|
||||
}
|
||||
});
|
||||
}, { once: true });
|
||||
}
|
||||
const teleports = {};
|
||||
for (const key in active) {
|
||||
const config = this.config[key] || {};
|
||||
let renderedNodes = renderMeta({ metainfo: active, slots }, key, active[key], config);
|
||||
let renderedNodes = renderMeta({ isSSR, metainfo: active, slots }, key, active[key], config);
|
||||
if (!renderedNodes) {
|
||||
continue;
|
||||
}
|
||||
@@ -848,7 +895,7 @@ var VueMeta = (function (exports, vue) {
|
||||
defaultTo = key;
|
||||
}
|
||||
for (const { to, vnode } of renderedNodes) {
|
||||
addVnode(teleports, to || defaultTo || 'head', vnode);
|
||||
addVnode(this.isSSR, teleports, to || defaultTo || 'head', vnode);
|
||||
}
|
||||
}
|
||||
if (slots) {
|
||||
@@ -860,16 +907,17 @@ var VueMeta = (function (exports, vue) {
|
||||
}
|
||||
const slot = slots[slotName];
|
||||
if (isFunction(slot)) {
|
||||
addVnode(teleports, tagName, slot({ metainfo: active }));
|
||||
addVnode(this.isSSR, teleports, tagName, slot({ metainfo: active }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(teleports).map((to) => {
|
||||
return vue.h(vue.Teleport, { to }, teleports[to]);
|
||||
const teleport = teleports[to];
|
||||
return vue.h(vue.Teleport, { to }, teleport);
|
||||
});
|
||||
}
|
||||
}
|
||||
MetaManager.create = (config, resolver) => {
|
||||
MetaManager.create = (isSSR, config, resolver) => {
|
||||
const resolve = (options, contexts, active, key, pathSegments) => {
|
||||
if (isFunction(resolver)) {
|
||||
return resolver(options, contexts, active, key, pathSegments);
|
||||
@@ -878,7 +926,7 @@ var VueMeta = (function (exports, vue) {
|
||||
};
|
||||
const mergedObject = createMergedObject(resolve, active);
|
||||
// TODO: validate resolver
|
||||
const manager = new MetaManager(config, mergedObject, resolver);
|
||||
const manager = new MetaManager(isSSR, config, mergedObject, resolver);
|
||||
return manager;
|
||||
};
|
||||
|
||||
|
||||
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.5",
|
||||
"version": "3.0.0-alpha.6",
|
||||
"description": "Manage HTML metadata in Vue.js components with SSR support",
|
||||
"main": "dist/vue-meta.cjs.js",
|
||||
"browser": "dist/vue-meta.esm-browser.min.js",
|
||||
|
||||
Reference in New Issue
Block a user