Nuxt/packages/vue-renderer/src/spa-meta.js

148 lines
4.0 KiB
JavaScript
Raw Normal View History

2018-11-21 13:08:03 +00:00
import { extname } from 'path'
2018-03-16 16:12:06 +00:00
import Vue from 'vue'
import VueMeta from 'vue-meta'
import { createRenderer } from 'vue-server-renderer'
2018-03-16 16:12:06 +00:00
import LRU from 'lru-cache'
2017-08-18 16:05:01 +00:00
export default class SPAMetaRenderer {
constructor(renderer) {
this.renderer = renderer
this.options = this.renderer.context.options
this.vueRenderer = createRenderer()
2018-11-21 13:32:13 +00:00
this.cache = new LRU()
2017-08-18 16:05:01 +00:00
2017-08-21 09:38:21 +00:00
// Add VueMeta to Vue (this is only for SPA mode)
// See app/index.js
2017-08-21 09:38:21 +00:00
Vue.use(VueMeta, {
keyName: 'head',
attribute: 'data-n-head',
ssrAttribute: 'data-n-head-ssr',
tagIDKeyName: 'hid'
})
}
2017-08-18 16:05:01 +00:00
2017-11-24 04:11:52 +00:00
async getMeta(url) {
const vm = new Vue({
render: h => h(), // Render empty html tag
head: this.options.head || {}
2017-08-21 09:38:21 +00:00
})
2017-11-24 04:11:52 +00:00
await this.vueRenderer.renderToString(vm)
return vm.$meta().inject()
2017-08-21 09:38:21 +00:00
}
2017-08-18 16:05:01 +00:00
async render({ url = '/' }) {
2017-08-21 09:38:21 +00:00
let meta = this.cache.get(url)
2017-08-18 16:05:01 +00:00
2017-08-21 09:38:21 +00:00
if (meta) {
return meta
2017-08-18 16:05:01 +00:00
}
2017-08-21 09:38:21 +00:00
meta = {
HTML_ATTRS: '',
BODY_ATTRS: '',
HEAD: '',
BODY_SCRIPTS: ''
2017-08-21 09:38:21 +00:00
}
2017-08-21 09:38:21 +00:00
// Get vue-meta context
const m = await this.getMeta(url)
2017-08-21 09:38:21 +00:00
// HTML_ATTRS
2017-09-05 09:04:59 +00:00
meta.HTML_ATTRS = m.htmlAttrs.text()
2017-08-21 09:38:21 +00:00
// BODY_ATTRS
meta.BODY_ATTRS = m.bodyAttrs.text()
2017-08-21 09:38:21 +00:00
// HEAD tags
2018-01-13 05:22:11 +00:00
meta.HEAD =
m.title.text() +
m.meta.text() +
2018-01-13 05:22:11 +00:00
m.link.text() +
m.style.text() +
m.script.text() +
m.noscript.text()
// BODY_SCRIPTS
2018-01-23 10:42:25 +00:00
meta.BODY_SCRIPTS = m.script.text({ body: true }) + m.noscript.text({ body: true })
2017-08-30 12:47:07 +00:00
// Resources Hints
2017-08-30 12:47:07 +00:00
meta.resourceHints = ''
const clientManifest = this.renderer.context.resources.clientManifest
2018-11-21 13:08:03 +00:00
const shouldPreload = this.options.render.bundleRenderer.shouldPreload
const shouldPrefetch = this.options.render.bundleRenderer.shouldPrefetch
if (this.options.render.resourceHints && clientManifest) {
const publicPath = clientManifest.publicPath || '/_nuxt/'
// Preload initial resources
if (Array.isArray(clientManifest.initial)) {
2018-01-13 05:22:11 +00:00
meta.resourceHints += clientManifest.initial
2018-11-21 13:08:03 +00:00
.map(SPAMetaRenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType))
.map(({ file, extension, fileWithoutQuery, asType }) => {
let extra = ''
if (asType === 'font') {
extra = ` type="font/${extension}" crossorigin`
}
return `<link rel="preload" href="${publicPath}${file}"${
asType !== '' ? ` as="${asType}"` : ''}${extra}>`
})
2018-01-13 05:22:11 +00:00
.join('')
}
2018-03-20 10:09:47 +00:00
// Prefetch async resources
if (Array.isArray(clientManifest.async)) {
2018-01-13 05:22:11 +00:00
meta.resourceHints += clientManifest.async
2018-11-21 13:08:03 +00:00
.map(SPAMetaRenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPrefetch(fileWithoutQuery, asType))
.map(({ file }) => `<link rel="prefetch" href="${publicPath}${file}">`)
2018-01-13 05:22:11 +00:00
.join('')
2017-08-30 12:47:07 +00:00
}
2017-08-30 12:47:07 +00:00
// Add them to HEAD
if (meta.resourceHints) {
meta.HEAD += meta.resourceHints
}
}
2018-01-04 22:33:46 +00:00
// Emulate getPreloadFiles from vue-server-renderer (works for JS chunks only)
2018-01-13 05:22:11 +00:00
meta.getPreloadFiles = () =>
clientManifest.initial
2018-11-21 13:08:03 +00:00
.map(SPAMetaRenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType))
2018-01-04 22:33:46 +00:00
2017-08-21 09:38:21 +00:00
// Set meta tags inside cache
this.cache.set(url, meta)
2017-08-18 16:05:01 +00:00
2017-08-21 09:38:21 +00:00
return meta
2017-08-18 16:05:01 +00:00
}
2018-11-21 13:08:03 +00:00
static normalizeFile(file) {
const withoutQuery = file.replace(/\?.*/, '')
const extension = extname(withoutQuery).slice(1)
return {
file,
extension,
fileWithoutQuery: withoutQuery,
asType: SPAMetaRenderer.getPreloadType(extension)
}
}
static getPreloadType(ext) {
if (ext === 'js') {
return 'script'
} else if (ext === 'css') {
return 'style'
} else if (/jpe?g|png|svg|gif|webp|ico/.test(ext)) {
return 'image'
} else if (/woff2?|ttf|otf|eot/.test(ext)) {
return 'font'
} else {
return ''
}
}
2017-08-18 16:05:01 +00:00
}