Skip to content

Commit 01cd096

Browse files
Sun Haorankefranabg
Sun Haoran
andauthoredMar 23, 2020
fix: duplicate meta tags (#2164)
* fix: duplicate meta in ssr * fix: page metas have higher priority * Revert "fix: duplicate meta in ssr" This reverts commit 5a02e2a. * fix: render meta tags during ssr * improve readability from suggestions Co-Authored-By: Franck Abgrall <abgrallkefran@gmail.com> * fix: missing spaces * refactor: remove unnecessary code * fix: siteMetaTags aren't correctly init Previous method will init siteMetaTags with entry page meta tags instead of site meta tags Co-authored-by: Franck Abgrall <abgrallkefran@gmail.com>
1 parent ffca02a commit 01cd096

File tree

4 files changed

+70
-48
lines changed

4 files changed

+70
-48
lines changed
 

‎packages/@vuepress/core/lib/client/index.ssr.html

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
<meta charset="utf-8">
55
<meta name="viewport" content="width=device-width,initial-scale=1">
66
<title>{{ title }}</title>
7-
<meta name="description" content="{{ description }}">
87
<meta name="generator" content="VuePress {{ version }}">
98
{{{ userHeadTags }}}
109
{{{ pageMeta }}}
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
1+
import unionBy from 'lodash/unionBy'
2+
13
export default {
4+
// created will be called on both client and ssr
25
created () {
6+
this.siteMeta = this.$site.headTags
7+
.filter(([headerType]) => headerType === 'meta')
8+
.map(([_, headerValue]) => headerValue)
9+
310
if (this.$ssrContext) {
11+
const mergedMetaItems = this.getMergedMetaTags()
12+
413
this.$ssrContext.title = this.$title
514
this.$ssrContext.lang = this.$lang
6-
this.$ssrContext.description = this.$page.description || this.$description
15+
this.$ssrContext.pageMeta = renderPageMeta(mergedMetaItems)
716
}
817
},
9-
18+
// Other life cycles will only be called at client
1019
mounted () {
20+
// init currentMetaTags from DOM
21+
this.currentMetaTags = [...document.querySelectorAll('meta')]
22+
1123
// update title / meta tags
12-
this.currentMetaTags = new Set()
1324
this.updateMeta()
1425
},
1526

1627
methods: {
1728
updateMeta () {
1829
document.title = this.$title
1930
document.documentElement.lang = this.$lang
20-
const userMeta = this.$page.frontmatter.meta || []
21-
const meta = userMeta.slice(0)
22-
const useGlobalDescription = userMeta.filter(m => m.name === 'description').length === 0
23-
24-
// #665 Avoid duplicate description meta at runtime.
25-
if (useGlobalDescription) {
26-
meta.push({ name: 'description', content: this.$description })
27-
}
2831

29-
// Including description meta coming from SSR.
30-
const descriptionMetas = document.querySelectorAll('meta[name="description"]')
31-
if (descriptionMetas.length) {
32-
descriptionMetas.forEach(m => this.currentMetaTags.add(m))
33-
}
32+
const newMetaTags = this.getMergedMetaTags()
33+
this.currentMetaTags = updateMetaTags(newMetaTags, this.currentMetaTags)
34+
},
3435

35-
this.currentMetaTags = new Set(updateMetaTags(meta, this.currentMetaTags))
36+
getMergedMetaTags () {
37+
const pageMeta = this.$page.frontmatter.meta || []
38+
// pageMetaTags have higher priority than siteMetaTags
39+
// description needs special attention as it has too many entries
40+
return unionBy([{ name: 'description', content: this.$description }],
41+
pageMeta, this.siteMeta, metaIdentifier)
3642
}
3743
},
3844

@@ -47,14 +53,20 @@ export default {
4753
}
4854
}
4955

50-
function updateMetaTags (meta, current) {
51-
if (current) {
52-
[...current].forEach(c => {
56+
/**
57+
* Replace currentMetaTags with newMetaTags
58+
* @param {Array<Object>} newMetaTags
59+
* @param {Array<HTMLElement>} currentMetaTags
60+
* @returns {Array<HTMLElement>}
61+
*/
62+
function updateMetaTags (newMetaTags, currentMetaTags) {
63+
if (currentMetaTags) {
64+
[...currentMetaTags].forEach(c => {
5365
document.head.removeChild(c)
5466
})
5567
}
56-
if (meta) {
57-
return meta.map(m => {
68+
if (newMetaTags) {
69+
return newMetaTags.map(m => {
5870
const tag = document.createElement('meta')
5971
Object.keys(m).forEach(key => {
6072
tag.setAttribute(key, m[key])
@@ -64,3 +76,35 @@ function updateMetaTags (meta, current) {
6476
})
6577
}
6678
}
79+
80+
/**
81+
* Try to identify a meta tag by name, property or itemprop
82+
*
83+
* Return a complete string if none provided
84+
* @param {Object} tag from frontmatter or siteMetaTags
85+
* @returns {String}
86+
*/
87+
function metaIdentifier (tag) {
88+
for (const item of ['name', 'property', 'itemprop']) {
89+
if (tag.hasOwnProperty(item)) return tag[item] + item
90+
}
91+
return JSON.stringify(tag)
92+
}
93+
94+
/**
95+
* Render meta tags
96+
*
97+
* @param {Array} meta
98+
* @returns {Array<string>}
99+
*/
100+
101+
function renderPageMeta (meta) {
102+
if (!meta) return ''
103+
return meta.map(m => {
104+
let res = `<meta`
105+
Object.keys(m).forEach(key => {
106+
res += ` ${key}="${m[key]}"`
107+
})
108+
return res + `>`
109+
}).join('\n ')
110+
}

‎packages/@vuepress/core/lib/node/App.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ module.exports = class App {
438438
title: this.siteConfig.title || '',
439439
description: this.siteConfig.description || '',
440440
base: this.base,
441+
headTags: this.siteConfig.head || [],
441442
pages: this.pages.map(page => page.toJson()),
442443
themeConfig: this.siteConfig.themeConfig || {},
443444
locales
@@ -499,4 +500,3 @@ module.exports = class App {
499500
return this
500501
}
501502
}
502-

‎packages/@vuepress/core/lib/node/build/index.js

+3-24
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ module.exports = class Build extends EventEmitter {
7777
})
7878

7979
// pre-render head tags from user config
80+
// filter out meta tags for they will be injected in updateMeta.js
8081
this.userHeadTags = (this.context.siteConfig.head || [])
82+
.filter(([headTagType]) => headTagType !== 'meta')
8183
.map(renderHeadTag)
82-
.join('\n ')
84+
.join('\n ')
8385

8486
// if the user does not have a custom 404.md, generate the theme's default
8587
if (!this.context.pages.some(p => p.path === '/404.html')) {
@@ -134,14 +136,9 @@ module.exports = class Build extends EventEmitter {
134136
async renderPage (page) {
135137
const pagePath = decodeURIComponent(page.path)
136138

137-
// #565 Avoid duplicate description meta at SSR.
138-
const meta = (page.frontmatter && page.frontmatter.meta || []).filter(item => item.name !== 'description')
139-
const pageMeta = renderPageMeta(meta)
140-
141139
const context = {
142140
url: page.path,
143141
userHeadTags: this.userHeadTags,
144-
pageMeta,
145142
title: 'VuePress',
146143
lang: 'en',
147144
description: '',
@@ -221,24 +218,6 @@ function renderAttrs (attrs = {}) {
221218
}
222219
}
223220

224-
/**
225-
* Render meta tags
226-
*
227-
* @param {Array} meta
228-
* @returns {Array<string>}
229-
*/
230-
231-
function renderPageMeta (meta) {
232-
if (!meta) return ''
233-
return meta.map(m => {
234-
let res = `<meta`
235-
Object.keys(m).forEach(key => {
236-
res += ` ${key}="${escape(m[key])}"`
237-
})
238-
return res + `>`
239-
}).join('')
240-
}
241-
242221
/**
243222
* find and remove empty style chunk caused by
244223
* https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85

0 commit comments

Comments
 (0)
Please sign in to comment.