Use vue-meta for meta tags

This commit is contained in:
Sebastien Chopin 2017-08-21 11:38:21 +02:00
parent e34d9d69bb
commit 39f431efdf
4 changed files with 123 additions and 64 deletions

View File

@ -151,24 +151,6 @@ export function flatRoutes (router, path = '', routes = []) {
return routes return routes
} }
export function attrsStr (attrObj = {}, exclude = []) {
return Object.keys(attrObj)
.filter(attr => !exclude.includes(attr))
.map(attr => {
if (typeof attrObj[attr] !== 'string') {
return attr
}
const val = attrObj[attr].replace('"', '\'')
if (attr === 'hid') {
attr = 'data-hid'
}
return `${attr}="${val}"`
}).join(' ')
}
export function cleanChildrenRoutes (routes, isChild = false) { export function cleanChildrenRoutes (routes, isChild = false) {
let start = -1 let start = -1
let routesIndex = [] let routesIndex = []

View File

@ -1,5 +1,7 @@
import { attrsStr } from 'utils' import Vue from 'vue'
import VueMeta from 'vue-meta'
import VueServerRenderer from 'vue-server-renderer'
import LRU from 'lru-cache' import LRU from 'lru-cache'
export default class MetaRenderer { export default class MetaRenderer {
@ -7,68 +9,70 @@ export default class MetaRenderer {
this.nuxt = nuxt this.nuxt = nuxt
this.renderer = renderer this.renderer = renderer
this.options = nuxt.options this.options = nuxt.options
this.vueRenderer = VueServerRenderer.createRenderer()
this.cache = LRU({}) this.cache = LRU({})
// Add VueMeta to Vue (this is only for SPA mode)
// See lib/app/index.js
Vue.use(VueMeta, {
keyName: 'head',
attribute: 'data-n-head',
ssrAttribute: 'data-n-head-ssr',
tagIDKeyName: 'hid'
})
} }
render ({ url = '/' }) { getMeta(url) {
let head = this.cache.get(url) return new Promise((resolve, reject) => {
const vm = new Vue({
if (head) { render: (h) => h(), // Render empty html tag
return head head: this.options.head || {}
}
head = ''
// Title
if (typeof this.options.head.title === 'string') {
head += `<title data-n-head="true">${this.options.head.title || ''}</title>`
}
// Meta
if (Array.isArray(this.options.head.meta)) {
this.options.head.meta.forEach(meta => {
head += `<meta data-n-head="true" ${attrsStr(meta)}/>`
}) })
} this.vueRenderer.renderToString(vm, (err) => {
if (err) return reject(err)
// Links resolve(vm.$meta().inject())
if (Array.isArray(this.options.head.link)) {
this.options.head.link.forEach(link => {
head += `<link data-n-head="true" ${attrsStr(link)}/>`
}) })
})
}
async render ({ url = '/' }) {
let meta = this.cache.get(url)
if (meta) {
return meta
} }
// Style meta = {
if (Array.isArray(this.options.head.style)) { HTML_ATTRS: '',
this.options.head.style.forEach(style => { BODY_ATTRS: '',
head += `<style data-n-head="true" ${attrsStr(style, ['cssText'])}>${style.cssText || ''}</style>` HEAD: ''
})
} }
// Get vue-meta context
// Script const m = await this.getMeta(url)
if (Array.isArray(this.options.head.script)) { // HTML_ATTRS
this.options.head.script.forEach(script => { meta.HTML_ATTRS = 'data-n-head-ssr ' + m.htmlAttrs.text()
head += `<script data-n-head="true" ${attrsStr(script, ['innerHTML'])}>${script.innerHTML || ''}</script>` // BODY_ATTRS
}) meta.BODY_ATTRS = m.bodyAttrs.text()
} // HEAD tags
meta.HEAD = m.meta.text() + m.title.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
// Resource Hints // Resource Hints
const clientManifest = this.renderer.resources.clientManifest const clientManifest = this.renderer.resources.clientManifest
if (this.options.render.resourceHints && clientManifest) { if (this.options.render.resourceHints && clientManifest) {
const publicPath = clientManifest.publicPath || '/_nuxt/' const publicPath = clientManifest.publicPath || '/_nuxt/'
// Pre-Load initial resources // Pre-Load initial resources
if (Array.isArray(clientManifest.initial)) { if (Array.isArray(clientManifest.initial)) {
head += clientManifest.initial.map(r => `<link rel="preload" href="${publicPath}${r}" as="script" />`).join('') meta.HEAD += clientManifest.initial.map(r => `<link rel="preload" href="${publicPath}${r}" as="script" />`).join('')
} }
// Pre-Fetch async resources // Pre-Fetch async resources
if (Array.isArray(clientManifest.async)) { if (Array.isArray(clientManifest.async)) {
head += clientManifest.async.map(r => `<link rel="prefetch" href="${publicPath}${r}" />`).join('') meta.HEAD += clientManifest.async.map(r => `<link rel="prefetch" href="${publicPath}${r}" />`).join('')
} }
} }
this.cache.set(url, head) // Set meta tags inside cache
this.cache.set(url, meta)
return head return meta
} }
} }

74
lib/core/meta.old.js Normal file
View File

@ -0,0 +1,74 @@
import { attrsStr } from 'utils'
import LRU from 'lru-cache'
export default class MetaRenderer {
constructor (nuxt, renderer) {
this.nuxt = nuxt
this.renderer = renderer
this.options = nuxt.options
this.cache = LRU({})
}
render ({ url = '/' }) {
let head = this.cache.get(url)
if (head) {
return head
}
head = ''
// Title
if (typeof this.options.head.title === 'string') {
head += `<title data-n-head="true">${this.options.head.title || ''}</title>`
}
// Meta
if (Array.isArray(this.options.head.meta)) {
this.options.head.meta.forEach(meta => {
head += `<meta data-n-head="true" ${attrsStr(meta)}/>`
})
}
// Links
if (Array.isArray(this.options.head.link)) {
this.options.head.link.forEach(link => {
head += `<link data-n-head="true" ${attrsStr(link)}/>`
})
}
// Style
if (Array.isArray(this.options.head.style)) {
this.options.head.style.forEach(style => {
head += `<style data-n-head="true" ${attrsStr(style, ['cssText'])}>${style.cssText || ''}</style>`
})
}
// Script
if (Array.isArray(this.options.head.script)) {
this.options.head.script.forEach(script => {
head += `<script data-n-head="true" ${attrsStr(script, ['innerHTML'])}>${script.innerHTML || ''}</script>`
})
}
// Resource Hints
const clientManifest = this.renderer.resources.clientManifest
if (this.options.render.resourceHints && clientManifest) {
const publicPath = clientManifest.publicPath || '/_nuxt/'
// Pre-Load initial resources
if (Array.isArray(clientManifest.initial)) {
head += clientManifest.initial.map(r => `<link rel="preload" href="${publicPath}${r}" as="script" />`).join('')
}
// Pre-Fetch async resources
if (Array.isArray(clientManifest.async)) {
head += clientManifest.async.map(r => `<link rel="prefetch" href="${publicPath}${r}" />`).join('')
}
}
this.cache.set(url, head)
return head
}
}

View File

@ -10,7 +10,7 @@ import _ from 'lodash'
import { join, resolve } from 'path' import { join, resolve } from 'path'
import fs from 'fs-extra' import fs from 'fs-extra'
import { createBundleRenderer } from 'vue-server-renderer' import { createBundleRenderer } from 'vue-server-renderer'
import { getContext, setAnsiColors, isUrl, attrsStr } from 'utils' import { getContext, setAnsiColors, isUrl } from 'utils'
import Debug from 'debug' import Debug from 'debug'
import Youch from '@nuxtjs/youch' import Youch from '@nuxtjs/youch'
import { SourceMapConsumer } from 'source-map' import { SourceMapConsumer } from 'source-map'
@ -446,13 +446,12 @@ export default class Renderer extends Tapable {
// Basic response if SSR is disabled or spa data provided // Basic response if SSR is disabled or spa data provided
const spa = context.spa || (context.res && context.res.spa) const spa = context.spa || (context.res && context.res.spa)
if (this.noSSR || spa) { if (this.noSSR || spa) {
const HEAD = this.metaRenderer.render(context) const { HTML_ATTRS, BODY_ATTRS, HEAD } = await this.metaRenderer.render(context)
const HTML_ATTRS = attrsStr(this.options.head.htmlAttrs)
const APP = `<div id="__nuxt">${this.resources.loadingHTML}</div>` const APP = `<div id="__nuxt">${this.resources.loadingHTML}</div>`
const data = { const data = {
HTML_ATTRS, HTML_ATTRS,
BODY_ATTRS: '', BODY_ATTRS,
HEAD, HEAD,
APP APP
} }