From 716307a2ce1dd9276070b263e40cec23f0a336c9 Mon Sep 17 00:00:00 2001 From: Declan de Wet Date: Fri, 4 Nov 2016 09:02:57 +0200 Subject: [PATCH] change to more versatile merge behaviour --- README.md | 64 ++++++++++++++++++++++++++++++++ src/shared/getComponentOption.js | 12 +++++- src/shared/getMetaInfo.js | 41 ++++++++------------ 3 files changed, 89 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 2fe19a0..87e9b40 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ - [`noscript` ([Object])](#noscript-object) - [`changed` (Function)](#changed-function) - [How `metaInfo` is Resolved](#how-metainfo-is-resolved) + - [Lists of Tags](#lists-of-tags) - [Performance](#performance) - [How to prevent the update on the initial page render](#how-to-prevent-the-update-on-the-initial-page-render) - [FAQ](#faq) @@ -517,6 +518,69 @@ You can define a `metaInfo` property on any component in the tree. Child compone If both `` _and_ `` define a `title` property inside `metaInfo`, then the `title` that gets rendered will resolve to the `title` defined inside ``. +#### Lists of Tags + +When specifying an array in `metaInfo`, like in the below examples, the default behaviour is to simply concatenate the lists. + +**Input:** +```js +// parent component +{ + metaInfo: { + meta: [ + { charset: 'utf-8' }, + { name: 'description', content: 'foo' } + ] + } +} +// child component +{ + metaInfo: { + meta: [ + { name: 'description', content: 'bar' } + ] + } +} +``` + +**Output:** +```html + + + +``` + +This is not what we want, since the meta `description` needs to be unique for every page. If you want to change this behaviour such that `description` is instead replaced, then give it a `vmid`: + +**Input:** +```js +// parent component +{ + metaInfo: { + meta: [ + { charset: 'utf-8' }, + { vmid: 'description', name: 'description', content: 'foo' } + ] + } +} +// child component +{ + metaInfo: { + meta: [ + { vmid: 'description', name: 'description', content: 'bar' } + ] + } +} +``` + +**Output:** +```html + + +``` + +While solutions like `react-helmet` manage the occurrence order and merge behaviour for you automatically, it involves a lot more code and is therefore prone to failure in some edge-cases, whereas this method is _almost_ bulletproof because of its versatility. + # Performance On the client, `vue-meta` batches DOM updates using [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame). It needs to do this because it registers a Vue mixin that subscribes to the [`beforeMount`](https://vuejs.org/api/#beforeMount) lifecycle hook on all components in order to be notified that renders have occurred and data is ready. If `vue-meta` did not batch updates, the DOM meta info would be re-calculated and re-updated for every component on the page in quick-succession. diff --git a/src/shared/getComponentOption.js b/src/shared/getComponentOption.js index 8a01318..0c337d9 100644 --- a/src/shared/getComponentOption.js +++ b/src/shared/getComponentOption.js @@ -34,14 +34,22 @@ export default function getComponentOption (opts, result = {}) { } // merge with existing options - result = deepmerge(result, data, { arrayMerge }) + result = deepmerge(result, data, { + clone: true, + arrayMerge + }) // collect & aggregate child options if deep = true if (deep) { const { $children } = component for (let i = 0, len = $children.length; i < len; i++) { const component = $children[i] - result = getComponentOption({ option, deep, component, arrayMerge }, result) + result = getComponentOption({ + option, + deep, + component, + arrayMerge + }, result) } } diff --git a/src/shared/getMetaInfo.js b/src/shared/getMetaInfo.js index 7c5bb57..34f27e3 100644 --- a/src/shared/getMetaInfo.js +++ b/src/shared/getMetaInfo.js @@ -9,40 +9,29 @@ import getComponentOption from './getComponentOption' */ export default function getMetaInfo (component) { // collect & aggregate all metaInfo $options - const info = getComponentOption({ + let info = getComponentOption({ component, option: 'metaInfo', deep: true, - // In order to prevent certain tags from being overwritten, - // (like being overwritten by - // ), we need to specify a different - // array merge strategy. This strategy exploits a trick - // with associative arrays in JS using O(1) lookup - - /* eslint-disable no-labels */ - arrayMerge (oldTags, newTags) { - const updatedTags = [] - for (let oldTagIndex in oldTags) { - const oldTag = oldTags[oldTagIndex] - let sharedAttributes = false - ifTagsHaveEqualSharedAttributeValues: for (let newTagIndex in newTags) { - const newTag = newTags[newTagIndex] - for (let attribute in newTag) { - if (newTag.hasOwnProperty(attribute) && oldTag.hasOwnProperty(attribute)) { - if (oldTag[attribute] === newTag[attribute]) { - sharedAttributes = true - break ifTagsHaveEqualSharedAttributeValues - } - } + arrayMerge (target, source) { + const destination = [] + for (let targetIndex in target) { + const targetItem = target[targetIndex] + let shared = false + for (let sourceIndex in source) { + const sourceItem = source[sourceIndex] + if (targetItem.vmid === sourceItem.vmid) { + shared = true + break } } - if (!sharedAttributes) { - updatedTags.push(oldTag) + if (!shared) { + destination.push(targetItem) } } - return updatedTags.concat(newTags) + + return destination.concat(source) } - /* eslint-enable no-labels */ }) // if any info options are a function, coerce them to the result of a call