709 lines
28 KiB
JavaScript
709 lines
28 KiB
JavaScript
import { j as joinRelativeURL, u as useRuntimeConfig, i as appRootTag, k as appRootAttrs, l as appSpaLoaderTag, m as appSpaLoaderAttrs, w as writeEarlyHints, e as createError, b as useNitroApp, n as getRouteRules, o as createHead, N as NUXT_PRERENDER_NO_SSR_ROUTES, p as unheadOptions, q as appId, r as getQuery, v as appHead, d as destr, x as NUXT_SSR_STREAMING, y as renderSSRHeadOptions, z as appTeleportAttrs, A as NUXT_NO_SCRIPTS_PATTERNS, B as relative, C as joinURL, D as NUXT_PAYLOAD_INLINE, E as appTeleportTag, F as NUXT_PAGE_PATTERNS, G as defineRenderHandler, H as toRequestEvent, I as setResponseHeader } from '../_/nitro.mjs';
|
|
import { defineDiagnostics, createConsoleReporter } from 'nostics';
|
|
import { renderToString } from 'vue/server-renderer';
|
|
import { createRenderer, getRequestDependencies, getPreloadLinks, getPrefetchLinks } from 'vue-bundle-renderer/runtime';
|
|
import { propsToString, renderSSRHead } from 'unhead/server';
|
|
import { stringify, uneval } from 'devalue';
|
|
|
|
//#region src/runtime/utils/response.ts
|
|
/**
|
|
* The response the renderer builds, keeping its body as the renderer produced it.
|
|
*
|
|
* A real `Response` turns every body into a `ReadableStream`, which would send a rendered
|
|
* document as a chunked stream where nitro sends the string it was given. The renderer only
|
|
* reads the fields below back, so this describes just those.
|
|
*/
|
|
var NodeRenderResponse = class {
|
|
body;
|
|
status;
|
|
statusText;
|
|
headers;
|
|
constructor(body, init) {
|
|
this.body = body;
|
|
this.status = init?.status || 200;
|
|
this.statusText = init?.statusText || "";
|
|
this.headers = init?.headers instanceof Headers ? init.headers : new Headers(init?.headers);
|
|
}
|
|
};
|
|
/**
|
|
* The headers of a node response as the record nitro's `render:response` hook passes,
|
|
* read from and written to the node response itself. `set-cookie` is omitted, as it
|
|
* cannot be represented as one string. The proxy target is kept as a snapshot for
|
|
* `util.inspect`, which bypasses the traps.
|
|
*/
|
|
function createResponseHeaderRecord(res) {
|
|
const read = (name) => {
|
|
if (typeof name !== "string" || name.toLowerCase() === "set-cookie") return;
|
|
const value = res.getHeader(name);
|
|
if (value === void 0) return;
|
|
return Array.isArray(value) ? value.join(", ") : String(value);
|
|
};
|
|
const snapshot = {};
|
|
const sync = (name) => {
|
|
const value = read(name);
|
|
if (value === void 0) delete snapshot[name.toLowerCase()];
|
|
else snapshot[name.toLowerCase()] = value;
|
|
};
|
|
for (const name of res.getHeaderNames()) sync(name);
|
|
return new Proxy(snapshot, {
|
|
get: (_, name) => read(name),
|
|
has: (_, name) => read(name) !== void 0,
|
|
set(_, name, value) {
|
|
if (typeof name === "string") {
|
|
if (value === void 0 || value === null) res.removeHeader(name);
|
|
else res.setHeader(name, value);
|
|
sync(name);
|
|
}
|
|
return true;
|
|
},
|
|
deleteProperty(_, name) {
|
|
if (typeof name === "string") {
|
|
res.removeHeader(name);
|
|
sync(name);
|
|
}
|
|
return true;
|
|
},
|
|
ownKeys: () => res.getHeaderNames().filter((name) => name !== "set-cookie"),
|
|
getOwnPropertyDescriptor(_, name) {
|
|
const value = read(name);
|
|
return value === void 0 ? void 0 : {
|
|
value,
|
|
writable: true,
|
|
enumerable: true,
|
|
configurable: true
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
//#region src/runtime/utils/paths.ts
|
|
function baseURL() {
|
|
return useRuntimeConfig().app.baseURL;
|
|
}
|
|
function buildAssetsDir() {
|
|
return useRuntimeConfig().app.buildAssetsDir;
|
|
}
|
|
function buildAssetsURL(...path) {
|
|
return joinRelativeURL(publicAssetsURL(), buildAssetsDir(), ...path);
|
|
}
|
|
function publicAssetsURL(...path) {
|
|
const app = useRuntimeConfig().app;
|
|
const publicBase = app.cdnURL || app.baseURL;
|
|
return path.length ? joinRelativeURL(publicBase, ...path) : publicBase;
|
|
}
|
|
|
|
//#region src/runtime/server/renderer/cache.ts
|
|
function lazyCachedFunction(fn) {
|
|
let res = null;
|
|
return () => {
|
|
if (res === null) res = fn().catch((err) => {
|
|
res = null;
|
|
throw err;
|
|
});
|
|
return res;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* E8xxx
|
|
* SSR rendering diagnostics, sharing the range with the server runtime that
|
|
* hosts the renderer.
|
|
*/
|
|
const docsBase = (code) => `https://nuxt.com/docs/4.x/errors/${code.replace("NUXT_", "").toLowerCase()}`;
|
|
const rendererDiagnostics = /* #__PURE__ */ defineDiagnostics({
|
|
docsBase,
|
|
reporters: [/* @__PURE__ */ createConsoleReporter(void 0)],
|
|
codes: {
|
|
NUXT_E8001: {
|
|
why: (p) => `\`render:html\` mutated \`body\`/\`bodyAppend\` while streaming (\`${p.path}\`). These fields are silently dropped because the body is about to stream.`,
|
|
fix: "Use the `render:html:close` hook instead.",
|
|
docs: false
|
|
},
|
|
NUXT_E8002: {
|
|
why: (p) => `SSR streaming committed the response before render completed (\`${p.path}\`). The following mutations did not reach the client and were dropped:\n - ${p.mutations}`,
|
|
fix: (p) => `Move the mutation into a plugin (which runs before the shell is flushed), or opt this route out of streaming with \`routeRules: { '${p.path}': { streaming: false } }\` or the \`render:route\` hook.`,
|
|
docs: false
|
|
},
|
|
NUXT_E8004: {
|
|
why: "The server bundle is not available.",
|
|
fix: "Ensure the Nuxt build completed successfully and the server entry was emitted by your builder.",
|
|
docs: false
|
|
},
|
|
NUXT_E8006: {
|
|
why: (p) => `The payload for \`${p.path}\` is ${p.size}, which will increase the page size and slow down hydration.${p.keys ? ` Largest payload keys:\n - ${p.keys}` : ""}`,
|
|
fix: "Use the `pick` or `transform` options of `useAsyncData`/`useFetch` to strip out data the client does not need.",
|
|
docs: false
|
|
},
|
|
NUXT_E8007: {
|
|
why: (p) => `\`${p.path}\` relies on client-side JavaScript, but \`features.noScripts: 'production'\` will strip scripts from this route in production:\n - ${p.reasons}`,
|
|
fix: "Remove the client-side dependency from this route, or scope script stripping with the `noScripts` route rule instead of enabling it globally."
|
|
},
|
|
NUXT_E8009: {
|
|
why: (p) => `A page error interrupted the stream for \`${p.path}\`, and ${p.what} also failed while rendering the error response.\n ${p.cause}`,
|
|
fix: "Fix the page error first. If it keeps happening, report the failure below - the browser will show a blank or partial page instead of the error page."
|
|
}
|
|
}
|
|
});
|
|
|
|
//#region src/runtime/server/renderer/build-files.ts
|
|
const APP_ROOT_OPEN_TAG = `<${appRootTag}${propsToString(appRootAttrs)}>`;
|
|
const APP_ROOT_CLOSE_TAG = `</${appRootTag}>`;
|
|
const getServerEntry = () => import('../virtual/entry.mjs').then(function (n) { return n.aj; }).then((r) => r.default || r);
|
|
const getPrecomputedDependencies = () => import('../virtual/precomputed.mjs').then((r) => "default" in r ? r.default : r).then((r) => typeof r === "function" ? r() : r);
|
|
/** Load the build artifacts for a set of renderer options, caching each of them on the returned object. */
|
|
function createBuildFiles(options) {
|
|
const buildAssetsURL = (...path) => options.buildAssetsURL(...path);
|
|
const getSSRRenderer = lazyCachedFunction(async () => {
|
|
const createSSRApp = await getServerEntry();
|
|
if (!createSSRApp) throw rendererDiagnostics.NUXT_E8004();
|
|
const precomputed = await getPrecomputedDependencies();
|
|
const renderer = createRenderer(createSSRApp, {
|
|
precomputed,
|
|
manifest: void 0,
|
|
renderToString: renderToString$1,
|
|
buildAssetsURL
|
|
});
|
|
async function renderToString$1(input, context) {
|
|
const html = await renderToString(input, context);
|
|
return APP_ROOT_OPEN_TAG + html + APP_ROOT_CLOSE_TAG;
|
|
}
|
|
return renderer;
|
|
});
|
|
const getSPARenderer = lazyCachedFunction(async () => {
|
|
const precomputed = await getPrecomputedDependencies();
|
|
const template = renderSPATemplate();
|
|
const renderer = createRenderer(() => () => {}, {
|
|
precomputed,
|
|
manifest: void 0,
|
|
renderToString: () => template,
|
|
buildAssetsURL
|
|
});
|
|
const result = await renderer.renderToString({});
|
|
const renderToString = (ssrContext) => {
|
|
const config = ssrContext.runtimeConfig;
|
|
ssrContext.modules ||= /* @__PURE__ */ new Set();
|
|
ssrContext.payload.serverRendered = false;
|
|
ssrContext.config = {
|
|
public: config.public,
|
|
app: config.app
|
|
};
|
|
return Promise.resolve(result);
|
|
};
|
|
return {
|
|
rendererContext: renderer.rendererContext,
|
|
renderToString
|
|
};
|
|
});
|
|
return {
|
|
getRenderer: (ssrContext) => ssrContext.noSSR ? getSPARenderer() : getSSRRenderer(),
|
|
getSSRRenderer,
|
|
getServerApp: lazyCachedFunction(getServerEntry)
|
|
};
|
|
}
|
|
function renderSPATemplate() {
|
|
{
|
|
`<${appSpaLoaderTag}${propsToString(appSpaLoaderAttrs)}>`;
|
|
return APP_ROOT_OPEN_TAG + APP_ROOT_CLOSE_TAG + ("");
|
|
}
|
|
}
|
|
const getSSRStyles = lazyCachedFunction(() => import('../virtual/styles.mjs').then((r) => r.default || r));
|
|
const getInlinedCSS = lazyCachedFunction(() => import('../virtual/styles.mjs').then((r) => r.inlinedCSS || {}));
|
|
|
|
//#region src/runtime/server/renderer/instance.ts
|
|
/**
|
|
* Create the renderer state for a set of options.
|
|
*
|
|
* Called by `createNuxtRenderer()`; call it directly only to render against the same
|
|
* artifacts as a renderer created elsewhere (an island handler sharing the server bundle
|
|
* loaded for page renders), and pass the result to `createNuxtRenderer()`.
|
|
*/
|
|
function createRendererInstance(options) {
|
|
return {
|
|
options,
|
|
...createBuildFiles(options)
|
|
};
|
|
}
|
|
|
|
//#region src/runtime/server/renderer/runtime.ts
|
|
/** The event to pass to application code and to the render hooks. */
|
|
function appEvent(event) {
|
|
return event["~app"] ?? event;
|
|
}
|
|
function getRequestState(event) {
|
|
return event.context.nuxt;
|
|
}
|
|
|
|
//#region src/runtime/utils/renderer/options.ts
|
|
globalThis.__buildAssetsURL = buildAssetsURL;
|
|
globalThis.__publicAssetsURL = publicAssetsURL;
|
|
/** The capabilities a nitropack v2 host provides to the Nuxt renderer. */
|
|
const rendererOptions = {
|
|
runtimeConfig: (event) => useRuntimeConfig(appEvent(event)),
|
|
buildAssetsURL,
|
|
publicAssetsURL,
|
|
getRouteRules: (event) => getRouteRules(appEvent(event)),
|
|
hooks: () => useNitroApp().hooks,
|
|
createResponse: (body, init) => new NodeRenderResponse(body, init),
|
|
createError: (init) => createError({
|
|
statusCode: init.status,
|
|
statusMessage: init.statusText,
|
|
message: init.statusText,
|
|
data: init.data
|
|
}),
|
|
writeEarlyHints: (event, hints) => writeEarlyHints(appEvent(event), hints.link),
|
|
onRenderSuccess: void 0,
|
|
prerender: void 0
|
|
};
|
|
/**
|
|
* The renderer the page and island handlers share, so that both render against a single
|
|
* load of the server bundle and its manifest.
|
|
*/
|
|
const rendererInstance = createRendererInstance(rendererOptions);
|
|
|
|
//#region src/runtime/server/renderer/url.ts
|
|
/**
|
|
* The fragment of a request URL, avoiding the lazy URL parse that reading `hash` triggers when
|
|
* there is none. A fragment is never sent over the wire, so it can only appear on a URL the
|
|
* server constructed itself.
|
|
*/
|
|
function urlHash(url) {
|
|
return url.href.includes("#") ? url.hash : "";
|
|
}
|
|
const PAYLOAD_BUILD_ID_PARAM = "_b";
|
|
/** Parse a request path without treating a leading `//` as an authority. */
|
|
function parseRequestPath(path) {
|
|
return new URL("http://localhost" + path);
|
|
}
|
|
/** The page route a `_payload.json` (or `_payload.js`) request renders, without the build id param. */
|
|
function payloadRequestToRoute(path, filename = "_payload.json") {
|
|
const payloadURL = parseRequestPath(path);
|
|
const url = payloadURL.pathname.slice(0, -`/${filename}`.length) || "/";
|
|
payloadURL.searchParams.delete(PAYLOAD_BUILD_ID_PARAM);
|
|
return url + payloadURL.search;
|
|
}
|
|
const LEADING_SLASHES_RE = /^\/+/;
|
|
const TRAILING_SLASHES_RE = /\/*$/;
|
|
/** The same-origin `_payload.json` (or `_payload.js`) URL for a page route, tagged with the build id. */
|
|
function routeToPayloadURL(baseURL, path, buildId, filename = "_payload.json") {
|
|
const request = parseRequestPath(path);
|
|
const base = new URL(baseURL.replace(TRAILING_SLASHES_RE, "/"), "http://localhost");
|
|
const route = request.pathname.replace(LEADING_SLASHES_RE, "").replace(TRAILING_SLASHES_RE, "/");
|
|
const url = new URL("./" + (route === "/" ? "" : route) + filename, base);
|
|
url.search = request.search;
|
|
url.searchParams.set(PAYLOAD_BUILD_ID_PARAM, buildId);
|
|
return URL.canParse(baseURL) ? url.href : url.pathname + url.search;
|
|
}
|
|
/** `url` with its path and query replaced by those of the request path `path`, keeping its origin. */
|
|
function withRequestPath(url, path) {
|
|
const request = parseRequestPath(path);
|
|
const target = new URL(url);
|
|
target.pathname = request.pathname;
|
|
target.search = request.search;
|
|
return target;
|
|
}
|
|
|
|
//#region src/runtime/server/renderer/app.ts
|
|
new Set(NUXT_PRERENDER_NO_SSR_ROUTES);
|
|
function createServerHead() {
|
|
return createHead(unheadOptions);
|
|
}
|
|
function createSSRContext(options, event) {
|
|
const url = event.url.pathname + event.url.search + urlHash(event.url);
|
|
const ssrContext = {
|
|
url,
|
|
event: appEvent(event),
|
|
runtimeConfig: options.runtimeConfig(event),
|
|
noSSR: getRequestState(event)?.noSSR || (false),
|
|
head: createServerHead(),
|
|
error: false,
|
|
nuxt: void 0,
|
|
payload: {},
|
|
["~payloadReducers"]: Object.create(null),
|
|
modules: /* @__PURE__ */ new Set()
|
|
};
|
|
return ssrContext;
|
|
}
|
|
/**
|
|
* Turn the response the renderer assembled into a web-standard `Response`, carrying the
|
|
* headers queued on the event alongside the ones the response names for itself.
|
|
*/
|
|
function returnRenderResponse(options, event, response) {
|
|
const headers = new Headers(event.res.headers);
|
|
for (const name in response.headers) headers.set(name, response.headers[name]);
|
|
return options.createResponse(response.body ?? null, {
|
|
status: response.statusCode ?? event.res.status,
|
|
statusText: response.statusMessage ?? event.res.statusText,
|
|
headers
|
|
});
|
|
}
|
|
function setSSRError(ssrContext, error) {
|
|
ssrContext.error = true;
|
|
ssrContext.payload = { error };
|
|
ssrContext.url = error.url;
|
|
}
|
|
|
|
//#region src/runtime/server/renderer/payload.ts
|
|
function renderPayloadResponse(ssrContext, event) {
|
|
return {
|
|
body: encodeForwardSlashes(stringify(splitPayload(ssrContext).payload, ssrContext["~payloadReducers"])) ,
|
|
statusCode: event.res.status,
|
|
statusMessage: event.res.statusText,
|
|
headers: {
|
|
"content-type": "application/json;charset=utf-8" ,
|
|
"x-powered-by": "Nuxt"
|
|
}
|
|
};
|
|
}
|
|
function renderPayloadJsonScript(opts) {
|
|
const contents = opts.data ? encodeForwardSlashes(stringify(opts.data, opts.ssrContext["~payloadReducers"])) : "";
|
|
const payload = {
|
|
"type": "application/json",
|
|
"innerHTML": contents,
|
|
"data-nuxt-data": appId,
|
|
"data-ssr": !(opts.ssrContext.noSSR)
|
|
};
|
|
payload.id = "__NUXT_DATA__";
|
|
if (opts.src) payload["data-src"] = opts.src;
|
|
const config = uneval(opts.ssrContext.config);
|
|
return [payload, { innerHTML: `window.__NUXT__={};window.__NUXT__.config=${config}` }];
|
|
}
|
|
/**
|
|
* Encode forward slashes as unicode escape sequences to prevent
|
|
* Google from treating them as internal links and trying to crawl them.
|
|
* @see https://github.com/nuxt/nuxt/issues/24175
|
|
*/
|
|
function encodeForwardSlashes(str) {
|
|
return str.replaceAll("/", "\\u002F");
|
|
}
|
|
function splitPayload(ssrContext) {
|
|
const { data, prerenderedAt, prefetchLinks, ...initial } = ssrContext.payload;
|
|
const payload = {
|
|
data,
|
|
prerenderedAt
|
|
};
|
|
if (prefetchLinks?.length) payload.prefetchLinks = prefetchLinks;
|
|
return {
|
|
initial: {
|
|
...initial,
|
|
prerenderedAt
|
|
},
|
|
payload
|
|
};
|
|
}
|
|
|
|
//#region src/runtime/server/renderer/inline-styles.ts
|
|
async function renderInlineStyles(usedModules) {
|
|
const styleMap = await getSSRStyles();
|
|
const inlinedStyles = /* @__PURE__ */ new Set();
|
|
const promises = [];
|
|
for (const mod of usedModules) if (mod in styleMap && styleMap[mod]) promises.push(styleMap[mod]());
|
|
for (const styles of await Promise.all(promises)) for (const style of styles) inlinedStyles.add(style);
|
|
return Array.from(inlinedStyles).map((style) => ({ innerHTML: style }));
|
|
}
|
|
|
|
//#region src/runtime/server/renderer/inlined-css.ts
|
|
/**
|
|
* Build a predicate telling whether the contents of an emitted CSS file were
|
|
* inlined as `<style>` tags for this request, meaning its `<link>` can be
|
|
* dropped.
|
|
*
|
|
* The builder can only remove a link at build time when every CSS source in
|
|
* the file is inlined for a module that is always rendered. For the rest it
|
|
* records the modules that inline each source, and the answer depends on what
|
|
* the request actually rendered: a component inside `<ClientOnly>` never
|
|
* reaches `ssrContext.modules`, so its styles are only ever delivered by the
|
|
* link. (#36058)
|
|
*/
|
|
async function createInlinedCSSFilter(modules) {
|
|
const inlinedCSS = await getInlinedCSS();
|
|
return (file) => {
|
|
const conditions = inlinedCSS[file.slice(file.lastIndexOf("/") + 1)];
|
|
if (!conditions) return false;
|
|
if (!modules?.size) return false;
|
|
for (const inliners of conditions) {
|
|
let inlined = false;
|
|
for (const id of inliners) if (modules.has(id)) {
|
|
inlined = true;
|
|
break;
|
|
}
|
|
if (!inlined) return false;
|
|
}
|
|
return true;
|
|
};
|
|
}
|
|
|
|
const entryIds = [];
|
|
|
|
const entryFileName = "DlDWjZRl.js";
|
|
|
|
//#region src/runtime/server/renderer/index.ts
|
|
const HAS_APP_TELEPORTS = !!(appTeleportAttrs.id);
|
|
const APP_TELEPORT_OPEN_TAG = HAS_APP_TELEPORTS ? `<${appTeleportTag}${propsToString(appTeleportAttrs)}>` : "";
|
|
const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : "";
|
|
const PAYLOAD_URL_RE = /^[^?]*\/_payload.json(?:\?.*)?$/ ;
|
|
const PAYLOAD_FILENAME = "_payload.json" ;
|
|
/**
|
|
* Create a Nuxt SSR renderer, returning a web-standard handler for the requests a Nuxt
|
|
* app serves from its pages.
|
|
*
|
|
* Everything the renderer reads belongs to the renderer it was created for, so a bundle
|
|
* may create as many as it needs. Pass an instance from `createRendererInstance()` to
|
|
* render against the artifacts already loaded for another renderer.
|
|
*/
|
|
function createNuxtRenderer(optionsOrInstance) {
|
|
const instance = "getRenderer" in optionsOrInstance ? optionsOrInstance : createRendererInstance(optionsOrInstance);
|
|
return { fetch: (event) => fetch(instance, event) };
|
|
}
|
|
function fetch(instance, event) {
|
|
const runtime = instance.options;
|
|
const isErrorRoute = event.url.pathname.startsWith("/__nuxt_error");
|
|
if (isErrorRoute && !getRequestState(event)?.["~rendering-error"]) {
|
|
return Promise.reject(runtime.createError({
|
|
status: 404,
|
|
statusText: "Page Not Found: /__nuxt_error"
|
|
}));
|
|
}
|
|
const ssrError = isErrorRoute ? getQuery(event.url.href) : null;
|
|
const render = () => renderRoute(instance, event, ssrError).then((response) => returnRenderResponse(runtime, event, response));
|
|
const rendering = render();
|
|
return rendering;
|
|
}
|
|
async function renderRoute(instance, event, ssrError) {
|
|
const runtime = instance.options;
|
|
const hooks = runtime.hooks();
|
|
const hookEvent = appEvent(event);
|
|
runtime.prerender?.payloadCache;
|
|
const ssrContext = createSSRContext(runtime, event);
|
|
ssrContext.head.push(appHead);
|
|
if (ssrError) {
|
|
const status = ssrError.status || ssrError.statusCode;
|
|
if (status) ssrError.status = ssrError.statusCode = Number.parseInt(status);
|
|
if (typeof ssrError.data === "string") try {
|
|
ssrError.data = destr(ssrError.data);
|
|
} catch {}
|
|
setSSRError(ssrContext, ssrError);
|
|
}
|
|
const routeOptions = runtime.getRouteRules(event);
|
|
const NO_SCRIPTS = !!routeOptions.noScripts;
|
|
if (routeOptions.ssr === false && true) ssrContext.noSSR = true;
|
|
const _PAYLOAD_EXTRACTION = !ssrContext.noSSR && !ssrError && !!((routeOptions.isr || routeOptions.cache));
|
|
const _PAYLOAD_INLINE = !_PAYLOAD_EXTRACTION || NUXT_PAYLOAD_INLINE;
|
|
const isRenderingPayload = (_PAYLOAD_EXTRACTION || false) && PAYLOAD_URL_RE.test(ssrContext.url);
|
|
if (isRenderingPayload) {
|
|
ssrContext.url = payloadRequestToRoute(ssrContext.url, PAYLOAD_FILENAME);
|
|
event.url = withRequestPath(event.url, ssrContext.url);
|
|
getPayloadCacheKey(ssrContext.url);
|
|
}
|
|
const payloadURL = _PAYLOAD_EXTRACTION ? buildPayloadURL(ssrContext) : void 0;
|
|
const renderer = await instance.getRenderer(ssrContext);
|
|
for (const id of entryIds) ssrContext.modules.add(id);
|
|
const canStream = NUXT_SSR_STREAMING;
|
|
const renderRouteContext = {
|
|
canStream,
|
|
prefersStream: false
|
|
};
|
|
await hooks.callHook("render:route", renderRouteContext, { event: hookEvent });
|
|
const _rendered = await (renderer.renderToString(ssrContext)).catch(async (error) => {
|
|
if ((ssrContext["~renderResponse"] || ssrContext._renderResponse) && error.message === "skipping render") return {};
|
|
const _err = !ssrError && ssrContext.payload?.error || error;
|
|
await ssrContext.nuxt?.hooks.callHook("app:error", _err);
|
|
throw _err;
|
|
});
|
|
const inlinedStyles = !ssrContext["~renderResponse"] && !ssrContext._renderResponse && !isRenderingPayload ? await renderInlineStyles(ssrContext.modules ?? []) : [];
|
|
await ssrContext.nuxt?.hooks.callHook("app:rendered", {
|
|
ssrContext,
|
|
renderResult: _rendered
|
|
});
|
|
if (ssrContext["~renderResponse"] || ssrContext._renderResponse) return ssrContext["~renderResponse"] || ssrContext._renderResponse;
|
|
if (ssrContext.payload?.error && !ssrError) throw ssrContext.payload.error;
|
|
if (isRenderingPayload) {
|
|
const response = renderPayloadResponse(ssrContext, event);
|
|
return response;
|
|
}
|
|
const { styles, scripts } = getRequestDependencies(ssrContext, renderer.rendererContext);
|
|
pushNoScriptsHints(ssrContext, NO_SCRIPTS);
|
|
if (!NO_SCRIPTS) {
|
|
const path = entryImportMapPath(instance, event, ssrContext);
|
|
ssrContext.head.push({ script: [{
|
|
type: "importmap",
|
|
innerHTML: { imports: { "#entry": path } }
|
|
}] });
|
|
}
|
|
if (_PAYLOAD_EXTRACTION && !_PAYLOAD_INLINE && !NO_SCRIPTS) ssrContext.head.push({ link: [{
|
|
rel: "preload",
|
|
as: "fetch",
|
|
crossorigin: "anonymous",
|
|
href: payloadURL
|
|
} ] });
|
|
if (inlinedStyles.length) ssrContext.head.push({ style: inlinedStyles });
|
|
const link = [];
|
|
const inlinedHrefs = [];
|
|
const isCSSInlined = await createInlinedCSSFilter(ssrContext.modules) ;
|
|
for (const resource of Object.values(styles)) {
|
|
if (isCSSInlined?.(resource.file)) {
|
|
inlinedHrefs.push(renderer.rendererContext.buildAssetsURL(resource.file));
|
|
continue;
|
|
}
|
|
link.push({
|
|
rel: "stylesheet",
|
|
href: renderer.rendererContext.buildAssetsURL(resource.file),
|
|
crossorigin: ""
|
|
});
|
|
}
|
|
if (link.length) ssrContext.head.push({ link });
|
|
const dependencyOptions = {
|
|
exclude: ssrContext["~lazyHydratedModules"]?.size ? ssrContext["~lazyHydratedModules"] : void 0,
|
|
scripts: !NO_SCRIPTS
|
|
};
|
|
const excludeHrefs = new Set(link.map((l) => l.href));
|
|
for (const href of inlinedHrefs) excludeHrefs.add(href);
|
|
for (const id of ssrContext["~neverHydratedModules"] ?? []) {
|
|
const file = renderer.rendererContext.manifest?.[id]?.file;
|
|
if (file) excludeHrefs.add(renderer.rendererContext.buildAssetsURL(file));
|
|
}
|
|
const hints = [];
|
|
for (const l of getPreloadLinks(ssrContext, renderer.rendererContext, dependencyOptions)) if (!excludeHrefs.has(l.href)) hints.push(l);
|
|
for (const l of getPrefetchLinks(ssrContext, renderer.rendererContext, dependencyOptions)) if (!excludeHrefs.has(l.href)) hints.push(l);
|
|
if (hints.length) ssrContext.head.push({ link: hints });
|
|
if (!NO_SCRIPTS) ssrContext.head.push({ script: _PAYLOAD_INLINE ? renderPayloadJsonScript({
|
|
ssrContext,
|
|
data: stripInlineOnlyPayloadFields(ssrContext.payload)
|
|
}) : renderPayloadJsonScript({
|
|
ssrContext,
|
|
data: splitPayload(ssrContext).initial,
|
|
src: payloadURL
|
|
}) }, {
|
|
tagPosition: "bodyClose",
|
|
tagPriority: "high"
|
|
});
|
|
if (!routeOptions.noScripts) {
|
|
const tagPosition = "head";
|
|
ssrContext.head.push({ script: Object.values(scripts).map((resource) => ({
|
|
type: resource.module ? "module" : null,
|
|
src: renderer.rendererContext.buildAssetsURL(resource.file),
|
|
defer: resource.module ? null : true,
|
|
tagPosition,
|
|
crossorigin: ""
|
|
})) });
|
|
}
|
|
const { headTags, bodyTags, bodyTagsOpen, htmlAttrs, bodyAttrs } = renderSSRHead(ssrContext.head, renderSSRHeadOptions);
|
|
const htmlContext = {
|
|
htmlAttrs: htmlAttrs ? [htmlAttrs] : [],
|
|
head: normalizeChunks([headTags]),
|
|
bodyAttrs: bodyAttrs ? [bodyAttrs] : [],
|
|
bodyPrepend: normalizeChunks([bodyTagsOpen, ssrContext.teleports?.body]),
|
|
body: [_rendered.html, APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportAttrs.id}`]]) : "") + APP_TELEPORT_CLOSE_TAG],
|
|
bodyAppend: [bodyTags]
|
|
};
|
|
await hooks.callHook("render:html", htmlContext, { event: hookEvent });
|
|
return {
|
|
body: renderHTMLDocument(htmlContext),
|
|
statusCode: event.res.status,
|
|
statusMessage: event.res.statusText,
|
|
headers: {
|
|
"content-type": "text/html;charset=utf-8",
|
|
"x-powered-by": "Nuxt"
|
|
}
|
|
};
|
|
}
|
|
function getPayloadCacheKey(url) {
|
|
const { pathname, search } = new URL(url, "http://localhost");
|
|
return (pathname === "/" ? "/" : pathname.replace(/\/$/, "")) + (search ? encodeURIComponent(search) : "") + ".json";
|
|
}
|
|
/**
|
|
* Routes served without scripts navigate with full-page loads. This emits the
|
|
* declarative navigation hints that speed those up, on both the pages served
|
|
* without scripts (a blanket rule over same-origin links) and scripted pages
|
|
* that may link to them (rules scoped to the `noScripts` route patterns):
|
|
*
|
|
* - speculation rules, so supporting browsers prefetch and prerender the
|
|
* target ahead of the navigation;
|
|
* - when view transitions are enabled, an opt-in to same-origin cross-document
|
|
* view transitions, animating the navigation without a client runtime (the
|
|
* client-side `startViewTransition` plugin is not shipped).
|
|
*
|
|
* Both tags are declarative and execute no JavaScript.
|
|
*/
|
|
function pushNoScriptsHints(ssrContext, noScripts) {
|
|
if (noScripts) pushSpeculationRulesScript(ssrContext, NUXT_PAGE_PATTERNS.length ? NUXT_PAGE_PATTERNS : ["/*"]);
|
|
else if (NUXT_NO_SCRIPTS_PATTERNS.length) pushSpeculationRulesScript(ssrContext, NUXT_NO_SCRIPTS_PATTERNS);
|
|
else return;
|
|
}
|
|
const entryPaths = /* @__PURE__ */ new WeakMap();
|
|
/** URL of the hashed entry chunk, which `#entry` is pinned to so chunk hashes stay stable across it. */
|
|
function entryImportMapPath(instance, event, ssrContext) {
|
|
const cached = entryPaths.get(instance);
|
|
if (cached) return cached;
|
|
const url = instance.options.buildAssetsURL(entryFileName);
|
|
if (ssrContext.runtimeConfig.app.cdnURL || /^(?:\/|\.+\/)/.test(url)) {
|
|
entryPaths.set(instance, url);
|
|
return url;
|
|
}
|
|
const path = relative(event.url.pathname.replace(/\/[^/]+$/, "/"), joinURL("/", url));
|
|
return /^(?:\/|\.+\/)/.test(path) ? path : `./${path}`;
|
|
}
|
|
function pushSpeculationRulesScript(ssrContext, patterns) {
|
|
const rules = patterns.map((href_matches) => ({
|
|
where: { href_matches },
|
|
eagerness: "moderate"
|
|
}));
|
|
ssrContext.head.push({ script: [{
|
|
tagPosition: "head",
|
|
type: "speculationrules",
|
|
innerHTML: {
|
|
prefetch: rules,
|
|
prerender: rules
|
|
}
|
|
}] });
|
|
}
|
|
function buildPayloadURL(ssrContext) {
|
|
const baseURL = ssrContext.runtimeConfig.app.cdnURL || ssrContext.runtimeConfig.app.baseURL;
|
|
return routeToPayloadURL(baseURL, ssrContext.url, ssrContext.runtimeConfig.app.buildId, PAYLOAD_FILENAME);
|
|
}
|
|
function normalizeChunks(chunks) {
|
|
const result = [];
|
|
for (const _chunk of chunks) {
|
|
const chunk = _chunk?.trim();
|
|
if (chunk) result.push(chunk);
|
|
}
|
|
return result;
|
|
}
|
|
function joinTags(tags) {
|
|
return tags.join("");
|
|
}
|
|
function joinAttrs(chunks) {
|
|
if (chunks.length === 0) return "";
|
|
return " " + chunks.join(" ");
|
|
}
|
|
function renderHTMLDocument(html) {
|
|
return `<!DOCTYPE html><html${joinAttrs(html.htmlAttrs)}><head>${joinTags(html.head)}</head><body${joinAttrs(html.bodyAttrs)}>${joinTags(html.bodyPrepend)}${joinTags(html.body)}${joinTags(html.bodyAppend)}</body></html>`;
|
|
}
|
|
function stripInlineOnlyPayloadFields(payload) {
|
|
if (!payload.prefetchLinks) return payload;
|
|
const { prefetchLinks: _, ...rest } = payload;
|
|
return rest;
|
|
}
|
|
|
|
const renderer = createNuxtRenderer(rendererInstance);
|
|
const handler = defineRenderHandler(async (event) => {
|
|
const response = await renderer.fetch(toRequestEvent(event));
|
|
for (const [name, value] of response.headers) {
|
|
if (name === "set-cookie") continue;
|
|
setResponseHeader(event, name, value);
|
|
}
|
|
return {
|
|
body: response.body,
|
|
statusCode: response.status,
|
|
statusMessage: response.statusText || void 0,
|
|
headers: createResponseHeaderRecord(event.node.res)
|
|
};
|
|
});
|
|
|
|
const renderer$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
__proto__: null,
|
|
default: handler
|
|
}, Symbol.toStringTag, { value: 'Module' }));
|
|
|
|
export { baseURL as b, publicAssetsURL as p, renderer$1 as r };
|
|
//# sourceMappingURL=renderer.mjs.map
|