/*! * @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 = /]*?src="([^"]*?)" defer( async)?><\/script>/g; const modernScripts = scripts.replace(scriptPattern, (scriptTag, jsFile) => { const legacyJsFile = jsFile.replace(this.publicPath, ''); const modernJsFile = this.assetsMapping[legacyJsFile]; if (!modernJsFile) { return scriptTag.replace('${utils.safariNoModuleFix}`; 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;