123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065 |
- /*!
- * @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 <base href=""> meta if router base specified
- if (this.options._routerBaseSpecified) {
- meta.HEAD += `<base href="${this.options.router.base}">`;
- }
- // 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 `<link rel="${rel}"${cors} href="${publicPath}${file}"${
- asType !== '' ? ` as="${asType}"` : ''}${extra}>`
- })
- .join('');
- }
- // Prefetch async resources
- if (Array.isArray(manifest.async)) {
- meta.resourceHints += manifest.async
- .map(SPARenderer.normalizeFile)
- .filter(({ fileWithoutQuery, asType }) => shouldPrefetch(fileWithoutQuery, asType))
- .map(({ file }) => `<link rel="prefetch" href="${publicPath}${file}">`)
- .join('');
- }
- // Add them to HEAD
- if (meta.resourceHints) {
- meta.HEAD += meta.resourceHints;
- }
- }
- // Serialize state (runtime config)
- let APP = `${meta.BODY_SCRIPTS_PREPEND}<div id="${this.serverContext.globals.id}">${this.serverContext.resources.loadingHTML}</div>${meta.BODY_SCRIPTS}`;
- const payload = {
- config: renderContext.runtimeConfig.public
- };
- if (renderContext.staticAssetsBase) {
- payload.staticAssetsBase = renderContext.staticAssetsBase;
- }
- APP += `<script>window.${this.serverContext.globals.context}=${devalue__default['default'](payload)}</script>`;
- // 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 = `<div id="${this.serverContext.globals.id}"></div>`;
- }
- // 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 <base href=""> meta if router base specified
- if (this.options._routerBaseSpecified) {
- HEAD += `<base href="${this.options.router.base}">`;
- }
- 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 += `<script defer async src="${stateUrl}"></script>`;
- } else {
- APP += `<script defer src="${stateUrl}"></script>`;
- }
- preloadScripts.push(stateUrl);
- } else {
- APP += `<script>${stateScript}</script>`;
- }
- // 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 += `<link rel="preload" href="${href}" as="script">`;
- }
- } 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 += `<script>${serializedSession}</script>`;
- }
- }
- // 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 += `<meta http-equiv="Content-Security-Policy" content="script-src ${cspScriptSrcHashes.join()}">`;
- }
- }
- // 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 = /<script[^>]*?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('<script', `<script nomodule`)
- }
- const moduleTag = scriptTag
- .replace('<script', `<script type="module"`)
- .replace(legacyJsFile, modernJsFile);
- const noModuleTag = scriptTag.replace('<script', `<script nomodule`);
- return noModuleTag + moduleTag
- });
- const safariNoModuleFixScript = `<script>${utils.safariNoModuleFix}</script>`;
- 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 = /<link[^>]*?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;
|