import Vue from 'vue'
import VueMeta from 'vue-meta'
import VueServerRenderer from 'vue-server-renderer'
import LRU from 'lru-cache'
export default class MetaRenderer {
constructor (nuxt, renderer) {
this.nuxt = nuxt
this.renderer = renderer
this.options = nuxt.options
this.vueRenderer = VueServerRenderer.createRenderer()
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'
})
}
getMeta(url) {
return new Promise((resolve, reject) => {
const vm = new Vue({
render: (h) => h(), // Render empty html tag
head: this.options.head || {}
})
this.vueRenderer.renderToString(vm, (err) => {
if (err) return reject(err)
resolve(vm.$meta().inject())
})
})
}
async render ({ url = '/' }) {
let meta = this.cache.get(url)
if (meta) {
return meta
}
meta = {
HTML_ATTRS: '',
BODY_ATTRS: '',
HEAD: ''
}
// Get vue-meta context
const m = await this.getMeta(url)
// HTML_ATTRS
meta.HTML_ATTRS = 'data-n-head-ssr ' + m.htmlAttrs.text()
// 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
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)) {
meta.HEAD += clientManifest.initial.map(r => ``).join('')
}
// Pre-Fetch async resources
if (Array.isArray(clientManifest.async)) {
meta.HEAD += clientManifest.async.map(r => ``).join('')
}
}
// Set meta tags inside cache
this.cache.set(url, meta)
return meta
}
}