@nuxt/nitro-server-nightly 5.0.0-29662437.89c933ea → 5.0.0-29664396.5668d45c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { EventHandler, H3Event } from "nitro/h3";
|
|
|
2
2
|
import { Nuxt } from "@nuxt/schema";
|
|
3
3
|
import { Nitro, NitroConfig, NitroDevEventHandler, NitroEventHandler, NitroOptions, NitroRouteConfig, NitroRuntimeConfig, NitroRuntimeConfigApp } from "nitro/types";
|
|
4
4
|
import { LogObject } from "consola";
|
|
5
|
-
import { NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext } from "nuxt/app";
|
|
5
|
+
import { NuxtIslandContext, NuxtIslandResponse, NuxtRenderChunkContext, NuxtRenderCloseContext, NuxtRenderHTMLContext, NuxtRenderRouteContext } from "nuxt/app";
|
|
6
6
|
import { HookResult, RuntimeConfig, TSReference } from "nuxt/schema";
|
|
7
7
|
|
|
8
8
|
//#region src/augments.d.ts
|
|
@@ -14,6 +14,7 @@ declare module "nitro/types" {
|
|
|
14
14
|
}
|
|
15
15
|
interface NitroRouteRules {
|
|
16
16
|
ssr?: boolean;
|
|
17
|
+
streaming?: boolean;
|
|
17
18
|
noScripts?: boolean;
|
|
18
19
|
/** @deprecated Use `noScripts` instead */
|
|
19
20
|
experimentalNoScripts?: boolean;
|
|
@@ -24,6 +25,7 @@ declare module "nitro/types" {
|
|
|
24
25
|
interface NitroRuntimeConfig extends RuntimeConfig {}
|
|
25
26
|
interface NitroRouteConfig {
|
|
26
27
|
ssr?: boolean;
|
|
28
|
+
streaming?: boolean;
|
|
27
29
|
noScripts?: boolean;
|
|
28
30
|
/** @deprecated Use `noScripts` instead */
|
|
29
31
|
experimentalNoScripts?: boolean;
|
|
@@ -35,6 +37,16 @@ declare module "nitro/types" {
|
|
|
35
37
|
}) => void | Promise<void>;
|
|
36
38
|
"render:html": (htmlContext: NuxtRenderHTMLContext, context: {
|
|
37
39
|
event: H3Event;
|
|
40
|
+
streaming?: boolean;
|
|
41
|
+
}) => void | Promise<void>;
|
|
42
|
+
"render:html:chunk": (chunkContext: NuxtRenderChunkContext, context: {
|
|
43
|
+
event: H3Event;
|
|
44
|
+
}) => void | Promise<void>;
|
|
45
|
+
"render:html:close": (closeContext: NuxtRenderCloseContext, context: {
|
|
46
|
+
event: H3Event;
|
|
47
|
+
}) => void | Promise<void>;
|
|
48
|
+
"render:route": (renderRouteContext: NuxtRenderRouteContext, context: {
|
|
49
|
+
event: H3Event;
|
|
38
50
|
}) => void | Promise<void>;
|
|
39
51
|
"render:island": (islandResponse: NuxtIslandResponse, context: {
|
|
40
52
|
event: H3Event;
|
package/dist/index.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { resolveModulePath } from "exsolve";
|
|
|
19
19
|
import { runtimeDependencies } from "nitro/meta";
|
|
20
20
|
import { resolveModuleExportNames } from "mlly";
|
|
21
21
|
//#region package.json
|
|
22
|
-
var version = "5.0.0-
|
|
22
|
+
var version = "5.0.0-29664396.5668d45c";
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region src/utils.ts
|
|
25
25
|
function toArray(value) {
|
|
@@ -124,7 +124,7 @@ ${[...references.map((ref) => renderReference(ref, typesDir)), ...declarations].
|
|
|
124
124
|
import type { RuntimeConfig } from 'nuxt/schema'
|
|
125
125
|
import type { H3Event } from 'nitro/h3'
|
|
126
126
|
import type { LogObject } from 'consola'
|
|
127
|
-
import type { NuxtIslandContext, NuxtIslandResponse, NuxtRenderHTMLContext } from 'nuxt/app'
|
|
127
|
+
import type { NuxtIslandContext, NuxtIslandResponse, NuxtRenderChunkContext, NuxtRenderCloseContext, NuxtRenderHTMLContext, NuxtRenderRouteContext } from 'nuxt/app'
|
|
128
128
|
|
|
129
129
|
declare module 'nitro/types' {
|
|
130
130
|
interface NitroRuntimeConfigApp {
|
|
@@ -135,12 +135,14 @@ declare module 'nitro/types' {
|
|
|
135
135
|
interface NitroRuntimeConfig extends RuntimeConfig {}
|
|
136
136
|
interface NitroRouteConfig {
|
|
137
137
|
ssr?: boolean
|
|
138
|
+
streaming?: boolean
|
|
138
139
|
noScripts?: boolean
|
|
139
140
|
/** @deprecated Use \`noScripts\` instead */
|
|
140
141
|
experimentalNoScripts?: boolean
|
|
141
142
|
}
|
|
142
143
|
interface NitroRouteRules {
|
|
143
144
|
ssr?: boolean
|
|
145
|
+
streaming?: boolean
|
|
144
146
|
noScripts?: boolean
|
|
145
147
|
/** @deprecated Use \`noScripts\` instead */
|
|
146
148
|
experimentalNoScripts?: boolean
|
|
@@ -149,7 +151,10 @@ declare module 'nitro/types' {
|
|
|
149
151
|
}
|
|
150
152
|
interface NitroRuntimeHooks {
|
|
151
153
|
'dev:ssr-logs': (ctx: { logs: LogObject[], path: string }) => void | Promise<void>
|
|
152
|
-
'render:html': (htmlContext: NuxtRenderHTMLContext, context: { event: H3Event }) => void | Promise<void>
|
|
154
|
+
'render:html': (htmlContext: NuxtRenderHTMLContext, context: { event: H3Event, streaming?: boolean }) => void | Promise<void>
|
|
155
|
+
'render:html:chunk': (chunkContext: NuxtRenderChunkContext, context: { event: H3Event }) => void | Promise<void>
|
|
156
|
+
'render:html:close': (closeContext: NuxtRenderCloseContext, context: { event: H3Event }) => void | Promise<void>
|
|
157
|
+
'render:route': (renderRouteContext: NuxtRenderRouteContext, context: { event: H3Event }) => void | Promise<void>
|
|
153
158
|
'render:island': (islandResponse: NuxtIslandResponse, context: { event: H3Event, islandContext: NuxtIslandContext }) => void | Promise<void>
|
|
154
159
|
}
|
|
155
160
|
}
|
|
@@ -337,6 +342,7 @@ async function bundle(nuxt) {
|
|
|
337
342
|
"#internal/nuxt/app-config": () => nuxt.vfs["#build/app.config.mjs"]?.replace(/\/\*\* client \*\*\/[\s\S]*\/\*\* client-end \*\*\//, "") || "",
|
|
338
343
|
"#spa-template": async () => `export const template = ${JSON.stringify(await spaLoadingTemplate(nuxt))}`,
|
|
339
344
|
"#internal/entry-chunk.mjs": () => `export const entryFileName = undefined`,
|
|
345
|
+
"#internal/streaming-iife-chunk.mjs": () => `export const iifeChunkFileName = undefined`,
|
|
340
346
|
"#internal/nuxt/entry-ids.mjs": () => `export default []`,
|
|
341
347
|
"#internal/nuxt/nitro-config.mjs": () => {
|
|
342
348
|
const hasCachedRoutes = nitro.routing.routeRules.routes.some((r) => r.data.isr || r.data.cache);
|
|
@@ -350,12 +356,17 @@ async function bundle(nuxt) {
|
|
|
350
356
|
`export const NUXT_SHARED_DATA = ${!!nuxt.options.experimental.sharedPrerenderData}`,
|
|
351
357
|
`export const NUXT_PAYLOAD_EXTRACTION = ${nuxt.options.experimental.payloadExtraction !== false}`,
|
|
352
358
|
`export const NUXT_PAYLOAD_INLINE = ${nuxt.options.experimental.payloadExtraction !== true}`,
|
|
353
|
-
`export const NUXT_RUNTIME_PAYLOAD_EXTRACTION = ${hasCachedRoutes}
|
|
359
|
+
`export const NUXT_RUNTIME_PAYLOAD_EXTRACTION = ${hasCachedRoutes}`,
|
|
360
|
+
`export const NUXT_SSR_STREAMING = ${!!(typeof nuxt.options.experimental.ssrStreaming === "object" && nuxt.options.experimental.ssrStreaming.enabled)}`,
|
|
361
|
+
`export const NUXT_SSR_STREAMING_BOT_RE = ${typeof nuxt.options.experimental.ssrStreaming === "object" && nuxt.options.experimental.ssrStreaming.botRegex instanceof RegExp ? String(nuxt.options.experimental.ssrStreaming.botRegex) : "/^$/"}`
|
|
354
362
|
].join("\n");
|
|
355
363
|
}
|
|
356
364
|
},
|
|
357
365
|
routeRules: {
|
|
358
|
-
"/**":
|
|
366
|
+
"/**": typeof nuxt.options.experimental.ssrStreaming === "object" && nuxt.options.experimental.ssrStreaming.enabled ? {
|
|
367
|
+
ssr: true,
|
|
368
|
+
streaming: true
|
|
369
|
+
} : { ssr: true },
|
|
359
370
|
"/__nuxt_error": { cache: false }
|
|
360
371
|
},
|
|
361
372
|
rolldownConfig: { tsconfig: false },
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { payloadCache, prerenderRenderingURLs } from "../utils/cache.mjs";
|
|
2
2
|
import { createSSRContext, setSSRError } from "../utils/renderer/app.mjs";
|
|
3
3
|
import { buildAssetsURL, publicAssetsURL } from "../utils/paths.mjs";
|
|
4
|
-
import { getRenderer } from "../utils/renderer/build-files.mjs";
|
|
4
|
+
import { APP_ROOT_CLOSE_TAG, APP_ROOT_OPEN_TAG, getRenderer, getServerApp } from "../utils/renderer/build-files.mjs";
|
|
5
5
|
import { renderInlineStyles } from "../utils/renderer/inline-styles.mjs";
|
|
6
|
-
import { replaceIslandTeleports } from "../utils/renderer/islands.mjs";
|
|
6
|
+
import { renderStreamedIslandTeleports, replaceIslandTeleports } from "../utils/renderer/islands.mjs";
|
|
7
7
|
import { renderPayloadJsonScript, renderPayloadResponse, splitPayload } from "../utils/renderer/payload.mjs";
|
|
8
8
|
import { getQuery, joinURL } from "ufo";
|
|
9
9
|
import { getRouteRules, useNitroHooks } from "nitro/app";
|
|
@@ -11,13 +11,17 @@ import destr$1 from "destr";
|
|
|
11
11
|
import { HTTPError, defineEventHandler, getQuery as getQuery$1, writeEarlyHints } from "nitro/h3";
|
|
12
12
|
import { getPrefetchLinks, getPreloadLinks, getRequestDependencies, renderResourceHeaders } from "vue-bundle-renderer/runtime";
|
|
13
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
-
import { NUXT_ASYNC_CONTEXT, NUXT_EARLY_HINTS, NUXT_INLINE_STYLES, NUXT_NO_SCRIPTS, NUXT_PAYLOAD_EXTRACTION, NUXT_PAYLOAD_INLINE, NUXT_RUNTIME_PAYLOAD_EXTRACTION, PARSE_ERROR_DATA } from "#internal/nuxt/nitro-config.mjs";
|
|
14
|
+
import { NUXT_ASYNC_CONTEXT, NUXT_EARLY_HINTS, NUXT_INLINE_STYLES, NUXT_NO_SCRIPTS, NUXT_PAYLOAD_EXTRACTION, NUXT_PAYLOAD_INLINE, NUXT_RUNTIME_PAYLOAD_EXTRACTION, NUXT_SSR_STREAMING, NUXT_SSR_STREAMING_BOT_RE, PARSE_ERROR_DATA } from "#internal/nuxt/nitro-config.mjs";
|
|
15
15
|
import { propsToString, renderSSRHead } from "@unhead/vue/server";
|
|
16
|
-
import {
|
|
16
|
+
import { renderToWebStream } from "vue/server-renderer";
|
|
17
|
+
import { appHead, appTeleportAttrs, appTeleportTag, componentIslands, componentIslandsActive } from "#internal/nuxt.config.mjs";
|
|
18
|
+
import { createBootstrapScript, renderSSRHeadSuspenseChunk, renderShell } from "@unhead/vue/stream/server";
|
|
19
|
+
import { streamingIifeCode } from "@unhead/vue/stream/iife";
|
|
17
20
|
import { relative } from "pathe";
|
|
18
21
|
import { renderSSRHeadOptions } from "#internal/unhead.config.mjs";
|
|
19
22
|
import entryIds from "#internal/nuxt/entry-ids.mjs";
|
|
20
23
|
import { entryFileName } from "#internal/entry-chunk.mjs";
|
|
24
|
+
import { iifeChunkFileName } from "#internal/streaming-iife-chunk.mjs";
|
|
21
25
|
//#region src/runtime/handlers/renderer.ts
|
|
22
26
|
globalThis.__buildAssetsURL = buildAssetsURL;
|
|
23
27
|
globalThis.__publicAssetsURL = publicAssetsURL;
|
|
@@ -28,6 +32,7 @@ const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : "";
|
|
|
28
32
|
const PAYLOAD_URL_RE = /^[^?]*\/_payload.json(?:\?.*)?$/;
|
|
29
33
|
const PAYLOAD_FILENAME = "_payload.json";
|
|
30
34
|
let entryPath;
|
|
35
|
+
const SSR_BOT_RE = NUXT_SSR_STREAMING_BOT_RE;
|
|
31
36
|
const handler = defineEventHandler((event) => {
|
|
32
37
|
const ssrError = event.url.pathname.startsWith("/__nuxt_error") ? getQuery$1(event) : null;
|
|
33
38
|
if (ssrError && !event.context.nuxt?.["~internal"]) throw new HTTPError({
|
|
@@ -72,17 +77,37 @@ async function renderRoute(event, ssrError) {
|
|
|
72
77
|
if (link) writeEarlyHints(event, { link });
|
|
73
78
|
}
|
|
74
79
|
if (NUXT_INLINE_STYLES) for (const id of entryIds) ssrContext.modules.add(id);
|
|
80
|
+
const canStream = NUXT_SSR_STREAMING && !ssrContext.noSSR && !ssrError && !isRenderingPayload && !import.meta.prerender && !(NUXT_NO_SCRIPTS && componentIslandsActive) && !routeOptions.noScripts && !!routeOptions.streaming && !routeOptions.cache && !routeOptions.isr && !routeOptions.redirect;
|
|
81
|
+
const renderRouteContext = {
|
|
82
|
+
canStream,
|
|
83
|
+
prefersStream: !!(NUXT_SSR_STREAMING && !SSR_BOT_RE.test(event.req.headers.get("user-agent") || ""))
|
|
84
|
+
};
|
|
85
|
+
const nitroHooks = useNitroHooks();
|
|
86
|
+
const renderRouteResult = nitroHooks.callHook("render:route", renderRouteContext, { event });
|
|
87
|
+
if (renderRouteResult instanceof Promise) await renderRouteResult;
|
|
88
|
+
if (NUXT_SSR_STREAMING && canStream && renderRouteContext.prefersStream) return renderStreamedResponse({
|
|
89
|
+
event,
|
|
90
|
+
ssrContext,
|
|
91
|
+
renderer,
|
|
92
|
+
routeOptions,
|
|
93
|
+
ssrError,
|
|
94
|
+
_PAYLOAD_EXTRACTION,
|
|
95
|
+
_PAYLOAD_INLINE,
|
|
96
|
+
payloadURL
|
|
97
|
+
});
|
|
75
98
|
const _rendered = await renderer.renderToString(ssrContext).catch(async (error) => {
|
|
76
99
|
if (ssrContext["~renderResponse"] && error.message === "skipping render") return {};
|
|
77
100
|
const _err = !ssrError && ssrContext.payload?.error || error;
|
|
78
|
-
|
|
101
|
+
const r = ssrContext.nuxt?.hooks.callHook("app:error", _err);
|
|
102
|
+
if (r instanceof Promise) await r;
|
|
79
103
|
throw _err;
|
|
80
104
|
});
|
|
81
105
|
const inlinedStyles = NUXT_INLINE_STYLES && !ssrContext["~renderResponse"] && !isRenderingPayload ? await renderInlineStyles(ssrContext.modules ?? []) : [];
|
|
82
|
-
|
|
106
|
+
const appRenderedResult = ssrContext.nuxt?.hooks.callHook("app:rendered", {
|
|
83
107
|
ssrContext,
|
|
84
108
|
renderResult: _rendered
|
|
85
109
|
});
|
|
110
|
+
if (appRenderedResult instanceof Promise) await appRenderedResult;
|
|
86
111
|
if (ssrContext["~renderResponse"]) return returnResponse(event, ssrContext["~renderResponse"]);
|
|
87
112
|
if (ssrContext.payload?.error && !ssrError) throw ssrContext.payload.error;
|
|
88
113
|
if (isRenderingPayload) {
|
|
@@ -107,6 +132,8 @@ async function renderRoute(event, ssrError) {
|
|
|
107
132
|
}
|
|
108
133
|
}
|
|
109
134
|
ssrContext.head.push({ script: [{
|
|
135
|
+
tagPosition: "head",
|
|
136
|
+
tagPriority: "critical",
|
|
110
137
|
type: "importmap",
|
|
111
138
|
innerHTML: { imports: { "#entry": path } }
|
|
112
139
|
}] });
|
|
@@ -160,11 +187,272 @@ async function renderRoute(event, ssrError) {
|
|
|
160
187
|
body: [componentIslands ? replaceIslandTeleports(ssrContext, _rendered.html) : _rendered.html, APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportAttrs.id}`]]) : "") + APP_TELEPORT_CLOSE_TAG],
|
|
161
188
|
bodyAppend: [bodyTags]
|
|
162
189
|
};
|
|
163
|
-
|
|
190
|
+
const renderHtmlResult = nitroHooks.callHook("render:html", htmlContext, { event });
|
|
191
|
+
if (renderHtmlResult instanceof Promise) await renderHtmlResult;
|
|
164
192
|
event.res.headers.set("content-type", "text/html;charset=utf-8");
|
|
165
193
|
event.res.headers.set("x-powered-by", "Nuxt");
|
|
166
194
|
return renderHTMLDocument(htmlContext);
|
|
167
195
|
}
|
|
196
|
+
async function renderStreamedResponse(ctx) {
|
|
197
|
+
const { event, ssrContext, renderer, routeOptions, ssrError, _PAYLOAD_EXTRACTION, _PAYLOAD_INLINE, payloadURL } = ctx;
|
|
198
|
+
const NO_SCRIPTS = NUXT_NO_SCRIPTS || !!routeOptions?.noScripts;
|
|
199
|
+
const { link: linkHeader } = renderResourceHeaders({}, renderer.rendererContext);
|
|
200
|
+
if (linkHeader) event.res.headers.append("link", linkHeader);
|
|
201
|
+
const entryInlineStyles = NUXT_INLINE_STYLES ? await renderInlineStyles(new Set(entryIds)) : [];
|
|
202
|
+
if (entryInlineStyles.length) ssrContext.head.push({ style: entryInlineStyles });
|
|
203
|
+
const { styles: entryStyles, scripts: entryScripts } = getRequestDependencies({}, renderer.rendererContext);
|
|
204
|
+
const shellLinks = [];
|
|
205
|
+
for (const resource of Object.values(entryStyles)) {
|
|
206
|
+
if (import.meta.dev && "inline" in getQuery(resource.file)) continue;
|
|
207
|
+
shellLinks.push({
|
|
208
|
+
rel: "stylesheet",
|
|
209
|
+
href: renderer.rendererContext.buildAssetsURL(resource.file),
|
|
210
|
+
crossorigin: ""
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
if (shellLinks.length) ssrContext.head.push({ link: shellLinks });
|
|
214
|
+
if (entryFileName && !NO_SCRIPTS) {
|
|
215
|
+
let path = entryPath;
|
|
216
|
+
if (!path) {
|
|
217
|
+
path = buildAssetsURL(entryFileName);
|
|
218
|
+
if (ssrContext.runtimeConfig.app.cdnURL || /^(?:\/|\.+\/)/.test(path)) entryPath = path;
|
|
219
|
+
else {
|
|
220
|
+
path = relative(event.url.pathname.replace(/\/[^/]+$/, "/"), joinURL("/", path));
|
|
221
|
+
if (!/^(?:\/|\.+\/)/.test(path)) path = `./${path}`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
ssrContext.head.push({ script: [{
|
|
225
|
+
tagPosition: "head",
|
|
226
|
+
tagPriority: "critical",
|
|
227
|
+
type: "importmap",
|
|
228
|
+
innerHTML: { imports: { "#entry": path } }
|
|
229
|
+
}] });
|
|
230
|
+
}
|
|
231
|
+
if (_PAYLOAD_EXTRACTION && !_PAYLOAD_INLINE && !NO_SCRIPTS) ssrContext.head.push({ link: [{
|
|
232
|
+
rel: "preload",
|
|
233
|
+
as: "fetch",
|
|
234
|
+
crossorigin: "anonymous",
|
|
235
|
+
href: payloadURL
|
|
236
|
+
}] });
|
|
237
|
+
if (!NO_SCRIPTS) {
|
|
238
|
+
ssrContext.head.push({ link: getPreloadLinks({}, renderer.rendererContext) });
|
|
239
|
+
ssrContext.head.push({ link: getPrefetchLinks({}, renderer.rendererContext) });
|
|
240
|
+
}
|
|
241
|
+
if (!NO_SCRIPTS) ssrContext.head.push({ script: Object.values(entryScripts).map((resource) => ({
|
|
242
|
+
type: resource.module ? "module" : null,
|
|
243
|
+
src: renderer.rendererContext.buildAssetsURL(resource.file),
|
|
244
|
+
defer: resource.module ? null : true,
|
|
245
|
+
tagPosition: "head",
|
|
246
|
+
crossorigin: ""
|
|
247
|
+
})) });
|
|
248
|
+
if (!NO_SCRIPTS && !import.meta.dev && iifeChunkFileName) ssrContext.head.push({ link: [{
|
|
249
|
+
rel: "preload",
|
|
250
|
+
as: "script",
|
|
251
|
+
href: buildAssetsURL(iifeChunkFileName)
|
|
252
|
+
}] });
|
|
253
|
+
const createSSRApp = await getServerApp();
|
|
254
|
+
let vueApp;
|
|
255
|
+
try {
|
|
256
|
+
vueApp = await createSSRApp(ssrContext);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
if (ssrContext["~renderResponse"] && error?.message === "skipping render") {
|
|
259
|
+
event.res.headers.delete("link");
|
|
260
|
+
return returnResponse(event, ssrContext["~renderResponse"]);
|
|
261
|
+
}
|
|
262
|
+
const r = ssrContext.nuxt?.hooks.callHook("app:error", error);
|
|
263
|
+
if (r instanceof Promise) await r;
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
if (ssrContext["~renderResponse"]) {
|
|
267
|
+
event.res.headers.delete("link");
|
|
268
|
+
return returnResponse(event, ssrContext["~renderResponse"]);
|
|
269
|
+
}
|
|
270
|
+
const { headTags, bodyTags, bodyTagsOpen, htmlAttrs, bodyAttrs } = renderShell(ssrContext.head);
|
|
271
|
+
const cspNonce = headTags.match(/<script[^>]+\bnonce="([^"]*)"/)?.[1];
|
|
272
|
+
const nonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
|
|
273
|
+
const bootstrapScript = NO_SCRIPTS ? "" : createBootstrapScript(void 0, cspNonce);
|
|
274
|
+
let iifeScript = "";
|
|
275
|
+
if (!NO_SCRIPTS) if (!import.meta.dev && iifeChunkFileName) iifeScript = `<script async${nonceAttr} src="${buildAssetsURL(iifeChunkFileName)}"><\/script>`;
|
|
276
|
+
else iifeScript = `<script${nonceAttr}>${streamingIifeCode}<\/script>`;
|
|
277
|
+
const shellContext = {
|
|
278
|
+
htmlAttrs: htmlAttrs ? [htmlAttrs] : [],
|
|
279
|
+
head: normalizeChunks([bootstrapScript, headTags]),
|
|
280
|
+
bodyAttrs: bodyAttrs ? [bodyAttrs] : [],
|
|
281
|
+
bodyPrepend: normalizeChunks([iifeScript, bodyTagsOpen]),
|
|
282
|
+
body: [],
|
|
283
|
+
bodyAppend: []
|
|
284
|
+
};
|
|
285
|
+
const nitroHooks = useNitroHooks();
|
|
286
|
+
if (import.meta.dev) {
|
|
287
|
+
const initialBodyLen = shellContext.body.length;
|
|
288
|
+
const initialAppendLen = shellContext.bodyAppend.length;
|
|
289
|
+
const r = nitroHooks.callHook("render:html", shellContext, {
|
|
290
|
+
event,
|
|
291
|
+
streaming: true
|
|
292
|
+
});
|
|
293
|
+
if (r instanceof Promise) await r;
|
|
294
|
+
if (shellContext.body.length !== initialBodyLen || shellContext.bodyAppend.length !== initialAppendLen) console.warn(`[nuxt] \`render:html\` mutated \`body\`/\`bodyAppend\` while streaming (${event.url.pathname}). These fields are silently dropped because the body is about to stream - use the \`render:html:close\` hook instead.`);
|
|
295
|
+
} else {
|
|
296
|
+
const r = nitroHooks.callHook("render:html", shellContext, {
|
|
297
|
+
event,
|
|
298
|
+
streaming: true
|
|
299
|
+
});
|
|
300
|
+
if (r instanceof Promise) await r;
|
|
301
|
+
}
|
|
302
|
+
const shellHtml = `<!DOCTYPE html><html${joinAttrs(shellContext.htmlAttrs)}><head>${joinTags(shellContext.head)}</head><body${joinAttrs(shellContext.bodyAttrs)}>` + joinTags(shellContext.bodyPrepend);
|
|
303
|
+
const reader = renderToWebStream(vueApp, ssrContext).getReader();
|
|
304
|
+
let firstChunk;
|
|
305
|
+
try {
|
|
306
|
+
const { done, value } = await reader.read();
|
|
307
|
+
if (!done) firstChunk = value;
|
|
308
|
+
} catch (error) {
|
|
309
|
+
reader.releaseLock();
|
|
310
|
+
event.res.headers.delete("link");
|
|
311
|
+
if (ssrContext["~renderResponse"]) return returnResponse(event, ssrContext["~renderResponse"]);
|
|
312
|
+
const _err = !ssrError && ssrContext.payload?.error || error;
|
|
313
|
+
const r = ssrContext.nuxt?.hooks.callHook("app:error", _err);
|
|
314
|
+
if (r instanceof Promise) await r;
|
|
315
|
+
throw _err;
|
|
316
|
+
}
|
|
317
|
+
if (ssrContext["~renderResponse"]) {
|
|
318
|
+
reader.cancel().catch(() => {});
|
|
319
|
+
event.res.headers.delete("link");
|
|
320
|
+
return returnResponse(event, ssrContext["~renderResponse"]);
|
|
321
|
+
}
|
|
322
|
+
if (ssrContext.payload?.error && !ssrError) {
|
|
323
|
+
reader.cancel().catch(() => {});
|
|
324
|
+
event.res.headers.delete("link");
|
|
325
|
+
throw ssrContext.payload.error;
|
|
326
|
+
}
|
|
327
|
+
const committedSnapshot = import.meta.dev ? {
|
|
328
|
+
status: event.res.status,
|
|
329
|
+
statusText: event.res.statusText,
|
|
330
|
+
headers: Array.from(event.res.headers.entries()).sort().map(([k, v]) => `${k}: ${v}`).join("\n")
|
|
331
|
+
} : null;
|
|
332
|
+
const encoder = new TextEncoder();
|
|
333
|
+
let chunkIndex = 0;
|
|
334
|
+
const enqueueChunk = (controller, chunk) => {
|
|
335
|
+
const chunkContext = {
|
|
336
|
+
chunk,
|
|
337
|
+
index: chunkIndex++
|
|
338
|
+
};
|
|
339
|
+
const result = nitroHooks.callHook("render:html:chunk", chunkContext, { event });
|
|
340
|
+
if (result instanceof Promise) return result.then(() => {
|
|
341
|
+
controller.enqueue(chunkContext.chunk);
|
|
342
|
+
});
|
|
343
|
+
controller.enqueue(chunkContext.chunk);
|
|
344
|
+
};
|
|
345
|
+
const emittedStyles = new Set(Object.values(entryStyles).map((r) => r.file));
|
|
346
|
+
const inlinedCss = new Set(entryInlineStyles.map((s) => String(s.innerHTML)));
|
|
347
|
+
const renderRouteStyles = async () => {
|
|
348
|
+
let tags = "";
|
|
349
|
+
if (NUXT_INLINE_STYLES) {
|
|
350
|
+
for (const style of await renderInlineStyles(ssrContext.modules ?? [])) {
|
|
351
|
+
const css = String(style.innerHTML);
|
|
352
|
+
if (!css || inlinedCss.has(css)) continue;
|
|
353
|
+
inlinedCss.add(css);
|
|
354
|
+
tags += `<style${nonceAttr}>${css}</style>`;
|
|
355
|
+
}
|
|
356
|
+
return tags;
|
|
357
|
+
}
|
|
358
|
+
for (const resource of Object.values(getRequestDependencies(ssrContext, renderer.rendererContext).styles)) {
|
|
359
|
+
if (emittedStyles.has(resource.file)) continue;
|
|
360
|
+
if (import.meta.dev && "inline" in getQuery(resource.file)) continue;
|
|
361
|
+
emittedStyles.add(resource.file);
|
|
362
|
+
tags += `<link rel="stylesheet" crossorigin href="${renderer.rendererContext.buildAssetsURL(resource.file)}">`;
|
|
363
|
+
}
|
|
364
|
+
return tags;
|
|
365
|
+
};
|
|
366
|
+
const outputStream = new ReadableStream({
|
|
367
|
+
async start(controller) {
|
|
368
|
+
try {
|
|
369
|
+
await enqueueChunk(controller, encoder.encode(shellHtml));
|
|
370
|
+
await enqueueChunk(controller, encoder.encode(await renderRouteStyles() + APP_ROOT_OPEN_TAG));
|
|
371
|
+
if (firstChunk) {
|
|
372
|
+
await enqueueChunk(controller, firstChunk);
|
|
373
|
+
const headChunk = renderSSRHeadSuspenseChunk(ssrContext.head);
|
|
374
|
+
if (headChunk && !NO_SCRIPTS) await enqueueChunk(controller, encoder.encode(`<script${nonceAttr}>${headChunk};document.currentScript.remove()<\/script>`));
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
while (true) {
|
|
378
|
+
const { done, value } = await reader.read();
|
|
379
|
+
if (done) break;
|
|
380
|
+
await enqueueChunk(controller, value);
|
|
381
|
+
const headChunk = renderSSRHeadSuspenseChunk(ssrContext.head);
|
|
382
|
+
if (headChunk && !NO_SCRIPTS) await enqueueChunk(controller, encoder.encode(`<script${nonceAttr}>${headChunk};document.currentScript.remove()<\/script>`));
|
|
383
|
+
}
|
|
384
|
+
} finally {
|
|
385
|
+
reader.releaseLock();
|
|
386
|
+
}
|
|
387
|
+
if (!NO_SCRIPTS) {
|
|
388
|
+
const finalHeadChunk = renderSSRHeadSuspenseChunk(ssrContext.head);
|
|
389
|
+
if (finalHeadChunk) await enqueueChunk(controller, encoder.encode(`<script${nonceAttr}>${finalHeadChunk};document.currentScript.remove()<\/script>`));
|
|
390
|
+
}
|
|
391
|
+
const appRenderedResult = ssrContext.nuxt?.hooks.callHook("app:rendered", {
|
|
392
|
+
ssrContext,
|
|
393
|
+
renderResult: {}
|
|
394
|
+
});
|
|
395
|
+
if (appRenderedResult instanceof Promise) await appRenderedResult;
|
|
396
|
+
if (ssrContext.payload?.error && !ssrError) {
|
|
397
|
+
const r = ssrContext.nuxt?.hooks.callHook("app:error", ssrContext.payload.error);
|
|
398
|
+
if (r instanceof Promise) await r;
|
|
399
|
+
}
|
|
400
|
+
if (!NO_SCRIPTS) ssrContext.head.push({ script: _PAYLOAD_INLINE ? renderPayloadJsonScript({
|
|
401
|
+
ssrContext,
|
|
402
|
+
data: ssrContext.payload
|
|
403
|
+
}) : renderPayloadJsonScript({
|
|
404
|
+
ssrContext,
|
|
405
|
+
data: splitPayload(ssrContext).initial,
|
|
406
|
+
src: payloadURL
|
|
407
|
+
}) }, {
|
|
408
|
+
tagPosition: "bodyClose",
|
|
409
|
+
tagPriority: "high"
|
|
410
|
+
});
|
|
411
|
+
const closeContext = { bodyAppend: normalizeChunks([bodyTags, applyRenderOptions(ssrContext.head.render(), renderSSRHeadOptions).bodyTags]) };
|
|
412
|
+
const closeResult = nitroHooks.callHook("render:html:close", closeContext, { event });
|
|
413
|
+
if (closeResult instanceof Promise) await closeResult;
|
|
414
|
+
const teleportHtml = APP_TELEPORT_OPEN_TAG + (HAS_APP_TELEPORTS ? joinTags([ssrContext.teleports?.[`#${appTeleportAttrs.id}`]]) : "") + APP_TELEPORT_CLOSE_TAG;
|
|
415
|
+
const islandTeleports = NO_SCRIPTS ? "" : renderStreamedIslandTeleports(ssrContext, nonceAttr);
|
|
416
|
+
const closingHtml = APP_ROOT_CLOSE_TAG + await renderRouteStyles() + teleportHtml + (ssrContext.teleports?.body || "") + islandTeleports + joinTags(closeContext.bodyAppend) + "</body></html>";
|
|
417
|
+
await enqueueChunk(controller, encoder.encode(closingHtml));
|
|
418
|
+
controller.close();
|
|
419
|
+
if (committedSnapshot) {
|
|
420
|
+
const currentHeaders = Array.from(event.res.headers.entries()).sort().map(([k, v]) => `${k}: ${v}`).join("\n");
|
|
421
|
+
const lateMutations = [];
|
|
422
|
+
if (event.res.status !== committedSnapshot.status) lateMutations.push(`response status changed from ${committedSnapshot.status || 200} to ${event.res.status} (e.g. \`setResponseStatus\`)`);
|
|
423
|
+
if (event.res.statusText !== committedSnapshot.statusText) lateMutations.push(`response statusText changed (e.g. \`setResponseStatus\`)`);
|
|
424
|
+
if (currentHeaders !== committedSnapshot.headers) lateMutations.push(`response headers changed during render (e.g. \`useCookie\`, \`useResponseHeader\`, \`setHeader\`)`);
|
|
425
|
+
if (lateMutations.length) console.warn(`[nuxt] SSR streaming committed the response before render completed. The following mutations did not reach the client and were dropped:\n - ${lateMutations.join("\n - ")}\n Path: ${event.url.pathname}\n Move the mutation into a plugin (which runs before the shell is flushed), or opt this route out of streaming with \`routeRules: { '${event.url.pathname}': { streaming: false } }\` or the \`render:route\` hook.`);
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
await Promise.resolve(ssrContext.nuxt?.hooks.callHook("app:error", error)).catch(() => {});
|
|
429
|
+
ssrContext.payload ||= {};
|
|
430
|
+
ssrContext.payload.error ||= error;
|
|
431
|
+
try {
|
|
432
|
+
if (!NO_SCRIPTS) {
|
|
433
|
+
ssrContext.head.push({ script: renderPayloadJsonScript({
|
|
434
|
+
ssrContext,
|
|
435
|
+
data: ssrContext.payload
|
|
436
|
+
}) }, {
|
|
437
|
+
tagPosition: "bodyClose",
|
|
438
|
+
tagPriority: "high"
|
|
439
|
+
});
|
|
440
|
+
const tail = applyRenderOptions(ssrContext.head.render(), renderSSRHeadOptions);
|
|
441
|
+
controller.enqueue(encoder.encode(tail.bodyTags));
|
|
442
|
+
}
|
|
443
|
+
} catch {}
|
|
444
|
+
controller.enqueue(encoder.encode(APP_ROOT_CLOSE_TAG + "</body></html>"));
|
|
445
|
+
controller.close();
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
cancel(reason) {
|
|
449
|
+
reader.cancel(reason).catch(() => {});
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
event.res.headers.set("content-type", "text/html;charset=utf-8");
|
|
453
|
+
event.res.headers.set("x-powered-by", "Nuxt");
|
|
454
|
+
return outputStream;
|
|
455
|
+
}
|
|
168
456
|
function normalizeChunks(chunks) {
|
|
169
457
|
const result = [];
|
|
170
458
|
for (const _chunk of chunks) {
|
|
@@ -183,6 +471,16 @@ function joinAttrs(chunks) {
|
|
|
183
471
|
function renderHTMLDocument(html) {
|
|
184
472
|
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>`;
|
|
185
473
|
}
|
|
474
|
+
function applyRenderOptions(payload, options) {
|
|
475
|
+
if (!options.omitLineBreaks) return payload;
|
|
476
|
+
return {
|
|
477
|
+
headTags: payload.headTags.replaceAll("\n", ""),
|
|
478
|
+
bodyTags: payload.bodyTags.replaceAll("\n", ""),
|
|
479
|
+
bodyTagsOpen: payload.bodyTagsOpen.replaceAll("\n", ""),
|
|
480
|
+
htmlAttrs: payload.htmlAttrs,
|
|
481
|
+
bodyAttrs: payload.bodyAttrs
|
|
482
|
+
};
|
|
483
|
+
}
|
|
186
484
|
function stripInlineOnlyPayloadFields(payload) {
|
|
187
485
|
if (!payload.prefetchLinks) return payload;
|
|
188
486
|
const { prefetchLinks: _, ...rest } = payload;
|
|
@@ -73,6 +73,7 @@ function lazyCachedFunction(fn) {
|
|
|
73
73
|
function getRenderer(ssrContext) {
|
|
74
74
|
return NUXT_NO_SSR || ssrContext.noSSR ? getSPARenderer() : getSSRRenderer();
|
|
75
75
|
}
|
|
76
|
+
const getServerApp = lazyCachedFunction(getServerEntry);
|
|
76
77
|
const getSSRStyles = lazyCachedFunction(() => import("#build/dist/server/styles.mjs").then((r) => r.default || r));
|
|
77
78
|
//#endregion
|
|
78
|
-
export { getRenderer, getSSRRenderer, getSSRStyles };
|
|
79
|
+
export { APP_ROOT_CLOSE_TAG, APP_ROOT_OPEN_TAG, getRenderer, getSSRRenderer, getSSRStyles, getServerApp };
|
|
@@ -45,6 +45,34 @@ function getComponentSlotTeleport(clientUid, teleports) {
|
|
|
45
45
|
}
|
|
46
46
|
return slots;
|
|
47
47
|
}
|
|
48
|
+
const ISLAND_TELEPORT_RELOCATE_SCRIPT = `(()=>{for(const t of document.querySelectorAll('template[data-island-uid]')){const u=t.getAttribute('data-island-uid'),s=t.getAttribute('data-island-slot'),c=t.getAttribute('data-island-component'),a=document.querySelector('[data-island-uid="'+u+'"]'+(s!==null?'[data-island-slot="'+s+'"]':'[data-island-component="'+c+'"]'));if(a){a.insertBefore(t.content,a.firstChild)}t.remove()}})()`;
|
|
49
|
+
/**
|
|
50
|
+
* Emit each island teleport as an inert `<template>` keyed by its anchor,
|
|
51
|
+
* followed by a relocation script that moves the content into place before
|
|
52
|
+
* hydration. Returns an empty string when there are no island teleports.
|
|
53
|
+
*/
|
|
54
|
+
function renderStreamedIslandTeleports(ssrContext, nonceAttr = "") {
|
|
55
|
+
const { teleports, islandContext } = ssrContext;
|
|
56
|
+
if (islandContext || !teleports) return "";
|
|
57
|
+
let templates = "";
|
|
58
|
+
for (const key in teleports) {
|
|
59
|
+
const matchClientComp = key.match(SSR_CLIENT_TELEPORT_MARKER);
|
|
60
|
+
if (matchClientComp) {
|
|
61
|
+
const [, uid, clientId] = matchClientComp;
|
|
62
|
+
if (!uid || !clientId) continue;
|
|
63
|
+
templates += `<template data-island-uid="${uid}" data-island-component="${clientId}">${teleports[key]}</template>`;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const matchSlot = key.match(SSR_SLOT_TELEPORT_MARKER);
|
|
67
|
+
if (matchSlot) {
|
|
68
|
+
const [, uid, slot] = matchSlot;
|
|
69
|
+
if (!uid || !slot) continue;
|
|
70
|
+
templates += `<template data-island-uid="${uid}" data-island-slot="${slot}">${teleports[key]}</template>`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!templates) return "";
|
|
74
|
+
return templates + `<script${nonceAttr}>${ISLAND_TELEPORT_RELOCATE_SCRIPT}<\/script>`;
|
|
75
|
+
}
|
|
48
76
|
function replaceIslandTeleports(ssrContext, html) {
|
|
49
77
|
const { teleports, islandContext } = ssrContext;
|
|
50
78
|
if (islandContext || !teleports) return html;
|
|
@@ -70,4 +98,4 @@ function replaceIslandTeleports(ssrContext, html) {
|
|
|
70
98
|
return html;
|
|
71
99
|
}
|
|
72
100
|
//#endregion
|
|
73
|
-
export { getClientIslandResponse, getComponentSlotTeleport, getServerComponentHTML, getSlotIslandResponse, replaceIslandTeleports };
|
|
101
|
+
export { getClientIslandResponse, getComponentSlotTeleport, getServerComponentHTML, getSlotIslandResponse, renderStreamedIslandTeleports, replaceIslandTeleports };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxt/nitro-server-nightly",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-29664396.5668d45c",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/nuxt/nuxt.git",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dist"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@nuxt/kit": "npm:@nuxt/kit-nightly@5.0.0-
|
|
30
|
+
"@nuxt/kit": "npm:@nuxt/kit-nightly@5.0.0-29664396.5668d45c",
|
|
31
31
|
"@unhead/vue": "^3.1.0",
|
|
32
32
|
"@vue/shared": "3.5.34",
|
|
33
33
|
"consola": "^3.4.2",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@babel/plugin-proposal-decorators": "^7.25.0",
|
|
59
59
|
"@babel/plugin-syntax-typescript": "^7.25.0",
|
|
60
60
|
"@rollup/plugin-babel": "^6.0.0 || ^7.0.0",
|
|
61
|
-
"nuxt": "npm:nuxt-nightly@5.0.0-
|
|
61
|
+
"nuxt": "npm:nuxt-nightly@5.0.0-29664396.5668d45c"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"@babel/plugin-proposal-decorators": {
|
|
@@ -74,10 +74,10 @@
|
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@babel/plugin-proposal-decorators": "7.29.0",
|
|
76
76
|
"@babel/plugin-syntax-typescript": "7.28.6",
|
|
77
|
-
"@nuxt/schema": "npm:@nuxt/schema-nightly@5.0.0-
|
|
77
|
+
"@nuxt/schema": "npm:@nuxt/schema-nightly@5.0.0-29664396.5668d45c",
|
|
78
78
|
"@rollup/plugin-babel": "7.0.0",
|
|
79
79
|
"hookable": "6.1.1",
|
|
80
|
-
"nuxt": "npm:nuxt-nightly@5.0.0-
|
|
80
|
+
"nuxt": "npm:nuxt-nightly@5.0.0-29664396.5668d45c",
|
|
81
81
|
"tsdown": "0.22.0",
|
|
82
82
|
"vitest": "4.1.7"
|
|
83
83
|
},
|