vinext 0.0.39 → 0.0.40
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/build/standalone.js +7 -0
- package/dist/build/standalone.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +2 -1
- package/dist/entries/app-rsc-entry.js +131 -245
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.js +80 -6
- package/dist/index.js.map +1 -1
- package/dist/plugins/server-externals-manifest.d.ts +11 -1
- package/dist/plugins/server-externals-manifest.js +10 -3
- package/dist/plugins/server-externals-manifest.js.map +1 -1
- package/dist/routing/app-router.d.ts +10 -2
- package/dist/routing/app-router.js +37 -22
- package/dist/routing/app-router.js.map +1 -1
- package/dist/server/app-page-response.d.ts +12 -1
- package/dist/server/app-page-response.js +26 -7
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +79 -0
- package/dist/server/app-page-route-wiring.js +165 -0
- package/dist/server/app-page-route-wiring.js.map +1 -0
- package/dist/server/app-page-stream.js +3 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-response.js +4 -1
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-router-entry.d.ts +6 -1
- package/dist/server/app-router-entry.js +9 -2
- package/dist/server/app-router-entry.js.map +1 -1
- package/dist/server/prod-server.d.ts +1 -1
- package/dist/server/prod-server.js +37 -11
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/worker-utils.d.ts +4 -1
- package/dist/server/worker-utils.js +31 -1
- package/dist/server/worker-utils.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +13 -4
- package/dist/shims/error-boundary.js +23 -3
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/navigation.d.ts +16 -1
- package/dist/shims/navigation.js +18 -3
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.js +127 -38
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.d.ts +17 -4
- package/dist/shims/server.js +91 -73
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/slot.d.ts +28 -0
- package/dist/shims/slot.js +49 -0
- package/dist/shims/slot.js.map +1 -0
- package/package.json +1 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-page-response.js","names":[],"sources":["../../src/server/app-page-response.ts"],"sourcesContent":["export type AppPageMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\nexport type AppPageResponseTiming = {\n compileEnd?: number;\n handlerStart: number;\n renderEnd?: number;\n responseKind: \"html\" | \"rsc\";\n};\n\nexport type AppPageResponsePolicy = {\n cacheControl?: string;\n cacheState?: \"MISS\" | \"STATIC\";\n};\n\ntype ResolveAppPageResponsePolicyBaseOptions = {\n isDynamicError: boolean;\n isForceDynamic: boolean;\n isForceStatic: boolean;\n isProduction: boolean;\n revalidateSeconds: number | null;\n};\n\nexport type ResolveAppPageRscResponsePolicyOptions = {\n dynamicUsedDuringBuild: boolean;\n} & ResolveAppPageResponsePolicyBaseOptions;\n\nexport type ResolveAppPageHtmlResponsePolicyOptions = {\n dynamicUsedDuringRender: boolean;\n} & ResolveAppPageResponsePolicyBaseOptions;\n\nexport type AppPageHtmlResponsePolicy = {\n shouldWriteToCache: boolean;\n} & AppPageResponsePolicy;\n\nexport type BuildAppPageRscResponseOptions = {\n middlewareContext: AppPageMiddlewareContext;\n params?: Record<string, unknown>;\n policy: AppPageResponsePolicy;\n timing?: AppPageResponseTiming;\n};\n\nexport type BuildAppPageHtmlResponseOptions = {\n draftCookie?: string | null;\n fontLinkHeader?: string;\n middlewareContext: AppPageMiddlewareContext;\n policy: AppPageResponsePolicy;\n timing?: AppPageResponseTiming;\n};\n\nconst STATIC_CACHE_CONTROL = \"s-maxage=31536000, stale-while-revalidate\";\nconst NO_STORE_CACHE_CONTROL = \"no-store, must-revalidate\";\n\nfunction buildRevalidateCacheControl(revalidateSeconds: number): string {\n return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;\n}\n\nfunction applyTimingHeader(headers: Headers, timing?: AppPageResponseTiming): void {\n if (!timing) {\n return;\n }\n\n const handlerStart = Math.round(timing.handlerStart);\n const compileMs =\n timing.compileEnd !== undefined ? Math.round(timing.compileEnd - timing.handlerStart) : -1;\n const renderMs =\n timing.responseKind === \"html\" &&\n timing.renderEnd !== undefined &&\n timing.compileEnd !== undefined\n ? Math.round(timing.renderEnd - timing.compileEnd)\n : -1;\n\n headers.set(\"x-vinext-timing\", `${handlerStart},${compileMs},${renderMs}`);\n}\n\nexport function resolveAppPageRscResponsePolicy(\n options: ResolveAppPageRscResponsePolicyOptions,\n): AppPageResponsePolicy {\n if (options.isForceDynamic || options.dynamicUsedDuringBuild) {\n return { cacheControl: NO_STORE_CACHE_CONTROL };\n }\n\n if (\n ((options.isForceStatic || options.isDynamicError) && !options.revalidateSeconds) ||\n options.revalidateSeconds === Infinity\n ) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n };\n }\n\n if (options.revalidateSeconds) {\n return {\n cacheControl: buildRevalidateCacheControl(options.revalidateSeconds),\n // Emit MISS as part of the initial RSC response shape rather than bolting\n // it on later in the cache-write block so response construction stays\n // centralized in this helper. This matches the eventual write path: the\n // first ISR-eligible production response is a cache miss.\n cacheState: options.isProduction ? \"MISS\" : undefined,\n };\n }\n\n return {};\n}\n\nexport function resolveAppPageHtmlResponsePolicy(\n options: ResolveAppPageHtmlResponsePolicyOptions,\n): AppPageHtmlResponsePolicy {\n if (options.isForceDynamic) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n if (\n (options.isForceStatic || options.isDynamicError) &&\n (options.revalidateSeconds === null || options.revalidateSeconds === 0)\n ) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n shouldWriteToCache: false,\n };\n }\n\n if (options.dynamicUsedDuringRender) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n if (\n options.revalidateSeconds !== null &&\n options.revalidateSeconds > 0 &&\n options.revalidateSeconds !== Infinity\n ) {\n return {\n cacheControl: buildRevalidateCacheControl(options.revalidateSeconds),\n cacheState: options.isProduction ? \"MISS\" : undefined,\n shouldWriteToCache: options.isProduction,\n };\n }\n\n if (options.revalidateSeconds === Infinity) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n shouldWriteToCache: false,\n };\n }\n\n return { shouldWriteToCache: false };\n}\n\nexport function buildAppPageRscResponse(\n body: ReadableStream,\n options: BuildAppPageRscResponseOptions,\n): Response {\n const headers = new Headers({\n \"Content-Type\": \"text/x-component; charset=utf-8\",\n Vary: \"RSC, Accept\",\n });\n\n if (options.params && Object.keys(options.params).length > 0) {\n // encodeURIComponent so non-ASCII params (e.g. Korean slugs) survive the\n // HTTP ByteString constraint — Headers.set() rejects chars above U+00FF.\n headers.set(\"X-Vinext-Params\", encodeURIComponent(JSON.stringify(options.params)));\n }\n if (options.policy.cacheControl) {\n headers.set(\"Cache-Control\", options.policy.cacheControl);\n }\n if (options.policy.cacheState) {\n headers.set(\"X-Vinext-Cache\", options.policy.cacheState);\n }\n\n if (options.middlewareContext.headers) {\n for (const [key, value] of options.middlewareContext.headers) {\n const lowerKey = key.toLowerCase();\n if (lowerKey === \"set-cookie\" || lowerKey === \"vary\") {\n headers.append(key, value);\n } else {\n // Keep parity with the old inline RSC path: middleware owns singular\n // response headers like Cache-Control here, while Set-Cookie and Vary\n // are accumulated. The HTML helper intentionally keeps its legacy\n // append-for-everything behavior below.\n headers.set(key, value);\n }\n }\n }\n\n applyTimingHeader(headers, options.timing);\n\n return new Response(body, {\n status: options.middlewareContext.status ?? 200,\n headers,\n });\n}\n\nexport function buildAppPageHtmlResponse(\n body: ReadableStream,\n options: BuildAppPageHtmlResponseOptions,\n): Response {\n const headers = new Headers({\n \"Content-Type\": \"text/html; charset=utf-8\",\n Vary: \"RSC, Accept\",\n });\n\n if (options.policy.cacheControl) {\n headers.set(\"Cache-Control\", options.policy.cacheControl);\n }\n if (options.policy.cacheState) {\n headers.set(\"X-Vinext-Cache\", options.policy.cacheState);\n }\n if (options.draftCookie) {\n headers.append(\"Set-Cookie\", options.draftCookie);\n }\n if (options.fontLinkHeader) {\n headers.set(\"Link\", options.fontLinkHeader);\n }\n\n if (options.middlewareContext.headers) {\n for (const [key, value] of options.middlewareContext.headers) {\n headers.append(key, value);\n }\n }\n\n applyTimingHeader(headers, options.timing);\n\n return new Response(body, {\n status: options.middlewareContext.status ?? 200,\n headers,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-page-response.js","names":[],"sources":["../../src/server/app-page-response.ts"],"sourcesContent":["export type AppPageMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\nexport type AppPageResponseTiming = {\n compileEnd?: number;\n handlerStart: number;\n renderEnd?: number;\n responseKind: \"html\" | \"rsc\";\n};\n\nexport type AppPageResponsePolicy = {\n cacheControl?: string;\n cacheState?: \"MISS\" | \"STATIC\";\n};\n\ntype ResolveAppPageResponsePolicyBaseOptions = {\n isDynamicError: boolean;\n isForceDynamic: boolean;\n isForceStatic: boolean;\n isProduction: boolean;\n revalidateSeconds: number | null;\n};\n\nexport type ResolveAppPageRscResponsePolicyOptions = {\n dynamicUsedDuringBuild: boolean;\n} & ResolveAppPageResponsePolicyBaseOptions;\n\nexport type ResolveAppPageHtmlResponsePolicyOptions = {\n dynamicUsedDuringRender: boolean;\n} & ResolveAppPageResponsePolicyBaseOptions;\n\nexport type AppPageHtmlResponsePolicy = {\n shouldWriteToCache: boolean;\n} & AppPageResponsePolicy;\n\nexport type BuildAppPageRscResponseOptions = {\n middlewareContext: AppPageMiddlewareContext;\n params?: Record<string, unknown>;\n policy: AppPageResponsePolicy;\n timing?: AppPageResponseTiming;\n};\n\nexport type BuildAppPageHtmlResponseOptions = {\n draftCookie?: string | null;\n fontLinkHeader?: string;\n middlewareContext: AppPageMiddlewareContext;\n policy: AppPageResponsePolicy;\n timing?: AppPageResponseTiming;\n};\n\nconst STATIC_CACHE_CONTROL = \"s-maxage=31536000, stale-while-revalidate\";\nconst NO_STORE_CACHE_CONTROL = \"no-store, must-revalidate\";\n\nfunction buildRevalidateCacheControl(revalidateSeconds: number): string {\n return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;\n}\n\nfunction applyTimingHeader(headers: Headers, timing?: AppPageResponseTiming): void {\n if (!timing) {\n return;\n }\n\n const handlerStart = Math.round(timing.handlerStart);\n const compileMs =\n timing.compileEnd !== undefined ? Math.round(timing.compileEnd - timing.handlerStart) : -1;\n const renderMs =\n timing.responseKind === \"html\" &&\n timing.renderEnd !== undefined &&\n timing.compileEnd !== undefined\n ? Math.round(timing.renderEnd - timing.compileEnd)\n : -1;\n\n headers.set(\"x-vinext-timing\", `${handlerStart},${compileMs},${renderMs}`);\n}\n\nexport function resolveAppPageRscResponsePolicy(\n options: ResolveAppPageRscResponsePolicyOptions,\n): AppPageResponsePolicy {\n if (options.isForceDynamic || options.dynamicUsedDuringBuild) {\n return { cacheControl: NO_STORE_CACHE_CONTROL };\n }\n\n // revalidate = 0 means \"always dynamic, never cache\" — equivalent to\n // force-dynamic for caching purposes. Must be checked before the\n // isForceStatic/isDynamicError branch below, which uses !revalidateSeconds\n // and would incorrectly catch 0 as a falsy value.\n if (options.revalidateSeconds === 0) {\n return { cacheControl: NO_STORE_CACHE_CONTROL };\n }\n\n if (\n ((options.isForceStatic || options.isDynamicError) && !options.revalidateSeconds) ||\n options.revalidateSeconds === Infinity\n ) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n };\n }\n\n if (options.revalidateSeconds) {\n return {\n cacheControl: buildRevalidateCacheControl(options.revalidateSeconds),\n // Emit MISS as part of the initial RSC response shape rather than bolting\n // it on later in the cache-write block so response construction stays\n // centralized in this helper. This matches the eventual write path: the\n // first ISR-eligible production response is a cache miss.\n cacheState: options.isProduction ? \"MISS\" : undefined,\n };\n }\n\n return {};\n}\n\nexport function resolveAppPageHtmlResponsePolicy(\n options: ResolveAppPageHtmlResponsePolicyOptions,\n): AppPageHtmlResponsePolicy {\n if (options.isForceDynamic) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n // revalidate = 0 means \"always dynamic, never cache\" — equivalent to\n // force-dynamic for caching purposes. Must be checked before the\n // isForceStatic/isDynamicError branch below, which matches revalidateSeconds\n // === 0 and would incorrectly return a static Cache-Control.\n if (options.revalidateSeconds === 0) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n if ((options.isForceStatic || options.isDynamicError) && options.revalidateSeconds === null) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n shouldWriteToCache: false,\n };\n }\n\n if (options.dynamicUsedDuringRender) {\n return {\n cacheControl: NO_STORE_CACHE_CONTROL,\n shouldWriteToCache: false,\n };\n }\n\n if (\n options.revalidateSeconds !== null &&\n options.revalidateSeconds > 0 &&\n options.revalidateSeconds !== Infinity\n ) {\n return {\n cacheControl: buildRevalidateCacheControl(options.revalidateSeconds),\n cacheState: options.isProduction ? \"MISS\" : undefined,\n shouldWriteToCache: options.isProduction,\n };\n }\n\n if (options.revalidateSeconds === Infinity) {\n return {\n cacheControl: STATIC_CACHE_CONTROL,\n cacheState: \"STATIC\",\n shouldWriteToCache: false,\n };\n }\n\n return { shouldWriteToCache: false };\n}\n\n/**\n * Merge middleware response headers into a target Headers object.\n *\n * Set-Cookie and Vary are accumulated (append) since multiple sources can\n * contribute values. All other headers use set() so middleware owns singular\n * response headers like Cache-Control.\n *\n * Used by buildAppPageRscResponse and the generated entry for intercepting\n * route and server action responses that bypass the normal page render path.\n */\nexport function mergeMiddlewareResponseHeaders(\n target: Headers,\n middlewareHeaders: Headers | null,\n): void {\n if (!middlewareHeaders) {\n return;\n }\n\n for (const [key, value] of middlewareHeaders) {\n const lowerKey = key.toLowerCase();\n if (lowerKey === \"set-cookie\" || lowerKey === \"vary\") {\n target.append(key, value);\n } else {\n target.set(key, value);\n }\n }\n}\n\nexport function buildAppPageRscResponse(\n body: ReadableStream,\n options: BuildAppPageRscResponseOptions,\n): Response {\n const headers = new Headers({\n \"Content-Type\": \"text/x-component; charset=utf-8\",\n Vary: \"RSC, Accept\",\n });\n\n if (options.params && Object.keys(options.params).length > 0) {\n // encodeURIComponent so non-ASCII params (e.g. Korean slugs) survive the\n // HTTP ByteString constraint — Headers.set() rejects chars above U+00FF.\n headers.set(\"X-Vinext-Params\", encodeURIComponent(JSON.stringify(options.params)));\n }\n if (options.policy.cacheControl) {\n headers.set(\"Cache-Control\", options.policy.cacheControl);\n }\n if (options.policy.cacheState) {\n headers.set(\"X-Vinext-Cache\", options.policy.cacheState);\n }\n\n mergeMiddlewareResponseHeaders(headers, options.middlewareContext.headers);\n\n applyTimingHeader(headers, options.timing);\n\n return new Response(body, {\n status: options.middlewareContext.status ?? 200,\n headers,\n });\n}\n\nexport function buildAppPageHtmlResponse(\n body: ReadableStream,\n options: BuildAppPageHtmlResponseOptions,\n): Response {\n const headers = new Headers({\n \"Content-Type\": \"text/html; charset=utf-8\",\n Vary: \"RSC, Accept\",\n });\n\n if (options.policy.cacheControl) {\n headers.set(\"Cache-Control\", options.policy.cacheControl);\n }\n if (options.policy.cacheState) {\n headers.set(\"X-Vinext-Cache\", options.policy.cacheState);\n }\n if (options.draftCookie) {\n headers.append(\"Set-Cookie\", options.draftCookie);\n }\n if (options.fontLinkHeader) {\n headers.set(\"Link\", options.fontLinkHeader);\n }\n\n if (options.middlewareContext.headers) {\n for (const [key, value] of options.middlewareContext.headers) {\n headers.append(key, value);\n }\n }\n\n applyTimingHeader(headers, options.timing);\n\n return new Response(body, {\n status: options.middlewareContext.status ?? 200,\n headers,\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { AppPageParams } from "./app-page-boundary.js";
|
|
2
|
+
import { Metadata, Viewport } from "../shims/metadata.js";
|
|
3
|
+
import { ComponentType, ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/server/app-page-route-wiring.d.ts
|
|
6
|
+
type AppPageComponentProps = {
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
error?: Error;
|
|
9
|
+
params?: unknown;
|
|
10
|
+
reset?: () => void;
|
|
11
|
+
} & Record<string, unknown>;
|
|
12
|
+
type AppPageComponent = ComponentType<AppPageComponentProps>;
|
|
13
|
+
type AppPageErrorComponent = ComponentType<{
|
|
14
|
+
error: Error;
|
|
15
|
+
reset: () => void;
|
|
16
|
+
}>;
|
|
17
|
+
type AppPageModule = Record<string, unknown> & {
|
|
18
|
+
default?: AppPageComponent | null | undefined;
|
|
19
|
+
};
|
|
20
|
+
type AppPageErrorModule = Record<string, unknown> & {
|
|
21
|
+
default?: AppPageErrorComponent | null | undefined;
|
|
22
|
+
};
|
|
23
|
+
type AppPageRouteWiringSlot<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
|
|
24
|
+
default?: TModule | null;
|
|
25
|
+
error?: TErrorModule | null;
|
|
26
|
+
layout?: TModule | null;
|
|
27
|
+
layoutIndex: number;
|
|
28
|
+
loading?: TModule | null;
|
|
29
|
+
page?: TModule | null;
|
|
30
|
+
/**
|
|
31
|
+
* Filesystem segments from the slot's root to its active page.
|
|
32
|
+
* Used to populate the LayoutSegmentProvider's segmentMap for this slot.
|
|
33
|
+
* null when the slot has no active page (showing default.tsx fallback).
|
|
34
|
+
*/
|
|
35
|
+
routeSegments?: readonly string[] | null;
|
|
36
|
+
};
|
|
37
|
+
type AppPageRouteWiringRoute<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
|
|
38
|
+
error?: TErrorModule | null;
|
|
39
|
+
errors?: readonly (TErrorModule | null | undefined)[] | null;
|
|
40
|
+
layoutTreePositions?: readonly number[] | null;
|
|
41
|
+
layouts: readonly (TModule | null | undefined)[];
|
|
42
|
+
loading?: TModule | null;
|
|
43
|
+
notFound?: TModule | null;
|
|
44
|
+
notFounds?: readonly (TModule | null | undefined)[] | null;
|
|
45
|
+
routeSegments?: readonly string[];
|
|
46
|
+
slots?: Readonly<Record<string, AppPageRouteWiringSlot<TModule, TErrorModule>>> | null;
|
|
47
|
+
templates?: readonly (TModule | null | undefined)[] | null;
|
|
48
|
+
};
|
|
49
|
+
type AppPageSlotOverride<TModule extends AppPageModule = AppPageModule> = {
|
|
50
|
+
pageModule: TModule;
|
|
51
|
+
params?: AppPageParams;
|
|
52
|
+
props?: Readonly<Record<string, unknown>>;
|
|
53
|
+
};
|
|
54
|
+
type AppPageLayoutEntry<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
|
|
55
|
+
errorModule?: TErrorModule | null | undefined;
|
|
56
|
+
id: string;
|
|
57
|
+
layoutModule?: TModule | null | undefined;
|
|
58
|
+
notFoundModule?: TModule | null | undefined;
|
|
59
|
+
treePath: string;
|
|
60
|
+
treePosition: number;
|
|
61
|
+
};
|
|
62
|
+
type BuildAppPageRouteElementOptions<TModule extends AppPageModule = AppPageModule, TErrorModule extends AppPageErrorModule = AppPageErrorModule> = {
|
|
63
|
+
element: ReactNode;
|
|
64
|
+
globalErrorModule?: TErrorModule | null;
|
|
65
|
+
makeThenableParams: (params: AppPageParams) => unknown;
|
|
66
|
+
matchedParams: AppPageParams;
|
|
67
|
+
resolvedMetadata: Metadata | null;
|
|
68
|
+
resolvedViewport: Viewport;
|
|
69
|
+
rootNotFoundModule?: TModule | null;
|
|
70
|
+
route: AppPageRouteWiringRoute<TModule, TErrorModule>;
|
|
71
|
+
slotOverrides?: Readonly<Record<string, AppPageSlotOverride<TModule>>> | null;
|
|
72
|
+
};
|
|
73
|
+
declare function createAppPageTreePath(routeSegments: readonly string[] | null | undefined, treePosition: number): string;
|
|
74
|
+
declare function createAppPageLayoutEntries<TModule extends AppPageModule, TErrorModule extends AppPageErrorModule>(route: Pick<AppPageRouteWiringRoute<TModule, TErrorModule>, "errors" | "layoutTreePositions" | "layouts" | "notFounds" | "routeSegments">): AppPageLayoutEntry<TModule, TErrorModule>[];
|
|
75
|
+
declare function resolveAppPageChildSegments(routeSegments: readonly string[], treePosition: number, params: Readonly<Record<string, string | string[] | undefined>>): string[];
|
|
76
|
+
declare function buildAppPageRouteElement<TModule extends AppPageModule, TErrorModule extends AppPageErrorModule>(options: BuildAppPageRouteElementOptions<TModule, TErrorModule>): ReactNode;
|
|
77
|
+
//#endregion
|
|
78
|
+
export { AppPageErrorModule, AppPageLayoutEntry, AppPageModule, AppPageRouteWiringRoute, AppPageRouteWiringSlot, AppPageSlotOverride, BuildAppPageRouteElementOptions, buildAppPageRouteElement, createAppPageLayoutEntries, createAppPageTreePath, resolveAppPageChildSegments };
|
|
79
|
+
//# sourceMappingURL=app-page-route-wiring.d.ts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { ErrorBoundary, NotFoundBoundary } from "../shims/error-boundary.js";
|
|
2
|
+
import { LayoutSegmentProvider } from "../shims/layout-segment-context.js";
|
|
3
|
+
import { MetadataHead, ViewportHead } from "../shims/metadata.js";
|
|
4
|
+
import { Suspense } from "react";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/server/app-page-route-wiring.tsx
|
|
7
|
+
function getDefaultExport(module) {
|
|
8
|
+
return module?.default ?? null;
|
|
9
|
+
}
|
|
10
|
+
function getErrorBoundaryExport(module) {
|
|
11
|
+
return module?.default ?? null;
|
|
12
|
+
}
|
|
13
|
+
function createAppPageTreePath(routeSegments, treePosition) {
|
|
14
|
+
const treePathSegments = routeSegments?.slice(0, treePosition) ?? [];
|
|
15
|
+
if (treePathSegments.length === 0) return "/";
|
|
16
|
+
return `/${treePathSegments.join("/")}`;
|
|
17
|
+
}
|
|
18
|
+
function createAppPageLayoutEntries(route) {
|
|
19
|
+
return route.layouts.map((layoutModule, index) => {
|
|
20
|
+
const treePosition = route.layoutTreePositions?.[index] ?? 0;
|
|
21
|
+
const treePath = createAppPageTreePath(route.routeSegments, treePosition);
|
|
22
|
+
return {
|
|
23
|
+
errorModule: route.errors?.[index] ?? null,
|
|
24
|
+
id: `layout:${treePath}`,
|
|
25
|
+
layoutModule,
|
|
26
|
+
notFoundModule: route.notFounds?.[index] ?? null,
|
|
27
|
+
treePath,
|
|
28
|
+
treePosition
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function resolveAppPageChildSegments(routeSegments, treePosition, params) {
|
|
33
|
+
const rawSegments = routeSegments.slice(treePosition);
|
|
34
|
+
const resolvedSegments = [];
|
|
35
|
+
for (const segment of rawSegments) {
|
|
36
|
+
if (segment.startsWith("[[...") && segment.endsWith("]]") && segment.length >= 8) {
|
|
37
|
+
const paramValue = params[segment.slice(5, -2)];
|
|
38
|
+
if (Array.isArray(paramValue) && paramValue.length === 0) continue;
|
|
39
|
+
if (paramValue === void 0) continue;
|
|
40
|
+
resolvedSegments.push(Array.isArray(paramValue) ? paramValue.join("/") : paramValue);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
|
44
|
+
const paramValue = params[segment.slice(4, -1)];
|
|
45
|
+
if (Array.isArray(paramValue)) {
|
|
46
|
+
resolvedSegments.push(paramValue.join("/"));
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
resolvedSegments.push(paramValue ?? segment);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (segment.startsWith("[") && segment.endsWith("]") && !segment.includes(".")) {
|
|
53
|
+
const paramValue = params[segment.slice(1, -1)];
|
|
54
|
+
resolvedSegments.push(Array.isArray(paramValue) ? paramValue.join("/") : paramValue ?? segment);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
resolvedSegments.push(segment);
|
|
58
|
+
}
|
|
59
|
+
return resolvedSegments;
|
|
60
|
+
}
|
|
61
|
+
function buildAppPageRouteElement(options) {
|
|
62
|
+
let element = /* @__PURE__ */ jsx(LayoutSegmentProvider, {
|
|
63
|
+
segmentMap: { children: [] },
|
|
64
|
+
children: options.element
|
|
65
|
+
});
|
|
66
|
+
element = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
67
|
+
/* @__PURE__ */ jsx("meta", { charSet: "utf-8" }),
|
|
68
|
+
options.resolvedMetadata ? /* @__PURE__ */ jsx(MetadataHead, { metadata: options.resolvedMetadata }) : null,
|
|
69
|
+
/* @__PURE__ */ jsx(ViewportHead, { viewport: options.resolvedViewport }),
|
|
70
|
+
element
|
|
71
|
+
] });
|
|
72
|
+
const loadingComponent = getDefaultExport(options.route.loading);
|
|
73
|
+
if (loadingComponent) element = /* @__PURE__ */ jsx(Suspense, {
|
|
74
|
+
fallback: /* @__PURE__ */ jsx(loadingComponent, {}),
|
|
75
|
+
children: element
|
|
76
|
+
});
|
|
77
|
+
const lastLayoutErrorModule = options.route.errors && options.route.errors.length > 0 ? options.route.errors[options.route.errors.length - 1] : null;
|
|
78
|
+
const pageErrorComponent = getErrorBoundaryExport(options.route.error);
|
|
79
|
+
if (pageErrorComponent && options.route.error !== lastLayoutErrorModule) element = /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
80
|
+
fallback: pageErrorComponent,
|
|
81
|
+
children: element
|
|
82
|
+
});
|
|
83
|
+
const notFoundComponent = getDefaultExport(options.route.notFound) ?? getDefaultExport(options.rootNotFoundModule);
|
|
84
|
+
if (notFoundComponent) element = /* @__PURE__ */ jsx(NotFoundBoundary, {
|
|
85
|
+
fallback: /* @__PURE__ */ jsx(notFoundComponent, {}),
|
|
86
|
+
children: element
|
|
87
|
+
});
|
|
88
|
+
const templates = options.route.templates ?? [];
|
|
89
|
+
const routeSlots = options.route.slots ?? {};
|
|
90
|
+
const layoutEntries = createAppPageLayoutEntries(options.route);
|
|
91
|
+
const routeThenableParams = options.makeThenableParams(options.matchedParams);
|
|
92
|
+
for (let index = layoutEntries.length - 1; index >= 0; index--) {
|
|
93
|
+
const layoutEntry = layoutEntries[index];
|
|
94
|
+
const layoutNotFoundComponent = getDefaultExport(layoutEntry.notFoundModule);
|
|
95
|
+
if (layoutNotFoundComponent) element = /* @__PURE__ */ jsx(NotFoundBoundary, {
|
|
96
|
+
fallback: /* @__PURE__ */ jsx(layoutNotFoundComponent, {}),
|
|
97
|
+
children: element
|
|
98
|
+
});
|
|
99
|
+
const layoutErrorComponent = getErrorBoundaryExport(layoutEntry.errorModule);
|
|
100
|
+
if (layoutErrorComponent) element = /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
101
|
+
fallback: layoutErrorComponent,
|
|
102
|
+
children: element
|
|
103
|
+
});
|
|
104
|
+
const templateComponent = getDefaultExport(templates[index]);
|
|
105
|
+
if (templateComponent) element = /* @__PURE__ */ jsx(templateComponent, {
|
|
106
|
+
params: options.matchedParams,
|
|
107
|
+
children: element
|
|
108
|
+
});
|
|
109
|
+
const layoutComponent = getDefaultExport(layoutEntry.layoutModule);
|
|
110
|
+
if (!layoutComponent) continue;
|
|
111
|
+
const layoutProps = { params: routeThenableParams };
|
|
112
|
+
for (const [slotName, slot] of Object.entries(routeSlots)) {
|
|
113
|
+
const targetIndex = slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1;
|
|
114
|
+
if (index !== targetIndex) continue;
|
|
115
|
+
const slotOverride = options.slotOverrides?.[slotName];
|
|
116
|
+
const slotParams = slotOverride?.params ?? options.matchedParams;
|
|
117
|
+
const slotComponent = getDefaultExport(slotOverride?.pageModule) ?? getDefaultExport(slot.page) ?? getDefaultExport(slot.default);
|
|
118
|
+
if (!slotComponent) continue;
|
|
119
|
+
const slotProps = { params: options.makeThenableParams(slotParams) };
|
|
120
|
+
if (slotOverride?.props) Object.assign(slotProps, slotOverride.props);
|
|
121
|
+
let slotElement = /* @__PURE__ */ jsx(slotComponent, { ...slotProps });
|
|
122
|
+
const slotLayoutComponent = getDefaultExport(slot.layout);
|
|
123
|
+
if (slotLayoutComponent) slotElement = /* @__PURE__ */ jsx(slotLayoutComponent, {
|
|
124
|
+
params: options.makeThenableParams(slotParams),
|
|
125
|
+
children: slotElement
|
|
126
|
+
});
|
|
127
|
+
const slotLoadingComponent = getDefaultExport(slot.loading);
|
|
128
|
+
if (slotLoadingComponent) slotElement = /* @__PURE__ */ jsx(Suspense, {
|
|
129
|
+
fallback: /* @__PURE__ */ jsx(slotLoadingComponent, {}),
|
|
130
|
+
children: slotElement
|
|
131
|
+
});
|
|
132
|
+
const slotErrorComponent = getErrorBoundaryExport(slot.error);
|
|
133
|
+
if (slotErrorComponent) slotElement = /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
134
|
+
fallback: slotErrorComponent,
|
|
135
|
+
children: slotElement
|
|
136
|
+
});
|
|
137
|
+
layoutProps[slotName] = slotElement;
|
|
138
|
+
}
|
|
139
|
+
element = /* @__PURE__ */ jsx(layoutComponent, {
|
|
140
|
+
...layoutProps,
|
|
141
|
+
children: element
|
|
142
|
+
});
|
|
143
|
+
const segmentMap = { children: resolveAppPageChildSegments(options.route.routeSegments ?? [], layoutEntry.treePosition, options.matchedParams) };
|
|
144
|
+
for (const [slotName, slot] of Object.entries(routeSlots)) {
|
|
145
|
+
const targetIndex = slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1;
|
|
146
|
+
if (index !== targetIndex) continue;
|
|
147
|
+
if (slot.routeSegments) segmentMap[slotName] = resolveAppPageChildSegments(slot.routeSegments, 0, options.matchedParams);
|
|
148
|
+
else segmentMap[slotName] = [];
|
|
149
|
+
}
|
|
150
|
+
element = /* @__PURE__ */ jsx(LayoutSegmentProvider, {
|
|
151
|
+
segmentMap,
|
|
152
|
+
children: element
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const globalErrorComponent = getErrorBoundaryExport(options.globalErrorModule);
|
|
156
|
+
if (globalErrorComponent) element = /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
157
|
+
fallback: globalErrorComponent,
|
|
158
|
+
children: element
|
|
159
|
+
});
|
|
160
|
+
return element;
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
export { buildAppPageRouteElement, createAppPageLayoutEntries, createAppPageTreePath, resolveAppPageChildSegments };
|
|
164
|
+
|
|
165
|
+
//# sourceMappingURL=app-page-route-wiring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-page-route-wiring.js","names":[],"sources":["../../src/server/app-page-route-wiring.tsx"],"sourcesContent":["import { Suspense, type ComponentType, type ReactNode } from \"react\";\nimport { ErrorBoundary, NotFoundBoundary } from \"../shims/error-boundary.js\";\nimport { LayoutSegmentProvider } from \"../shims/layout-segment-context.js\";\nimport { MetadataHead, ViewportHead, type Metadata, type Viewport } from \"../shims/metadata.js\";\nimport type { AppPageParams } from \"./app-page-boundary.js\";\n\ntype AppPageComponentProps = {\n children?: ReactNode;\n error?: Error;\n params?: unknown;\n reset?: () => void;\n} & Record<string, unknown>;\n\ntype AppPageComponent = ComponentType<AppPageComponentProps>;\ntype AppPageErrorComponent = ComponentType<{ error: Error; reset: () => void }>;\n\nexport type AppPageModule = Record<string, unknown> & {\n default?: AppPageComponent | null | undefined;\n};\n\nexport type AppPageErrorModule = Record<string, unknown> & {\n default?: AppPageErrorComponent | null | undefined;\n};\n\nexport type AppPageRouteWiringSlot<\n TModule extends AppPageModule = AppPageModule,\n TErrorModule extends AppPageErrorModule = AppPageErrorModule,\n> = {\n default?: TModule | null;\n error?: TErrorModule | null;\n layout?: TModule | null;\n layoutIndex: number;\n loading?: TModule | null;\n page?: TModule | null;\n /**\n * Filesystem segments from the slot's root to its active page.\n * Used to populate the LayoutSegmentProvider's segmentMap for this slot.\n * null when the slot has no active page (showing default.tsx fallback).\n */\n routeSegments?: readonly string[] | null;\n};\n\nexport type AppPageRouteWiringRoute<\n TModule extends AppPageModule = AppPageModule,\n TErrorModule extends AppPageErrorModule = AppPageErrorModule,\n> = {\n error?: TErrorModule | null;\n errors?: readonly (TErrorModule | null | undefined)[] | null;\n layoutTreePositions?: readonly number[] | null;\n layouts: readonly (TModule | null | undefined)[];\n loading?: TModule | null;\n notFound?: TModule | null;\n notFounds?: readonly (TModule | null | undefined)[] | null;\n routeSegments?: readonly string[];\n slots?: Readonly<Record<string, AppPageRouteWiringSlot<TModule, TErrorModule>>> | null;\n templates?: readonly (TModule | null | undefined)[] | null;\n};\n\nexport type AppPageSlotOverride<TModule extends AppPageModule = AppPageModule> = {\n pageModule: TModule;\n params?: AppPageParams;\n props?: Readonly<Record<string, unknown>>;\n};\n\nexport type AppPageLayoutEntry<\n TModule extends AppPageModule = AppPageModule,\n TErrorModule extends AppPageErrorModule = AppPageErrorModule,\n> = {\n errorModule?: TErrorModule | null | undefined;\n id: string;\n layoutModule?: TModule | null | undefined;\n notFoundModule?: TModule | null | undefined;\n treePath: string;\n treePosition: number;\n};\n\nexport type BuildAppPageRouteElementOptions<\n TModule extends AppPageModule = AppPageModule,\n TErrorModule extends AppPageErrorModule = AppPageErrorModule,\n> = {\n element: ReactNode;\n globalErrorModule?: TErrorModule | null;\n makeThenableParams: (params: AppPageParams) => unknown;\n matchedParams: AppPageParams;\n resolvedMetadata: Metadata | null;\n resolvedViewport: Viewport;\n rootNotFoundModule?: TModule | null;\n route: AppPageRouteWiringRoute<TModule, TErrorModule>;\n slotOverrides?: Readonly<Record<string, AppPageSlotOverride<TModule>>> | null;\n};\n\nfunction getDefaultExport<TModule extends AppPageModule>(\n module: TModule | null | undefined,\n): AppPageComponent | null {\n return module?.default ?? null;\n}\n\nfunction getErrorBoundaryExport<TModule extends AppPageErrorModule>(\n module: TModule | null | undefined,\n): AppPageErrorComponent | null {\n return module?.default ?? null;\n}\n\nexport function createAppPageTreePath(\n routeSegments: readonly string[] | null | undefined,\n treePosition: number,\n): string {\n const treePathSegments = routeSegments?.slice(0, treePosition) ?? [];\n if (treePathSegments.length === 0) {\n return \"/\";\n }\n return `/${treePathSegments.join(\"/\")}`;\n}\n\nexport function createAppPageLayoutEntries<\n TModule extends AppPageModule,\n TErrorModule extends AppPageErrorModule,\n>(\n route: Pick<\n AppPageRouteWiringRoute<TModule, TErrorModule>,\n \"errors\" | \"layoutTreePositions\" | \"layouts\" | \"notFounds\" | \"routeSegments\"\n >,\n): AppPageLayoutEntry<TModule, TErrorModule>[] {\n return route.layouts.map((layoutModule, index) => {\n const treePosition = route.layoutTreePositions?.[index] ?? 0;\n const treePath = createAppPageTreePath(route.routeSegments, treePosition);\n return {\n errorModule: route.errors?.[index] ?? null,\n id: `layout:${treePath}`,\n layoutModule,\n notFoundModule: route.notFounds?.[index] ?? null,\n treePath,\n treePosition,\n };\n });\n}\n\nexport function resolveAppPageChildSegments(\n routeSegments: readonly string[],\n treePosition: number,\n params: Readonly<Record<string, string | string[] | undefined>>,\n): string[] {\n const rawSegments = routeSegments.slice(treePosition);\n const resolvedSegments: string[] = [];\n\n for (const segment of rawSegments) {\n if (\n segment.startsWith(\"[[...\") &&\n segment.endsWith(\"]]\") &&\n segment.length >= \"[[...x]]\".length\n ) {\n const paramName = segment.slice(5, -2);\n const paramValue = params[paramName];\n if (Array.isArray(paramValue) && paramValue.length === 0) {\n continue;\n }\n if (paramValue === undefined) {\n continue;\n }\n resolvedSegments.push(Array.isArray(paramValue) ? paramValue.join(\"/\") : paramValue);\n continue;\n }\n\n if (segment.startsWith(\"[...\") && segment.endsWith(\"]\")) {\n const paramName = segment.slice(4, -1);\n const paramValue = params[paramName];\n if (Array.isArray(paramValue)) {\n resolvedSegments.push(paramValue.join(\"/\"));\n continue;\n }\n resolvedSegments.push(paramValue ?? segment);\n continue;\n }\n\n if (segment.startsWith(\"[\") && segment.endsWith(\"]\") && !segment.includes(\".\")) {\n const paramName = segment.slice(1, -1);\n const paramValue = params[paramName];\n resolvedSegments.push(\n Array.isArray(paramValue) ? paramValue.join(\"/\") : (paramValue ?? segment),\n );\n continue;\n }\n\n resolvedSegments.push(segment);\n }\n\n return resolvedSegments;\n}\n\nexport function buildAppPageRouteElement<\n TModule extends AppPageModule,\n TErrorModule extends AppPageErrorModule,\n>(options: BuildAppPageRouteElementOptions<TModule, TErrorModule>): ReactNode {\n let element: ReactNode = (\n <LayoutSegmentProvider segmentMap={{ children: [] }}>{options.element}</LayoutSegmentProvider>\n );\n\n element = (\n <>\n <meta charSet=\"utf-8\" />\n {options.resolvedMetadata ? <MetadataHead metadata={options.resolvedMetadata} /> : null}\n <ViewportHead viewport={options.resolvedViewport} />\n {element}\n </>\n );\n\n const loadingComponent = getDefaultExport(options.route.loading);\n if (loadingComponent) {\n const LoadingComponent = loadingComponent;\n element = <Suspense fallback={<LoadingComponent />}>{element}</Suspense>;\n }\n\n const lastLayoutErrorModule =\n options.route.errors && options.route.errors.length > 0\n ? options.route.errors[options.route.errors.length - 1]\n : null;\n const pageErrorComponent = getErrorBoundaryExport(options.route.error);\n if (pageErrorComponent && options.route.error !== lastLayoutErrorModule) {\n element = <ErrorBoundary fallback={pageErrorComponent}>{element}</ErrorBoundary>;\n }\n\n const notFoundComponent =\n getDefaultExport(options.route.notFound) ?? getDefaultExport(options.rootNotFoundModule);\n if (notFoundComponent) {\n const NotFoundComponent = notFoundComponent;\n element = <NotFoundBoundary fallback={<NotFoundComponent />}>{element}</NotFoundBoundary>;\n }\n\n const templates = options.route.templates ?? [];\n const routeSlots = options.route.slots ?? {};\n const layoutEntries = createAppPageLayoutEntries(options.route);\n const routeThenableParams = options.makeThenableParams(options.matchedParams);\n\n for (let index = layoutEntries.length - 1; index >= 0; index--) {\n const layoutEntry = layoutEntries[index];\n\n // Next.js nesting per segment (outer to inner): Layout > Template > Error > NotFound > children\n // Building bottom-up: NotFoundBoundary is the innermost wrapper, then ErrorBoundary, then Template.\n const layoutNotFoundComponent = getDefaultExport(layoutEntry.notFoundModule);\n if (layoutNotFoundComponent) {\n const LayoutNotFoundComponent = layoutNotFoundComponent;\n element = (\n <NotFoundBoundary fallback={<LayoutNotFoundComponent />}>{element}</NotFoundBoundary>\n );\n }\n\n const layoutErrorComponent = getErrorBoundaryExport(layoutEntry.errorModule);\n if (layoutErrorComponent) {\n element = <ErrorBoundary fallback={layoutErrorComponent}>{element}</ErrorBoundary>;\n }\n\n const templateComponent = getDefaultExport(templates[index]);\n if (templateComponent) {\n const TemplateComponent = templateComponent;\n element = <TemplateComponent params={options.matchedParams}>{element}</TemplateComponent>;\n }\n\n const layoutComponent = getDefaultExport(layoutEntry.layoutModule);\n if (!layoutComponent) {\n continue;\n }\n\n const layoutProps: Record<string, unknown> = {\n params: routeThenableParams,\n };\n\n for (const [slotName, slot] of Object.entries(routeSlots)) {\n const targetIndex = slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1;\n if (index !== targetIndex) {\n continue;\n }\n\n const slotOverride = options.slotOverrides?.[slotName];\n const slotParams = slotOverride?.params ?? options.matchedParams;\n const slotComponent =\n getDefaultExport(slotOverride?.pageModule) ??\n getDefaultExport(slot.page) ??\n getDefaultExport(slot.default);\n if (!slotComponent) {\n continue;\n }\n\n const slotProps: Record<string, unknown> = {\n params: options.makeThenableParams(slotParams),\n };\n if (slotOverride?.props) {\n Object.assign(slotProps, slotOverride.props);\n }\n\n const SlotComponent = slotComponent;\n let slotElement: ReactNode = <SlotComponent {...slotProps} />;\n\n const slotLayoutComponent = getDefaultExport(slot.layout);\n if (slotLayoutComponent) {\n const SlotLayoutComponent = slotLayoutComponent;\n slotElement = (\n <SlotLayoutComponent params={options.makeThenableParams(slotParams)}>\n {slotElement}\n </SlotLayoutComponent>\n );\n }\n\n const slotLoadingComponent = getDefaultExport(slot.loading);\n if (slotLoadingComponent) {\n const SlotLoadingComponent = slotLoadingComponent;\n slotElement = <Suspense fallback={<SlotLoadingComponent />}>{slotElement}</Suspense>;\n }\n\n const slotErrorComponent = getErrorBoundaryExport(slot.error);\n if (slotErrorComponent) {\n slotElement = <ErrorBoundary fallback={slotErrorComponent}>{slotElement}</ErrorBoundary>;\n }\n\n layoutProps[slotName] = slotElement;\n }\n\n const LayoutComponent = layoutComponent;\n element = <LayoutComponent {...layoutProps}>{element}</LayoutComponent>;\n\n // Build the segment map for this layout level. The \"children\" key always\n // contains the route segments below this layout. Named parallel slots at\n // this layout level add their own keys with per-slot segment data.\n const segmentMap: { children: string[] } & Record<string, string[]> = {\n children: resolveAppPageChildSegments(\n options.route.routeSegments ?? [],\n layoutEntry.treePosition,\n options.matchedParams,\n ),\n };\n for (const [slotName, slot] of Object.entries(routeSlots)) {\n const targetIndex = slot.layoutIndex >= 0 ? slot.layoutIndex : layoutEntries.length - 1;\n if (index !== targetIndex) {\n continue;\n }\n if (slot.routeSegments) {\n // Slot has an active page — resolve its segments (dynamic params → values)\n segmentMap[slotName] = resolveAppPageChildSegments(\n slot.routeSegments,\n 0, // Slot segments are already relative to the slot root\n options.matchedParams,\n );\n } else {\n // Slot is showing default.tsx or has no page — empty segments\n segmentMap[slotName] = [];\n }\n }\n element = <LayoutSegmentProvider segmentMap={segmentMap}>{element}</LayoutSegmentProvider>;\n }\n\n const globalErrorComponent = getErrorBoundaryExport(options.globalErrorModule);\n if (globalErrorComponent) {\n element = <ErrorBoundary fallback={globalErrorComponent}>{element}</ErrorBoundary>;\n }\n\n return element;\n}\n"],"mappings":"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"}
|
|
@@ -33,6 +33,9 @@ function deferUntilStreamConsumed(stream, onFlush) {
|
|
|
33
33
|
return reader.read().then(({ done, value }) => {
|
|
34
34
|
if (done) controller.close();
|
|
35
35
|
else controller.enqueue(value);
|
|
36
|
+
}, (error) => {
|
|
37
|
+
once();
|
|
38
|
+
controller.error(error);
|
|
36
39
|
});
|
|
37
40
|
},
|
|
38
41
|
cancel(reason) {
|
|
@@ -1 +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 type AppPageFontData = {\n links: string[];\n preloads: readonly AppPageFontPreload[];\n styles: string[];\n};\n\nexport type CreateAppPageFontDataOptions = {\n getLinks: () => string[];\n getPreloads: () => AppPageFontPreload[];\n getStyles: () => string[];\n};\n\nexport type AppPageSsrHandler = {\n handleSsr: (\n rscStream: ReadableStream<Uint8Array>,\n navigationContext: unknown,\n fontData: AppPageFontData,\n ) => Promise<ReadableStream<Uint8Array>>;\n};\n\nexport type RenderAppPageHtmlStreamOptions = {\n fontData: AppPageFontData;\n navigationContext: unknown;\n rscStream: ReadableStream<Uint8Array>;\n ssrHandler: AppPageSsrHandler;\n};\n\nexport type RenderAppPageHtmlResponseOptions = {\n clearRequestContext: () => void;\n fontLinkHeader?: string;\n status: number;\n} & RenderAppPageHtmlStreamOptions;\n\nexport type AppPageHtmlStreamRecoveryResult = {\n htmlStream: ReadableStream<Uint8Array> | null;\n response: Response | null;\n};\n\nexport type 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 type AppPageRscErrorTracker = {\n getCapturedError: () => unknown;\n onRenderError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown;\n};\n\nexport type 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
|
|
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 type AppPageFontData = {\n links: string[];\n preloads: readonly AppPageFontPreload[];\n styles: string[];\n};\n\nexport type CreateAppPageFontDataOptions = {\n getLinks: () => string[];\n getPreloads: () => AppPageFontPreload[];\n getStyles: () => string[];\n};\n\nexport type AppPageSsrHandler = {\n handleSsr: (\n rscStream: ReadableStream<Uint8Array>,\n navigationContext: unknown,\n fontData: AppPageFontData,\n ) => Promise<ReadableStream<Uint8Array>>;\n};\n\nexport type RenderAppPageHtmlStreamOptions = {\n fontData: AppPageFontData;\n navigationContext: unknown;\n rscStream: ReadableStream<Uint8Array>;\n ssrHandler: AppPageSsrHandler;\n};\n\nexport type RenderAppPageHtmlResponseOptions = {\n clearRequestContext: () => void;\n fontLinkHeader?: string;\n status: number;\n} & RenderAppPageHtmlStreamOptions;\n\nexport type AppPageHtmlStreamRecoveryResult = {\n htmlStream: ReadableStream<Uint8Array> | null;\n response: Response | null;\n};\n\nexport type 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 type AppPageRscErrorTracker = {\n getCapturedError: () => unknown;\n onRenderError: (error: unknown, requestInfo: unknown, errorContext: unknown) => unknown;\n};\n\nexport type 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(\n ({ done, value }) => {\n if (done) {\n controller.close();\n } else {\n controller.enqueue(value);\n }\n },\n (error) => {\n once();\n controller.error(error);\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,MAClB,EAAE,MAAM,YAAY;AACnB,QAAI,KACF,YAAW,OAAO;QAElB,YAAW,QAAQ,MAAM;OAG5B,UAAU;AACT,UAAM;AACN,eAAW,MAAM,MAAM;KAE1B;;EAEH,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"}
|
|
@@ -34,8 +34,11 @@ async function buildAppRouteCacheValue(response) {
|
|
|
34
34
|
const body = await response.arrayBuffer();
|
|
35
35
|
const headers = {};
|
|
36
36
|
response.headers.forEach((value, key) => {
|
|
37
|
-
if (key
|
|
37
|
+
if (key === "set-cookie" || key === "x-vinext-cache" || key === "cache-control") return;
|
|
38
|
+
headers[key] = value;
|
|
38
39
|
});
|
|
40
|
+
const setCookies = response.headers.getSetCookie?.() ?? [];
|
|
41
|
+
if (setCookies.length > 0) headers["set-cookie"] = setCookies;
|
|
39
42
|
return {
|
|
40
43
|
kind: "APP_ROUTE",
|
|
41
44
|
body,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-route-handler-response.js","names":[],"sources":["../../src/server/app-route-handler-response.ts"],"sourcesContent":["import type { CachedRouteValue } from \"../shims/cache.js\";\n\nexport type RouteHandlerMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\nexport type BuildRouteHandlerCachedResponseOptions = {\n cacheState: \"HIT\" | \"STALE\";\n isHead: boolean;\n revalidateSeconds: number;\n};\n\nexport type FinalizeRouteHandlerResponseOptions = {\n pendingCookies: string[];\n draftCookie?: string | null;\n isHead: boolean;\n};\n\nfunction buildRouteHandlerCacheControl(\n cacheState: BuildRouteHandlerCachedResponseOptions[\"cacheState\"],\n revalidateSeconds: number,\n): string {\n if (cacheState === \"STALE\") {\n return \"s-maxage=0, stale-while-revalidate\";\n }\n\n return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;\n}\n\nexport function applyRouteHandlerMiddlewareContext(\n response: Response,\n middlewareContext: RouteHandlerMiddlewareContext,\n): Response {\n if (!middlewareContext.headers && middlewareContext.status == null) {\n return response;\n }\n\n const responseHeaders = new Headers(response.headers);\n if (middlewareContext.headers) {\n for (const [key, value] of middlewareContext.headers) {\n responseHeaders.append(key, value);\n }\n }\n\n return new Response(response.body, {\n status: middlewareContext.status ?? response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n });\n}\n\nexport function buildRouteHandlerCachedResponse(\n cachedValue: CachedRouteValue,\n options: BuildRouteHandlerCachedResponseOptions,\n): Response {\n const headers = new Headers();\n for (const [key, value] of Object.entries(cachedValue.headers)) {\n if (Array.isArray(value)) {\n for (const entry of value) {\n headers.append(key, entry);\n }\n } else {\n headers.set(key, value);\n }\n }\n headers.set(\"X-Vinext-Cache\", options.cacheState);\n headers.set(\n \"Cache-Control\",\n buildRouteHandlerCacheControl(options.cacheState, options.revalidateSeconds),\n );\n\n return new Response(options.isHead ? null : cachedValue.body, {\n status: cachedValue.status,\n headers,\n });\n}\n\nexport function applyRouteHandlerRevalidateHeader(\n response: Response,\n revalidateSeconds: number,\n): void {\n response.headers.set(\"cache-control\", buildRouteHandlerCacheControl(\"HIT\", revalidateSeconds));\n}\n\nexport function markRouteHandlerCacheMiss(response: Response): void {\n response.headers.set(\"X-Vinext-Cache\", \"MISS\");\n}\n\nexport async function buildAppRouteCacheValue(response: Response): Promise<CachedRouteValue> {\n const body = await response.arrayBuffer();\n const headers: CachedRouteValue[\"headers\"] = {};\n\n response.headers.forEach((value, key) => {\n if (key
|
|
1
|
+
{"version":3,"file":"app-route-handler-response.js","names":[],"sources":["../../src/server/app-route-handler-response.ts"],"sourcesContent":["import type { CachedRouteValue } from \"../shims/cache.js\";\n\nexport type RouteHandlerMiddlewareContext = {\n headers: Headers | null;\n status: number | null;\n};\n\nexport type BuildRouteHandlerCachedResponseOptions = {\n cacheState: \"HIT\" | \"STALE\";\n isHead: boolean;\n revalidateSeconds: number;\n};\n\nexport type FinalizeRouteHandlerResponseOptions = {\n pendingCookies: string[];\n draftCookie?: string | null;\n isHead: boolean;\n};\n\nfunction buildRouteHandlerCacheControl(\n cacheState: BuildRouteHandlerCachedResponseOptions[\"cacheState\"],\n revalidateSeconds: number,\n): string {\n if (cacheState === \"STALE\") {\n return \"s-maxage=0, stale-while-revalidate\";\n }\n\n return `s-maxage=${revalidateSeconds}, stale-while-revalidate`;\n}\n\nexport function applyRouteHandlerMiddlewareContext(\n response: Response,\n middlewareContext: RouteHandlerMiddlewareContext,\n): Response {\n if (!middlewareContext.headers && middlewareContext.status == null) {\n return response;\n }\n\n const responseHeaders = new Headers(response.headers);\n if (middlewareContext.headers) {\n for (const [key, value] of middlewareContext.headers) {\n responseHeaders.append(key, value);\n }\n }\n\n return new Response(response.body, {\n status: middlewareContext.status ?? response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n });\n}\n\nexport function buildRouteHandlerCachedResponse(\n cachedValue: CachedRouteValue,\n options: BuildRouteHandlerCachedResponseOptions,\n): Response {\n const headers = new Headers();\n for (const [key, value] of Object.entries(cachedValue.headers)) {\n if (Array.isArray(value)) {\n for (const entry of value) {\n headers.append(key, entry);\n }\n } else {\n headers.set(key, value);\n }\n }\n headers.set(\"X-Vinext-Cache\", options.cacheState);\n headers.set(\n \"Cache-Control\",\n buildRouteHandlerCacheControl(options.cacheState, options.revalidateSeconds),\n );\n\n return new Response(options.isHead ? null : cachedValue.body, {\n status: cachedValue.status,\n headers,\n });\n}\n\nexport function applyRouteHandlerRevalidateHeader(\n response: Response,\n revalidateSeconds: number,\n): void {\n response.headers.set(\"cache-control\", buildRouteHandlerCacheControl(\"HIT\", revalidateSeconds));\n}\n\nexport function markRouteHandlerCacheMiss(response: Response): void {\n response.headers.set(\"X-Vinext-Cache\", \"MISS\");\n}\n\nexport async function buildAppRouteCacheValue(response: Response): Promise<CachedRouteValue> {\n const body = await response.arrayBuffer();\n const headers: CachedRouteValue[\"headers\"] = {};\n\n response.headers.forEach((value, key) => {\n if (key === \"set-cookie\" || key === \"x-vinext-cache\" || key === \"cache-control\") return;\n headers[key] = value;\n });\n const setCookies = response.headers.getSetCookie?.() ?? [];\n if (setCookies.length > 0) {\n headers[\"set-cookie\"] = setCookies;\n }\n\n return {\n kind: \"APP_ROUTE\",\n body,\n status: response.status,\n headers,\n };\n}\n\nexport function finalizeRouteHandlerResponse(\n response: Response,\n options: FinalizeRouteHandlerResponseOptions,\n): Response {\n const { pendingCookies, draftCookie, isHead } = options;\n if (pendingCookies.length === 0 && !draftCookie && !isHead) {\n return response;\n }\n\n const headers = new Headers(response.headers);\n for (const cookie of pendingCookies) {\n headers.append(\"Set-Cookie\", cookie);\n }\n if (draftCookie) {\n headers.append(\"Set-Cookie\", draftCookie);\n }\n\n return new Response(isHead ? null : response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n"],"mappings":";AAmBA,SAAS,8BACP,YACA,mBACQ;AACR,KAAI,eAAe,QACjB,QAAO;AAGT,QAAO,YAAY,kBAAkB;;AAGvC,SAAgB,mCACd,UACA,mBACU;AACV,KAAI,CAAC,kBAAkB,WAAW,kBAAkB,UAAU,KAC5D,QAAO;CAGT,MAAM,kBAAkB,IAAI,QAAQ,SAAS,QAAQ;AACrD,KAAI,kBAAkB,QACpB,MAAK,MAAM,CAAC,KAAK,UAAU,kBAAkB,QAC3C,iBAAgB,OAAO,KAAK,MAAM;AAItC,QAAO,IAAI,SAAS,SAAS,MAAM;EACjC,QAAQ,kBAAkB,UAAU,SAAS;EAC7C,YAAY,SAAS;EACrB,SAAS;EACV,CAAC;;AAGJ,SAAgB,gCACd,aACA,SACU;CACV,MAAM,UAAU,IAAI,SAAS;AAC7B,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,YAAY,QAAQ,CAC5D,KAAI,MAAM,QAAQ,MAAM,CACtB,MAAK,MAAM,SAAS,MAClB,SAAQ,OAAO,KAAK,MAAM;KAG5B,SAAQ,IAAI,KAAK,MAAM;AAG3B,SAAQ,IAAI,kBAAkB,QAAQ,WAAW;AACjD,SAAQ,IACN,iBACA,8BAA8B,QAAQ,YAAY,QAAQ,kBAAkB,CAC7E;AAED,QAAO,IAAI,SAAS,QAAQ,SAAS,OAAO,YAAY,MAAM;EAC5D,QAAQ,YAAY;EACpB;EACD,CAAC;;AAGJ,SAAgB,kCACd,UACA,mBACM;AACN,UAAS,QAAQ,IAAI,iBAAiB,8BAA8B,OAAO,kBAAkB,CAAC;;AAGhG,SAAgB,0BAA0B,UAA0B;AAClE,UAAS,QAAQ,IAAI,kBAAkB,OAAO;;AAGhD,eAAsB,wBAAwB,UAA+C;CAC3F,MAAM,OAAO,MAAM,SAAS,aAAa;CACzC,MAAM,UAAuC,EAAE;AAE/C,UAAS,QAAQ,SAAS,OAAO,QAAQ;AACvC,MAAI,QAAQ,gBAAgB,QAAQ,oBAAoB,QAAQ,gBAAiB;AACjF,UAAQ,OAAO;GACf;CACF,MAAM,aAAa,SAAS,QAAQ,gBAAgB,IAAI,EAAE;AAC1D,KAAI,WAAW,SAAS,EACtB,SAAQ,gBAAgB;AAG1B,QAAO;EACL,MAAM;EACN;EACA,QAAQ,SAAS;EACjB;EACD;;AAGH,SAAgB,6BACd,UACA,SACU;CACV,MAAM,EAAE,gBAAgB,aAAa,WAAW;AAChD,KAAI,eAAe,WAAW,KAAK,CAAC,eAAe,CAAC,OAClD,QAAO;CAGT,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;AAC7C,MAAK,MAAM,UAAU,eACnB,SAAQ,OAAO,cAAc,OAAO;AAEtC,KAAI,YACF,SAAQ,OAAO,cAAc,YAAY;AAG3C,QAAO,IAAI,SAAS,SAAS,OAAO,SAAS,MAAM;EACjD,QAAQ,SAAS;EACjB,YAAY,SAAS;EACrB;EACD,CAAC"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { ExecutionContextLike } from "../shims/request-context.js";
|
|
2
2
|
|
|
3
3
|
//#region src/server/app-router-entry.d.ts
|
|
4
|
+
type WorkerAssetEnv = {
|
|
5
|
+
ASSETS?: {
|
|
6
|
+
fetch(request: Request): Promise<Response> | Response;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
4
9
|
declare const _default: {
|
|
5
|
-
fetch(request: Request,
|
|
10
|
+
fetch(request: Request, env?: WorkerAssetEnv, ctx?: ExecutionContextLike): Promise<Response>;
|
|
6
11
|
};
|
|
7
12
|
//#endregion
|
|
8
13
|
export { _default as default };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runWithExecutionContext } from "../shims/request-context.js";
|
|
2
|
+
import { resolveStaticAssetSignal } from "./worker-utils.js";
|
|
2
3
|
import rscHandler from "virtual:vinext-rsc-entry";
|
|
3
4
|
//#region src/server/app-router-entry.ts
|
|
4
5
|
/**
|
|
@@ -14,11 +15,17 @@ import rscHandler from "virtual:vinext-rsc-entry";
|
|
|
14
15
|
* This file runs in the RSC environment. Configure the Cloudflare plugin with:
|
|
15
16
|
* cloudflare({ viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] } })
|
|
16
17
|
*/
|
|
17
|
-
var app_router_entry_default = { async fetch(request,
|
|
18
|
+
var app_router_entry_default = { async fetch(request, env, ctx) {
|
|
18
19
|
if (new URL(request.url).pathname.replaceAll("\\", "/").startsWith("//")) return new Response("404 Not Found", { status: 404 });
|
|
19
20
|
const handleFn = () => rscHandler(request, ctx);
|
|
20
21
|
const result = await (ctx ? runWithExecutionContext(ctx, handleFn) : handleFn());
|
|
21
|
-
if (result instanceof Response)
|
|
22
|
+
if (result instanceof Response) {
|
|
23
|
+
if (env?.ASSETS) {
|
|
24
|
+
const assetResponse = await resolveStaticAssetSignal(result, { fetchAsset: (path) => Promise.resolve(env.ASSETS.fetch(new Request(new URL(path, request.url)))) });
|
|
25
|
+
if (assetResponse) return assetResponse;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
22
29
|
if (result === null || result === void 0) return new Response("Not Found", { status: 404 });
|
|
23
30
|
return new Response(String(result), { status: 200 });
|
|
24
31
|
} };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-router-entry.js","names":[],"sources":["../../src/server/app-router-entry.ts"],"sourcesContent":["/**\n * Default Cloudflare Worker entry point for vinext App Router.\n *\n * Use this directly in wrangler.jsonc:\n * \"main\": \"vinext/server/app-router-entry\"\n *\n * Or import and delegate to it from a custom worker:\n * import handler from \"vinext/server/app-router-entry\";\n * return handler.fetch(request, env, ctx);\n *\n * This file runs in the RSC environment. Configure the Cloudflare plugin with:\n * cloudflare({ viteEnvironment: { name: \"rsc\", childEnvironments: [\"ssr\"] } })\n */\n\n// @ts-expect-error — virtual module resolved by vinext\nimport rscHandler from \"virtual:vinext-rsc-entry\";\nimport { runWithExecutionContext, type ExecutionContextLike } from \"../shims/request-context.js\";\n\nexport default {\n async fetch(request: Request
|
|
1
|
+
{"version":3,"file":"app-router-entry.js","names":[],"sources":["../../src/server/app-router-entry.ts"],"sourcesContent":["/**\n * Default Cloudflare Worker entry point for vinext App Router.\n *\n * Use this directly in wrangler.jsonc:\n * \"main\": \"vinext/server/app-router-entry\"\n *\n * Or import and delegate to it from a custom worker:\n * import handler from \"vinext/server/app-router-entry\";\n * return handler.fetch(request, env, ctx);\n *\n * This file runs in the RSC environment. Configure the Cloudflare plugin with:\n * cloudflare({ viteEnvironment: { name: \"rsc\", childEnvironments: [\"ssr\"] } })\n */\n\n// @ts-expect-error — virtual module resolved by vinext\nimport rscHandler from \"virtual:vinext-rsc-entry\";\nimport { runWithExecutionContext, type ExecutionContextLike } from \"../shims/request-context.js\";\nimport { resolveStaticAssetSignal } from \"./worker-utils.js\";\n\ntype WorkerAssetEnv = {\n ASSETS?: {\n fetch(request: Request): Promise<Response> | Response;\n };\n};\n\nexport default {\n async fetch(\n request: Request,\n env?: WorkerAssetEnv,\n ctx?: ExecutionContextLike,\n ): Promise<Response> {\n const url = new URL(request.url);\n\n // Normalize backslashes (browsers treat /\\ as //) before any other checks.\n const rawPathname = url.pathname.replaceAll(\"\\\\\", \"/\");\n\n // Block protocol-relative URL open redirects (//evil.com/ or /\\evil.com/).\n // Check rawPathname BEFORE decode so the guard fires before normalization.\n if (rawPathname.startsWith(\"//\")) {\n return new Response(\"404 Not Found\", { status: 404 });\n }\n\n // Validate that percent-encoding is well-formed. The RSC handler performs\n // the actual decode + normalize; we only check here to return a clean 400\n // instead of letting a malformed sequence crash downstream.\n try {\n decodeURIComponent(rawPathname);\n } catch {\n // Malformed percent-encoding (e.g. /%E0%A4%A) — return 400 instead of throwing.\n return new Response(\"Bad Request\", { status: 400 });\n }\n\n // Do NOT decode/normalize the pathname here. The RSC handler\n // (virtual:vinext-rsc-entry) is the single point of decoding — it calls\n // decodeURIComponent + normalizePath on the incoming URL. Decoding here\n // AND in the handler would double-decode, causing inconsistent path\n // matching between middleware and routing.\n\n // Delegate to RSC handler (which decodes + normalizes the pathname itself),\n // wrapping in the ExecutionContext ALS scope so downstream code can reach\n // ctx.waitUntil() without having ctx threaded through every call site.\n const handleFn = () => rscHandler(request, ctx);\n const result = await (ctx ? runWithExecutionContext(ctx, handleFn) : handleFn());\n\n if (result instanceof Response) {\n if (env?.ASSETS) {\n const assetResponse = await resolveStaticAssetSignal(result, {\n fetchAsset: (path) =>\n Promise.resolve(env.ASSETS!.fetch(new Request(new URL(path, request.url)))),\n });\n if (assetResponse) return assetResponse;\n }\n return result;\n }\n\n if (result === null || result === undefined) {\n return new Response(\"Not Found\", { status: 404 });\n }\n\n return new Response(String(result), { status: 200 });\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAyBA,IAAA,2BAAe,EACb,MAAM,MACJ,SACA,KACA,KACmB;AAQnB,KAPY,IAAI,IAAI,QAAQ,IAAI,CAGR,SAAS,WAAW,MAAM,IAAI,CAItC,WAAW,KAAK,CAC9B,QAAO,IAAI,SAAS,iBAAiB,EAAE,QAAQ,KAAK,CAAC;CAsBvD,MAAM,iBAAiB,WAAW,SAAS,IAAI;CAC/C,MAAM,SAAS,OAAO,MAAM,wBAAwB,KAAK,SAAS,GAAG,UAAU;AAE/E,KAAI,kBAAkB,UAAU;AAC9B,MAAI,KAAK,QAAQ;GACf,MAAM,gBAAgB,MAAM,yBAAyB,QAAQ,EAC3D,aAAa,SACX,QAAQ,QAAQ,IAAI,OAAQ,MAAM,IAAI,QAAQ,IAAI,IAAI,MAAM,QAAQ,IAAI,CAAC,CAAC,CAAC,EAC9E,CAAC;AACF,OAAI,cAAe,QAAO;;AAE5B,SAAO;;AAGT,KAAI,WAAW,QAAQ,WAAW,KAAA,EAChC,QAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,KAAK,CAAC;AAGnD,QAAO,IAAI,SAAS,OAAO,OAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;GAEvD"}
|
|
@@ -43,7 +43,7 @@ declare function sendCompressed(req: IncomingMessage, res: ServerResponse, body:
|
|
|
43
43
|
* Without a cache, falls back to async filesystem probing (still non-blocking,
|
|
44
44
|
* unlike the old sync existsSync/statSync approach).
|
|
45
45
|
*/
|
|
46
|
-
declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]
|
|
46
|
+
declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]>, statusCode?: number): Promise<boolean>;
|
|
47
47
|
/**
|
|
48
48
|
* Resolve the host for a request, ignoring X-Forwarded-Host to prevent
|
|
49
49
|
* host header poisoning attacks (open redirects, cache poisoning).
|
|
@@ -248,8 +248,10 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
|
|
|
248
248
|
* Without a cache, falls back to async filesystem probing (still non-blocking,
|
|
249
249
|
* unlike the old sync existsSync/statSync approach).
|
|
250
250
|
*/
|
|
251
|
-
async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders) {
|
|
251
|
+
async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders, statusCode) {
|
|
252
252
|
if (pathname === "/") return false;
|
|
253
|
+
const responseStatus = statusCode ?? 200;
|
|
254
|
+
const omitBody = isNoBodyResponseStatus(responseStatus);
|
|
253
255
|
if (cache) {
|
|
254
256
|
let lookupPath;
|
|
255
257
|
if (pathname.includes("%")) {
|
|
@@ -266,7 +268,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
266
268
|
const entry = cache.lookup(lookupPath);
|
|
267
269
|
if (!entry) return false;
|
|
268
270
|
const ifNoneMatch = req.headers["if-none-match"];
|
|
269
|
-
if (typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
|
|
271
|
+
if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
|
|
270
272
|
if (extraHeaders) res.writeHead(304, {
|
|
271
273
|
...entry.notModifiedHeaders,
|
|
272
274
|
...extraHeaders
|
|
@@ -278,12 +280,12 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
278
280
|
const rawAe = compress ? req.headers["accept-encoding"] : void 0;
|
|
279
281
|
const ae = typeof rawAe === "string" ? rawAe.toLowerCase() : void 0;
|
|
280
282
|
const variant = ae ? ae.includes("zstd") && entry.zst || ae.includes("br") && entry.br || ae.includes("gzip") && entry.gz || entry.original : entry.original;
|
|
281
|
-
if (extraHeaders) res.writeHead(
|
|
283
|
+
if (extraHeaders) res.writeHead(responseStatus, {
|
|
282
284
|
...variant.headers,
|
|
283
285
|
...extraHeaders
|
|
284
286
|
});
|
|
285
|
-
else res.writeHead(
|
|
286
|
-
if (req.method === "HEAD") {
|
|
287
|
+
else res.writeHead(responseStatus, variant.headers);
|
|
288
|
+
if (omitBody || req.method === "HEAD") {
|
|
287
289
|
res.end();
|
|
288
290
|
return true;
|
|
289
291
|
}
|
|
@@ -316,7 +318,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
316
318
|
const baseType = ct.split(";")[0].trim();
|
|
317
319
|
const isCompressible = compress && COMPRESSIBLE_TYPES.has(baseType);
|
|
318
320
|
const ifNoneMatch = req.headers["if-none-match"];
|
|
319
|
-
if (typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
|
|
321
|
+
if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
|
|
320
322
|
const notModifiedHeaders = {
|
|
321
323
|
ETag: etag,
|
|
322
324
|
"Cache-Control": cacheControl,
|
|
@@ -336,12 +338,12 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
336
338
|
if (isCompressible) {
|
|
337
339
|
const encoding = negotiateEncoding(req);
|
|
338
340
|
if (encoding) {
|
|
339
|
-
res.writeHead(
|
|
341
|
+
res.writeHead(responseStatus, {
|
|
340
342
|
...baseHeaders,
|
|
341
343
|
"Content-Encoding": encoding,
|
|
342
344
|
Vary: "Accept-Encoding"
|
|
343
345
|
});
|
|
344
|
-
if (req.method === "HEAD") {
|
|
346
|
+
if (omitBody || req.method === "HEAD") {
|
|
345
347
|
res.end();
|
|
346
348
|
return true;
|
|
347
349
|
}
|
|
@@ -355,11 +357,11 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
|
|
|
355
357
|
return true;
|
|
356
358
|
}
|
|
357
359
|
}
|
|
358
|
-
res.writeHead(
|
|
360
|
+
res.writeHead(responseStatus, {
|
|
359
361
|
...baseHeaders,
|
|
360
362
|
"Content-Length": String(resolved.size)
|
|
361
363
|
});
|
|
362
|
-
if (req.method === "HEAD") {
|
|
364
|
+
if (omitBody || req.method === "HEAD") {
|
|
363
365
|
res.end();
|
|
364
366
|
return true;
|
|
365
367
|
}
|
|
@@ -644,7 +646,31 @@ async function startAppRouterServer(options) {
|
|
|
644
646
|
}
|
|
645
647
|
try {
|
|
646
648
|
const qs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
|
|
647
|
-
|
|
649
|
+
const response = await rscHandler(nodeToWebRequest(req, pathname + qs));
|
|
650
|
+
const staticFileSignal = response.headers.get("x-vinext-static-file");
|
|
651
|
+
if (staticFileSignal) {
|
|
652
|
+
let staticFilePath = "/";
|
|
653
|
+
try {
|
|
654
|
+
staticFilePath = decodeURIComponent(staticFileSignal);
|
|
655
|
+
} catch {
|
|
656
|
+
staticFilePath = staticFileSignal;
|
|
657
|
+
}
|
|
658
|
+
const staticResponseHeaders = omitHeadersCaseInsensitive(mergeResponseHeaders({}, response), [
|
|
659
|
+
"x-vinext-static-file",
|
|
660
|
+
"content-encoding",
|
|
661
|
+
"content-length",
|
|
662
|
+
"content-type"
|
|
663
|
+
]);
|
|
664
|
+
const served = await tryServeStatic(req, res, clientDir, staticFilePath, compress, staticCache, staticResponseHeaders, response.status);
|
|
665
|
+
cancelResponseBody(response);
|
|
666
|
+
if (served) return;
|
|
667
|
+
await sendWebResponse(new Response("Not Found", {
|
|
668
|
+
status: 404,
|
|
669
|
+
headers: toWebHeaders(staticResponseHeaders)
|
|
670
|
+
}), req, res, compress);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
await sendWebResponse(response, req, res, compress);
|
|
648
674
|
} catch (e) {
|
|
649
675
|
console.error("[vinext] Server error:", e);
|
|
650
676
|
if (!res.headersSent) {
|