mirror of
https://github.com/tenrok/vue-meta.git
synced 2026-06-24 02:40:33 +03:00
Merge pull request #15 from declandewet/fix/taglist-duplicate-overrides
Favor function syntax over per-attribute function syntax
This commit is contained in:
@@ -75,8 +75,7 @@
|
|||||||
- [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)
|
||||||
- [How do I use component data in `metaInfo`?](#how-do-i-use-component-data-in-metainfo)
|
- [How do I use component props and/or component data in `metaInfo`?](#how-do-i-use-component-props-andor-component-data-in-metainfo)
|
||||||
- [How do I use component props in `metaInfo`?](#how-do-i-use-component-props-in-metainfo)
|
|
||||||
- [How do I populate `metaInfo` from the result of an asynchronous action?](#how-do-i-populate-metainfo-from-the-result-of-an-asynchronous-action)
|
- [How do I populate `metaInfo` from the result of an asynchronous action?](#how-do-i-populate-metainfo-from-the-result-of-an-asynchronous-action)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
|
|
||||||
@@ -610,71 +609,56 @@ Add the `data-vue-meta-server-rendered` attribute to the `<html>` tag on the ser
|
|||||||
|
|
||||||
Here are some answers to some frequently asked questions.
|
Here are some answers to some frequently asked questions.
|
||||||
|
|
||||||
## How do I use component data in `metaInfo`?
|
## How do I use component props and/or component data in `metaInfo`?
|
||||||
Specify a function instead of an object. It will need to return the same type as its definition.
|
|
||||||
|
|
||||||
**BlogPost.vue:**
|
Easy. Instead of defining `metaInfo` as an object, define it as a function and access `this` as usual:
|
||||||
|
|
||||||
|
**Post.vue:**
|
||||||
```html
|
```html
|
||||||
<template>
|
<template>
|
||||||
<div id="page">
|
<div>
|
||||||
<h1>{{ title }}</h1>
|
<h1>{{ title }}</h1>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'BlogPost',
|
name: 'post',
|
||||||
data: () => ({
|
props: ['title'],
|
||||||
title: 'Sample blog post'
|
data () {
|
||||||
}),
|
return {
|
||||||
metaInfo: {
|
description: 'A blog post about some stuff'
|
||||||
title () {
|
}
|
||||||
return this.title
|
},
|
||||||
|
metaInfo ()
|
||||||
|
return {
|
||||||
|
title: this.title,
|
||||||
|
meta: [
|
||||||
|
{ vmid: 'description', name: 'description', content: this.description }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## How do I use component props in `metaInfo`?
|
**PostContainer.vue:**
|
||||||
The same way you use data - specify a function instead of an object. It will need to return the same type as its definition.
|
|
||||||
|
|
||||||
**BlogPostWrapper.vue**
|
|
||||||
```html
|
```html
|
||||||
<template>
|
<template>
|
||||||
<div id="page">
|
<div>
|
||||||
<blog-post :title="title"></blog-post>
|
<post :title="title"></post>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import BlogPost from './BlogPost.vue'
|
import Post from './Post.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BlogPostWrapper',
|
name: 'post-container',
|
||||||
components: { BlogPost },
|
components: { Post },
|
||||||
data: () => ({
|
data () {
|
||||||
title: 'Example blog post'
|
return {
|
||||||
})
|
title: 'Example blog post'
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
**BlogPost.vue**
|
|
||||||
```html
|
|
||||||
<template>
|
|
||||||
<div id="page">
|
|
||||||
<h1>{{ title }}</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'BlogPost',
|
|
||||||
props: ['title'],
|
|
||||||
metaInfo: {
|
|
||||||
title () {
|
|
||||||
return this.title
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ Vue.component('child', {
|
|||||||
render (h) {
|
render (h) {
|
||||||
return h('h3', null, this.page)
|
return h('h3', null, this.page)
|
||||||
},
|
},
|
||||||
metaInfo: {
|
metaInfo () {
|
||||||
title () {
|
return {
|
||||||
return this.page
|
title: this.page
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ new Vue({
|
|||||||
<p>Inspect Element to see the meta info</p>
|
<p>Inspect Element to see the meta info</p>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
metaInfo: {
|
metaInfo: () => ({
|
||||||
title: 'Basic',
|
title: 'Basic',
|
||||||
titleTemplate: '%s | Vue Meta Examples',
|
titleTemplate: '%s | Vue Meta Examples',
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
amp: undefined
|
amp: undefined
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}).$mount('#app')
|
}).$mount('#app')
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ const ChildComponent = {
|
|||||||
name: `child-component`,
|
name: `child-component`,
|
||||||
props: ['page'],
|
props: ['page'],
|
||||||
template: `<h3>You're looking at the <strong>{{ page }}</strong> page</h3>`,
|
template: `<h3>You're looking at the <strong>{{ page }}</strong> page</h3>`,
|
||||||
metaInfo: {
|
metaInfo () {
|
||||||
title () {
|
return {
|
||||||
return this.page
|
title: this.page
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,13 @@
|
|||||||
<p>Inspect Element to see the meta info</p>
|
<p>Inspect Element to see the meta info</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
metaInfo: {
|
||||||
|
meta: [
|
||||||
|
{ vmid: 'charset', charset: 'utf-8' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -20,7 +20,10 @@
|
|||||||
postsCount: 'publishedPostsCount'
|
postsCount: 'publishedPostsCount'
|
||||||
}),
|
}),
|
||||||
metaInfo: {
|
metaInfo: {
|
||||||
title: 'Home'
|
title: 'Home',
|
||||||
|
meta: [
|
||||||
|
{ vmid: 'description', name: 'description', content: 'The home page' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -28,9 +28,12 @@
|
|||||||
'isLoading',
|
'isLoading',
|
||||||
'post'
|
'post'
|
||||||
]),
|
]),
|
||||||
metaInfo: {
|
metaInfo () {
|
||||||
title () {
|
return {
|
||||||
return this.isLoading ? 'Loading...' : this.post.title
|
title: this.isLoading ? 'Loading...' : this.post.title,
|
||||||
|
meta: [
|
||||||
|
{ vmid: 'description', name: 'description', content: this.post.title }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,13 @@
|
|||||||
<p>Inspect Element to see the meta info</p>
|
<p>Inspect Element to see the meta info</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
metaInfo: {
|
||||||
|
meta: [
|
||||||
|
{ vmid: 'charset', charset: 'utf-8' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -20,7 +20,10 @@
|
|||||||
postsCount: 'publishedPostsCount'
|
postsCount: 'publishedPostsCount'
|
||||||
}),
|
}),
|
||||||
metaInfo: {
|
metaInfo: {
|
||||||
title: 'Home'
|
title: 'Home',
|
||||||
|
meta: [
|
||||||
|
{ vmid: 'description', name: 'description', content: 'The home page' }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,9 +18,12 @@
|
|||||||
computed: mapGetters([
|
computed: mapGetters([
|
||||||
'post'
|
'post'
|
||||||
]),
|
]),
|
||||||
metaInfo: {
|
metaInfo () {
|
||||||
title () {
|
return {
|
||||||
return this.post.title
|
title: this.post.title,
|
||||||
|
meta: [
|
||||||
|
{ vmid: 'description', name: 'description', content: this.post.title }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,26 +13,28 @@ import deepmerge from 'deepmerge'
|
|||||||
* @param {Function} opts.arrayMerge - how should arrays be merged?
|
* @param {Function} opts.arrayMerge - how should arrays be merged?
|
||||||
* @param {Object} [result={}] - result so far
|
* @param {Object} [result={}] - result so far
|
||||||
* @return {Object} result - final aggregated result
|
* @return {Object} result - final aggregated result
|
||||||
* @return {Object} result.mergedOption - the actual merged options
|
|
||||||
* @return {Object} result.deepestComponentWithMetaInfo - the deepest component in the heirarchy that has a `metaInfo` instance property
|
|
||||||
*/
|
*/
|
||||||
export default function getComponentOption (opts, result = { mergedOption: {} }) {
|
export default function getComponentOption (opts, result = {}) {
|
||||||
const { component, option, deep, arrayMerge } = opts
|
const { component, option, deep, arrayMerge } = opts
|
||||||
const { $options } = component
|
const { $options } = component
|
||||||
|
|
||||||
// only collect option data if it exists
|
// only collect option data if it exists
|
||||||
if (typeof $options[option] !== 'undefined' && $options[option] !== null) {
|
if (typeof $options[option] !== 'undefined' && $options[option] !== null) {
|
||||||
const data = $options[option]
|
let data = $options[option]
|
||||||
|
|
||||||
|
// if option is a function, replace it with it's result
|
||||||
|
if (typeof data === 'function') {
|
||||||
|
data = data.call(component)
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof data === 'object') {
|
if (typeof data === 'object') {
|
||||||
// merge with existing options
|
// merge with existing options
|
||||||
result.mergedOption = deepmerge(result.mergedOption, data, {
|
result = deepmerge(result, data, {
|
||||||
clone: true,
|
clone: true,
|
||||||
arrayMerge
|
arrayMerge
|
||||||
})
|
})
|
||||||
result.deepestComponentWithMetaInfo = component
|
|
||||||
} else {
|
} else {
|
||||||
result.mergedOption = data
|
result = data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function getMetaInfo (component) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// collect & aggregate all metaInfo $options
|
// collect & aggregate all metaInfo $options
|
||||||
const { mergedOption: info, deepestComponentWithMetaInfo } = getComponentOption({
|
const info = getComponentOption({
|
||||||
component,
|
component,
|
||||||
option: 'metaInfo',
|
option: 'metaInfo',
|
||||||
deep: true,
|
deep: true,
|
||||||
@@ -71,15 +71,5 @@ export default function getMetaInfo (component) {
|
|||||||
info.base = Object.keys(info.base).length ? [info.base] : []
|
info.base = Object.keys(info.base).length ? [info.base] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
const metaInfo = deepmerge(defaultInfo, info)
|
return deepmerge(defaultInfo, info)
|
||||||
|
|
||||||
// inject component context into functions & call to normalize data
|
|
||||||
Object.keys(metaInfo).forEach((key) => {
|
|
||||||
const val = metaInfo[key]
|
|
||||||
if (typeof val === 'function') {
|
|
||||||
metaInfo[key] = val.call(deepestComponentWithMetaInfo)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return metaInfo
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,27 @@ describe('getComponentOption', () => {
|
|||||||
|
|
||||||
it('returns an empty object when no matching options are found', () => {
|
it('returns an empty object when no matching options are found', () => {
|
||||||
component = new Vue()
|
component = new Vue()
|
||||||
const { mergedOption } = getComponentOption({ component, option: 'noop' })
|
const mergedOption = getComponentOption({ component, option: 'noop' })
|
||||||
expect(mergedOption).to.eql({})
|
expect(mergedOption).to.eql({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('fetches the given option from the given component', () => {
|
it('fetches the given option from the given component', () => {
|
||||||
component = new Vue({ someOption: 'foo' })
|
component = new Vue({ someOption: 'foo' })
|
||||||
const { mergedOption } = getComponentOption({ component, option: 'someOption' })
|
const mergedOption = getComponentOption({ component, option: 'someOption' })
|
||||||
expect(mergedOption).to.eql('foo')
|
expect(mergedOption).to.eql('foo')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('calls a function option, injecting the component as context', () => {
|
||||||
|
component = new Vue({
|
||||||
|
name: 'foobar',
|
||||||
|
someFunc () {
|
||||||
|
return this.$options.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const mergedOption = getComponentOption({ component, option: 'someFunc' })
|
||||||
|
expect(mergedOption).to.eql('foobar')
|
||||||
|
})
|
||||||
|
|
||||||
it('fetches deeply nested component options and merges them', () => {
|
it('fetches deeply nested component options and merges them', () => {
|
||||||
Vue.component('merge-child', { template: '<div></div>', foo: { bar: 'baz' } })
|
Vue.component('merge-child', { template: '<div></div>', foo: { bar: 'baz' } })
|
||||||
|
|
||||||
@@ -28,7 +39,7 @@ describe('getComponentOption', () => {
|
|||||||
el: container
|
el: container
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mergedOption } = getComponentOption({ component, option: 'foo', deep: true })
|
const mergedOption = getComponentOption({ component, option: 'foo', deep: true })
|
||||||
expect(mergedOption).to.eql({ bar: 'baz', fizz: 'buzz' })
|
expect(mergedOption).to.eql({ bar: 'baz', fizz: 'buzz' })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -48,7 +59,7 @@ describe('getComponentOption', () => {
|
|||||||
el: container
|
el: container
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mergedOption } = getComponentOption({
|
const mergedOption = getComponentOption({
|
||||||
component,
|
component,
|
||||||
option: 'foo',
|
option: 'foo',
|
||||||
deep: true,
|
deep: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user