vinext 0.0.33 → 0.0.35
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/README.md +1 -0
- package/dist/build/prerender.d.ts +9 -4
- package/dist/build/prerender.js +27 -9
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/run-prerender.js +4 -1
- package/dist/build/run-prerender.js.map +1 -1
- package/dist/config/next-config.d.ts +7 -1
- package/dist/config/next-config.js +39 -26
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +52 -4
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-rsc-entry.js +278 -740
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +60 -134
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +339 -26
- package/dist/index.js.map +1 -1
- package/dist/plugins/optimize-imports.js +28 -2
- package/dist/plugins/optimize-imports.js.map +1 -1
- package/dist/server/app-browser-entry.js +4 -5
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-page-boundary-render.d.ts +63 -0
- package/dist/server/app-page-boundary-render.js +182 -0
- package/dist/server/app-page-boundary-render.js.map +1 -0
- package/dist/server/app-page-boundary.d.ts +57 -0
- package/dist/server/app-page-boundary.js +60 -0
- package/dist/server/app-page-boundary.js.map +1 -0
- package/dist/server/app-page-execution.d.ts +46 -0
- package/dist/server/app-page-execution.js +109 -0
- package/dist/server/app-page-execution.js.map +1 -0
- package/dist/server/app-page-probe.d.ts +17 -0
- package/dist/server/app-page-probe.js +35 -0
- package/dist/server/app-page-probe.js.map +1 -0
- package/dist/server/app-page-render.d.ts +59 -0
- package/dist/server/app-page-render.js +176 -0
- package/dist/server/app-page-render.js.map +1 -0
- package/dist/server/app-page-request.d.ts +58 -0
- package/dist/server/app-page-request.js +79 -0
- package/dist/server/app-page-request.js.map +1 -0
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +63 -0
- package/dist/server/app-page-stream.js +98 -0
- package/dist/server/app-page-stream.js.map +1 -0
- package/dist/server/app-ssr-stream.js +6 -4
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +54 -0
- package/dist/server/pages-page-response.js +140 -0
- package/dist/server/pages-page-response.js.map +1 -0
- package/dist/server/prod-server.d.ts +13 -1
- package/dist/server/prod-server.js +116 -19
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/seed-cache.d.ts +44 -0
- package/dist/server/seed-cache.js +127 -0
- package/dist/server/seed-cache.js.map +1 -0
- package/dist/server/worker-utils.d.ts +0 -6
- package/dist/server/worker-utils.js +41 -5
- package/dist/server/worker-utils.js.map +1 -1
- package/dist/shims/error-boundary.js +1 -1
- package/dist/shims/font-google-base.js +1 -1
- package/dist/shims/font-google-base.js.map +1 -1
- package/dist/shims/font-google.d.ts +2 -3
- package/dist/shims/font-google.js +2 -3
- package/dist/shims/image.js +4 -2
- package/dist/shims/image.js.map +1 -1
- package/package.json +1 -1
- package/dist/shims/font-google.generated.d.ts +0 -1929
- package/dist/shims/font-google.generated.js +0 -1929
- package/dist/shims/font-google.generated.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-page-stream.js","names":[],"sources":["../../src/server/app-page-stream.ts"],"sourcesContent":["import type { AppPageFontPreload } from \"./app-page-execution.js\";\n\nexport interface AppPageFontData {\n links: string[];\n preloads: readonly AppPageFontPreload[];\n styles: string[];\n}\n\nexport interface CreateAppPageFontDataOptions {\n getLinks: () => string[];\n getPreloads: () => AppPageFontPreload[];\n getStyles: () => string[];\n}\n\nexport interface AppPageSsrHandler {\n handleSsr: (\n rscStream: ReadableStream<Uint8Array>,\n navigationContext: unknown,\n fontData: AppPageFontData,\n ) => Promise<ReadableStream<Uint8Array>>;\n}\n\nexport interface RenderAppPageHtmlStreamOptions {\n fontData: AppPageFontData;\n navigationContext: unknown;\n rscStream: ReadableStream<Uint8Array>;\n ssrHandler: AppPageSsrHandler;\n}\n\nexport interface RenderAppPageHtmlResponseOptions extends RenderAppPageHtmlStreamOptions {\n clearRequestContext: () => void;\n fontLinkHeader?: string;\n status: number;\n}\n\nexport interface AppPageHtmlStreamRecoveryResult {\n htmlStream: ReadableStream<Uint8Array> | null;\n response: Response | null;\n}\n\nexport interface RenderAppPageHtmlStreamWithRecoveryOptions<TSpecialError> {\n onShellRendered?: () => void;\n renderErrorBoundaryResponse: (error: unknown) => Promise<Response | null>;\n renderHtmlStream: () => Promise<ReadableStream<Uint8Array>>;\n renderSpecialErrorResponse: (specialError: TSpecialError) => Promise<Response>;\n resolveSpecialError: (error: unknown) => TSpecialError | null;\n}\n\nexport interface AppPageRscErrorTracker {\n getCapturedError: () => unknown;\n onRenderError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown;\n}\n\nexport interface ShouldRerenderAppPageWithGlobalErrorOptions {\n capturedError: unknown;\n hasLocalBoundary: boolean;\n}\n\nexport function createAppPageFontData(options: CreateAppPageFontDataOptions): AppPageFontData {\n return {\n links: options.getLinks(),\n preloads: options.getPreloads(),\n styles: options.getStyles(),\n };\n}\n\nexport async function renderAppPageHtmlStream(\n options: RenderAppPageHtmlStreamOptions,\n): Promise<ReadableStream<Uint8Array>> {\n return options.ssrHandler.handleSsr(\n options.rscStream,\n options.navigationContext,\n options.fontData,\n );\n}\n\n/**\n * Wraps a stream so that `onFlush` is called when the last byte has been read\n * by the downstream consumer (i.e. when the HTTP layer finishes draining the\n * response body). This is the correct place to clear per-request context,\n * because the RSC/SSR pipeline is lazy — components execute while the stream\n * is being consumed, not when the stream handle is first obtained.\n */\nexport function deferUntilStreamConsumed(\n stream: ReadableStream<Uint8Array>,\n onFlush: () => void,\n): ReadableStream<Uint8Array> {\n let called = false;\n const once = () => {\n if (!called) {\n called = true;\n onFlush();\n }\n };\n\n const cleanup = new TransformStream<Uint8Array, Uint8Array>({\n flush() {\n once();\n },\n });\n\n const piped = stream.pipeThrough(cleanup);\n\n // Wrap with a ReadableStream so we can intercept cancel() — the TransformStream\n // Transformer interface does not expose a cancel hook in the Web Streams spec.\n const reader = piped.getReader();\n return new ReadableStream<Uint8Array>({\n pull(controller) {\n return reader.read().then(({ done, value }) => {\n if (done) {\n controller.close();\n } else {\n controller.enqueue(value);\n }\n });\n },\n cancel(reason) {\n // Stream cancelled before fully consumed (e.g. client disconnected).\n // Still clear per-request context to avoid leaks.\n once();\n return reader.cancel(reason);\n },\n });\n}\n\nexport async function renderAppPageHtmlResponse(\n options: RenderAppPageHtmlResponseOptions,\n): Promise<Response> {\n const htmlStream = await renderAppPageHtmlStream(options);\n\n // Defer clearRequestContext() until the stream is fully consumed by the HTTP\n // layer. Calling it synchronously here would race the lazy RSC/SSR pipeline:\n // components execute while the stream is being pulled, not when the handle\n // is first returned. See: https://github.com/cloudflare/vinext/issues/660\n const safeStream = deferUntilStreamConsumed(htmlStream, () => {\n options.clearRequestContext();\n });\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/html; charset=utf-8\",\n Vary: \"RSC, Accept\",\n };\n\n if (options.fontLinkHeader) {\n headers.Link = options.fontLinkHeader;\n }\n\n return new Response(safeStream, {\n status: options.status,\n headers,\n });\n}\n\nexport async function renderAppPageHtmlStreamWithRecovery<TSpecialError>(\n options: RenderAppPageHtmlStreamWithRecoveryOptions<TSpecialError>,\n): Promise<AppPageHtmlStreamRecoveryResult> {\n try {\n const htmlStream = await options.renderHtmlStream();\n options.onShellRendered?.();\n return {\n htmlStream,\n response: null,\n };\n } catch (error) {\n const specialError = options.resolveSpecialError(error);\n if (specialError) {\n return {\n htmlStream: null,\n response: await options.renderSpecialErrorResponse(specialError),\n };\n }\n\n const boundaryResponse = await options.renderErrorBoundaryResponse(error);\n if (boundaryResponse) {\n return {\n htmlStream: null,\n response: boundaryResponse,\n };\n }\n\n throw error;\n }\n}\n\nexport function createAppPageRscErrorTracker(\n baseOnError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown,\n): AppPageRscErrorTracker {\n let capturedError: unknown = null;\n\n return {\n getCapturedError() {\n return capturedError;\n },\n onRenderError(error, requestInfo, errorContext) {\n if (!(error && typeof error === \"object\" && \"digest\" in error)) {\n capturedError = error;\n }\n return baseOnError(error, requestInfo, errorContext);\n },\n };\n}\n\nexport function shouldRerenderAppPageWithGlobalError(\n options: ShouldRerenderAppPageWithGlobalErrorOptions,\n): boolean {\n return Boolean(options.capturedError) && !options.hasLocalBoundary;\n}\n"],"mappings":";AA0DA,SAAgB,sBAAsB,SAAwD;AAC5F,QAAO;EACL,OAAO,QAAQ,UAAU;EACzB,UAAU,QAAQ,aAAa;EAC/B,QAAQ,QAAQ,WAAW;EAC5B;;AAGH,eAAsB,wBACpB,SACqC;AACrC,QAAO,QAAQ,WAAW,UACxB,QAAQ,WACR,QAAQ,mBACR,QAAQ,SACT;;;;;;;;;AAUH,SAAgB,yBACd,QACA,SAC4B;CAC5B,IAAI,SAAS;CACb,MAAM,aAAa;AACjB,MAAI,CAAC,QAAQ;AACX,YAAS;AACT,YAAS;;;CAIb,MAAM,UAAU,IAAI,gBAAwC,EAC1D,QAAQ;AACN,QAAM;IAET,CAAC;CAMF,MAAM,SAJQ,OAAO,YAAY,QAAQ,CAIpB,WAAW;AAChC,QAAO,IAAI,eAA2B;EACpC,KAAK,YAAY;AACf,UAAO,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,YAAY;AAC7C,QAAI,KACF,YAAW,OAAO;QAElB,YAAW,QAAQ,MAAM;KAE3B;;EAEJ,OAAO,QAAQ;AAGb,SAAM;AACN,UAAO,OAAO,OAAO,OAAO;;EAE/B,CAAC;;AAGJ,eAAsB,0BACpB,SACmB;CAOnB,MAAM,aAAa,yBANA,MAAM,wBAAwB,QAAQ,QAMK;AAC5D,UAAQ,qBAAqB;GAC7B;CAEF,MAAM,UAAkC;EACtC,gBAAgB;EAChB,MAAM;EACP;AAED,KAAI,QAAQ,eACV,SAAQ,OAAO,QAAQ;AAGzB,QAAO,IAAI,SAAS,YAAY;EAC9B,QAAQ,QAAQ;EAChB;EACD,CAAC;;AAGJ,eAAsB,oCACpB,SAC0C;AAC1C,KAAI;EACF,MAAM,aAAa,MAAM,QAAQ,kBAAkB;AACnD,UAAQ,mBAAmB;AAC3B,SAAO;GACL;GACA,UAAU;GACX;UACM,OAAO;EACd,MAAM,eAAe,QAAQ,oBAAoB,MAAM;AACvD,MAAI,aACF,QAAO;GACL,YAAY;GACZ,UAAU,MAAM,QAAQ,2BAA2B,aAAa;GACjE;EAGH,MAAM,mBAAmB,MAAM,QAAQ,4BAA4B,MAAM;AACzE,MAAI,iBACF,QAAO;GACL,YAAY;GACZ,UAAU;GACX;AAGH,QAAM;;;AAIV,SAAgB,6BACd,aACwB;CACxB,IAAI,gBAAyB;AAE7B,QAAO;EACL,mBAAmB;AACjB,UAAO;;EAET,cAAc,OAAO,aAAa,cAAc;AAC9C,OAAI,EAAE,SAAS,OAAO,UAAU,YAAY,YAAY,OACtD,iBAAgB;AAElB,UAAO,YAAY,OAAO,aAAa,aAAa;;EAEvD;;AAGH,SAAgB,qCACd,SACS;AACT,QAAO,QAAQ,QAAQ,cAAc,IAAI,CAAC,QAAQ"}
|
|
@@ -6,7 +6,7 @@ import { safeJsonStringify } from "./html.js";
|
|
|
6
6
|
* the HTML spec requires as="style" for <link rel="preload">.
|
|
7
7
|
*/
|
|
8
8
|
function fixFlightHints(text) {
|
|
9
|
-
return text.replace(/(\d
|
|
9
|
+
return text.replace(/(\d*:HL\[.*?),"stylesheet"(\]|,)/g, "$1,\"style\"$2");
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Create a helper that progressively embeds RSC chunks as inline <script> tags.
|
|
@@ -92,9 +92,11 @@ function createTickBufferedTransform(rscEmbed, injectHTML = "") {
|
|
|
92
92
|
buffered.push(fixPreloadAs(decoder.decode(chunk, { stream: true })));
|
|
93
93
|
if (timeoutId !== null) return;
|
|
94
94
|
timeoutId = setTimeout(() => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
try {
|
|
96
|
+
flushBuffered(controller);
|
|
97
|
+
const rscScripts = rscEmbed.flush();
|
|
98
|
+
if (rscScripts) controller.enqueue(encoder.encode(rscScripts));
|
|
99
|
+
} catch {}
|
|
98
100
|
timeoutId = null;
|
|
99
101
|
}, 0);
|
|
100
102
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-ssr-stream.js","names":[],"sources":["../../src/server/app-ssr-stream.ts"],"sourcesContent":["import { safeJsonStringify } from \"./html.js\";\n\nexport interface RscEmbedTransform {\n flush(): string;\n finalize(): Promise<string>;\n}\n\n/**\n * Fix invalid preload \"as\" values in RSC Flight hint lines before they reach\n * the client. React Flight emits HL hints with as=\"stylesheet\" for CSS, but\n * the HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixFlightHints(text: string): string {\n return text.replace(/(\\d
|
|
1
|
+
{"version":3,"file":"app-ssr-stream.js","names":[],"sources":["../../src/server/app-ssr-stream.ts"],"sourcesContent":["import { safeJsonStringify } from \"./html.js\";\n\nexport interface RscEmbedTransform {\n flush(): string;\n finalize(): Promise<string>;\n}\n\n/**\n * Fix invalid preload \"as\" values in RSC Flight hint lines before they reach\n * the client. React Flight emits HL hints with as=\"stylesheet\" for CSS, but\n * the HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixFlightHints(text: string): string {\n return text.replace(/(\\d*:HL\\[.*?),\"stylesheet\"(\\]|,)/g, '$1,\"style\"$2');\n}\n\n/**\n * Create a helper that progressively embeds RSC chunks as inline <script> tags.\n * The browser entry turns the embedded text chunks back into Uint8Array data.\n */\nexport function createRscEmbedTransform(\n embedStream: ReadableStream<Uint8Array>,\n): RscEmbedTransform {\n const reader = embedStream.getReader();\n const decoder = new TextDecoder();\n let pendingChunks: string[] = [];\n let reading = false;\n\n async function pumpReader(): Promise<void> {\n if (reading) return;\n reading = true;\n try {\n while (true) {\n const result = await reader.read();\n if (result.done) break;\n const text = decoder.decode(result.value, { stream: true });\n // The RSC entry already fixes HL hints at the source. Keep this second\n // pass as defense in depth for any embed stream that bypasses that\n // wrapper; the rewrite is idempotent, so double-application is safe.\n pendingChunks.push(fixFlightHints(text));\n }\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n console.warn(\"[vinext] RSC embed stream read error:\", error);\n }\n } finally {\n reading = false;\n }\n }\n\n const pumpPromise = pumpReader();\n\n return {\n flush(): string {\n if (pendingChunks.length === 0) return \"\";\n\n const chunks = pendingChunks;\n pendingChunks = [];\n\n let scripts = \"\";\n for (const chunk of chunks) {\n scripts +=\n \"<script>self.__VINEXT_RSC_CHUNKS__=self.__VINEXT_RSC_CHUNKS__||[];self.__VINEXT_RSC_CHUNKS__.push(\" +\n safeJsonStringify(chunk) +\n \")</script>\";\n }\n return scripts;\n },\n\n async finalize(): Promise<string> {\n await pumpPromise;\n let scripts = this.flush();\n scripts += \"<script>self.__VINEXT_RSC_DONE__=true</script>\";\n return scripts;\n },\n };\n}\n\n/**\n * Fix invalid preload \"as\" values in server-rendered HTML.\n * React Fizz emits <link rel=\"preload\" as=\"stylesheet\"> for CSS, but the\n * HTML spec requires as=\"style\" for <link rel=\"preload\">.\n */\nexport function fixPreloadAs(html: string): string {\n return html.replace(/<link(?=[^>]*\\srel=\"preload\")[^>]*>/g, (tag) => {\n return tag.replace(' as=\"stylesheet\"', ' as=\"style\"');\n });\n}\n\n/**\n * Create the tick-buffered HTML transform that injects RSC scripts between\n * React Fizz flush cycles without corrupting split HTML chunks.\n */\nexport function createTickBufferedTransform(\n rscEmbed: RscEmbedTransform,\n injectHTML = \"\",\n): TransformStream<Uint8Array, Uint8Array> {\n const decoder = new TextDecoder();\n const encoder = new TextEncoder();\n let injected = false;\n let buffered: string[] = [];\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const flushBuffered = (controller: TransformStreamDefaultController<Uint8Array>): void => {\n for (const chunk of buffered) {\n if (!injected) {\n const headEnd = chunk.indexOf(\"</head>\");\n if (headEnd !== -1) {\n const before = chunk.slice(0, headEnd);\n const after = chunk.slice(headEnd);\n controller.enqueue(encoder.encode(before + injectHTML + after));\n injected = true;\n continue;\n }\n }\n controller.enqueue(encoder.encode(chunk));\n }\n buffered = [];\n };\n\n return new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n buffered.push(fixPreloadAs(decoder.decode(chunk, { stream: true })));\n\n if (timeoutId !== null) return;\n\n timeoutId = setTimeout(() => {\n try {\n flushBuffered(controller);\n\n const rscScripts = rscEmbed.flush();\n if (rscScripts) {\n controller.enqueue(encoder.encode(rscScripts));\n }\n } catch {\n // Stream was cancelled between when the timeout was registered and\n // when it fired (e.g. client disconnected, health-check cancelled\n // the response body). Ignore — the stream is already closed.\n }\n\n timeoutId = null;\n }, 0);\n },\n\n async flush(controller) {\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n\n flushBuffered(controller);\n\n if (!injected && injectHTML) {\n controller.enqueue(encoder.encode(injectHTML));\n }\n\n const finalScripts = await rscEmbed.finalize();\n if (finalScripts) {\n controller.enqueue(encoder.encode(finalScripts));\n }\n },\n });\n}\n"],"mappings":";;;;;;;AAYA,SAAgB,eAAe,MAAsB;AACnD,QAAO,KAAK,QAAQ,qCAAqC,iBAAe;;;;;;AAO1E,SAAgB,wBACd,aACmB;CACnB,MAAM,SAAS,YAAY,WAAW;CACtC,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,gBAA0B,EAAE;CAChC,IAAI,UAAU;CAEd,eAAe,aAA4B;AACzC,MAAI,QAAS;AACb,YAAU;AACV,MAAI;AACF,UAAO,MAAM;IACX,MAAM,SAAS,MAAM,OAAO,MAAM;AAClC,QAAI,OAAO,KAAM;IACjB,MAAM,OAAO,QAAQ,OAAO,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;AAI3D,kBAAc,KAAK,eAAe,KAAK,CAAC;;WAEnC,OAAO;AACd,OAAI,QAAQ,IAAI,aAAa,aAC3B,SAAQ,KAAK,yCAAyC,MAAM;YAEtD;AACR,aAAU;;;CAId,MAAM,cAAc,YAAY;AAEhC,QAAO;EACL,QAAgB;AACd,OAAI,cAAc,WAAW,EAAG,QAAO;GAEvC,MAAM,SAAS;AACf,mBAAgB,EAAE;GAElB,IAAI,UAAU;AACd,QAAK,MAAM,SAAS,OAClB,YACE,uGACA,kBAAkB,MAAM,GACxB;AAEJ,UAAO;;EAGT,MAAM,WAA4B;AAChC,SAAM;GACN,IAAI,UAAU,KAAK,OAAO;AAC1B,cAAW;AACX,UAAO;;EAEV;;;;;;;AAQH,SAAgB,aAAa,MAAsB;AACjD,QAAO,KAAK,QAAQ,yCAAyC,QAAQ;AACnE,SAAO,IAAI,QAAQ,sBAAoB,gBAAc;GACrD;;;;;;AAOJ,SAAgB,4BACd,UACA,aAAa,IAC4B;CACzC,MAAM,UAAU,IAAI,aAAa;CACjC,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,WAAW;CACf,IAAI,WAAqB,EAAE;CAC3B,IAAI,YAAkD;CAEtD,MAAM,iBAAiB,eAAmE;AACxF,OAAK,MAAM,SAAS,UAAU;AAC5B,OAAI,CAAC,UAAU;IACb,MAAM,UAAU,MAAM,QAAQ,UAAU;AACxC,QAAI,YAAY,IAAI;KAClB,MAAM,SAAS,MAAM,MAAM,GAAG,QAAQ;KACtC,MAAM,QAAQ,MAAM,MAAM,QAAQ;AAClC,gBAAW,QAAQ,QAAQ,OAAO,SAAS,aAAa,MAAM,CAAC;AAC/D,gBAAW;AACX;;;AAGJ,cAAW,QAAQ,QAAQ,OAAO,MAAM,CAAC;;AAE3C,aAAW,EAAE;;AAGf,QAAO,IAAI,gBAAwC;EACjD,UAAU,OAAO,YAAY;AAC3B,YAAS,KAAK,aAAa,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC,CAAC,CAAC;AAEpE,OAAI,cAAc,KAAM;AAExB,eAAY,iBAAiB;AAC3B,QAAI;AACF,mBAAc,WAAW;KAEzB,MAAM,aAAa,SAAS,OAAO;AACnC,SAAI,WACF,YAAW,QAAQ,QAAQ,OAAO,WAAW,CAAC;YAE1C;AAMR,gBAAY;MACX,EAAE;;EAGP,MAAM,MAAM,YAAY;AACtB,OAAI,cAAc,MAAM;AACtB,iBAAa,UAAU;AACvB,gBAAY;;AAGd,iBAAc,WAAW;AAEzB,OAAI,CAAC,YAAY,WACf,YAAW,QAAQ,QAAQ,OAAO,WAAW,CAAC;GAGhD,MAAM,eAAe,MAAM,SAAS,UAAU;AAC9C,OAAI,aACF,YAAW,QAAQ,QAAQ,OAAO,aAAa,CAAC;;EAGrD,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/server/pages-page-response.d.ts
|
|
4
|
+
interface PagesFontPreload {
|
|
5
|
+
href: string;
|
|
6
|
+
type: string;
|
|
7
|
+
}
|
|
8
|
+
interface PagesI18nRenderContext {
|
|
9
|
+
locale?: string;
|
|
10
|
+
locales?: string[];
|
|
11
|
+
defaultLocale?: string;
|
|
12
|
+
domainLocales?: unknown;
|
|
13
|
+
}
|
|
14
|
+
interface PagesGsspResponse {
|
|
15
|
+
statusCode: number;
|
|
16
|
+
getHeaders(): Record<string, string | number | boolean | string[]>;
|
|
17
|
+
}
|
|
18
|
+
interface RenderPagesPageResponseOptions {
|
|
19
|
+
assetTags: string;
|
|
20
|
+
buildId: string | null;
|
|
21
|
+
clearSsrContext: () => void;
|
|
22
|
+
createPageElement: (pageProps: Record<string, unknown>) => ReactNode;
|
|
23
|
+
DocumentComponent: ComponentType | null;
|
|
24
|
+
flushPreloads?: (() => Promise<void> | void) | undefined;
|
|
25
|
+
fontLinkHeader: string;
|
|
26
|
+
fontPreloads: PagesFontPreload[];
|
|
27
|
+
getFontLinks: () => string[];
|
|
28
|
+
getFontStyles: () => string[];
|
|
29
|
+
getSSRHeadHTML?: (() => string) | undefined;
|
|
30
|
+
gsspRes: PagesGsspResponse | null;
|
|
31
|
+
isrCacheKey: (router: string, pathname: string) => string;
|
|
32
|
+
isrRevalidateSeconds: number | null;
|
|
33
|
+
isrSet: (key: string, data: {
|
|
34
|
+
kind: "PAGES";
|
|
35
|
+
html: string;
|
|
36
|
+
pageData: Record<string, unknown>;
|
|
37
|
+
headers: undefined;
|
|
38
|
+
status: undefined;
|
|
39
|
+
}, revalidateSeconds: number) => Promise<void>;
|
|
40
|
+
i18n: PagesI18nRenderContext;
|
|
41
|
+
pageProps: Record<string, unknown>;
|
|
42
|
+
params: Record<string, unknown>;
|
|
43
|
+
renderDocumentToString: (element: ReactNode) => Promise<string>;
|
|
44
|
+
renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;
|
|
45
|
+
renderToReadableStream: (element: ReactNode) => Promise<ReadableStream<Uint8Array>>;
|
|
46
|
+
resetSSRHead?: (() => void) | undefined;
|
|
47
|
+
routePattern: string;
|
|
48
|
+
routeUrl: string;
|
|
49
|
+
safeJsonStringify: (value: unknown) => string;
|
|
50
|
+
}
|
|
51
|
+
declare function renderPagesPageResponse(options: RenderPagesPageResponseOptions): Promise<Response>;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { PagesFontPreload, PagesGsspResponse, PagesI18nRenderContext, RenderPagesPageResponseOptions, renderPagesPageResponse };
|
|
54
|
+
//# sourceMappingURL=pages-page-response.d.ts.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
//#region src/server/pages-page-response.ts
|
|
3
|
+
function escapeAttr(value) {
|
|
4
|
+
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
5
|
+
}
|
|
6
|
+
function buildPagesFontHeadHtml(fontLinks, fontPreloads, fontStyles) {
|
|
7
|
+
let html = "";
|
|
8
|
+
for (const link of fontLinks) html += `<link rel="stylesheet" href="${escapeAttr(link)}" />\n `;
|
|
9
|
+
for (const preload of fontPreloads) html += `<link rel="preload" href="${escapeAttr(preload.href)}" as="font" type="${escapeAttr(preload.type)}" crossorigin />\n `;
|
|
10
|
+
if (fontStyles.length > 0) html += `<style data-vinext-fonts>${fontStyles.join("\n")}</style>\n `;
|
|
11
|
+
return html;
|
|
12
|
+
}
|
|
13
|
+
function buildPagesNextDataScript(options) {
|
|
14
|
+
const nextDataPayload = {
|
|
15
|
+
props: { pageProps: options.pageProps },
|
|
16
|
+
page: options.routePattern,
|
|
17
|
+
query: options.params,
|
|
18
|
+
buildId: options.buildId,
|
|
19
|
+
isFallback: false
|
|
20
|
+
};
|
|
21
|
+
if (options.i18n.locales) {
|
|
22
|
+
nextDataPayload.locale = options.i18n.locale;
|
|
23
|
+
nextDataPayload.locales = options.i18n.locales;
|
|
24
|
+
nextDataPayload.defaultLocale = options.i18n.defaultLocale;
|
|
25
|
+
nextDataPayload.domainLocales = options.i18n.domainLocales;
|
|
26
|
+
}
|
|
27
|
+
const localeGlobals = options.i18n.locales ? `;window.__VINEXT_LOCALE__=${options.safeJsonStringify(options.i18n.locale)};window.__VINEXT_LOCALES__=${options.safeJsonStringify(options.i18n.locales)};window.__VINEXT_DEFAULT_LOCALE__=${options.safeJsonStringify(options.i18n.defaultLocale)}` : "";
|
|
28
|
+
return `<script>window.__NEXT_DATA__ = ${options.safeJsonStringify(nextDataPayload)}${localeGlobals}<\/script>`;
|
|
29
|
+
}
|
|
30
|
+
async function buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, options) {
|
|
31
|
+
if (options.DocumentComponent) {
|
|
32
|
+
let html = await options.renderDocumentToString(React.createElement(options.DocumentComponent));
|
|
33
|
+
html = html.replace("__NEXT_MAIN__", bodyMarker);
|
|
34
|
+
if (options.ssrHeadHTML || options.assetTags || fontHeadHTML) html = html.replace("</head>", ` ${fontHeadHTML}${options.ssrHeadHTML}\n ${options.assetTags}\n</head>`);
|
|
35
|
+
html = html.replace("<!-- __NEXT_SCRIPTS__ -->", nextDataScript);
|
|
36
|
+
if (!html.includes("__NEXT_DATA__")) html = html.replace("</body>", ` ${nextDataScript}\n</body>`);
|
|
37
|
+
return html;
|
|
38
|
+
}
|
|
39
|
+
return `<!DOCTYPE html>
|
|
40
|
+
<html>
|
|
41
|
+
<head>
|
|
42
|
+
<meta charset="utf-8" />
|
|
43
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
44
|
+
${fontHeadHTML}${options.ssrHeadHTML}\n ${options.assetTags}\n</head>
|
|
45
|
+
<body>
|
|
46
|
+
<div id="__next">${bodyMarker}</div>\n ${nextDataScript}\n</body>
|
|
47
|
+
</html>`;
|
|
48
|
+
}
|
|
49
|
+
async function buildPagesCompositeStream(bodyStream, shellPrefix, shellSuffix) {
|
|
50
|
+
const encoder = new TextEncoder();
|
|
51
|
+
return new ReadableStream({ async start(controller) {
|
|
52
|
+
controller.enqueue(encoder.encode(shellPrefix));
|
|
53
|
+
const reader = bodyStream.getReader();
|
|
54
|
+
try {
|
|
55
|
+
for (;;) {
|
|
56
|
+
const chunk = await reader.read();
|
|
57
|
+
if (chunk.done) break;
|
|
58
|
+
controller.enqueue(chunk.value);
|
|
59
|
+
}
|
|
60
|
+
} finally {
|
|
61
|
+
reader.releaseLock();
|
|
62
|
+
}
|
|
63
|
+
controller.enqueue(encoder.encode(shellSuffix));
|
|
64
|
+
controller.close();
|
|
65
|
+
} });
|
|
66
|
+
}
|
|
67
|
+
function applyGsspHeaders(headers, gsspRes) {
|
|
68
|
+
if (!gsspRes) return 200;
|
|
69
|
+
const gsspHeaders = gsspRes.getHeaders();
|
|
70
|
+
for (const key of Object.keys(gsspHeaders)) {
|
|
71
|
+
const value = gsspHeaders[key];
|
|
72
|
+
if (key.toLowerCase() === "set-cookie" && Array.isArray(value)) {
|
|
73
|
+
for (const cookie of value) headers.append("set-cookie", String(cookie));
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
headers.set(key, value.join(", "));
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") headers.set(key, String(value));
|
|
81
|
+
}
|
|
82
|
+
headers.set("Content-Type", "text/html");
|
|
83
|
+
return gsspRes.statusCode;
|
|
84
|
+
}
|
|
85
|
+
async function renderPagesPageResponse(options) {
|
|
86
|
+
const pageElement = options.createPageElement(options.pageProps);
|
|
87
|
+
options.resetSSRHead?.();
|
|
88
|
+
await options.flushPreloads?.();
|
|
89
|
+
const fontHeadHTML = buildPagesFontHeadHtml(options.getFontLinks(), options.fontPreloads, options.getFontStyles());
|
|
90
|
+
const nextDataScript = buildPagesNextDataScript({
|
|
91
|
+
buildId: options.buildId,
|
|
92
|
+
i18n: options.i18n,
|
|
93
|
+
pageProps: options.pageProps,
|
|
94
|
+
params: options.params,
|
|
95
|
+
routePattern: options.routePattern,
|
|
96
|
+
safeJsonStringify: options.safeJsonStringify
|
|
97
|
+
});
|
|
98
|
+
const bodyMarker = "<!--VINEXT_STREAM_BODY-->";
|
|
99
|
+
const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {
|
|
100
|
+
assetTags: options.assetTags,
|
|
101
|
+
DocumentComponent: options.DocumentComponent,
|
|
102
|
+
renderDocumentToString: options.renderDocumentToString,
|
|
103
|
+
ssrHeadHTML: options.getSSRHeadHTML?.() ?? ""
|
|
104
|
+
});
|
|
105
|
+
options.clearSsrContext();
|
|
106
|
+
const markerIndex = shellHtml.indexOf(bodyMarker);
|
|
107
|
+
const shellPrefix = shellHtml.slice(0, markerIndex);
|
|
108
|
+
const shellSuffix = shellHtml.slice(markerIndex + 25);
|
|
109
|
+
const compositeStream = await buildPagesCompositeStream(await options.renderToReadableStream(pageElement), shellPrefix, shellSuffix);
|
|
110
|
+
if (options.isrRevalidateSeconds !== null && options.isrRevalidateSeconds > 0) {
|
|
111
|
+
const isrElement = options.createPageElement(options.pageProps);
|
|
112
|
+
const fullHtml = shellPrefix + await options.renderIsrPassToStringAsync(isrElement) + shellSuffix;
|
|
113
|
+
const isrPathname = options.routeUrl.split("?")[0];
|
|
114
|
+
const cacheKey = options.isrCacheKey("pages", isrPathname);
|
|
115
|
+
await options.isrSet(cacheKey, {
|
|
116
|
+
kind: "PAGES",
|
|
117
|
+
html: fullHtml,
|
|
118
|
+
pageData: options.pageProps,
|
|
119
|
+
headers: void 0,
|
|
120
|
+
status: void 0
|
|
121
|
+
}, options.isrRevalidateSeconds);
|
|
122
|
+
}
|
|
123
|
+
const responseHeaders = new Headers({ "Content-Type": "text/html" });
|
|
124
|
+
const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes);
|
|
125
|
+
if (options.isrRevalidateSeconds) {
|
|
126
|
+
responseHeaders.set("Cache-Control", `s-maxage=${options.isrRevalidateSeconds}, stale-while-revalidate`);
|
|
127
|
+
responseHeaders.set("X-Vinext-Cache", "MISS");
|
|
128
|
+
}
|
|
129
|
+
if (options.fontLinkHeader) responseHeaders.set("Link", options.fontLinkHeader);
|
|
130
|
+
const response = new Response(compositeStream, {
|
|
131
|
+
status: finalStatus,
|
|
132
|
+
headers: responseHeaders
|
|
133
|
+
});
|
|
134
|
+
response.__vinextStreamedHtmlResponse = true;
|
|
135
|
+
return response;
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
export { renderPagesPageResponse };
|
|
139
|
+
|
|
140
|
+
//# sourceMappingURL=pages-page-response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pages-page-response.js","names":[],"sources":["../../src/server/pages-page-response.ts"],"sourcesContent":["import React, { type ComponentType, type ReactNode } from \"react\";\n\nexport interface PagesFontPreload {\n href: string;\n type: string;\n}\n\nexport interface PagesI18nRenderContext {\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n domainLocales?: unknown;\n}\n\nexport interface PagesGsspResponse {\n statusCode: number;\n getHeaders(): Record<string, string | number | boolean | string[]>;\n}\n\ninterface PagesStreamedHtmlResponse extends Response {\n __vinextStreamedHtmlResponse?: boolean;\n}\n\nexport interface RenderPagesPageResponseOptions {\n assetTags: string;\n buildId: string | null;\n clearSsrContext: () => void;\n createPageElement: (pageProps: Record<string, unknown>) => ReactNode;\n DocumentComponent: ComponentType | null;\n flushPreloads?: (() => Promise<void> | void) | undefined;\n fontLinkHeader: string;\n fontPreloads: PagesFontPreload[];\n getFontLinks: () => string[];\n getFontStyles: () => string[];\n getSSRHeadHTML?: (() => string) | undefined;\n gsspRes: PagesGsspResponse | null;\n isrCacheKey: (router: string, pathname: string) => string;\n isrRevalidateSeconds: number | null;\n isrSet: (\n key: string,\n data: {\n kind: \"PAGES\";\n html: string;\n pageData: Record<string, unknown>;\n headers: undefined;\n status: undefined;\n },\n revalidateSeconds: number,\n ) => Promise<void>;\n i18n: PagesI18nRenderContext;\n pageProps: Record<string, unknown>;\n params: Record<string, unknown>;\n renderDocumentToString: (element: ReactNode) => Promise<string>;\n renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;\n renderToReadableStream: (element: ReactNode) => Promise<ReadableStream<Uint8Array>>;\n resetSSRHead?: (() => void) | undefined;\n routePattern: string;\n routeUrl: string;\n safeJsonStringify: (value: unknown) => string;\n}\n\nfunction escapeAttr(value: string): string {\n return value.replace(/&/g, \"&\").replace(/\"/g, \""\");\n}\n\nfunction buildPagesFontHeadHtml(\n fontLinks: string[],\n fontPreloads: PagesFontPreload[],\n fontStyles: string[],\n): string {\n let html = \"\";\n\n for (const link of fontLinks) {\n html += `<link rel=\"stylesheet\" href=\"${escapeAttr(link)}\" />\\n `;\n }\n\n for (const preload of fontPreloads) {\n html += `<link rel=\"preload\" href=\"${escapeAttr(preload.href)}\" as=\"font\" type=\"${escapeAttr(preload.type)}\" crossorigin />\\n `;\n }\n\n if (fontStyles.length > 0) {\n html += `<style data-vinext-fonts>${fontStyles.join(\"\\n\")}</style>\\n `;\n }\n\n return html;\n}\n\nfunction buildPagesNextDataScript(\n options: Pick<\n RenderPagesPageResponseOptions,\n \"buildId\" | \"i18n\" | \"pageProps\" | \"params\" | \"routePattern\" | \"safeJsonStringify\"\n >,\n): string {\n const nextDataPayload: Record<string, unknown> = {\n props: { pageProps: options.pageProps },\n page: options.routePattern,\n query: options.params,\n buildId: options.buildId,\n isFallback: false,\n };\n\n if (options.i18n.locales) {\n nextDataPayload.locale = options.i18n.locale;\n nextDataPayload.locales = options.i18n.locales;\n nextDataPayload.defaultLocale = options.i18n.defaultLocale;\n nextDataPayload.domainLocales = options.i18n.domainLocales;\n }\n\n const localeGlobals = options.i18n.locales\n ? `;window.__VINEXT_LOCALE__=${options.safeJsonStringify(options.i18n.locale)}` +\n `;window.__VINEXT_LOCALES__=${options.safeJsonStringify(options.i18n.locales)}` +\n `;window.__VINEXT_DEFAULT_LOCALE__=${options.safeJsonStringify(options.i18n.defaultLocale)}`\n : \"\";\n\n return `<script>window.__NEXT_DATA__ = ${options.safeJsonStringify(nextDataPayload)}${localeGlobals}</script>`;\n}\n\nasync function buildPagesShellHtml(\n bodyMarker: string,\n fontHeadHTML: string,\n nextDataScript: string,\n options: Pick<\n RenderPagesPageResponseOptions,\n \"assetTags\" | \"DocumentComponent\" | \"renderDocumentToString\"\n > & {\n ssrHeadHTML: string;\n },\n): Promise<string> {\n if (options.DocumentComponent) {\n let html = await options.renderDocumentToString(React.createElement(options.DocumentComponent));\n html = html.replace(\"__NEXT_MAIN__\", bodyMarker);\n if (options.ssrHeadHTML || options.assetTags || fontHeadHTML) {\n html = html.replace(\n \"</head>\",\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n ${options.assetTags}\\n</head>`,\n );\n }\n html = html.replace(\"<!-- __NEXT_SCRIPTS__ -->\", nextDataScript);\n if (!html.includes(\"__NEXT_DATA__\")) {\n html = html.replace(\"</body>\", ` ${nextDataScript}\\n</body>`);\n }\n return html;\n }\n\n return (\n \"<!DOCTYPE html>\\n<html>\\n<head>\\n\" +\n ' <meta charset=\"utf-8\" />\\n' +\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\\n' +\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n` +\n ` ${options.assetTags}\\n` +\n \"</head>\\n<body>\\n\" +\n ` <div id=\"__next\">${bodyMarker}</div>\\n` +\n ` ${nextDataScript}\\n` +\n \"</body>\\n</html>\"\n );\n}\n\nasync function buildPagesCompositeStream(\n bodyStream: ReadableStream<Uint8Array>,\n shellPrefix: string,\n shellSuffix: string,\n): Promise<ReadableStream<Uint8Array>> {\n const encoder = new TextEncoder();\n\n return new ReadableStream({\n async start(controller) {\n controller.enqueue(encoder.encode(shellPrefix));\n const reader = bodyStream.getReader();\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n break;\n }\n controller.enqueue(chunk.value);\n }\n } finally {\n reader.releaseLock();\n }\n controller.enqueue(encoder.encode(shellSuffix));\n controller.close();\n },\n });\n}\n\nfunction applyGsspHeaders(headers: Headers, gsspRes: PagesGsspResponse | null): number {\n if (!gsspRes) {\n return 200;\n }\n\n const gsspHeaders = gsspRes.getHeaders();\n for (const key of Object.keys(gsspHeaders)) {\n const value = gsspHeaders[key];\n const lowerKey = key.toLowerCase();\n if (lowerKey === \"set-cookie\" && Array.isArray(value)) {\n for (const cookie of value) {\n headers.append(\"set-cookie\", String(cookie));\n }\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(key, value.join(\", \"));\n continue;\n }\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n headers.set(key, String(value));\n }\n }\n headers.set(\"Content-Type\", \"text/html\");\n return gsspRes.statusCode;\n}\n\nexport async function renderPagesPageResponse(\n options: RenderPagesPageResponseOptions,\n): Promise<Response> {\n const pageElement = options.createPageElement(options.pageProps);\n\n options.resetSSRHead?.();\n await options.flushPreloads?.();\n\n const fontHeadHTML = buildPagesFontHeadHtml(\n options.getFontLinks(),\n options.fontPreloads,\n options.getFontStyles(),\n );\n const nextDataScript = buildPagesNextDataScript({\n buildId: options.buildId,\n i18n: options.i18n,\n pageProps: options.pageProps,\n params: options.params,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n });\n const bodyMarker = \"<!--VINEXT_STREAM_BODY-->\";\n const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {\n assetTags: options.assetTags,\n DocumentComponent: options.DocumentComponent,\n renderDocumentToString: options.renderDocumentToString,\n ssrHeadHTML: options.getSSRHeadHTML?.() ?? \"\",\n });\n\n options.clearSsrContext();\n\n const markerIndex = shellHtml.indexOf(bodyMarker);\n const shellPrefix = shellHtml.slice(0, markerIndex);\n const shellSuffix = shellHtml.slice(markerIndex + bodyMarker.length);\n const bodyStream = await options.renderToReadableStream(pageElement);\n const compositeStream = await buildPagesCompositeStream(bodyStream, shellPrefix, shellSuffix);\n\n if (options.isrRevalidateSeconds !== null && options.isrRevalidateSeconds > 0) {\n const isrElement = options.createPageElement(options.pageProps);\n const isrHtml = await options.renderIsrPassToStringAsync(isrElement);\n const fullHtml = shellPrefix + isrHtml + shellSuffix;\n const isrPathname = options.routeUrl.split(\"?\")[0];\n const cacheKey = options.isrCacheKey(\"pages\", isrPathname);\n await options.isrSet(\n cacheKey,\n {\n kind: \"PAGES\",\n html: fullHtml,\n pageData: options.pageProps,\n headers: undefined,\n status: undefined,\n },\n options.isrRevalidateSeconds,\n );\n }\n\n const responseHeaders = new Headers({ \"Content-Type\": \"text/html\" });\n const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes);\n\n if (options.isrRevalidateSeconds) {\n responseHeaders.set(\n \"Cache-Control\",\n `s-maxage=${options.isrRevalidateSeconds}, stale-while-revalidate`,\n );\n responseHeaders.set(\"X-Vinext-Cache\", \"MISS\");\n }\n if (options.fontLinkHeader) {\n responseHeaders.set(\"Link\", options.fontLinkHeader);\n }\n\n const response = new Response(compositeStream, {\n status: finalStatus,\n headers: responseHeaders,\n }) as PagesStreamedHtmlResponse;\n // Mark the normal streamed HTML render so the Node prod server can strip\n // stale Content-Length only for this path, not for custom gSSP responses.\n response.__vinextStreamedHtmlResponse = true;\n return response;\n}\n"],"mappings":"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"}
|
|
@@ -27,6 +27,13 @@ declare function negotiateEncoding(req: IncomingMessage): "br" | "gzip" | "defla
|
|
|
27
27
|
* to preserve multiple Set-Cookie values instead of flattening them.
|
|
28
28
|
*/
|
|
29
29
|
declare function mergeResponseHeaders(middlewareHeaders: Record<string, string | string[]>, response: Response): Record<string, string | string[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Merge middleware/config headers and an optional status override into a new
|
|
32
|
+
* Web Response while preserving the original body stream when allowed.
|
|
33
|
+
* Keep this in sync with server/worker-utils.ts and the generated copy in
|
|
34
|
+
* deploy.ts.
|
|
35
|
+
*/
|
|
36
|
+
declare function mergeWebResponse(middlewareHeaders: Record<string, string | string[]>, response: Response, statusOverride?: number): Response;
|
|
30
37
|
/**
|
|
31
38
|
* Send a compressed response if the content type is compressible and the
|
|
32
39
|
* client supports compression. Otherwise send uncompressed.
|
|
@@ -63,6 +70,11 @@ declare const trustProxy: boolean;
|
|
|
63
70
|
* the result here so the downstream RSC handler doesn't re-normalize).
|
|
64
71
|
*/
|
|
65
72
|
declare function nodeToWebRequest(req: IncomingMessage, urlOverride?: string): Request;
|
|
73
|
+
/**
|
|
74
|
+
* Stream a Web Response back to a Node.js ServerResponse.
|
|
75
|
+
* Supports streaming compression for SSR responses.
|
|
76
|
+
*/
|
|
77
|
+
declare function sendWebResponse(webResponse: Response, req: IncomingMessage, res: ServerResponse, compress: boolean): Promise<void>;
|
|
66
78
|
/**
|
|
67
79
|
* Start the production server.
|
|
68
80
|
*
|
|
@@ -74,5 +86,5 @@ declare function startProdServer(options?: ProdServerOptions): Promise<{
|
|
|
74
86
|
port: number;
|
|
75
87
|
}>;
|
|
76
88
|
//#endregion
|
|
77
|
-
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, ProdServerOptions, mergeResponseHeaders, negotiateEncoding, nodeToWebRequest, resolveHost, sendCompressed, startProdServer, trustProxy, trustedHosts };
|
|
89
|
+
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, ProdServerOptions, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveHost, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts };
|
|
78
90
|
//# sourceMappingURL=prod-server.d.ts.map
|
|
@@ -5,6 +5,7 @@ import { hasBasePath, stripBasePath } from "../utils/base-path.js";
|
|
|
5
5
|
import { manifestFileWithBase } from "../utils/manifest-paths.js";
|
|
6
6
|
import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
|
|
7
7
|
import { readPrerenderSecret } from "../build/server-manifest.js";
|
|
8
|
+
import { seedMemoryCacheFromPrerender } from "./seed-cache.js";
|
|
8
9
|
import { computeLazyChunks } from "../index.js";
|
|
9
10
|
import fs from "node:fs";
|
|
10
11
|
import path from "node:path";
|
|
@@ -75,11 +76,20 @@ function negotiateEncoding(req) {
|
|
|
75
76
|
/**
|
|
76
77
|
* Create a compression stream for the given encoding.
|
|
77
78
|
*/
|
|
78
|
-
function createCompressor(encoding) {
|
|
79
|
+
function createCompressor(encoding, mode = "default") {
|
|
79
80
|
switch (encoding) {
|
|
80
|
-
case "br": return zlib.createBrotliCompress({
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
case "br": return zlib.createBrotliCompress({
|
|
82
|
+
...mode === "streaming" ? { flush: zlib.constants.BROTLI_OPERATION_FLUSH } : {},
|
|
83
|
+
params: { [zlib.constants.BROTLI_PARAM_QUALITY]: 4 }
|
|
84
|
+
});
|
|
85
|
+
case "gzip": return zlib.createGzip({
|
|
86
|
+
level: 6,
|
|
87
|
+
...mode === "streaming" ? { flush: zlib.constants.Z_SYNC_FLUSH } : {}
|
|
88
|
+
});
|
|
89
|
+
case "deflate": return zlib.createDeflate({
|
|
90
|
+
level: 6,
|
|
91
|
+
...mode === "streaming" ? { flush: zlib.constants.Z_SYNC_FLUSH } : {}
|
|
92
|
+
});
|
|
83
93
|
}
|
|
84
94
|
}
|
|
85
95
|
/**
|
|
@@ -100,6 +110,79 @@ function mergeResponseHeaders(middlewareHeaders, response) {
|
|
|
100
110
|
}
|
|
101
111
|
return merged;
|
|
102
112
|
}
|
|
113
|
+
function toWebHeaders(headersRecord) {
|
|
114
|
+
const headers = new Headers();
|
|
115
|
+
for (const [key, value] of Object.entries(headersRecord)) if (Array.isArray(value)) for (const item of value) headers.append(key, item);
|
|
116
|
+
else headers.set(key, value);
|
|
117
|
+
return headers;
|
|
118
|
+
}
|
|
119
|
+
const NO_BODY_RESPONSE_STATUSES = new Set([
|
|
120
|
+
204,
|
|
121
|
+
205,
|
|
122
|
+
304
|
|
123
|
+
]);
|
|
124
|
+
function hasHeader(headersRecord, name) {
|
|
125
|
+
const target = name.toLowerCase();
|
|
126
|
+
return Object.keys(headersRecord).some((key) => key.toLowerCase() === target);
|
|
127
|
+
}
|
|
128
|
+
function omitHeadersCaseInsensitive(headersRecord, names) {
|
|
129
|
+
const targets = new Set(names.map((name) => name.toLowerCase()));
|
|
130
|
+
const filtered = {};
|
|
131
|
+
for (const [key, value] of Object.entries(headersRecord)) {
|
|
132
|
+
if (targets.has(key.toLowerCase())) continue;
|
|
133
|
+
filtered[key] = value;
|
|
134
|
+
}
|
|
135
|
+
return filtered;
|
|
136
|
+
}
|
|
137
|
+
function stripHeaders(headersRecord, names) {
|
|
138
|
+
const targets = new Set(names.map((name) => name.toLowerCase()));
|
|
139
|
+
for (const key of Object.keys(headersRecord)) if (targets.has(key.toLowerCase())) delete headersRecord[key];
|
|
140
|
+
}
|
|
141
|
+
function isNoBodyResponseStatus(status) {
|
|
142
|
+
return NO_BODY_RESPONSE_STATUSES.has(status);
|
|
143
|
+
}
|
|
144
|
+
function cancelResponseBody(response) {
|
|
145
|
+
const body = response.body;
|
|
146
|
+
if (!body || body.locked) return;
|
|
147
|
+
body.cancel().catch(() => {});
|
|
148
|
+
}
|
|
149
|
+
function isVinextStreamedHtmlResponse(response) {
|
|
150
|
+
return response.__vinextStreamedHtmlResponse === true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Merge middleware/config headers and an optional status override into a new
|
|
154
|
+
* Web Response while preserving the original body stream when allowed.
|
|
155
|
+
* Keep this in sync with server/worker-utils.ts and the generated copy in
|
|
156
|
+
* deploy.ts.
|
|
157
|
+
*/
|
|
158
|
+
function mergeWebResponse(middlewareHeaders, response, statusOverride) {
|
|
159
|
+
const filteredMiddlewareHeaders = omitHeadersCaseInsensitive(middlewareHeaders, ["content-length"]);
|
|
160
|
+
const status = statusOverride ?? response.status;
|
|
161
|
+
const mergedHeaders = mergeResponseHeaders(filteredMiddlewareHeaders, response);
|
|
162
|
+
const shouldDropBody = isNoBodyResponseStatus(status);
|
|
163
|
+
const shouldStripStreamLength = isVinextStreamedHtmlResponse(response) && hasHeader(mergedHeaders, "content-length");
|
|
164
|
+
if (!Object.keys(filteredMiddlewareHeaders).length && statusOverride === void 0 && !shouldDropBody && !shouldStripStreamLength) return response;
|
|
165
|
+
if (shouldDropBody) {
|
|
166
|
+
cancelResponseBody(response);
|
|
167
|
+
stripHeaders(mergedHeaders, [
|
|
168
|
+
"content-encoding",
|
|
169
|
+
"content-length",
|
|
170
|
+
"content-type",
|
|
171
|
+
"transfer-encoding"
|
|
172
|
+
]);
|
|
173
|
+
return new Response(null, {
|
|
174
|
+
status,
|
|
175
|
+
statusText: status === response.status ? response.statusText : void 0,
|
|
176
|
+
headers: toWebHeaders(mergedHeaders)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (shouldStripStreamLength) stripHeaders(mergedHeaders, ["content-length"]);
|
|
180
|
+
return new Response(response.body, {
|
|
181
|
+
status,
|
|
182
|
+
statusText: status === response.status ? response.statusText : void 0,
|
|
183
|
+
headers: toWebHeaders(mergedHeaders)
|
|
184
|
+
});
|
|
185
|
+
}
|
|
103
186
|
/**
|
|
104
187
|
* Send a compressed response if the content type is compressible and the
|
|
105
188
|
* client supports compression. Otherwise send uncompressed.
|
|
@@ -108,6 +191,7 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
|
|
|
108
191
|
const buf = typeof body === "string" ? Buffer.from(body) : body;
|
|
109
192
|
const baseType = contentType.split(";")[0].trim();
|
|
110
193
|
const encoding = compress ? negotiateEncoding(req) : null;
|
|
194
|
+
const headersWithoutBodyHeaders = omitHeadersCaseInsensitive(extraHeaders, ["content-length", "content-type"]);
|
|
111
195
|
const writeHead = (headers) => {
|
|
112
196
|
if (statusText) res.writeHead(statusCode, statusText, headers);
|
|
113
197
|
else res.writeHead(statusCode, headers);
|
|
@@ -120,7 +204,7 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
|
|
|
120
204
|
if (existingVary) varyValue = existingVary.toLowerCase().includes("accept-encoding") ? existingVary : existingVary + ", Accept-Encoding";
|
|
121
205
|
else varyValue = "Accept-Encoding";
|
|
122
206
|
writeHead({
|
|
123
|
-
...
|
|
207
|
+
...headersWithoutBodyHeaders,
|
|
124
208
|
"Content-Type": contentType,
|
|
125
209
|
"Content-Encoding": encoding,
|
|
126
210
|
Vary: varyValue
|
|
@@ -128,9 +212,8 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
|
|
|
128
212
|
compressor.end(buf);
|
|
129
213
|
pipeline(compressor, res, () => {});
|
|
130
214
|
} else {
|
|
131
|
-
const { "content-length": _cl, "Content-Length": _CL, ...headersWithoutLength } = extraHeaders;
|
|
132
215
|
writeHead({
|
|
133
|
-
...
|
|
216
|
+
...headersWithoutBodyHeaders,
|
|
134
217
|
"Content-Type": contentType,
|
|
135
218
|
"Content-Length": String(buf.length)
|
|
136
219
|
});
|
|
@@ -308,11 +391,12 @@ async function sendWebResponse(webResponse, req, res, compress) {
|
|
|
308
391
|
}
|
|
309
392
|
writeHead(nodeHeaders);
|
|
310
393
|
if (req.method === "HEAD") {
|
|
394
|
+
cancelResponseBody(webResponse);
|
|
311
395
|
res.end();
|
|
312
396
|
return;
|
|
313
397
|
}
|
|
314
398
|
const nodeStream = Readable.fromWeb(webResponse.body);
|
|
315
|
-
if (shouldCompress) pipeline(nodeStream, createCompressor(encoding), res, () => {});
|
|
399
|
+
if (shouldCompress) pipeline(nodeStream, createCompressor(encoding, "streaming"), res, () => {});
|
|
316
400
|
else pipeline(nodeStream, res, () => {});
|
|
317
401
|
}
|
|
318
402
|
/**
|
|
@@ -393,6 +477,8 @@ async function startAppRouterServer(options) {
|
|
|
393
477
|
const prerenderSecret = readPrerenderSecret(path.dirname(rscEntryPath));
|
|
394
478
|
const rscMtime = fs.statSync(rscEntryPath).mtimeMs;
|
|
395
479
|
const rscHandler = resolveAppRouterHandler((await import(`${pathToFileURL(rscEntryPath).href}?t=${rscMtime}`)).default);
|
|
480
|
+
const seededRoutes = await seedMemoryCacheFromPrerender(path.dirname(rscEntryPath));
|
|
481
|
+
if (seededRoutes > 0) console.log(`[vinext] Seeded ${seededRoutes} pre-rendered route${seededRoutes !== 1 ? "s" : ""} into memory cache`);
|
|
396
482
|
const server = createServer(async (req, res) => {
|
|
397
483
|
const rawUrl = req.url ?? "/";
|
|
398
484
|
const rawPathname = rawUrl.split("?")[0].replaceAll("\\", "/");
|
|
@@ -698,11 +784,16 @@ async function startPagesRouterServer(options) {
|
|
|
698
784
|
let response;
|
|
699
785
|
if (typeof handleApi === "function") response = await handleApi(webRequest, resolvedUrl);
|
|
700
786
|
else response = new Response("404 - API route not found", { status: 404 });
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
787
|
+
const mergedResponse = mergeWebResponse(middlewareHeaders, response, middlewareRewriteStatus);
|
|
788
|
+
if (!mergedResponse.body) {
|
|
789
|
+
await sendWebResponse(mergedResponse, req, res, compress);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const responseBody = Buffer.from(await mergedResponse.arrayBuffer());
|
|
793
|
+
const ct = mergedResponse.headers.get("content-type") ?? "application/octet-stream";
|
|
794
|
+
const responseHeaders = mergeResponseHeaders({}, mergedResponse);
|
|
795
|
+
const finalStatusText = mergedResponse.statusText || void 0;
|
|
796
|
+
sendCompressed(req, res, responseBody, ct, mergedResponse.status, responseHeaders, compress, finalStatusText);
|
|
706
797
|
return;
|
|
707
798
|
}
|
|
708
799
|
if (configRewrites.afterFiles?.length) {
|
|
@@ -735,11 +826,17 @@ async function startPagesRouterServer(options) {
|
|
|
735
826
|
res.end("404 - Not found");
|
|
736
827
|
return;
|
|
737
828
|
}
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
829
|
+
const shouldStreamPagesResponse = isVinextStreamedHtmlResponse(response);
|
|
830
|
+
const mergedResponse = mergeWebResponse(middlewareHeaders, response, middlewareRewriteStatus);
|
|
831
|
+
if (shouldStreamPagesResponse || !mergedResponse.body) {
|
|
832
|
+
await sendWebResponse(mergedResponse, req, res, compress);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const responseBody = Buffer.from(await mergedResponse.arrayBuffer());
|
|
836
|
+
const ct = mergedResponse.headers.get("content-type") ?? "text/html";
|
|
837
|
+
const responseHeaders = mergeResponseHeaders({}, mergedResponse);
|
|
838
|
+
const finalStatusText = mergedResponse.statusText || void 0;
|
|
839
|
+
sendCompressed(req, res, responseBody, ct, mergedResponse.status, responseHeaders, compress, finalStatusText);
|
|
743
840
|
} catch (e) {
|
|
744
841
|
console.error("[vinext] Server error:", e);
|
|
745
842
|
if (!res.headersSent) {
|
|
@@ -763,6 +860,6 @@ async function startPagesRouterServer(options) {
|
|
|
763
860
|
};
|
|
764
861
|
}
|
|
765
862
|
//#endregion
|
|
766
|
-
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, mergeResponseHeaders, negotiateEncoding, nodeToWebRequest, resolveHost, sendCompressed, startProdServer, trustProxy, trustedHosts };
|
|
863
|
+
export { COMPRESSIBLE_TYPES, COMPRESS_THRESHOLD, mergeResponseHeaders, mergeWebResponse, negotiateEncoding, nodeToWebRequest, resolveHost, sendCompressed, sendWebResponse, startProdServer, trustProxy, trustedHosts };
|
|
767
864
|
|
|
768
865
|
//# sourceMappingURL=prod-server.js.map
|