/*!
* @nuxt/vue-renderer v2.15.8 (c) 2016-2021
* Released under the MIT License
* Repository: https://github.com/nuxt/nuxt.js
* Website: https://nuxtjs.org
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const path = require('path');
const fs = require('fs-extra');
const consola = require('consola');
const lodash = require('lodash');
const utils = require('@nuxt/utils');
const ufo = require('ufo');
const defu = require('defu');
const VueMeta = require('vue-meta');
const vueServerRenderer = require('vue-server-renderer');
const LRU = require('lru-cache');
const devalue = require('@nuxt/devalue');
const crypto = require('crypto');
const util = require('util');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
const defu__default = /*#__PURE__*/_interopDefaultLegacy(defu);
const VueMeta__default = /*#__PURE__*/_interopDefaultLegacy(VueMeta);
const LRU__default = /*#__PURE__*/_interopDefaultLegacy(LRU);
const devalue__default = /*#__PURE__*/_interopDefaultLegacy(devalue);
const crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
class BaseRenderer {
constructor (serverContext) {
this.serverContext = serverContext;
this.options = serverContext.options;
this.vueRenderer = this.createRenderer();
}
createRenderer () {
throw new Error('`createRenderer()` needs to be implemented')
}
renderTemplate (templateFn, opts) {
// Fix problem with HTMLPlugin's minify option (#3392)
opts.html_attrs = opts.HTML_ATTRS;
opts.head_attrs = opts.HEAD_ATTRS;
opts.body_attrs = opts.BODY_ATTRS;
return templateFn(opts)
}
render () {
throw new Error('`render()` needs to be implemented')
}
}
class SPARenderer extends BaseRenderer {
constructor (serverContext) {
super(serverContext);
this.cache = new LRU__default['default']();
this.vueMetaConfig = {
ssrAppId: '1',
...this.options.vueMeta,
keyName: 'head',
attribute: 'data-n-head',
ssrAttribute: 'data-n-head-ssr',
tagIDKeyName: 'hid'
};
}
createRenderer () {
return vueServerRenderer.createRenderer()
}
async render (renderContext) {
const { url = '/', req = {} } = renderContext;
const modernMode = this.options.modern;
const modern = (modernMode && this.options.target === utils.TARGETS.static) || utils.isModernRequest(req, modernMode);
const cacheKey = `${modern ? 'modern:' : 'legacy:'}${url}`;
let meta = this.cache.get(cacheKey);
if (meta) {
// Return a copy of the content, so that future
// modifications do not effect the data in cache
return lodash.cloneDeep(meta)
}
meta = {
HTML_ATTRS: '',
HEAD_ATTRS: '',
BODY_ATTRS: '',
HEAD: '',
BODY_SCRIPTS_PREPEND: '',
BODY_SCRIPTS: ''
};
if (this.options.features.meta) {
// Get vue-meta context
renderContext.head = typeof this.options.head === 'function'
? this.options.head()
: lodash.cloneDeep(this.options.head);
}
// Allow overriding renderContext
await this.serverContext.nuxt.callHook('vue-renderer:spa:prepareContext', renderContext);
if (this.options.features.meta) {
const m = VueMeta__default['default'].generate(renderContext.head || {}, this.vueMetaConfig);
// HTML_ATTRS
meta.HTML_ATTRS = m.htmlAttrs.text();
// HEAD_ATTRS
meta.HEAD_ATTRS = m.headAttrs.text();
// BODY_ATTRS
meta.BODY_ATTRS = m.bodyAttrs.text();
// HEAD tags
meta.HEAD =
m.title.text() +
m.meta.text() +
m.link.text() +
m.style.text() +
m.script.text() +
m.noscript.text();
// Add meta if router base specified
if (this.options._routerBaseSpecified) {
meta.HEAD += ``;
}
// BODY_SCRIPTS (PREPEND)
meta.BODY_SCRIPTS_PREPEND =
m.meta.text({ pbody: true }) +
m.link.text({ pbody: true }) +
m.style.text({ pbody: true }) +
m.script.text({ pbody: true }) +
m.noscript.text({ pbody: true });
// BODY_SCRIPTS (APPEND)
meta.BODY_SCRIPTS =
m.meta.text({ body: true }) +
m.link.text({ body: true }) +
m.style.text({ body: true }) +
m.script.text({ body: true }) +
m.noscript.text({ body: true });
}
// Resources Hints
meta.resourceHints = '';
const { resources: { modernManifest, clientManifest } } = this.serverContext;
const manifest = modern ? modernManifest : clientManifest;
const { shouldPreload, shouldPrefetch } = this.options.render.bundleRenderer;
if (this.options.render.resourceHints && manifest) {
const publicPath = manifest.publicPath || '/_nuxt/';
// Preload initial resources
if (Array.isArray(manifest.initial)) {
const { crossorigin } = this.options.render;
const cors = `${crossorigin ? ` crossorigin="${crossorigin}"` : ''}`;
meta.preloadFiles = manifest.initial
.map(SPARenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPreload(fileWithoutQuery, asType))
.map(file => ({ ...file, modern }));
meta.resourceHints += meta.preloadFiles
.map(({ file, extension, fileWithoutQuery, asType, modern }) => {
let extra = '';
if (asType === 'font') {
extra = ` type="font/${extension}"${cors ? '' : ' crossorigin'}`;
}
const rel = modern && asType === 'script' ? 'modulepreload' : 'preload';
return ``
})
.join('');
}
// Prefetch async resources
if (Array.isArray(manifest.async)) {
meta.resourceHints += manifest.async
.map(SPARenderer.normalizeFile)
.filter(({ fileWithoutQuery, asType }) => shouldPrefetch(fileWithoutQuery, asType))
.map(({ file }) => ``)
.join('');
}
// Add them to HEAD
if (meta.resourceHints) {
meta.HEAD += meta.resourceHints;
}
}
// Serialize state (runtime config)
let APP = `${meta.BODY_SCRIPTS_PREPEND}
${this.serverContext.resources.loadingHTML}
${meta.BODY_SCRIPTS}`;
const payload = {
config: renderContext.runtimeConfig.public
};
if (renderContext.staticAssetsBase) {
payload.staticAssetsBase = renderContext.staticAssetsBase;
}
APP += ``;
// Prepare template params
const templateParams = {
...meta,
APP,
ENV: this.options.env
};
// Call spa:templateParams hook
await this.serverContext.nuxt.callHook('vue-renderer:spa:templateParams', templateParams);
// Render with SPA template
const html = this.renderTemplate(this.serverContext.resources.spaTemplate, templateParams);
const content = {
html,
preloadFiles: meta.preloadFiles || []
};
// Set meta tags inside cache
this.cache.set(cacheKey, content);
// Return a copy of the content, so that future
// modifications do not effect the data in cache
return lodash.cloneDeep(content)
}
static normalizeFile (file) {
const withoutQuery = file.replace(/\?.*/, '');
const extension = path.extname(withoutQuery).slice(1);
return {
file,
extension,
fileWithoutQuery: withoutQuery,
asType: SPARenderer.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|avif/.test(ext)) {
return 'image'
} else if (/woff2?|ttf|otf|eot/.test(ext)) {
return 'font'
} else {
return ''
}
}
}
class SSRRenderer extends BaseRenderer {
get rendererOptions () {
const hasModules = fs__default['default'].existsSync(path__default['default'].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
}
}
addAttrs (tags, referenceTag, referenceAttr) {
const reference = referenceTag ? `<${referenceTag}` : referenceAttr;
if (!reference) {
return tags
}
const { render: { crossorigin } } = this.options;
if (crossorigin) {
tags = tags.replace(
new RegExp(reference, 'g'),
`${reference} crossorigin="${crossorigin}"`
);
}
return tags
}
renderResourceHints (renderContext) {
return this.addAttrs(renderContext.renderResourceHints(), null, 'rel="preload"')
}
renderScripts (renderContext) {
let renderedScripts = this.addAttrs(renderContext.renderScripts(), 'script');
if (this.options.render.asyncScripts) {
renderedScripts = renderedScripts.replace(/defer>/g, 'defer async>');
}
return renderedScripts
}
renderStyles (renderContext) {
return this.addAttrs(renderContext.renderStyles(), 'link')
}
getPreloadFiles (renderContext) {
return renderContext.getPreloadFiles()
}
createRenderer () {
// Create bundle renderer for SSR
return vueServerRenderer.createBundleRenderer(
this.serverContext.resources.serverManifest,
this.rendererOptions
)
}
useSSRLog () {
if (!this.options.render.ssrLog) {
return
}
const logs = [];
const devReporter = {
log (logObj) {
logs.push({
...logObj,
args: logObj.args.map(arg => util.format(arg))
});
}
};
consola__default['default'].addReporter(devReporter);
return () => {
consola__default['default'].removeReporter(devReporter);
return logs
}
}
async render (renderContext) {
// Call ssr:context hook to extend context from modules
await this.serverContext.nuxt.callHook('vue-renderer:ssr:prepareContext', renderContext);
const getSSRLog = this.useSSRLog();
// Call Vue renderer renderToString
let APP = await this.vueRenderer.renderToString(renderContext);
if (typeof getSSRLog === 'function') {
renderContext.nuxt.logs = getSSRLog();
}
// Call ssr:context hook
await this.serverContext.nuxt.callHook('vue-renderer:ssr:context', renderContext);
// TODO: Remove in next major release (#4722)
await this.serverContext.nuxt.callHook('_render:context', renderContext.nuxt);
// Fallback to empty response
if (!renderContext.nuxt.serverRendered) {
APP = ``;
}
// Perf: early returns if server target and redirected
if (renderContext.redirected && renderContext.target === utils.TARGETS.server) {
return {
html: APP,
error: renderContext.nuxt.error,
redirected: renderContext.redirected
}
}
let HEAD = '';
// Inject head meta
// (this is unset when features.meta is false in server template)
const meta = renderContext.meta && renderContext.meta.inject({
isSSR: renderContext.nuxt.serverRendered,
ln: this.options.dev
});
if (meta) {
HEAD += meta.title.text() + meta.meta.text();
}
// Add meta if router base specified
if (this.options._routerBaseSpecified) {
HEAD += ``;
}
if (meta) {
HEAD += meta.link.text() +
meta.style.text() +
meta.script.text() +
meta.noscript.text();
}
// Check if we need to inject scripts and state
const shouldInjectScripts = this.options.render.injectScripts !== false;
// Inject resource hints
if (this.options.render.resourceHints && shouldInjectScripts) {
HEAD += this.renderResourceHints(renderContext);
}
// Inject styles
HEAD += this.renderStyles(renderContext);
if (meta) {
const prependInjectorOptions = { pbody: true };
const BODY_PREPEND =
meta.meta.text(prependInjectorOptions) +
meta.link.text(prependInjectorOptions) +
meta.style.text(prependInjectorOptions) +
meta.script.text(prependInjectorOptions) +
meta.noscript.text(prependInjectorOptions);
if (BODY_PREPEND) {
APP = `${BODY_PREPEND}${APP}`;
}
}
const { csp } = this.options.render;
// Only add the hash if 'unsafe-inline' rule isn't present to avoid conflicts (#5387)
const containsUnsafeInlineScriptSrc = csp.policies && csp.policies['script-src'] && csp.policies['script-src'].includes('\'unsafe-inline\'');
const shouldHashCspScriptSrc = csp && (csp.unsafeInlineCompatibility || !containsUnsafeInlineScriptSrc);
const inlineScripts = [];
if (shouldInjectScripts && renderContext.staticAssetsBase) {
const preloadScripts = [];
renderContext.staticAssets = [];
const { staticAssetsBase, url, nuxt, staticAssets } = renderContext;
const { data, fetch, mutations, ...state } = nuxt;
// Initial state
const stateScript = `window.${this.serverContext.globals.context}=${devalue__default['default']({
staticAssetsBase,
...state
})};`;
// Make chunk for initial state > 10 KB
const stateScriptKb = (stateScript.length * 4 /* utf8 */) / 100;
if (stateScriptKb > 10) {
const statePath = utils.urlJoin(url, 'state.js');
const stateUrl = utils.urlJoin(staticAssetsBase, statePath);
staticAssets.push({ path: statePath, src: stateScript });
if (this.options.render.asyncScripts) {
APP += ``;
} else {
APP += ``;
}
preloadScripts.push(stateUrl);
} else {
APP += ``;
}
// Save payload only if no error or redirection were made
if (!renderContext.nuxt.error && !renderContext.redirected) {
// Page level payload.js (async loaded for CSR)
const payloadPath = utils.urlJoin(url, 'payload.js');
const payloadUrl = utils.urlJoin(staticAssetsBase, payloadPath);
const routePath = ufo.withoutTrailingSlash(ufo.parsePath(url).pathname);
const payloadScript = `__NUXT_JSONP__("${routePath}", ${devalue__default['default']({ data, fetch, mutations })});`;
staticAssets.push({ path: payloadPath, src: payloadScript });
preloadScripts.push(payloadUrl);
// Add manifest preload
if (this.options.generate.manifest) {
const manifestUrl = utils.urlJoin(staticAssetsBase, 'manifest.js');
preloadScripts.push(manifestUrl);
}
}
// 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__default['default'](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__default['default'].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) {
const appendInjectorOptions = { body: true };
// Append body scripts
APP += meta.meta.text(appendInjectorOptions);
APP += meta.link.text(appendInjectorOptions);
APP += meta.style.text(appendInjectorOptions);
APP += meta.script.text(appendInjectorOptions);
APP += meta.noscript.text(appendInjectorOptions);
}
// Template params
const templateParams = {
HTML_ATTRS: meta ? meta.htmlAttrs.text(renderContext.nuxt.serverRendered /* 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
}
}
}
class ModernRenderer extends SSRRenderer {
constructor (serverContext) {
super(serverContext);
const { build: { publicPath }, router: { base } } = this.options;
this.publicPath = utils.isUrl(publicPath) || ufo.isRelative(publicPath) ? publicPath : utils.urlJoin(base, publicPath);
}
get assetsMapping () {
if (this._assetsMapping) {
return this._assetsMapping
}
const { clientManifest, modernManifest } = this.serverContext.resources;
const legacyAssets = clientManifest.assetsMapping;
const modernAssets = modernManifest.assetsMapping;
const mapping = {};
Object.keys(legacyAssets).forEach((componentHash) => {
const modernComponentAssets = modernAssets[componentHash] || [];
legacyAssets[componentHash].forEach((legacyAssetName, index) => {
mapping[legacyAssetName] = modernComponentAssets[index];
});
});
delete clientManifest.assetsMapping;
delete modernManifest.assetsMapping;
this._assetsMapping = mapping;
return mapping
}
get isServerMode () {
return this.options.modern === 'server'
}
get rendererOptions () {
const rendererOptions = super.rendererOptions;
if (this.isServerMode) {
rendererOptions.clientManifest = this.serverContext.resources.modernManifest;
}
return rendererOptions
}
renderScripts (renderContext) {
const scripts = super.renderScripts(renderContext);
if (this.isServerMode) {
return scripts
}
const scriptPattern = /`;
return safariNoModuleFixScript + modernScripts
}
getModernFiles (legacyFiles = []) {
const modernFiles = [];
for (const legacyJsFile of legacyFiles) {
const modernFile = { ...legacyJsFile, modern: true };
if (modernFile.asType === 'script') {
const file = this.assetsMapping[legacyJsFile.file];
modernFile.file = file;
modernFile.fileWithoutQuery = file.replace(/\?.*/, '');
}
modernFiles.push(modernFile);
}
return modernFiles
}
getPreloadFiles (renderContext) {
const preloadFiles = super.getPreloadFiles(renderContext);
// In eligible server modern mode, preloadFiles are modern bundles from modern renderer
return this.isServerMode ? preloadFiles : this.getModernFiles(preloadFiles)
}
renderResourceHints (renderContext) {
const resourceHints = super.renderResourceHints(renderContext);
if (this.isServerMode) {
return resourceHints
}
const linkPattern = /]*?href="([^"]*?)"[^>]*?as="script"[^>]*?>/g;
return resourceHints.replace(linkPattern, (linkTag, jsFile) => {
const legacyJsFile = jsFile.replace(this.publicPath, '');
const modernJsFile = this.assetsMapping[legacyJsFile];
if (!modernJsFile) {
return ''
}
return linkTag
.replace('rel="preload"', `rel="modulepreload"`)
.replace(legacyJsFile, modernJsFile)
})
}
render (renderContext) {
if (this.isServerMode) {
renderContext.res.setHeader('Vary', 'User-Agent');
}
return super.render(renderContext)
}
}
class VueRenderer {
constructor (context) {
this.serverContext = context;
this.options = this.serverContext.options;
// Will be set by createRenderer
this.renderer = {
ssr: undefined,
modern: undefined,
spa: undefined
};
// Renderer runtime resources
Object.assign(this.serverContext.resources, {
clientManifest: undefined,
modernManifest: undefined,
serverManifest: undefined,
ssrTemplate: undefined,
spaTemplate: undefined,
errorTemplate: this.parseTemplate('Nuxt Internal Server Error')
});
// Default status
this._state = 'created';
this._error = null;
}
ready () {
if (!this._readyPromise) {
this._state = 'loading';
this._readyPromise = this._ready()
.then(() => {
this._state = 'ready';
return this
})
.catch((error) => {
this._state = 'error';
this._error = error;
throw error
});
}
return this._readyPromise
}
async _ready () {
// Resolve dist path
this.distPath = path__default['default'].resolve(this.options.buildDir, 'dist', 'server');
// -- Development mode --
if (this.options.dev) {
this.serverContext.nuxt.hook('build:resources', mfs => this.loadResources(mfs));
return
}
// -- Production mode --
// Try once to load SSR resources from fs
await this.loadResources(fs__default['default']);
// Without using `nuxt start` (programmatic, tests and generate)
if (!this.options._start) {
this.serverContext.nuxt.hook('build:resources', () => this.loadResources(fs__default['default']));
return
}
// Verify resources
if (this.options.modern && !this.isModernReady) {
throw new Error(
`No modern build files found in ${this.distPath}.\nUse either \`nuxt build --modern\` or \`modern\` option to build modern files.`
)
} else if (!this.isReady) {
throw new Error(
`No build files found in ${this.distPath}.\nUse either \`nuxt build\` or \`builder.build()\` or start nuxt in development mode.`
)
}
}
async loadResources (_fs) {
const updated = [];
const readResource = async (fileName, encoding) => {
try {
const fullPath = path__default['default'].resolve(this.distPath, fileName);
if (!await _fs.exists(fullPath)) {
return
}
const contents = await _fs.readFile(fullPath, encoding);
return contents
} catch (err) {
consola__default['default'].error('Unable to load resource:', fileName, err);
}
};
for (const resourceName in this.resourceMap) {
const { fileName, transform, encoding } = this.resourceMap[resourceName];
// Load resource
let resource = await readResource(fileName, encoding);
// Skip unavailable resources
if (!resource) {
continue
}
// Apply transforms
if (typeof transform === 'function') {
resource = await transform(resource, { readResource });
}
// Update resource
this.serverContext.resources[resourceName] = resource;
updated.push(resourceName);
}
// Load templates
await this.loadTemplates();
await this.serverContext.nuxt.callHook('render:resourcesLoaded', this.serverContext.resources);
// Detect if any resource updated
if (updated.length > 0) {
// Create new renderer
this.createRenderer();
}
}
async loadTemplates () {
// Reload error template
const errorTemplatePath = path__default['default'].resolve(this.options.buildDir, 'views/error.html');
if (await fs__default['default'].exists(errorTemplatePath)) {
const errorTemplate = await fs__default['default'].readFile(errorTemplatePath, 'utf8');
this.serverContext.resources.errorTemplate = this.parseTemplate(errorTemplate);
}
// Reload loading template
const loadingHTMLPath = path__default['default'].resolve(this.options.buildDir, 'loading.html');
if (await fs__default['default'].exists(loadingHTMLPath)) {
this.serverContext.resources.loadingHTML = await fs__default['default'].readFile(loadingHTMLPath, 'utf8');
this.serverContext.resources.loadingHTML = this.serverContext.resources.loadingHTML.replace(/\r|\n|[\t\s]{3,}/g, '');
} else {
this.serverContext.resources.loadingHTML = '';
}
}
// TODO: Remove in Nuxt 3
get noSSR () { /* Backward compatibility */
return this.options.render.ssr === false
}
get SSR () {
return this.options.render.ssr === true
}
get isReady () {
// SPA
if (!this.serverContext.resources.spaTemplate || !this.renderer.spa) {
return false
}
// SSR
if (this.SSR && (!this.serverContext.resources.ssrTemplate || !this.renderer.ssr)) {
return false
}
return true
}
get isModernReady () {
return this.isReady && this.serverContext.resources.modernManifest
}
// TODO: Remove in Nuxt 3
get isResourcesAvailable () { /* Backward compatibility */
return this.isReady
}
detectModernBuild () {
const { options, resources } = this.serverContext;
if ([false, 'client', 'server'].includes(options.modern)) {
return
}
const isExplicitStaticModern = options.target === utils.TARGETS.static && options.modern;
if (!resources.modernManifest && !isExplicitStaticModern) {
options.modern = false;
return
}
options.modern = options.render.ssr ? 'server' : 'client';
consola__default['default'].info(`Modern bundles are detected. Modern mode (\`${options.modern}\`) is enabled now.`);
}
createRenderer () {
// Resource clientManifest is always required
if (!this.serverContext.resources.clientManifest) {
return
}
this.detectModernBuild();
// Create SPA renderer
if (this.serverContext.resources.spaTemplate) {
this.renderer.spa = new SPARenderer(this.serverContext);
}
// Skip the rest if SSR resources are not available
if (this.serverContext.resources.ssrTemplate && this.serverContext.resources.serverManifest) {
// Create bundle renderer for SSR
this.renderer.ssr = new SSRRenderer(this.serverContext);
if (this.options.modern !== false) {
this.renderer.modern = new ModernRenderer(this.serverContext);
}
}
}
renderSPA (renderContext) {
return this.renderer.spa.render(renderContext)
}
renderSSR (renderContext) {
// Call renderToString from the bundleRenderer and generate the HTML (will update the renderContext as well)
const renderer = renderContext.modern ? this.renderer.modern : this.renderer.ssr;
return renderer.render(renderContext)
}
async renderRoute (url, renderContext = {}, _retried = 0) {
/* istanbul ignore if */
if (!this.isReady) {
// Fall-back to loading-screen if enabled
if (this.options.build.loadingScreen) {
// Tell nuxt middleware to use `server:nuxt:renderLoading hook
return false
}
// Retry
const retryLimit = this.options.dev ? 60 : 3;
if (_retried < retryLimit && this._state !== 'error') {
await this.ready().then(() => utils.waitFor(1000));
return this.renderRoute(url, renderContext, _retried + 1)
}
// Throw Error
switch (this._state) {
case 'created':
throw new Error('Renderer ready() is not called! Please ensure `nuxt.ready()` is called and awaited.')
case 'loading':
throw new Error('Renderer is loading.')
case 'error':
throw this._error
case 'ready':
throw new Error(`Renderer resources are not loaded! Please check possible console errors and ensure dist (${this.distPath}) exists.`)
default:
throw new Error('Renderer is in unknown state!')
}
}
// Log rendered url
consola__default['default'].debug(`Rendering url ${url}`);
// Add url to the renderContext
renderContext.url = ufo.normalizeURL(url);
// Add target to the renderContext
renderContext.target = this.options.target;
const { req = {}, res = {} } = renderContext;
// renderContext.spa
if (renderContext.spa === undefined) {
// TODO: Remove reading from renderContext.res in Nuxt3
renderContext.spa = !this.SSR || req.spa || res.spa;
}
// renderContext.modern
if (renderContext.modern === undefined) {
const modernMode = this.options.modern;
renderContext.modern = modernMode === 'client' || utils.isModernRequest(req, modernMode);
}
// Set runtime config on renderContext
renderContext.runtimeConfig = {
private: renderContext.spa ? {} : defu__default['default'](this.options.privateRuntimeConfig, this.options.publicRuntimeConfig),
public: { ...this.options.publicRuntimeConfig }
};
// Call renderContext hook
await this.serverContext.nuxt.callHook('vue-renderer:context', renderContext);
// Render SPA or SSR
return renderContext.spa
? this.renderSPA(renderContext)
: this.renderSSR(renderContext)
}
get resourceMap () {
const publicPath = utils.urlJoin(this.options.app.cdnURL, this.options.app.assetsPath);
return {
clientManifest: {
fileName: 'client.manifest.json',
transform: src => Object.assign(JSON.parse(src), { publicPath })
},
modernManifest: {
fileName: 'modern.manifest.json',
transform: src => Object.assign(JSON.parse(src), { publicPath })
},
serverManifest: {
fileName: 'server.manifest.json',
// BundleRenderer needs resolved contents
transform: async (src, { readResource }) => {
const serverManifest = JSON.parse(src);
const readResources = async (obj) => {
const _obj = {};
await Promise.all(Object.keys(obj).map(async (key) => {
_obj[key] = await readResource(obj[key]);
}));
return _obj
};
const [files, maps] = await Promise.all([
readResources(serverManifest.files),
readResources(serverManifest.maps)
]);
// Try to parse sourcemaps
for (const map in maps) {
if (maps[map] && maps[map].version) {
continue
}
try {
maps[map] = JSON.parse(maps[map]);
} catch (e) {
maps[map] = { version: 3, sources: [], mappings: '' };
}
}
return {
...serverManifest,
files,
maps
}
}
},
ssrTemplate: {
fileName: 'index.ssr.html',
transform: src => this.parseTemplate(src)
},
spaTemplate: {
fileName: 'index.spa.html',
transform: src => this.parseTemplate(src)
}
}
}
parseTemplate (templateStr) {
return lodash.template(templateStr, {
interpolate: /{{([\s\S]+?)}}/g,
evaluate: /{%([\s\S]+?)%}/g
})
}
close () {
if (this.__closed) {
return
}
this.__closed = true;
for (const key in this.renderer) {
delete this.renderer[key];
}
}
}
exports.VueRenderer = VueRenderer;