Skip to content

fix: meta content templates #429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions examples/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ app.use(express.static(__dirname))

app.use(async (req, res, next) => {
if (!req.url.startsWith('/ssr')) {
next()
return next()
}

try {
const html = await renderPage()
const context = { url: req.url }
const html = await renderPage(context)
res.send(html)
} catch (e) {
consola.error('SSR Oops:', e)
Expand Down
86 changes: 69 additions & 17 deletions examples/ssr/App.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,82 @@
import Vue from 'vue'
import Router from 'vue-router'
import VueMeta from '../../'

Vue.use(Router)
Vue.use(VueMeta, {
tagIDKeyName: 'hid'
})

export default function createApp () {
return new Vue({
components: {
Hello: {
template: '<p>Hello World</p>',
metaInfo: {
title: 'Hello World',
meta: [
{
hid: 'description',
name: 'description',
content: 'The description'
}
]
const Home = {
template: `<div>
<router-link to="/about">About</router-link>

<p>Hello World</p>
</div>`,
metaInfo: {
title: 'Hello World',
meta: [
{
hid: 'og:title',
name: 'og:title',
content: 'Hello World'
},
{
hid: 'description',
name: 'description',
content: 'Hello World'
}
}
},
]
}
}

const About = {
template: `<div>
<router-link to="/">Home</router-link>

<p>About</p>
</div>`,
metaInfo: {
title: 'About World',
meta: [
{
hid: 'og:title',
name: 'og:title',
content: 'About World'
},
{
hid: 'description',
name: 'description',
content: 'About World'
}
]
}
}

const router = new Router({
mode: 'history',
base: '/ssr',
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
})

const app = new Vue({
router,
metaInfo () {
return {
title: 'Boring Title',
htmlAttrs: { amp: true },
meta: [
{
skip: this.count < 1,
hid: 'og:title',
name: 'og:title',
template: chunk => `${chunk} - My Site`,
content: 'Default Title'
},
{
hid: 'description',
name: 'description',
Expand Down Expand Up @@ -73,8 +123,6 @@ export default function createApp () {
},
template: `
<div id="app">
<hello/>

<p>{{ count }} users loaded</p>

<ul>
Expand All @@ -85,6 +133,10 @@ export default function createApp () {
{{ user.id }}: {{ user.name }}
</li>
</ul>

<router-view />
</div>`
})

return { app, router }
}
3 changes: 2 additions & 1 deletion examples/ssr/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import createApp from './App'

window.users = []

createApp().$mount('#app')
const { app } = createApp()
app.$mount('#app')
29 changes: 21 additions & 8 deletions examples/ssr/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,27 @@ const compiled = template(templateContent, { interpolate: /{{([\s\S]+?)}}/g })

process.server = true

export async function renderPage () {
const app = await createApp()
const appHtml = await renderer.renderToString(app)
export async function renderPage ({ url }) {
const { app, router } = await createApp()

const pageHtml = compiled({
app: appHtml,
...app.$meta().inject()
})
router.push(url.substr(4))

return new Promise((resolve, reject) => {
router.onReady(async () => {
const matchedComponents = router.getMatchedComponents()
// no matched routes, reject with 404
if (!matchedComponents.length) {
return reject({ code: 404 })
}

const appHtml = await renderer.renderToString(app)

return pageHtml
const pageHtml = compiled({
app: appHtml,
...app.$meta().inject()
})

resolve(pageHtml)
})
})
}
4 changes: 2 additions & 2 deletions src/client/updaters/tag.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { booleanHtmlAttributes, commonDataAttributes } from '../../shared/constants'
import { booleanHtmlAttributes, commonDataAttributes, tagProperties } from '../../shared/constants'
import { includes } from '../../utils/array'
import { queryElements, getElementsKey } from '../../utils/elements.js'

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

for (const attr in tag) {
/* istanbul ignore next */
if (!tag.hasOwnProperty(attr)) {
if (!tag.hasOwnProperty(attr) || includes(tagProperties, attr)) {
continue
}

Expand Down
3 changes: 2 additions & 1 deletion src/server/generators/tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
tagsWithoutEndTag,
tagsWithInnerContent,
tagAttributeAsInnerContent,
tagProperties,
commonDataAttributes
} from '../../shared/constants'

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

Expand Down
2 changes: 2 additions & 0 deletions src/shared/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export const tagsWithInnerContent = ['noscript', 'script', 'style']
// Attributes which are inserted as childNodes instead of HTMLAttribute
export const tagAttributeAsInnerContent = ['innerHTML', 'cssText', 'json']

export const tagProperties = ['once', 'template']

// Attributes which should be added with data- prefix
export const commonDataAttributes = ['body', 'pbody']

Expand Down
19 changes: 1 addition & 18 deletions src/shared/getComponentOption.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { isFunction, isObject } from '../utils/is-type'
import { findIndex } from '../utils/array'
import { defaultInfo } from './constants'
import { merge } from './merge'
import { applyTemplate } from './template'
import { inMetaInfoBranch } from './meta-helpers'

export function getComponentMetaInfo (options = {}, component) {
Expand All @@ -24,7 +22,7 @@ export function getComponentMetaInfo (options = {}, component) {
* @return {Object} result - final aggregated result
*/
export function getComponentOption (options = {}, component, result = {}) {
const { keyName, metaTemplateKeyName, tagIDKeyName } = options
const { keyName } = options
const { $options, $children } = component

if (component._inactive) {
Expand Down Expand Up @@ -62,20 +60,5 @@ export function getComponentOption (options = {}, component, result = {}) {
})
}

if (metaTemplateKeyName && result.meta) {
// apply templates if needed
result.meta.forEach(metaObject => applyTemplate(options, metaObject))

// remove meta items with duplicate vmid's
result.meta = result.meta.filter((metaItem, index, arr) => {
return (
// keep meta item if it doesnt has a vmid
!metaItem.hasOwnProperty(tagIDKeyName) ||
// or if it's the first item in the array with this vmid
index === findIndex(arr, item => item[tagIDKeyName] === metaItem[tagIDKeyName])
)
})
}

return result
}
18 changes: 18 additions & 0 deletions src/shared/getMetaInfo.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { findIndex } from '../utils/array'
import { escapeMetaInfo } from '../shared/escaping'
import { applyTemplate } from './template'

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

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

if (info.meta) {
// remove meta items with duplicate vmid's
info.meta = info.meta.filter((metaItem, index, arr) => {
const hasVmid = metaItem.hasOwnProperty(tagIDKeyName)
if (!hasVmid) {
return true
}

const isFirstItemForVmid = index === findIndex(arr, item => item[tagIDKeyName] === metaItem[tagIDKeyName])
return isFirstItemForVmid
})

// apply templates if needed
info.meta.forEach(metaObject => applyTemplate(options, metaObject))
}

return escapeMetaInfo(options, info, escapeSequences)
}
15 changes: 12 additions & 3 deletions src/shared/merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export function arrayMerge ({ component, tagIDKeyName, metaTemplateKeyName, cont
// using an O(1) lookup associative array exploit
const destination = []

if (!target.length && !source.length) {
return destination
}

target.forEach((targetItem, targetIndex) => {
// no tagID so no need to check for duplicity
if (!targetItem[tagIDKeyName]) {
Expand Down Expand Up @@ -53,12 +57,17 @@ export function arrayMerge ({ component, tagIDKeyName, metaTemplateKeyName, cont
}

const sourceTemplate = sourceItem[metaTemplateKeyName]

if (!sourceTemplate) {
// use parent template and child content
applyTemplate({ component, metaTemplateKeyName, contentKeyName }, sourceItem, targetTemplate)
} else if (!sourceItem[contentKeyName]) {
// use child template and parent content

// set template to true to indicate template was already applied
sourceItem.template = true
return
}

if (!sourceItem[contentKeyName]) {
// use parent content and child template
applyTemplate({ component, metaTemplateKeyName, contentKeyName }, sourceItem, undefined, targetItem[contentKeyName])
}
})
Expand Down
14 changes: 12 additions & 2 deletions src/shared/template.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { isUndefined, isFunction } from '../utils/is-type'

export function applyTemplate ({ component, metaTemplateKeyName, contentKeyName }, headObject, template, chunk) {
if (isUndefined(template)) {
if (template === true || headObject[metaTemplateKeyName] === true) {
// abort, template was already applied
return false
}

if (isUndefined(template) && headObject[metaTemplateKeyName]) {
template = headObject[metaTemplateKeyName]
delete headObject[metaTemplateKeyName]
headObject[metaTemplateKeyName] = true
}

// return early if no template defined
if (!template) {
// cleanup faulty template properties
if (headObject.hasOwnProperty(metaTemplateKeyName)) {
delete headObject[metaTemplateKeyName]
}

return false
}

Expand Down
Loading