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 } } }