import path from 'path'
import crypto from 'crypto'
import { format } from 'util'
import fs from 'fs-extra'
import consola from 'consola'
import { TARGETS, urlJoin } from '@nuxt/utils'
import devalue from '@nuxt/devalue'
import { createBundleRenderer } from 'vue-server-renderer'
import BaseRenderer from './base'
export default class SSRRenderer extends BaseRenderer {
get rendererOptions () {
const hasModules = fs.existsSync(path.resolve(this.options.rootDir, 'node_modules'))
return {
clientManifest: this.serverContext.resources.clientManifest,
// for globally installed nuxt command, search dependencies in global dir
basedir: hasModules ? this.options.rootDir : __dirname,
...this.options.render.bundleRenderer
}
}
renderScripts (renderContext) {
const scripts = renderContext.renderScripts()
const { render: { crossorigin } } = this.options
if (!crossorigin) {
return scripts
}
return scripts.replace(
/`
APP += ``
preloadScripts.push(stateUrl)
} else {
APP += ``
}
// Page level payload.js (async loaded for CSR)
const payloadPath = urlJoin(url, 'payload.js')
const payloadUrl = urlJoin(staticAssetsBase, payloadPath)
const routePath = (url.replace(/\/+$/, '') || '/').split('?')[0] // remove trailing slah and query params
const payloadScript = `__NUXT_JSONP__("${routePath}", ${devalue({ data, fetch })});`
staticAssets.push({ path: payloadPath, src: payloadScript })
preloadScripts.push(payloadUrl)
// Preload links
for (const href of preloadScripts) {
HEAD += ``
}
} else {
// Serialize state
let serializedSession
if (shouldInjectScripts || shouldHashCspScriptSrc) {
// Only serialized session if need inject scripts or csp hash
serializedSession = `window.${this.serverContext.globals.context}=${devalue(renderContext.nuxt)};`
inlineScripts.push(serializedSession)
}
if (shouldInjectScripts) {
APP += ``
}
}
// Calculate CSP hashes
const cspScriptSrcHashes = []
if (csp) {
if (shouldHashCspScriptSrc) {
for (const script of inlineScripts) {
const hash = crypto.createHash(csp.hashAlgorithm)
hash.update(script)
cspScriptSrcHashes.push(`'${csp.hashAlgorithm}-${hash.digest('base64')}'`)
}
}
// Call ssr:csp hook
await this.serverContext.nuxt.callHook('vue-renderer:ssr:csp', cspScriptSrcHashes)
// Add csp meta tags
if (csp.addMeta) {
HEAD += ``
}
}
// Prepend scripts
if (shouldInjectScripts) {
APP += this.renderScripts(renderContext)
}
if (meta) {
// Append body scripts
APP += meta.meta.text({ body: true })
APP += meta.link.text({ body: true })
APP += meta.style.text({ body: true })
APP += meta.script.text({ body: true })
APP += meta.noscript.text({ body: true })
}
// Template params
const templateParams = {
HTML_ATTRS: meta ? meta.htmlAttrs.text(true /* addSrrAttribute */) : '',
HEAD_ATTRS: meta ? meta.headAttrs.text() : '',
BODY_ATTRS: meta ? meta.bodyAttrs.text() : '',
HEAD,
APP,
ENV: this.options.env
}
// Call ssr:templateParams hook
await this.serverContext.nuxt.callHook('vue-renderer:ssr:templateParams', templateParams, renderContext)
// Render with SSR template
const html = this.renderTemplate(this.serverContext.resources.ssrTemplate, templateParams)
let preloadFiles
if (this.options.render.http2.push) {
preloadFiles = this.getPreloadFiles(renderContext)
}
return {
html,
cspScriptSrcHashes,
preloadFiles,
error: renderContext.nuxt.error,
redirected: renderContext.redirected
}
}
}