Skip to content

Commit 6907f9a

Browse files
authored
fix: meta content templates (#429)
* examples: add content templates to ssr example * fix: improve meta content templates * chore: cleanup debug helper * refactor: split long if into variables
1 parent 66d98ee commit 6907f9a

File tree

12 files changed

+188
-73
lines changed

12 files changed

+188
-73
lines changed

examples/server.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ app.use(express.static(__dirname))
3232

3333
app.use(async (req, res, next) => {
3434
if (!req.url.startsWith('/ssr')) {
35-
next()
35+
return next()
3636
}
3737

3838
try {
39-
const html = await renderPage()
39+
const context = { url: req.url }
40+
const html = await renderPage(context)
4041
res.send(html)
4142
} catch (e) {
4243
consola.error('SSR Oops:', e)

examples/ssr/App.js

+69-17
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,82 @@
11
import Vue from 'vue'
2+
import Router from 'vue-router'
23
import VueMeta from '../../'
34

5+
Vue.use(Router)
46
Vue.use(VueMeta, {
57
tagIDKeyName: 'hid'
68
})
79

810
export default function createApp () {
9-
return new Vue({
10-
components: {
11-
Hello: {
12-
template: '<p>Hello World</p>',
13-
metaInfo: {
14-
title: 'Hello World',
15-
meta: [
16-
{
17-
hid: 'description',
18-
name: 'description',
19-
content: 'The description'
20-
}
21-
]
11+
const Home = {
12+
template: `<div>
13+
<router-link to="/about">About</router-link>
14+
15+
<p>Hello World</p>
16+
</div>`,
17+
metaInfo: {
18+
title: 'Hello World',
19+
meta: [
20+
{
21+
hid: 'og:title',
22+
name: 'og:title',
23+
content: 'Hello World'
24+
},
25+
{
26+
hid: 'description',
27+
name: 'description',
28+
content: 'Hello World'
2229
}
23-
}
24-
},
30+
]
31+
}
32+
}
33+
34+
const About = {
35+
template: `<div>
36+
<router-link to="/">Home</router-link>
37+
38+
<p>About</p>
39+
</div>`,
40+
metaInfo: {
41+
title: 'About World',
42+
meta: [
43+
{
44+
hid: 'og:title',
45+
name: 'og:title',
46+
content: 'About World'
47+
},
48+
{
49+
hid: 'description',
50+
name: 'description',
51+
content: 'About World'
52+
}
53+
]
54+
}
55+
}
56+
57+
const router = new Router({
58+
mode: 'history',
59+
base: '/ssr',
60+
routes: [
61+
{ path: '/', component: Home },
62+
{ path: '/about', component: About }
63+
]
64+
})
65+
66+
const app = new Vue({
67+
router,
2568
metaInfo () {
2669
return {
2770
title: 'Boring Title',
2871
htmlAttrs: { amp: true },
2972
meta: [
73+
{
74+
skip: this.count < 1,
75+
hid: 'og:title',
76+
name: 'og:title',
77+
template: chunk => `${chunk} - My Site`,
78+
content: 'Default Title'
79+
},
3080
{
3181
hid: 'description',
3282
name: 'description',
@@ -73,8 +123,6 @@ export default function createApp () {
73123
},
74124
template: `
75125
<div id="app">
76-
<hello/>
77-
78126
<p>{{ count }} users loaded</p>
79127
80128
<ul>
@@ -85,6 +133,10 @@ export default function createApp () {
85133
{{ user.id }}: {{ user.name }}
86134
</li>
87135
</ul>
136+
137+
<router-view />
88138
</div>`
89139
})
140+
141+
return { app, router }
90142
}

examples/ssr/browser.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ import createApp from './App'
22

33
window.users = []
44

5-
createApp().$mount('#app')
5+
const { app } = createApp()
6+
app.$mount('#app')

examples/ssr/server.js

+21-8
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,27 @@ const compiled = template(templateContent, { interpolate: /{{([\s\S]+?)}}/g })
1414

1515
process.server = true
1616

17-
export async function renderPage () {
18-
const app = await createApp()
19-
const appHtml = await renderer.renderToString(app)
17+
export async function renderPage ({ url }) {
18+
const { app, router } = await createApp()
2019

21-
const pageHtml = compiled({
22-
app: appHtml,
23-
...app.$meta().inject()
24-
})
20+
router.push(url.substr(4))
21+
22+
return new Promise((resolve, reject) => {
23+
router.onReady(async () => {
24+
const matchedComponents = router.getMatchedComponents()
25+
// no matched routes, reject with 404
26+
if (!matchedComponents.length) {
27+
return reject({ code: 404 })
28+
}
29+
30+
const appHtml = await renderer.renderToString(app)
2531

26-
return pageHtml
32+
const pageHtml = compiled({
33+
app: appHtml,
34+
...app.$meta().inject()
35+
})
36+
37+
resolve(pageHtml)
38+
})
39+
})
2740
}

src/client/updaters/tag.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { booleanHtmlAttributes, commonDataAttributes } from '../../shared/constants'
1+
import { booleanHtmlAttributes, commonDataAttributes, tagProperties } from '../../shared/constants'
22
import { includes } from '../../utils/array'
33
import { queryElements, getElementsKey } from '../../utils/elements.js'
44

@@ -48,7 +48,7 @@ export default function updateTag (appId, options = {}, type, tags, head, body)
4848

4949
for (const attr in tag) {
5050
/* istanbul ignore next */
51-
if (!tag.hasOwnProperty(attr)) {
51+
if (!tag.hasOwnProperty(attr) || includes(tagProperties, attr)) {
5252
continue
5353
}
5454

src/server/generators/tag.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
tagsWithoutEndTag,
44
tagsWithInnerContent,
55
tagAttributeAsInnerContent,
6+
tagProperties,
67
commonDataAttributes
78
} from '../../shared/constants'
89

@@ -43,7 +44,7 @@ export default function tagGenerator ({ ssrAppId, attribute, tagIDKeyName } = {}
4344
// build a string containing all attributes of this tag
4445
for (const attr in tag) {
4546
// these attributes are treated as children on the tag
46-
if (tagAttributeAsInnerContent.includes(attr) || attr === 'once') {
47+
if (tagAttributeAsInnerContent.includes(attr) || tagProperties.includes(attr)) {
4748
continue
4849
}
4950

src/shared/constants.js

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export const tagsWithInnerContent = ['noscript', 'script', 'style']
9292
// Attributes which are inserted as childNodes instead of HTMLAttribute
9393
export const tagAttributeAsInnerContent = ['innerHTML', 'cssText', 'json']
9494

95+
export const tagProperties = ['once', 'template']
96+
9597
// Attributes which should be added with data- prefix
9698
export const commonDataAttributes = ['body', 'pbody']
9799

src/shared/getComponentOption.js

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { isFunction, isObject } from '../utils/is-type'
2-
import { findIndex } from '../utils/array'
32
import { defaultInfo } from './constants'
43
import { merge } from './merge'
5-
import { applyTemplate } from './template'
64
import { inMetaInfoBranch } from './meta-helpers'
75

86
export function getComponentMetaInfo (options = {}, component) {
@@ -24,7 +22,7 @@ export function getComponentMetaInfo (options = {}, component) {
2422
* @return {Object} result - final aggregated result
2523
*/
2624
export function getComponentOption (options = {}, component, result = {}) {
27-
const { keyName, metaTemplateKeyName, tagIDKeyName } = options
25+
const { keyName } = options
2826
const { $options, $children } = component
2927

3028
if (component._inactive) {
@@ -62,20 +60,5 @@ export function getComponentOption (options = {}, component, result = {}) {
6260
})
6361
}
6462

65-
if (metaTemplateKeyName && result.meta) {
66-
// apply templates if needed
67-
result.meta.forEach(metaObject => applyTemplate(options, metaObject))
68-
69-
// remove meta items with duplicate vmid's
70-
result.meta = result.meta.filter((metaItem, index, arr) => {
71-
return (
72-
// keep meta item if it doesnt has a vmid
73-
!metaItem.hasOwnProperty(tagIDKeyName) ||
74-
// or if it's the first item in the array with this vmid
75-
index === findIndex(arr, item => item[tagIDKeyName] === metaItem[tagIDKeyName])
76-
)
77-
})
78-
}
79-
8063
return result
8164
}

src/shared/getMetaInfo.js

+18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { findIndex } from '../utils/array'
12
import { escapeMetaInfo } from '../shared/escaping'
23
import { applyTemplate } from './template'
34

@@ -9,6 +10,7 @@ import { applyTemplate } from './template'
910
* @return {Object} - returned meta info
1011
*/
1112
export default function getMetaInfo (options = {}, info, escapeSequences = [], component) {
13+
const { tagIDKeyName } = options
1214
// Remove all "template" tags from meta
1315

1416
// backup the title chunk in case user wants access to it
@@ -27,5 +29,21 @@ export default function getMetaInfo (options = {}, info, escapeSequences = [], c
2729
info.base = Object.keys(info.base).length ? [info.base] : []
2830
}
2931

32+
if (info.meta) {
33+
// remove meta items with duplicate vmid's
34+
info.meta = info.meta.filter((metaItem, index, arr) => {
35+
const hasVmid = metaItem.hasOwnProperty(tagIDKeyName)
36+
if (!hasVmid) {
37+
return true
38+
}
39+
40+
const isFirstItemForVmid = index === findIndex(arr, item => item[tagIDKeyName] === metaItem[tagIDKeyName])
41+
return isFirstItemForVmid
42+
})
43+
44+
// apply templates if needed
45+
info.meta.forEach(metaObject => applyTemplate(options, metaObject))
46+
}
47+
3048
return escapeMetaInfo(options, info, escapeSequences)
3149
}

src/shared/merge.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export function arrayMerge ({ component, tagIDKeyName, metaTemplateKeyName, cont
1010
// using an O(1) lookup associative array exploit
1111
const destination = []
1212

13+
if (!target.length && !source.length) {
14+
return destination
15+
}
16+
1317
target.forEach((targetItem, targetIndex) => {
1418
// no tagID so no need to check for duplicity
1519
if (!targetItem[tagIDKeyName]) {
@@ -53,12 +57,17 @@ export function arrayMerge ({ component, tagIDKeyName, metaTemplateKeyName, cont
5357
}
5458

5559
const sourceTemplate = sourceItem[metaTemplateKeyName]
56-
5760
if (!sourceTemplate) {
5861
// use parent template and child content
5962
applyTemplate({ component, metaTemplateKeyName, contentKeyName }, sourceItem, targetTemplate)
60-
} else if (!sourceItem[contentKeyName]) {
61-
// use child template and parent content
63+
64+
// set template to true to indicate template was already applied
65+
sourceItem.template = true
66+
return
67+
}
68+
69+
if (!sourceItem[contentKeyName]) {
70+
// use parent content and child template
6271
applyTemplate({ component, metaTemplateKeyName, contentKeyName }, sourceItem, undefined, targetItem[contentKeyName])
6372
}
6473
})

src/shared/template.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
import { isUndefined, isFunction } from '../utils/is-type'
22

33
export function applyTemplate ({ component, metaTemplateKeyName, contentKeyName }, headObject, template, chunk) {
4-
if (isUndefined(template)) {
4+
if (template === true || headObject[metaTemplateKeyName] === true) {
5+
// abort, template was already applied
6+
return false
7+
}
8+
9+
if (isUndefined(template) && headObject[metaTemplateKeyName]) {
510
template = headObject[metaTemplateKeyName]
6-
delete headObject[metaTemplateKeyName]
11+
headObject[metaTemplateKeyName] = true
712
}
813

914
// return early if no template defined
1015
if (!template) {
16+
// cleanup faulty template properties
17+
if (headObject.hasOwnProperty(metaTemplateKeyName)) {
18+
delete headObject[metaTemplateKeyName]
19+
}
20+
1121
return false
1222
}
1323

0 commit comments

Comments
 (0)