mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-24 21:20:34 +03:00
change to more versatile merge behaviour
This commit is contained in:
@@ -67,6 +67,7 @@
|
|||||||
- [`noscript` ([Object])](#noscript-object)
|
- [`noscript` ([Object])](#noscript-object)
|
||||||
- [`changed` (Function)](#changed-function)
|
- [`changed` (Function)](#changed-function)
|
||||||
- [How `metaInfo` is Resolved](#how-metainfo-is-resolved)
|
- [How `metaInfo` is Resolved](#how-metainfo-is-resolved)
|
||||||
|
- [Lists of Tags](#lists-of-tags)
|
||||||
- [Performance](#performance)
|
- [Performance](#performance)
|
||||||
- [How to prevent the update on the initial page render](#how-to-prevent-the-update-on-the-initial-page-render)
|
- [How to prevent the update on the initial page render](#how-to-prevent-the-update-on-the-initial-page-render)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
@@ -517,6 +518,69 @@ You can define a `metaInfo` property on any component in the tree. Child compone
|
|||||||
|
|
||||||
If both `<parent>` _and_ `<child>` define a `title` property inside `metaInfo`, then the `title` that gets rendered will resolve to the `title` defined inside `<child>`.
|
If both `<parent>` _and_ `<child>` define a `title` property inside `metaInfo`, then the `title` that gets rendered will resolve to the `title` defined inside `<child>`.
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="description" content="foo">
|
||||||
|
<meta name="description" content="bar">
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta vmid="description" name="description" content="bar">
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
# 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.
|
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.
|
||||||
|
|||||||
@@ -34,14 +34,22 @@ export default function getComponentOption (opts, result = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// merge with existing options
|
// merge with existing options
|
||||||
result = deepmerge(result, data, { arrayMerge })
|
result = deepmerge(result, data, {
|
||||||
|
clone: true,
|
||||||
|
arrayMerge
|
||||||
|
})
|
||||||
|
|
||||||
// collect & aggregate child options if deep = true
|
// collect & aggregate child options if deep = true
|
||||||
if (deep) {
|
if (deep) {
|
||||||
const { $children } = component
|
const { $children } = component
|
||||||
for (let i = 0, len = $children.length; i < len; i++) {
|
for (let i = 0, len = $children.length; i < len; i++) {
|
||||||
const component = $children[i]
|
const component = $children[i]
|
||||||
result = getComponentOption({ option, deep, component, arrayMerge }, result)
|
result = getComponentOption({
|
||||||
|
option,
|
||||||
|
deep,
|
||||||
|
component,
|
||||||
|
arrayMerge
|
||||||
|
}, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-26
@@ -9,40 +9,29 @@ import getComponentOption from './getComponentOption'
|
|||||||
*/
|
*/
|
||||||
export default function getMetaInfo (component) {
|
export default function getMetaInfo (component) {
|
||||||
// collect & aggregate all metaInfo $options
|
// collect & aggregate all metaInfo $options
|
||||||
const info = getComponentOption({
|
let info = getComponentOption({
|
||||||
component,
|
component,
|
||||||
option: 'metaInfo',
|
option: 'metaInfo',
|
||||||
deep: true,
|
deep: true,
|
||||||
// In order to prevent certain tags from being overwritten,
|
arrayMerge (target, source) {
|
||||||
// (like <meta name="description" ...> being overwritten by
|
const destination = []
|
||||||
// <meta name="keywords" ...>), we need to specify a different
|
for (let targetIndex in target) {
|
||||||
// array merge strategy. This strategy exploits a trick
|
const targetItem = target[targetIndex]
|
||||||
// with associative arrays in JS using O(1) lookup
|
let shared = false
|
||||||
|
for (let sourceIndex in source) {
|
||||||
/* eslint-disable no-labels */
|
const sourceItem = source[sourceIndex]
|
||||||
arrayMerge (oldTags, newTags) {
|
if (targetItem.vmid === sourceItem.vmid) {
|
||||||
const updatedTags = []
|
shared = true
|
||||||
for (let oldTagIndex in oldTags) {
|
break
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sharedAttributes) {
|
if (!shared) {
|
||||||
updatedTags.push(oldTag)
|
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
|
// if any info options are a function, coerce them to the result of a call
|
||||||
|
|||||||
Reference in New Issue
Block a user