Skip to content

Commit

Permalink
fix: meta content templates (#429)
Browse files Browse the repository at this point in the history
* examples: add content templates to ssr example

* fix: improve meta content templates

* chore: cleanup debug helper

* refactor: split long if into variables
  • Loading branch information
pimlie authored Aug 3, 2019
1 parent 66d98ee commit 6907f9a
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 73 deletions.
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

0 comments on commit 6907f9a

Please sign in to comment.