Skip to content

Commit

Permalink
fix: duplicate meta tags (#2164)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Sun Haoran and kefranabg committed Mar 23, 2020
1 parent ffca02a commit 01cd096
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 48 deletions.
1 change: 0 additions & 1 deletion packages/@vuepress/core/lib/client/index.ssr.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ title }}</title>
<meta name="description" content="{{ description }}">
<meta name="generator" content="VuePress {{ version }}">
{{{ userHeadTags }}}
{{{ pageMeta }}}
Expand Down
88 changes: 66 additions & 22 deletions packages/@vuepress/core/lib/client/root-mixins/updateMeta.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
import unionBy from 'lodash/unionBy'

export default {
// created will be called on both client and ssr
created () {
this.siteMeta = this.$site.headTags
.filter(([headerType]) => headerType === 'meta')
.map(([_, headerValue]) => headerValue)

if (this.$ssrContext) {
const mergedMetaItems = this.getMergedMetaTags()

this.$ssrContext.title = this.$title
this.$ssrContext.lang = this.$lang
this.$ssrContext.description = this.$page.description || this.$description
this.$ssrContext.pageMeta = renderPageMeta(mergedMetaItems)
}
},

// Other life cycles will only be called at client
mounted () {
// init currentMetaTags from DOM
this.currentMetaTags = [...document.querySelectorAll('meta')]

// update title / meta tags
this.currentMetaTags = new Set()
this.updateMeta()
},

methods: {
updateMeta () {
document.title = this.$title
document.documentElement.lang = this.$lang
const userMeta = this.$page.frontmatter.meta || []
const meta = userMeta.slice(0)
const useGlobalDescription = userMeta.filter(m => m.name === 'description').length === 0

// #665 Avoid duplicate description meta at runtime.
if (useGlobalDescription) {
meta.push({ name: 'description', content: this.$description })
}

// Including description meta coming from SSR.
const descriptionMetas = document.querySelectorAll('meta[name="description"]')
if (descriptionMetas.length) {
descriptionMetas.forEach(m => this.currentMetaTags.add(m))
}
const newMetaTags = this.getMergedMetaTags()
this.currentMetaTags = updateMetaTags(newMetaTags, this.currentMetaTags)
},

this.currentMetaTags = new Set(updateMetaTags(meta, this.currentMetaTags))
getMergedMetaTags () {
const pageMeta = this.$page.frontmatter.meta || []
// pageMetaTags have higher priority than siteMetaTags
// description needs special attention as it has too many entries
return unionBy([{ name: 'description', content: this.$description }],
pageMeta, this.siteMeta, metaIdentifier)
}
},

Expand All @@ -47,14 +53,20 @@ export default {
}
}

function updateMetaTags (meta, current) {
if (current) {
[...current].forEach(c => {
/**
* Replace currentMetaTags with newMetaTags
* @param {Array<Object>} newMetaTags
* @param {Array<HTMLElement>} currentMetaTags
* @returns {Array<HTMLElement>}
*/
function updateMetaTags (newMetaTags, currentMetaTags) {
if (currentMetaTags) {
[...currentMetaTags].forEach(c => {
document.head.removeChild(c)
})
}
if (meta) {
return meta.map(m => {
if (newMetaTags) {
return newMetaTags.map(m => {
const tag = document.createElement('meta')
Object.keys(m).forEach(key => {
tag.setAttribute(key, m[key])
Expand All @@ -64,3 +76,35 @@ function updateMetaTags (meta, current) {
})
}
}

/**
* Try to identify a meta tag by name, property or itemprop
*
* Return a complete string if none provided
* @param {Object} tag from frontmatter or siteMetaTags
* @returns {String}
*/
function metaIdentifier (tag) {
for (const item of ['name', 'property', 'itemprop']) {
if (tag.hasOwnProperty(item)) return tag[item] + item
}
return JSON.stringify(tag)
}

/**
* Render meta tags
*
* @param {Array} meta
* @returns {Array<string>}
*/

function renderPageMeta (meta) {
if (!meta) return ''
return meta.map(m => {
let res = `<meta`
Object.keys(m).forEach(key => {
res += ` ${key}="${m[key]}"`
})
return res + `>`
}).join('\n ')
}
2 changes: 1 addition & 1 deletion packages/@vuepress/core/lib/node/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ module.exports = class App {
title: this.siteConfig.title || '',
description: this.siteConfig.description || '',
base: this.base,
headTags: this.siteConfig.head || [],
pages: this.pages.map(page => page.toJson()),
themeConfig: this.siteConfig.themeConfig || {},
locales
Expand Down Expand Up @@ -499,4 +500,3 @@ module.exports = class App {
return this
}
}

27 changes: 3 additions & 24 deletions packages/@vuepress/core/lib/node/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ module.exports = class Build extends EventEmitter {
})

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

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

// #565 Avoid duplicate description meta at SSR.
const meta = (page.frontmatter && page.frontmatter.meta || []).filter(item => item.name !== 'description')
const pageMeta = renderPageMeta(meta)

const context = {
url: page.path,
userHeadTags: this.userHeadTags,
pageMeta,
title: 'VuePress',
lang: 'en',
description: '',
Expand Down Expand Up @@ -221,24 +218,6 @@ function renderAttrs (attrs = {}) {
}
}

/**
* Render meta tags
*
* @param {Array} meta
* @returns {Array<string>}
*/

function renderPageMeta (meta) {
if (!meta) return ''
return meta.map(m => {
let res = `<meta`
Object.keys(m).forEach(key => {
res += ` ${key}="${escape(m[key])}"`
})
return res + `>`
}).join('')
}

/**
* find and remove empty style chunk caused by
* https://github.com/webpack-contrib/mini-css-extract-plugin/issues/85
Expand Down

0 comments on commit 01cd096

Please sign in to comment.