1+ import unionBy from 'lodash/unionBy'
2+
13export 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+ }
0 commit comments