@reckona/mreact-router 0.0.202 → 0.0.204
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 +58 -11
- package/dist/actions.d.ts +8 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +125 -45
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts +15 -4
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +301 -63
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +5 -2
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +35 -11
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/edge.d.ts +1 -1
- package/dist/adapters/edge.d.ts.map +1 -1
- package/dist/adapters/edge.js.map +1 -1
- package/dist/adapters/node.d.ts +5 -3
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +2 -1
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +24 -2
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +4 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +185 -13
- package/dist/build.js.map +1 -1
- package/dist/built-runtime.d.ts +2 -0
- package/dist/built-runtime.d.ts.map +1 -1
- package/dist/built-runtime.js +16 -0
- package/dist/built-runtime.js.map +1 -1
- package/dist/cache.d.ts +4 -2
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +216 -17
- package/dist/cache.js.map +1 -1
- package/dist/cli-options.d.ts +3 -0
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +10 -2
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +14 -3
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +5 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +43 -3
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +21 -2
- package/dist/config.js.map +1 -1
- package/dist/csp.d.ts +1 -0
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +9 -0
- package/dist/csp.js.map +1 -1
- package/dist/dehydrate-policy.d.ts +3 -0
- package/dist/dehydrate-policy.d.ts.map +1 -0
- package/dist/dehydrate-policy.js +16 -0
- package/dist/dehydrate-policy.js.map +1 -0
- package/dist/dev-server.d.ts +7 -2
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +120 -11
- package/dist/dev-server.js.map +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +147 -7
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +22 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +22 -8
- package/dist/logger.js.map +1 -1
- package/dist/metadata.d.ts +8 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +60 -22
- package/dist/metadata.js.map +1 -1
- package/dist/navigation-marker.d.ts +2 -0
- package/dist/navigation-marker.d.ts.map +1 -0
- package/dist/navigation-marker.js +357 -0
- package/dist/navigation-marker.js.map +1 -0
- package/dist/navigation-runtime.d.ts +1 -1
- package/dist/navigation-runtime.d.ts.map +1 -1
- package/dist/navigation-runtime.js +1 -1
- package/dist/navigation-runtime.js.map +1 -1
- package/dist/navigation.d.ts +1 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +1 -1
- package/dist/navigation.js.map +1 -1
- package/dist/node-server.d.ts +5 -2
- package/dist/node-server.d.ts.map +1 -1
- package/dist/node-server.js +75 -6
- package/dist/node-server.js.map +1 -1
- package/dist/prerender-entry.d.ts +17 -1
- package/dist/prerender-entry.d.ts.map +1 -1
- package/dist/prerender-entry.js +98 -3
- package/dist/prerender-entry.js.map +1 -1
- package/dist/render.d.ts +16 -2
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +142 -135
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +67 -41
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +52 -19
- package/dist/session.js.map +1 -1
- package/dist/upgrade.d.ts +59 -2
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +282 -1
- package/dist/upgrade.js.map +1 -1
- package/dist/vite-config.d.ts +2 -0
- package/dist/vite-config.d.ts.map +1 -1
- package/dist/vite-config.js +2 -0
- package/dist/vite-config.js.map +1 -1
- package/dist/vite.d.ts +4 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +117 -6
- package/dist/vite.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +146 -60
- package/src/adapters/aws-lambda.ts +426 -115
- package/src/adapters/cloudflare.ts +58 -12
- package/src/adapters/edge.ts +2 -0
- package/src/adapters/node.ts +8 -10
- package/src/adapters/static.ts +31 -2
- package/src/build.ts +217 -13
- package/src/built-runtime.ts +28 -0
- package/src/cache.ts +272 -19
- package/src/cli-options.ts +15 -2
- package/src/cli.ts +16 -2
- package/src/client.ts +47 -3
- package/src/config.ts +43 -6
- package/src/csp.ts +11 -0
- package/src/dehydrate-policy.ts +23 -0
- package/src/dev-server.ts +188 -15
- package/src/http.ts +172 -6
- package/src/index.ts +14 -1
- package/src/logger.ts +47 -10
- package/src/metadata.ts +125 -60
- package/src/navigation-marker.ts +423 -0
- package/src/navigation-runtime.ts +1 -0
- package/src/navigation.ts +1 -1
- package/src/node-server.ts +128 -22
- package/src/prerender-entry.ts +127 -5
- package/src/render.ts +859 -749
- package/src/serve.ts +112 -82
- package/src/session.ts +56 -25
- package/src/upgrade.ts +402 -3
- package/src/vite-config.ts +6 -0
- package/src/vite.ts +157 -5
package/src/render.ts
CHANGED
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
installQueryAsyncStorage,
|
|
17
17
|
runWithQueryClient,
|
|
18
18
|
type DehydratedQueryClient,
|
|
19
|
+
type DehydrateOptions,
|
|
19
20
|
type QueryClient,
|
|
20
21
|
} from "@reckona/mreact-query";
|
|
21
22
|
import {
|
|
22
23
|
createStringSink,
|
|
23
24
|
type HtmlSink,
|
|
24
|
-
|
|
25
|
+
renderOutOfOrderBoundary,
|
|
25
26
|
renderOutOfOrderReorderScript,
|
|
26
27
|
renderReactNodeToString,
|
|
27
28
|
renderToReadableStream,
|
|
@@ -35,9 +36,9 @@ import {
|
|
|
35
36
|
} from "./client-route-inference.js";
|
|
36
37
|
import {
|
|
37
38
|
hydrationMarkerParts,
|
|
39
|
+
routeMarkerParts,
|
|
38
40
|
routeIdForPath,
|
|
39
41
|
withHydrationMarkers,
|
|
40
|
-
withRouteMarkers,
|
|
41
42
|
} from "./navigation-runtime.js";
|
|
42
43
|
import { assetPath } from "./assets.js";
|
|
43
44
|
import { escapeHtmlAttribute } from "@reckona/mreact-shared/html-escape";
|
|
@@ -66,11 +67,9 @@ import {
|
|
|
66
67
|
type TrackedHeaderRequest,
|
|
67
68
|
withTrackedRequest,
|
|
68
69
|
} from "./request-header-tracking.js";
|
|
69
|
-
import {
|
|
70
|
-
configuredHstsHeader,
|
|
71
|
-
hasInvalidConfiguredHsts,
|
|
72
|
-
} from "./security-headers.js";
|
|
70
|
+
import { configuredHstsHeader, hasInvalidConfiguredHsts } from "./security-headers.js";
|
|
73
71
|
import { resolveRouterCacheLimit } from "./cache-config.js";
|
|
72
|
+
import { protectNonceBearingResponse } from "./prerender-entry.js";
|
|
74
73
|
import {
|
|
75
74
|
importAppRouterBuiltFileModule,
|
|
76
75
|
importAppRouterFileModule,
|
|
@@ -129,13 +128,15 @@ import {
|
|
|
129
128
|
type RouteMiddlewareControl,
|
|
130
129
|
} from "./middleware.js";
|
|
131
130
|
import {
|
|
132
|
-
|
|
131
|
+
applyFileConventionMetadataTemplate,
|
|
132
|
+
fileConventionMetadataTemplate,
|
|
133
133
|
injectHeadMetadata,
|
|
134
134
|
mergeRouteMetadata,
|
|
135
135
|
responseHeadersForMetadata,
|
|
136
136
|
serializeRobots,
|
|
137
137
|
serializeSitemap,
|
|
138
138
|
validateRouteMetadata,
|
|
139
|
+
type FileConventionMetadataTemplate,
|
|
139
140
|
} from "./metadata.js";
|
|
140
141
|
import {
|
|
141
142
|
createSlotRenderContext,
|
|
@@ -202,6 +203,7 @@ export interface RenderAppRequestOptions {
|
|
|
202
203
|
clientStylesByFile?: ReadonlyMap<string, readonly string[]>;
|
|
203
204
|
clientStyles?: ReadonlyMap<string, readonly string[]>;
|
|
204
205
|
define?: UserConfig["define"] | undefined;
|
|
206
|
+
dehydrateOptions?: DehydrateOptions | undefined;
|
|
205
207
|
env?: unknown;
|
|
206
208
|
importPolicy?: AppRouterImportPolicy | undefined;
|
|
207
209
|
instrumentation?: RouterInstrumentation | undefined;
|
|
@@ -217,6 +219,7 @@ export interface RenderAppRequestOptions {
|
|
|
217
219
|
routeMatcher?: RouteMatcher | undefined;
|
|
218
220
|
routes?: readonly AppRoute[] | undefined;
|
|
219
221
|
serverModules?: ReadonlyMap<string, BuiltServerModuleArtifact> | undefined;
|
|
222
|
+
serverRenderArtifactLoader?: AppRouterServerRenderArtifactLoader | undefined;
|
|
220
223
|
serverModuleCacheVersion?: string | undefined;
|
|
221
224
|
devServerModuleCacheVersion?: string | undefined;
|
|
222
225
|
// True when serving through a dev server: request-time server transforms
|
|
@@ -233,6 +236,14 @@ export interface RenderAppRequestOptions {
|
|
|
233
236
|
}
|
|
234
237
|
|
|
235
238
|
export interface RenderAppRequestRuntimeOptions extends RenderAppRequestOptions {
|
|
239
|
+
/**
|
|
240
|
+
* Wraps a server-only page with build-time variant capture comments.
|
|
241
|
+
*
|
|
242
|
+
* This is an in-process render signal. It must not be derived from request
|
|
243
|
+
* headers because network clients are not allowed to change response shape
|
|
244
|
+
* outside the route-cache key.
|
|
245
|
+
*/
|
|
246
|
+
prerenderVariantCapture?: boolean | undefined;
|
|
236
247
|
/**
|
|
237
248
|
* Receives whether the render depended on the incoming request headers.
|
|
238
249
|
*
|
|
@@ -243,8 +254,12 @@ export interface RenderAppRequestRuntimeOptions extends RenderAppRequestOptions
|
|
|
243
254
|
* dependent. Call it only after draining the response body, because a
|
|
244
255
|
* streamed render can read headers while its deferred parts resolve.
|
|
245
256
|
*/
|
|
246
|
-
renderSignals?:
|
|
247
|
-
|
|
257
|
+
renderSignals?:
|
|
258
|
+
| {
|
|
259
|
+
headerDependent: () => boolean;
|
|
260
|
+
strictTransportSecurity: () => string | undefined;
|
|
261
|
+
}
|
|
262
|
+
| undefined;
|
|
248
263
|
}
|
|
249
264
|
|
|
250
265
|
export interface AppRouterRenderPreload {
|
|
@@ -565,7 +580,12 @@ const middlewareModuleCache = new Map<string, Promise<MiddlewareModule>>();
|
|
|
565
580
|
const middlewareModuleCacheCounters = createRouterRuntimeCacheCounters();
|
|
566
581
|
const serverRouteModuleCache = new Map<string, Promise<Record<string, unknown>>>();
|
|
567
582
|
const serverRouteModuleCacheCounters = createRouterRuntimeCacheCounters();
|
|
568
|
-
|
|
583
|
+
interface ComposedRouteMetadataCacheEntry {
|
|
584
|
+
metadata: RouteMetadata | undefined;
|
|
585
|
+
template: FileConventionMetadataTemplate;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const composedRouteMetadataCache = new Map<string, Promise<ComposedRouteMetadataCacheEntry>>();
|
|
569
589
|
const composedRouteMetadataCacheCounters = createRouterRuntimeCacheCounters();
|
|
570
590
|
const maxServerTransformCacheEntries = resolveRouterCacheLimit("SERVER_TRANSFORM", 512);
|
|
571
591
|
const maxServerSourceFileCacheEntries = resolveRouterCacheLimit("SERVER_SOURCE_FILE", 512);
|
|
@@ -688,7 +708,9 @@ export async function renderAppRequest(options: RenderAppRequestOptions): Promis
|
|
|
688
708
|
};
|
|
689
709
|
invokeRouterInstrumentation(options.instrumentation?.onRequestStart, requestEvent);
|
|
690
710
|
const response = await renderAppRequestInternal({ ...options, requestUrl: url });
|
|
691
|
-
const finalResponse =
|
|
711
|
+
const finalResponse = protectNonceBearingResponse(
|
|
712
|
+
await applyAppRouterResponseHook(response, options),
|
|
713
|
+
);
|
|
692
714
|
invokeRouterInstrumentation(options.instrumentation?.onRequestEnd, {
|
|
693
715
|
...requestEvent,
|
|
694
716
|
status: finalResponse.status,
|
|
@@ -717,43 +739,24 @@ function finishRenderTimingPhase(
|
|
|
717
739
|
timing.phases[phaseName] = logDurationMs(startedAt);
|
|
718
740
|
}
|
|
719
741
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
promise.then((value) => ({ settled: true as const, value })),
|
|
725
|
-
new Promise<{ settled: false }>((resolve) => {
|
|
726
|
-
setTimeout(() => resolve({ settled: false }), 0);
|
|
727
|
-
}),
|
|
728
|
-
]);
|
|
729
|
-
}
|
|
742
|
+
type ObservedPromiseSettlement<T> =
|
|
743
|
+
| { status: "pending" }
|
|
744
|
+
| { status: "fulfilled"; value: T }
|
|
745
|
+
| { error: unknown; status: "rejected" };
|
|
730
746
|
|
|
731
|
-
function
|
|
732
|
-
|
|
733
|
-
resolve(): void;
|
|
734
|
-
} {
|
|
735
|
-
let resolve: (() => void) | undefined;
|
|
736
|
-
const promise = new Promise<void>((innerResolve) => {
|
|
737
|
-
resolve = innerResolve;
|
|
738
|
-
});
|
|
747
|
+
function observePromiseSettlement<T>(promise: Promise<T>): () => ObservedPromiseSettlement<T> {
|
|
748
|
+
let settlement: ObservedPromiseSettlement<T> = { status: "pending" };
|
|
739
749
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
resolve?.();
|
|
750
|
+
void promise.then(
|
|
751
|
+
(value) => {
|
|
752
|
+
settlement = { status: "fulfilled", value };
|
|
744
753
|
},
|
|
745
|
-
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
dataPromise: Promise<unknown>,
|
|
750
|
-
loaderReady: Promise<void> | undefined,
|
|
751
|
-
): Promise<void> {
|
|
752
|
-
if (loaderReady === undefined) {
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
754
|
+
(error: unknown) => {
|
|
755
|
+
settlement = { error, status: "rejected" };
|
|
756
|
+
},
|
|
757
|
+
);
|
|
755
758
|
|
|
756
|
-
|
|
759
|
+
return () => settlement;
|
|
757
760
|
}
|
|
758
761
|
|
|
759
762
|
function addRenderTimingPhaseDuration(
|
|
@@ -768,14 +771,6 @@ function addRenderTimingPhaseDuration(
|
|
|
768
771
|
timing.phases[phaseName] = (timing.phases[phaseName] ?? 0) + logDurationMs(startedAt);
|
|
769
772
|
}
|
|
770
773
|
|
|
771
|
-
function routeLoaderMayReturnControlResponse(code: string): boolean {
|
|
772
|
-
return (
|
|
773
|
-
/\b(?:redirect|redirectExternal|rewrite|notFound|throwNotFound)\s*\(/.test(code) ||
|
|
774
|
-
/\bnew\s+Response\s*\(/.test(code) ||
|
|
775
|
-
/\bResponse\.(?:redirect|json)\s*\(/.test(code)
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
774
|
async function waitForRenderPreload(
|
|
780
775
|
options: Pick<RenderAppRequestOptions, "preload">,
|
|
781
776
|
timing: RenderTiming | undefined,
|
|
@@ -1000,211 +995,526 @@ async function renderAppRequestInternal(
|
|
|
1000
995
|
let trackedRequest: TrackedHeaderRequest | undefined;
|
|
1001
996
|
let sourceUsesRequestInput = true;
|
|
1002
997
|
let invalidConfiguredHsts = false;
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
if (matched.route.kind === "metadata") {
|
|
1013
|
-
return await dispatchMetadataRoute({
|
|
1014
|
-
appDir: options.appDir,
|
|
1015
|
-
file: matched.route.file,
|
|
1016
|
-
importPolicy: options.importPolicy,
|
|
1017
|
-
params: matched.params,
|
|
1018
|
-
request: options.request,
|
|
1019
|
-
route: matched.route,
|
|
1020
|
-
serverModules: options.serverModules,
|
|
1021
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1022
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1023
|
-
vitePlugins: options.vitePlugins,
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
if (matched.route.kind === "server") {
|
|
1028
|
-
return await dispatchServerRoute({
|
|
1029
|
-
appDir: options.appDir,
|
|
1030
|
-
env: options.env,
|
|
1031
|
-
file: matched.route.file,
|
|
1032
|
-
importPolicy: options.importPolicy,
|
|
1033
|
-
params: matched.params,
|
|
1034
|
-
request: options.request,
|
|
1035
|
-
route: matched.route,
|
|
1036
|
-
serverModules: options.serverModules,
|
|
1037
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1038
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1039
|
-
vitePlugins: options.vitePlugins,
|
|
1040
|
-
});
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
// Issue 080: page routes render HTML for GET / HEAD only. Other
|
|
1044
|
-
// methods (PUT, PATCH, DELETE, PROPFIND, ...) get 405 with an
|
|
1045
|
-
// Allow header so the response shape complies with RFC 9110 §9
|
|
1046
|
-
// and so caching intermediaries do not cross-cache method results.
|
|
1047
|
-
const methodResponse = pageRouteMethodResponse(options.request.method);
|
|
1048
|
-
if (methodResponse !== undefined) {
|
|
1049
|
-
return methodResponse;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const clientScript = options.clientScripts?.get(matched.route.path);
|
|
1053
|
-
const clientStyleSheets = options.clientStyles?.get(matched.route.path);
|
|
1054
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1055
|
-
const originalCode = await readServerSourceFile(
|
|
1056
|
-
matched.route.file,
|
|
1057
|
-
options.serverModuleCacheVersion,
|
|
1058
|
-
options.serverSourceFiles,
|
|
1059
|
-
);
|
|
1060
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "readSourceMs");
|
|
1061
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1062
|
-
const originalAnalysis = await analyzeRouteSource({
|
|
1063
|
-
appDir: options.appDir,
|
|
1064
|
-
artifact: options.serverModules?.get(matched.route.file)?.analysis,
|
|
1065
|
-
code: originalCode,
|
|
1066
|
-
filename: matched.route.file,
|
|
1067
|
-
routePath: matched.route.path,
|
|
1068
|
-
clientRouteInferenceCache,
|
|
1069
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1070
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1071
|
-
define: options.define,
|
|
1072
|
-
timing,
|
|
1073
|
-
vitePlugins: options.vitePlugins,
|
|
1074
|
-
});
|
|
1075
|
-
sourceUsesRequestInput = originalAnalysis.usesRequestInput;
|
|
1076
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "sourceAnalysisMs");
|
|
1077
|
-
const cachePolicy = originalAnalysis.cachePolicy;
|
|
1078
|
-
const navigationScript = options.navigationScripts?.get(matched.route.path);
|
|
1079
|
-
const cacheKey = routeCacheKey(options.appDir, matched.route.path, url);
|
|
1080
|
-
const mayUseRouteCache =
|
|
1081
|
-
!sourceUsesRequestInput &&
|
|
1082
|
-
(cachePolicy === undefined
|
|
1083
|
-
? originalAnalysis.usesRuntimeCacheControl
|
|
1084
|
-
: cachePolicy.revalidateSeconds !== 0);
|
|
1085
|
-
const reloadRouteCache = isNavigationRouteCacheReloadRequest(options.request);
|
|
1086
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1087
|
-
const cachedResponse =
|
|
1088
|
-
!mayUseRouteCache || reloadRouteCache
|
|
1089
|
-
? undefined
|
|
1090
|
-
: await cachedRouteResponse({
|
|
1091
|
-
cache: options.routeCache,
|
|
1092
|
-
key: cacheKey,
|
|
998
|
+
let renderedStrictTransportSecurity: string | undefined;
|
|
999
|
+
return (
|
|
1000
|
+
await withRouteCacheContext(options.routeCache, async () => {
|
|
1001
|
+
try {
|
|
1002
|
+
if (matched.route.kind === "asset") {
|
|
1003
|
+
return await dispatchConventionAssetRoute({
|
|
1004
|
+
file: matched.route.file,
|
|
1093
1005
|
request: options.request,
|
|
1094
1006
|
});
|
|
1095
|
-
|
|
1007
|
+
}
|
|
1096
1008
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1009
|
+
if (matched.route.kind === "metadata") {
|
|
1010
|
+
return await dispatchMetadataRoute({
|
|
1011
|
+
appDir: options.appDir,
|
|
1012
|
+
file: matched.route.file,
|
|
1013
|
+
importPolicy: options.importPolicy,
|
|
1014
|
+
params: matched.params,
|
|
1015
|
+
request: options.request,
|
|
1016
|
+
route: matched.route,
|
|
1017
|
+
serverModules: options.serverModules,
|
|
1018
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1019
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1020
|
+
vitePlugins: options.vitePlugins,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1100
1023
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
// headers through this request so that a render depending on one is never
|
|
1104
|
-
// stored under a key that ignores it. Callers that store by path alone ask
|
|
1105
|
-
// for tracking through `renderSignals` even when the route itself declares
|
|
1106
|
-
// no cache policy.
|
|
1107
|
-
trackedRequest =
|
|
1108
|
-
mayUseRouteCache || options.renderSignals !== undefined
|
|
1109
|
-
? trackRequestHeaderReads(options.request)
|
|
1110
|
-
: undefined;
|
|
1111
|
-
const appRequest = trackedRequest?.request ?? options.request;
|
|
1112
|
-
|
|
1113
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1114
|
-
const preparedActions = await prepareRouteServerActions({
|
|
1115
|
-
appDir: options.appDir,
|
|
1116
|
-
code: originalCode,
|
|
1117
|
-
formActionReferences: options.serverActionReferencesByFile?.get(matched.route.file),
|
|
1118
|
-
placeholders: true,
|
|
1119
|
-
pageFile: matched.route.file,
|
|
1120
|
-
request: options.request,
|
|
1121
|
-
});
|
|
1122
|
-
for (const diagnostic of preparedActions.diagnostics ?? []) {
|
|
1123
|
-
console.warn(`${diagnostic.code}: ${diagnostic.message}`);
|
|
1124
|
-
}
|
|
1125
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "serverActionsMs");
|
|
1126
|
-
const code = preparedActions.code;
|
|
1127
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1128
|
-
const routeAnalysis =
|
|
1129
|
-
code === originalCode
|
|
1130
|
-
? originalAnalysis
|
|
1131
|
-
: await analyzeRouteSource({
|
|
1024
|
+
if (matched.route.kind === "server") {
|
|
1025
|
+
return await dispatchServerRoute({
|
|
1132
1026
|
appDir: options.appDir,
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1027
|
+
env: options.env,
|
|
1028
|
+
file: matched.route.file,
|
|
1029
|
+
importPolicy: options.importPolicy,
|
|
1030
|
+
params: matched.params,
|
|
1031
|
+
request: options.request,
|
|
1032
|
+
route: matched.route,
|
|
1033
|
+
serverModules: options.serverModules,
|
|
1034
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1139
1035
|
serverSourceFiles: options.serverSourceFiles,
|
|
1140
|
-
define: options.define,
|
|
1141
1036
|
vitePlugins: options.vitePlugins,
|
|
1142
1037
|
});
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Issue 080: page routes render HTML for GET / HEAD only. Other
|
|
1041
|
+
// methods (PUT, PATCH, DELETE, PROPFIND, ...) get 405 with an
|
|
1042
|
+
// Allow header so the response shape complies with RFC 9110 §9
|
|
1043
|
+
// and so caching intermediaries do not cross-cache method results.
|
|
1044
|
+
const methodResponse = pageRouteMethodResponse(options.request.method);
|
|
1045
|
+
if (methodResponse !== undefined) {
|
|
1046
|
+
return methodResponse;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
const clientScript = options.clientScripts?.get(matched.route.path);
|
|
1050
|
+
const clientStyleSheets = options.clientStyles?.get(matched.route.path);
|
|
1051
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1052
|
+
const originalCode = await readServerSourceFile(
|
|
1053
|
+
matched.route.file,
|
|
1054
|
+
options.serverModuleCacheVersion,
|
|
1055
|
+
options.serverSourceFiles,
|
|
1056
|
+
);
|
|
1057
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "readSourceMs");
|
|
1058
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1059
|
+
const originalAnalysis = await analyzeRouteSource({
|
|
1152
1060
|
appDir: options.appDir,
|
|
1061
|
+
artifact: options.serverModules?.get(matched.route.file)?.analysis,
|
|
1153
1062
|
code: originalCode,
|
|
1154
|
-
context: withTrackedRequest({
|
|
1155
|
-
env: options.env,
|
|
1156
|
-
params: matched.params,
|
|
1157
|
-
queryClient,
|
|
1158
|
-
}, appRequest, trackedRequest),
|
|
1159
1063
|
filename: matched.route.file,
|
|
1160
|
-
importPolicy: options.importPolicy,
|
|
1161
|
-
instrumentation: options.instrumentation,
|
|
1162
|
-
devServerModuleCacheVersion: options.devServerModuleCacheVersion,
|
|
1163
|
-
request: options.request,
|
|
1164
|
-
routeId: routeIdForPath(matched.route.path),
|
|
1165
1064
|
routePath: matched.route.path,
|
|
1166
|
-
|
|
1065
|
+
clientRouteInferenceCache,
|
|
1167
1066
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1067
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1168
1068
|
define: options.define,
|
|
1169
|
-
vitePlugins: options.vitePlugins,
|
|
1170
1069
|
timing,
|
|
1171
|
-
|
|
1172
|
-
})
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1070
|
+
vitePlugins: options.vitePlugins,
|
|
1071
|
+
});
|
|
1072
|
+
sourceUsesRequestInput = originalAnalysis.usesRequestInput;
|
|
1073
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "sourceAnalysisMs");
|
|
1074
|
+
const cachePolicy = originalAnalysis.cachePolicy;
|
|
1075
|
+
const navigationScript = options.navigationScripts?.get(matched.route.path);
|
|
1076
|
+
const navigationRequest = isNavigationRequest(options.request);
|
|
1077
|
+
const cacheKey = routeCacheKey(
|
|
1078
|
+
options.appDir,
|
|
1079
|
+
matched.route.path,
|
|
1080
|
+
url,
|
|
1081
|
+
navigationRequest ? "navigation" : "document",
|
|
1082
|
+
);
|
|
1083
|
+
const mayUseRouteCache =
|
|
1084
|
+
!sourceUsesRequestInput &&
|
|
1085
|
+
(cachePolicy === undefined
|
|
1086
|
+
? originalAnalysis.usesRuntimeCacheControl
|
|
1087
|
+
: cachePolicy.revalidateSeconds !== 0);
|
|
1088
|
+
const reloadRouteCache = isNavigationRouteCacheReloadRequest(options.request);
|
|
1089
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1090
|
+
const cachedResponse =
|
|
1091
|
+
!mayUseRouteCache || reloadRouteCache
|
|
1092
|
+
? undefined
|
|
1093
|
+
: await cachedRouteResponse({
|
|
1094
|
+
cache: options.routeCache,
|
|
1095
|
+
key: cacheKey,
|
|
1096
|
+
request: options.request,
|
|
1097
|
+
});
|
|
1098
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeCacheMs");
|
|
1099
|
+
|
|
1100
|
+
if (cachedResponse !== undefined) {
|
|
1101
|
+
return cachedResponse;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Only a route whose result may be stored pays for header-read tracking;
|
|
1105
|
+
// cache hits returned above never construct it. Application code reads
|
|
1106
|
+
// headers through this request so that a render depending on one is never
|
|
1107
|
+
// stored under a key that ignores it. Callers that store by path alone ask
|
|
1108
|
+
// for tracking through `renderSignals` even when the route itself declares
|
|
1109
|
+
// no cache policy.
|
|
1110
|
+
trackedRequest =
|
|
1111
|
+
mayUseRouteCache || options.renderSignals !== undefined
|
|
1112
|
+
? trackRequestHeaderReads(options.request)
|
|
1113
|
+
: undefined;
|
|
1114
|
+
const appRequest = trackedRequest?.request ?? options.request;
|
|
1115
|
+
|
|
1116
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1117
|
+
const preparedActions = await prepareRouteServerActions({
|
|
1118
|
+
appDir: options.appDir,
|
|
1119
|
+
code: originalCode,
|
|
1120
|
+
formActionReferences: options.serverActionReferencesByFile?.get(matched.route.file),
|
|
1121
|
+
placeholders: true,
|
|
1122
|
+
pageFile: matched.route.file,
|
|
1123
|
+
request: options.request,
|
|
1124
|
+
});
|
|
1125
|
+
for (const diagnostic of preparedActions.diagnostics ?? []) {
|
|
1126
|
+
console.warn(`${diagnostic.code}: ${diagnostic.message}`);
|
|
1127
|
+
}
|
|
1128
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "serverActionsMs");
|
|
1129
|
+
const code = preparedActions.code;
|
|
1130
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1131
|
+
const routeAnalysis =
|
|
1132
|
+
code === originalCode
|
|
1133
|
+
? originalAnalysis
|
|
1134
|
+
: await analyzeRouteSource({
|
|
1135
|
+
appDir: options.appDir,
|
|
1136
|
+
buildRenderCode: preparedActions.htmlReplacements !== undefined,
|
|
1137
|
+
code,
|
|
1138
|
+
filename: matched.route.file,
|
|
1139
|
+
routePath: matched.route.path,
|
|
1140
|
+
clientRouteInferenceCache,
|
|
1141
|
+
serverModuleCacheVersion: undefined,
|
|
1142
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1143
|
+
define: options.define,
|
|
1144
|
+
vitePlugins: options.vitePlugins,
|
|
1145
|
+
});
|
|
1146
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "routeCodeAnalysisMs");
|
|
1147
|
+
const routeCode = routeAnalysis.routeCode;
|
|
1148
|
+
const streamRoute = routeAnalysis.streamRoute;
|
|
1149
|
+
const clientInference = routeAnalysis.clientInference;
|
|
1150
|
+
const clientRoute = clientInference.client;
|
|
1151
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1152
|
+
const dataPromise = routeAnalysis.hasLoader
|
|
1153
|
+
? loadRouteDataWithInstrumentation({
|
|
1154
|
+
appDir: options.appDir,
|
|
1155
|
+
code: originalCode,
|
|
1156
|
+
context: withTrackedRequest(
|
|
1157
|
+
{
|
|
1158
|
+
env: options.env,
|
|
1159
|
+
params: matched.params,
|
|
1160
|
+
queryClient,
|
|
1161
|
+
},
|
|
1162
|
+
appRequest,
|
|
1163
|
+
trackedRequest,
|
|
1164
|
+
),
|
|
1165
|
+
filename: matched.route.file,
|
|
1166
|
+
importPolicy: options.importPolicy,
|
|
1167
|
+
instrumentation: options.instrumentation,
|
|
1168
|
+
devServerModuleCacheVersion: options.devServerModuleCacheVersion,
|
|
1169
|
+
request: options.request,
|
|
1170
|
+
routeId: routeIdForPath(matched.route.path),
|
|
1171
|
+
routePath: matched.route.path,
|
|
1172
|
+
serverModules: options.serverModules,
|
|
1173
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1174
|
+
define: options.define,
|
|
1175
|
+
vitePlugins: options.vitePlugins,
|
|
1176
|
+
timing,
|
|
1177
|
+
})
|
|
1178
|
+
: undefined;
|
|
1179
|
+
// Route analysis and loading-boundary discovery can continue before the loader is awaited.
|
|
1180
|
+
// Observe an early rejection immediately so strict unhandled-rejection runtimes do not
|
|
1181
|
+
// terminate before the request-level control/error handler receives the same promise.
|
|
1182
|
+
void dataPromise?.catch(() => {});
|
|
1183
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loaderStartMs");
|
|
1184
|
+
recoveryRoute = {
|
|
1185
|
+
clientRoute,
|
|
1186
|
+
props: {
|
|
1187
|
+
params: matched.params,
|
|
1188
|
+
request: { url: url.pathname },
|
|
1189
|
+
},
|
|
1190
|
+
routePath: matched.route.path,
|
|
1191
|
+
script: clientScript,
|
|
1192
|
+
};
|
|
1193
|
+
if (streamRoute) {
|
|
1194
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1195
|
+
const loadingFile = await nearestExistingBoundaryFileForPage({
|
|
1196
|
+
appDir: options.appDir,
|
|
1197
|
+
filename: "loading.mreact.tsx",
|
|
1198
|
+
pageFile: matched.route.file,
|
|
1199
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1200
|
+
});
|
|
1201
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loadingBoundaryLookupMs");
|
|
1202
|
+
const streamShellResponseHeaders = {
|
|
1203
|
+
"content-type": "text/html; charset=utf-8",
|
|
1204
|
+
"x-mreact-stream": "1",
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1208
|
+
const mayRenderOutOfOrder = await mayRenderOutOfOrderBoundaryDeep({
|
|
1209
|
+
code: routeCode,
|
|
1210
|
+
filename: matched.route.file,
|
|
1211
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1212
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1213
|
+
});
|
|
1214
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "outOfOrderAnalysisMs");
|
|
1215
|
+
|
|
1216
|
+
if (loadingFile === undefined && !mayRenderOutOfOrder) {
|
|
1217
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1218
|
+
let data: unknown;
|
|
1219
|
+
try {
|
|
1220
|
+
data = dataPromise === undefined ? undefined : await dataPromise;
|
|
1221
|
+
} finally {
|
|
1222
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
|
|
1223
|
+
}
|
|
1224
|
+
if (data instanceof Response) {
|
|
1225
|
+
emitRenderTiming(options, timing, data.status);
|
|
1226
|
+
return data;
|
|
1227
|
+
}
|
|
1228
|
+
await waitForRenderPreload(options, timing);
|
|
1229
|
+
await loadServerRenderArtifacts(options, matched.route.file, timing);
|
|
1230
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1231
|
+
const stringOutput = transformServerModule({
|
|
1232
|
+
code: routeCode,
|
|
1233
|
+
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
1234
|
+
clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
|
|
1235
|
+
filename: matched.route.file,
|
|
1236
|
+
serverModules: options.serverModules,
|
|
1237
|
+
serverOutput: "string",
|
|
1238
|
+
});
|
|
1239
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "stringTransformMs");
|
|
1240
|
+
const stringFatalDiagnostics = fatalServerDiagnostics(stringOutput.diagnostics);
|
|
1241
|
+
|
|
1242
|
+
if (stringFatalDiagnostics.length > 0) {
|
|
1243
|
+
return new Response(
|
|
1244
|
+
formatServerDiagnostics(matched.route.file, stringFatalDiagnostics),
|
|
1245
|
+
{
|
|
1246
|
+
status: 500,
|
|
1247
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
1248
|
+
},
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const renderedPage = await runWithQueryClient(queryClient, () =>
|
|
1253
|
+
runServerModuleWithSlots(
|
|
1254
|
+
stringOutput.code,
|
|
1255
|
+
withTrackedRequest(
|
|
1256
|
+
{
|
|
1257
|
+
data,
|
|
1258
|
+
params: matched.params,
|
|
1259
|
+
queryClient,
|
|
1260
|
+
},
|
|
1261
|
+
appRequest,
|
|
1262
|
+
trackedRequest,
|
|
1263
|
+
),
|
|
1264
|
+
matched.route.file,
|
|
1265
|
+
options.serverModules,
|
|
1266
|
+
options.serverModuleCacheVersion,
|
|
1267
|
+
options.define,
|
|
1268
|
+
options.vitePlugins,
|
|
1269
|
+
undefined,
|
|
1270
|
+
options.importPolicy,
|
|
1271
|
+
options.devServerModuleCacheVersion,
|
|
1272
|
+
),
|
|
1273
|
+
);
|
|
1274
|
+
const pageHtml = renderedPage.html;
|
|
1275
|
+
const pageHtmlForLayout = clientRoute
|
|
1276
|
+
? withHydrationMarkers({
|
|
1277
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
1278
|
+
clientReferenceManifest: stringOutput.metadata.clientReferenceManifest,
|
|
1279
|
+
html: pageHtml,
|
|
1280
|
+
routePath: matched.route.path,
|
|
1281
|
+
script: clientScript,
|
|
1282
|
+
props: {
|
|
1283
|
+
params: matched.params,
|
|
1284
|
+
request: { url: url.pathname },
|
|
1285
|
+
data,
|
|
1286
|
+
},
|
|
1287
|
+
})
|
|
1288
|
+
: withServerOnlyRouteMarkers(
|
|
1289
|
+
pageHtml,
|
|
1290
|
+
matched.route.path,
|
|
1291
|
+
options.request,
|
|
1292
|
+
options.prerenderVariantCapture === true,
|
|
1293
|
+
);
|
|
1294
|
+
let html = await runWithQueryClient(queryClient, () =>
|
|
1295
|
+
applyLayouts({
|
|
1296
|
+
appDir: options.appDir,
|
|
1297
|
+
pageFile: matched.route.file,
|
|
1298
|
+
html: pageHtmlForLayout,
|
|
1299
|
+
props: withTrackedRequest(
|
|
1300
|
+
{
|
|
1301
|
+
data,
|
|
1302
|
+
params: matched.params,
|
|
1303
|
+
queryClient,
|
|
1304
|
+
},
|
|
1305
|
+
appRequest,
|
|
1306
|
+
trackedRequest,
|
|
1307
|
+
),
|
|
1308
|
+
slots: renderedPage.slots,
|
|
1309
|
+
serverModules: options.serverModules,
|
|
1310
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1311
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1312
|
+
clientRouteInferenceCache,
|
|
1313
|
+
timing,
|
|
1314
|
+
vitePlugins: options.vitePlugins,
|
|
1315
|
+
importPolicy: options.importPolicy,
|
|
1316
|
+
}),
|
|
1317
|
+
);
|
|
1318
|
+
const metadata = await loadComposedRouteMetadata({
|
|
1319
|
+
appDir: options.appDir,
|
|
1320
|
+
code: originalCode,
|
|
1321
|
+
context: withTrackedRequest(
|
|
1322
|
+
{
|
|
1323
|
+
data,
|
|
1324
|
+
params: matched.params,
|
|
1325
|
+
},
|
|
1326
|
+
appRequest,
|
|
1327
|
+
trackedRequest,
|
|
1328
|
+
),
|
|
1329
|
+
filename: matched.route.file,
|
|
1330
|
+
importPolicy: options.importPolicy,
|
|
1331
|
+
routes,
|
|
1332
|
+
serverModules: options.serverModules,
|
|
1333
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1334
|
+
devServerModuleCacheVersion: options.devServerModuleCacheVersion,
|
|
1335
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1336
|
+
define: options.define,
|
|
1337
|
+
vitePlugins: options.vitePlugins,
|
|
1338
|
+
});
|
|
1339
|
+
html = injectHeadMetadata(html, metadata);
|
|
1340
|
+
warnIfCspNonceWouldBlockInlineTags({
|
|
1341
|
+
html,
|
|
1342
|
+
logger: options.logger,
|
|
1343
|
+
metadata,
|
|
1344
|
+
request: options.request,
|
|
1345
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1346
|
+
});
|
|
1347
|
+
html = injectAuthSessionClaims(
|
|
1348
|
+
html,
|
|
1349
|
+
originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined,
|
|
1350
|
+
);
|
|
1351
|
+
html = injectQueryState(html, dehydrate(queryClient, options.dehydrateOptions));
|
|
1352
|
+
const response = withOptionalActionCookie(
|
|
1353
|
+
htmlResponse(
|
|
1354
|
+
applyActionHtmlReplacements(
|
|
1355
|
+
`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
1356
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
1357
|
+
currentStyleSheets: clientStyleSheets,
|
|
1358
|
+
currentScript: clientRoute ? clientScript : undefined,
|
|
1359
|
+
currentNavigationScript: navigationScript,
|
|
1360
|
+
routeScripts: options.clientScripts,
|
|
1361
|
+
})}${html}`,
|
|
1362
|
+
preparedActions.htmlReplacements,
|
|
1363
|
+
),
|
|
1364
|
+
{
|
|
1365
|
+
headers: responseHeadersForMetadata(metadata, options.request, {
|
|
1366
|
+
"x-mreact-stream": "1",
|
|
1367
|
+
}),
|
|
1368
|
+
},
|
|
1369
|
+
),
|
|
1370
|
+
preparedActions.csrfToken,
|
|
1371
|
+
preparedActions.csrfTokenIsNew === true,
|
|
1372
|
+
);
|
|
1373
|
+
appendNavigationVary(response.headers);
|
|
1374
|
+
emitRenderTiming(options, timing, response.status);
|
|
1375
|
+
return response;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
let streamData: unknown;
|
|
1379
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1380
|
+
try {
|
|
1381
|
+
streamData = dataPromise === undefined ? undefined : await dataPromise;
|
|
1382
|
+
} finally {
|
|
1383
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
|
|
1384
|
+
}
|
|
1385
|
+
if (streamData instanceof Response) {
|
|
1386
|
+
emitRenderTiming(options, timing, streamData.status);
|
|
1387
|
+
return streamData;
|
|
1388
|
+
}
|
|
1389
|
+
const streamDataPromise = Promise.resolve(streamData);
|
|
1390
|
+
|
|
1391
|
+
const streamDataSettlement =
|
|
1392
|
+
loadingFile === undefined ? undefined : observePromiseSettlement(streamDataPromise);
|
|
1393
|
+
|
|
1394
|
+
await waitForRenderPreload(options, timing);
|
|
1395
|
+
await loadServerRenderArtifacts(options, matched.route.file, timing);
|
|
1396
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1397
|
+
const output = transformServerModule({
|
|
1398
|
+
code: routeCode,
|
|
1399
|
+
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
1400
|
+
clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
|
|
1401
|
+
filename: matched.route.file,
|
|
1402
|
+
serverModules: options.serverModules,
|
|
1403
|
+
serverOutput: "stream",
|
|
1404
|
+
serverAwaitHydration: clientRoute,
|
|
1405
|
+
});
|
|
1406
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamTransformMs");
|
|
1407
|
+
const fatalDiagnostics = fatalServerDiagnostics(output.diagnostics);
|
|
1408
|
+
|
|
1409
|
+
if (fatalDiagnostics.length > 0) {
|
|
1410
|
+
return new Response(formatServerDiagnostics(matched.route.file, fatalDiagnostics), {
|
|
1411
|
+
status: 500,
|
|
1412
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (loadingFile !== undefined && streamDataSettlement !== undefined) {
|
|
1417
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1418
|
+
const errorFile = await nearestExistingBoundaryFileForPage({
|
|
1419
|
+
appDir: options.appDir,
|
|
1420
|
+
filename: "error.mreact.tsx",
|
|
1421
|
+
pageFile: matched.route.file,
|
|
1422
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1423
|
+
});
|
|
1424
|
+
const streamResult = await runServerStreamModuleWithLoading(output.code, {
|
|
1425
|
+
appDir: options.appDir,
|
|
1426
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
1427
|
+
clientRoute,
|
|
1428
|
+
data: streamDataPromise,
|
|
1429
|
+
dataSettlement: streamDataSettlement,
|
|
1430
|
+
define: options.define,
|
|
1431
|
+
errorFile,
|
|
1432
|
+
loadingFile,
|
|
1433
|
+
markerRequest: options.request,
|
|
1434
|
+
onRenderError: options.onRenderError,
|
|
1435
|
+
prerenderVariantCapture: options.prerenderVariantCapture,
|
|
1436
|
+
pageFile: matched.route.file,
|
|
1437
|
+
params: matched.params,
|
|
1438
|
+
queryClient,
|
|
1439
|
+
request: appRequest,
|
|
1440
|
+
requestUrl: url.pathname,
|
|
1441
|
+
trackedRequest,
|
|
1442
|
+
routePath: matched.route.path,
|
|
1443
|
+
routeScripts: options.clientScripts,
|
|
1444
|
+
serverModules: options.serverModules,
|
|
1445
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1446
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1447
|
+
vitePlugins: options.vitePlugins,
|
|
1448
|
+
clientRouteInferenceCache,
|
|
1449
|
+
importPolicy: options.importPolicy,
|
|
1450
|
+
script: clientScript,
|
|
1451
|
+
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1452
|
+
});
|
|
1453
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
1454
|
+
|
|
1455
|
+
if (streamResult instanceof Response) {
|
|
1456
|
+
emitRenderTiming(options, timing, streamResult.status);
|
|
1457
|
+
return streamResult;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
const response = withOptionalActionCookie(
|
|
1461
|
+
new Response(streamResult, {
|
|
1462
|
+
headers: streamShellResponseHeaders,
|
|
1463
|
+
}),
|
|
1464
|
+
preparedActions.csrfToken,
|
|
1465
|
+
preparedActions.csrfTokenIsNew === true,
|
|
1466
|
+
);
|
|
1467
|
+
appendNavigationVary(response.headers);
|
|
1468
|
+
emitRenderTiming(options, timing, response.status);
|
|
1469
|
+
return response;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
const data = streamData;
|
|
1473
|
+
const props = withTrackedRequest(
|
|
1474
|
+
{
|
|
1475
|
+
data,
|
|
1476
|
+
params: matched.params,
|
|
1477
|
+
queryClient,
|
|
1478
|
+
},
|
|
1479
|
+
appRequest,
|
|
1480
|
+
trackedRequest,
|
|
1481
|
+
);
|
|
1482
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1483
|
+
const stream = runServerStreamModule(output.code, {
|
|
1484
|
+
appDir: options.appDir,
|
|
1485
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
1486
|
+
pageFile: matched.route.file,
|
|
1487
|
+
markerRequest: options.request,
|
|
1488
|
+
prerenderVariantCapture: options.prerenderVariantCapture,
|
|
1489
|
+
props,
|
|
1490
|
+
requestUrl: url.pathname,
|
|
1491
|
+
routePath: matched.route.path,
|
|
1492
|
+
routeScripts: options.clientScripts,
|
|
1493
|
+
serverModules: options.serverModules,
|
|
1494
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1495
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1496
|
+
define: options.define,
|
|
1497
|
+
vitePlugins: options.vitePlugins,
|
|
1498
|
+
clientRouteInferenceCache,
|
|
1499
|
+
importPolicy: options.importPolicy,
|
|
1500
|
+
clientRoute,
|
|
1501
|
+
script: clientScript,
|
|
1502
|
+
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1503
|
+
});
|
|
1504
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
1505
|
+
|
|
1506
|
+
const response = withOptionalActionCookie(
|
|
1507
|
+
new Response(stream, {
|
|
1508
|
+
headers: streamShellResponseHeaders,
|
|
1509
|
+
}),
|
|
1510
|
+
preparedActions.csrfToken,
|
|
1511
|
+
preparedActions.csrfTokenIsNew === true,
|
|
1512
|
+
);
|
|
1513
|
+
appendNavigationVary(response.headers);
|
|
1514
|
+
emitRenderTiming(options, timing, response.status);
|
|
1515
|
+
return response;
|
|
1516
|
+
}
|
|
1206
1517
|
|
|
1207
|
-
if (loadingFile === undefined && !mayRenderOutOfOrder) {
|
|
1208
1518
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1209
1519
|
let data: unknown;
|
|
1210
1520
|
try {
|
|
@@ -1216,10 +1526,16 @@ async function renderAppRequestInternal(
|
|
|
1216
1526
|
emitRenderTiming(options, timing, data.status);
|
|
1217
1527
|
return data;
|
|
1218
1528
|
}
|
|
1529
|
+
sourceUsesRequestInput ||= await routeShellsMayUseRequestInput({
|
|
1530
|
+
appDir: options.appDir,
|
|
1531
|
+
pageFile: matched.route.file,
|
|
1532
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1533
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1534
|
+
});
|
|
1219
1535
|
await waitForRenderPreload(options, timing);
|
|
1220
1536
|
await loadServerRenderArtifacts(options, matched.route.file, timing);
|
|
1221
1537
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1222
|
-
const
|
|
1538
|
+
const output = transformServerModule({
|
|
1223
1539
|
code: routeCode,
|
|
1224
1540
|
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
1225
1541
|
clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
|
|
@@ -1228,63 +1544,79 @@ async function renderAppRequestInternal(
|
|
|
1228
1544
|
serverOutput: "string",
|
|
1229
1545
|
});
|
|
1230
1546
|
finishRenderTimingPhase(timing, phaseStartedAt, "stringTransformMs");
|
|
1231
|
-
const
|
|
1547
|
+
const fatalDiagnostics = fatalServerDiagnostics(output.diagnostics);
|
|
1232
1548
|
|
|
1233
|
-
if (
|
|
1234
|
-
return new Response(formatServerDiagnostics(matched.route.file,
|
|
1549
|
+
if (fatalDiagnostics.length > 0) {
|
|
1550
|
+
return new Response(formatServerDiagnostics(matched.route.file, fatalDiagnostics), {
|
|
1235
1551
|
status: 500,
|
|
1236
1552
|
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
1237
1553
|
});
|
|
1238
1554
|
}
|
|
1239
1555
|
|
|
1556
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1240
1557
|
const renderedPage = await runWithQueryClient(queryClient, () =>
|
|
1241
1558
|
runServerModuleWithSlots(
|
|
1242
|
-
|
|
1243
|
-
withTrackedRequest(
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1559
|
+
output.code,
|
|
1560
|
+
withTrackedRequest(
|
|
1561
|
+
{
|
|
1562
|
+
data,
|
|
1563
|
+
params: matched.params,
|
|
1564
|
+
queryClient,
|
|
1565
|
+
},
|
|
1566
|
+
appRequest,
|
|
1567
|
+
trackedRequest,
|
|
1568
|
+
),
|
|
1248
1569
|
matched.route.file,
|
|
1249
1570
|
options.serverModules,
|
|
1250
1571
|
options.serverModuleCacheVersion,
|
|
1251
1572
|
options.define,
|
|
1252
1573
|
options.vitePlugins,
|
|
1253
|
-
|
|
1574
|
+
timing,
|
|
1254
1575
|
options.importPolicy,
|
|
1255
1576
|
options.devServerModuleCacheVersion,
|
|
1256
1577
|
),
|
|
1257
1578
|
);
|
|
1579
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "pageRenderMs");
|
|
1258
1580
|
const pageHtml = renderedPage.html;
|
|
1581
|
+
// Wrap the page (not the full document) with the hydration marker so
|
|
1582
|
+
// the marker sits inside <body>, not around <html>. Wrapping <html>
|
|
1583
|
+
// forces the browser HTML parser to strip the wrappers and promote
|
|
1584
|
+
// <head> / <body> children up to the marker, which flattens the
|
|
1585
|
+
// layout into the marker and breaks the hydration target lookup.
|
|
1259
1586
|
const pageHtmlForLayout = clientRoute
|
|
1260
1587
|
? withHydrationMarkers({
|
|
1261
1588
|
assetBaseUrl: options.assetBaseUrl,
|
|
1262
|
-
clientReferenceManifest:
|
|
1589
|
+
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1263
1590
|
html: pageHtml,
|
|
1264
1591
|
routePath: matched.route.path,
|
|
1265
1592
|
script: clientScript,
|
|
1266
1593
|
props: {
|
|
1267
1594
|
params: matched.params,
|
|
1268
|
-
request: { url:
|
|
1595
|
+
request: { url: url.pathname },
|
|
1269
1596
|
data,
|
|
1270
1597
|
},
|
|
1271
1598
|
})
|
|
1272
|
-
:
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1599
|
+
: withServerOnlyRouteMarkers(
|
|
1600
|
+
pageHtml,
|
|
1601
|
+
matched.route.path,
|
|
1602
|
+
options.request,
|
|
1603
|
+
options.prerenderVariantCapture === true,
|
|
1604
|
+
);
|
|
1605
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1278
1606
|
let html = await runWithQueryClient(queryClient, () =>
|
|
1279
1607
|
applyLayouts({
|
|
1280
1608
|
appDir: options.appDir,
|
|
1281
1609
|
pageFile: matched.route.file,
|
|
1282
1610
|
html: pageHtmlForLayout,
|
|
1283
|
-
props: withTrackedRequest(
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1611
|
+
props: withTrackedRequest(
|
|
1612
|
+
{
|
|
1613
|
+
data,
|
|
1614
|
+
params: matched.params,
|
|
1615
|
+
queryClient,
|
|
1616
|
+
},
|
|
1617
|
+
appRequest,
|
|
1618
|
+
trackedRequest,
|
|
1619
|
+
),
|
|
1288
1620
|
slots: renderedPage.slots,
|
|
1289
1621
|
serverModules: options.serverModules,
|
|
1290
1622
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
@@ -1292,16 +1624,21 @@ async function renderAppRequestInternal(
|
|
|
1292
1624
|
clientRouteInferenceCache,
|
|
1293
1625
|
timing,
|
|
1294
1626
|
vitePlugins: options.vitePlugins,
|
|
1295
|
-
importPolicy: options.importPolicy,
|
|
1296
1627
|
}),
|
|
1297
1628
|
);
|
|
1629
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "layoutRenderMs");
|
|
1630
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1298
1631
|
const metadata = await loadComposedRouteMetadata({
|
|
1299
1632
|
appDir: options.appDir,
|
|
1300
1633
|
code: originalCode,
|
|
1301
|
-
context: withTrackedRequest(
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1634
|
+
context: withTrackedRequest(
|
|
1635
|
+
{
|
|
1636
|
+
data,
|
|
1637
|
+
params: matched.params,
|
|
1638
|
+
},
|
|
1639
|
+
appRequest,
|
|
1640
|
+
trackedRequest,
|
|
1641
|
+
),
|
|
1305
1642
|
filename: matched.route.file,
|
|
1306
1643
|
importPolicy: options.importPolicy,
|
|
1307
1644
|
routes,
|
|
@@ -1312,6 +1649,8 @@ async function renderAppRequestInternal(
|
|
|
1312
1649
|
define: options.define,
|
|
1313
1650
|
vitePlugins: options.vitePlugins,
|
|
1314
1651
|
});
|
|
1652
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "metadataMs");
|
|
1653
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1315
1654
|
html = injectHeadMetadata(html, metadata);
|
|
1316
1655
|
warnIfCspNonceWouldBlockInlineTags({
|
|
1317
1656
|
html,
|
|
@@ -1324,7 +1663,8 @@ async function renderAppRequestInternal(
|
|
|
1324
1663
|
html,
|
|
1325
1664
|
originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined,
|
|
1326
1665
|
);
|
|
1327
|
-
html = injectQueryState(html, dehydrate(queryClient));
|
|
1666
|
+
html = injectQueryState(html, dehydrate(queryClient, options.dehydrateOptions));
|
|
1667
|
+
|
|
1328
1668
|
const response = withOptionalActionCookie(
|
|
1329
1669
|
htmlResponse(
|
|
1330
1670
|
applyActionHtmlReplacements(
|
|
@@ -1338,408 +1678,122 @@ async function renderAppRequestInternal(
|
|
|
1338
1678
|
preparedActions.htmlReplacements,
|
|
1339
1679
|
),
|
|
1340
1680
|
{
|
|
1341
|
-
headers: responseHeadersForMetadata(metadata, options.request,
|
|
1342
|
-
"x-mreact-stream": "1",
|
|
1343
|
-
}),
|
|
1681
|
+
headers: responseHeadersForMetadata(metadata, options.request),
|
|
1344
1682
|
},
|
|
1345
1683
|
),
|
|
1346
1684
|
preparedActions.csrfToken,
|
|
1347
1685
|
preparedActions.csrfTokenIsNew === true,
|
|
1348
1686
|
);
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1687
|
+
appendNavigationVary(response.headers);
|
|
1688
|
+
|
|
1689
|
+
const effectiveCachePolicy = cachePolicy ?? activeRouteCacheContext()?.cachePolicy;
|
|
1690
|
+
const configuredHsts = configuredHstsHeader(metadata?.security);
|
|
1691
|
+
renderedStrictTransportSecurity = configuredHsts;
|
|
1692
|
+
invalidConfiguredHsts = hasInvalidConfiguredHsts(metadata?.security);
|
|
1693
|
+
|
|
1694
|
+
const finalResponse = preparedActions.hasFormActions
|
|
1695
|
+
? withRouteCacheHeader(response, effectiveCachePolicy)
|
|
1696
|
+
: await cacheRouteResponse({
|
|
1697
|
+
key: cacheKey,
|
|
1698
|
+
cache: options.routeCache,
|
|
1699
|
+
// A policy can still arrive from a runtime cacheControl() call made
|
|
1700
|
+
// outside the page source, which is what `mayUseRouteCache` scans.
|
|
1701
|
+
// Without a tracker there is no evidence the render ignored request
|
|
1702
|
+
// headers, so the entry is treated as header dependent.
|
|
1703
|
+
headerDependent:
|
|
1704
|
+
sourceUsesRequestInput ||
|
|
1705
|
+
invalidConfiguredHsts ||
|
|
1706
|
+
(trackedRequest?.requestDependent() ?? true),
|
|
1707
|
+
...(configuredHsts === undefined ? {} : { strictTransportSecurity: configuredHsts }),
|
|
1708
|
+
path: matched.route.path,
|
|
1709
|
+
policy: effectiveCachePolicy,
|
|
1710
|
+
request: options.request,
|
|
1711
|
+
response,
|
|
1712
|
+
});
|
|
1713
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "responseBuildMs");
|
|
1714
|
+
emitRenderTiming(options, timing, finalResponse.status);
|
|
1715
|
+
return finalResponse;
|
|
1716
|
+
} catch (error) {
|
|
1717
|
+
if (isRedirectError(error)) {
|
|
1718
|
+
const response = new Response(null, {
|
|
1719
|
+
headers: { location: error.location },
|
|
1720
|
+
status: error.status,
|
|
1721
|
+
});
|
|
1722
|
+
emitRenderTiming(options, timing, response.status);
|
|
1723
|
+
return response;
|
|
1381
1724
|
}
|
|
1382
|
-
}
|
|
1383
1725
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
|
|
1391
|
-
filename: matched.route.file,
|
|
1392
|
-
serverModules: options.serverModules,
|
|
1393
|
-
serverOutput: "stream",
|
|
1394
|
-
serverAwaitHydration: clientRoute,
|
|
1395
|
-
});
|
|
1396
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "streamTransformMs");
|
|
1397
|
-
const fatalDiagnostics = fatalServerDiagnostics(output.diagnostics);
|
|
1726
|
+
if (isNotFoundError(error)) {
|
|
1727
|
+
const notFoundFile = await nearestBoundaryFileForPage({
|
|
1728
|
+
appDir: options.appDir,
|
|
1729
|
+
filename: "not-found.mreact.tsx",
|
|
1730
|
+
pageFile: matched.route.file,
|
|
1731
|
+
});
|
|
1398
1732
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1733
|
+
const response = await renderSpecialRoute({
|
|
1734
|
+
appDir: options.appDir,
|
|
1735
|
+
assetBaseUrl: options.assetBaseUrl,
|
|
1736
|
+
currentStyleSheets: options.clientStylesByFile?.get(notFoundFile),
|
|
1737
|
+
error: undefined,
|
|
1738
|
+
request: options.request,
|
|
1739
|
+
routePath: matched.route.path,
|
|
1740
|
+
routeFile: notFoundFile,
|
|
1741
|
+
routeScripts: options.clientScripts,
|
|
1742
|
+
serverModules: options.serverModules,
|
|
1743
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1744
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1745
|
+
vitePlugins: options.vitePlugins,
|
|
1746
|
+
navigation: recoveryRoute,
|
|
1747
|
+
status: 404,
|
|
1748
|
+
textFallback: "Not Found",
|
|
1749
|
+
});
|
|
1750
|
+
emitRenderTiming(options, timing, response.status);
|
|
1751
|
+
return response;
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
const errorFile = await nearestBoundaryFileForPage({
|
|
1755
|
+
appDir: options.appDir,
|
|
1756
|
+
filename: "error.mreact.tsx",
|
|
1757
|
+
pageFile: matched.route.file,
|
|
1403
1758
|
});
|
|
1404
|
-
|
|
1759
|
+
options.onRenderError?.(error);
|
|
1405
1760
|
|
|
1406
|
-
|
|
1407
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1408
|
-
const stream = await runServerStreamModuleWithLoading(output.code, {
|
|
1761
|
+
const response = await renderSpecialRoute({
|
|
1409
1762
|
appDir: options.appDir,
|
|
1410
1763
|
assetBaseUrl: options.assetBaseUrl,
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
loadingFile,
|
|
1415
|
-
pageFile: matched.route.file,
|
|
1416
|
-
params: matched.params,
|
|
1417
|
-
queryClient,
|
|
1418
|
-
request: appRequest,
|
|
1419
|
-
trackedRequest,
|
|
1764
|
+
currentStyleSheets: options.clientStylesByFile?.get(errorFile),
|
|
1765
|
+
error,
|
|
1766
|
+
request: options.request,
|
|
1420
1767
|
routePath: matched.route.path,
|
|
1768
|
+
routeFile: errorFile,
|
|
1421
1769
|
routeScripts: options.clientScripts,
|
|
1422
1770
|
serverModules: options.serverModules,
|
|
1423
1771
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1424
1772
|
serverSourceFiles: options.serverSourceFiles,
|
|
1425
1773
|
vitePlugins: options.vitePlugins,
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1774
|
+
navigation: recoveryRoute,
|
|
1775
|
+
status: 500,
|
|
1776
|
+
textFallback: "Internal Server Error",
|
|
1430
1777
|
});
|
|
1431
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
1432
|
-
|
|
1433
|
-
const response = withOptionalActionCookie(
|
|
1434
|
-
new Response(stream, {
|
|
1435
|
-
headers: streamShellResponseHeaders,
|
|
1436
|
-
}),
|
|
1437
|
-
preparedActions.csrfToken,
|
|
1438
|
-
preparedActions.csrfTokenIsNew === true,
|
|
1439
|
-
);
|
|
1440
1778
|
emitRenderTiming(options, timing, response.status);
|
|
1441
1779
|
return response;
|
|
1442
|
-
}
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
const stream = runServerStreamModule(output.code, {
|
|
1452
|
-
appDir: options.appDir,
|
|
1453
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1454
|
-
pageFile: matched.route.file,
|
|
1455
|
-
props,
|
|
1456
|
-
routePath: matched.route.path,
|
|
1457
|
-
routeScripts: options.clientScripts,
|
|
1458
|
-
serverModules: options.serverModules,
|
|
1459
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1460
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1461
|
-
define: options.define,
|
|
1462
|
-
vitePlugins: options.vitePlugins,
|
|
1463
|
-
clientRouteInferenceCache,
|
|
1464
|
-
importPolicy: options.importPolicy,
|
|
1465
|
-
clientRoute,
|
|
1466
|
-
script: clientScript,
|
|
1467
|
-
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1468
|
-
});
|
|
1469
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
1470
|
-
|
|
1471
|
-
const response = withOptionalActionCookie(
|
|
1472
|
-
new Response(stream, {
|
|
1473
|
-
headers: streamShellResponseHeaders,
|
|
1474
|
-
}),
|
|
1475
|
-
preparedActions.csrfToken,
|
|
1476
|
-
preparedActions.csrfTokenIsNew === true,
|
|
1477
|
-
);
|
|
1478
|
-
emitRenderTiming(options, timing, response.status);
|
|
1479
|
-
return response;
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1483
|
-
let data: unknown;
|
|
1484
|
-
try {
|
|
1485
|
-
data = dataPromise === undefined ? undefined : await dataPromise;
|
|
1486
|
-
} finally {
|
|
1487
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
|
|
1488
|
-
}
|
|
1489
|
-
if (data instanceof Response) {
|
|
1490
|
-
emitRenderTiming(options, timing, data.status);
|
|
1491
|
-
return data;
|
|
1492
|
-
}
|
|
1493
|
-
sourceUsesRequestInput ||= await routeShellsMayUseRequestInput({
|
|
1494
|
-
appDir: options.appDir,
|
|
1495
|
-
pageFile: matched.route.file,
|
|
1496
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1497
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1498
|
-
});
|
|
1499
|
-
await waitForRenderPreload(options, timing);
|
|
1500
|
-
await loadServerRenderArtifacts(options, matched.route.file, timing);
|
|
1501
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1502
|
-
const output = transformServerModule({
|
|
1503
|
-
code: routeCode,
|
|
1504
|
-
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
1505
|
-
clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
|
|
1506
|
-
filename: matched.route.file,
|
|
1507
|
-
serverModules: options.serverModules,
|
|
1508
|
-
serverOutput: "string",
|
|
1509
|
-
});
|
|
1510
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "stringTransformMs");
|
|
1511
|
-
const fatalDiagnostics = fatalServerDiagnostics(output.diagnostics);
|
|
1512
|
-
|
|
1513
|
-
if (fatalDiagnostics.length > 0) {
|
|
1514
|
-
return new Response(formatServerDiagnostics(matched.route.file, fatalDiagnostics), {
|
|
1515
|
-
status: 500,
|
|
1516
|
-
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
1517
|
-
});
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1521
|
-
const renderedPage = await runWithQueryClient(queryClient, () =>
|
|
1522
|
-
runServerModuleWithSlots(
|
|
1523
|
-
output.code,
|
|
1524
|
-
withTrackedRequest({
|
|
1525
|
-
data,
|
|
1526
|
-
params: matched.params,
|
|
1527
|
-
queryClient,
|
|
1528
|
-
}, appRequest, trackedRequest),
|
|
1529
|
-
matched.route.file,
|
|
1530
|
-
options.serverModules,
|
|
1531
|
-
options.serverModuleCacheVersion,
|
|
1532
|
-
options.define,
|
|
1533
|
-
options.vitePlugins,
|
|
1534
|
-
timing,
|
|
1535
|
-
options.importPolicy,
|
|
1536
|
-
options.devServerModuleCacheVersion,
|
|
1537
|
-
),
|
|
1538
|
-
);
|
|
1539
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "pageRenderMs");
|
|
1540
|
-
const pageHtml = renderedPage.html;
|
|
1541
|
-
// Wrap the page (not the full document) with the hydration marker so
|
|
1542
|
-
// the marker sits inside <body>, not around <html>. Wrapping <html>
|
|
1543
|
-
// forces the browser HTML parser to strip the wrappers and promote
|
|
1544
|
-
// <head> / <body> children up to the marker, which flattens the
|
|
1545
|
-
// layout into the marker and breaks the hydration target lookup.
|
|
1546
|
-
const pageHtmlForLayout = clientRoute
|
|
1547
|
-
? withHydrationMarkers({
|
|
1548
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1549
|
-
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1550
|
-
html: pageHtml,
|
|
1551
|
-
routePath: matched.route.path,
|
|
1552
|
-
script: clientScript,
|
|
1553
|
-
props: {
|
|
1554
|
-
params: matched.params,
|
|
1555
|
-
request: { url: options.request.url },
|
|
1556
|
-
data,
|
|
1557
|
-
},
|
|
1558
|
-
})
|
|
1559
|
-
: isNavigationRequest(options.request)
|
|
1560
|
-
? withRouteMarkers({
|
|
1561
|
-
html: pageHtml,
|
|
1562
|
-
routePath: matched.route.path,
|
|
1563
|
-
})
|
|
1564
|
-
: pageHtml;
|
|
1565
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1566
|
-
let html = await runWithQueryClient(queryClient, () =>
|
|
1567
|
-
applyLayouts({
|
|
1568
|
-
appDir: options.appDir,
|
|
1569
|
-
pageFile: matched.route.file,
|
|
1570
|
-
html: pageHtmlForLayout,
|
|
1571
|
-
props: withTrackedRequest({
|
|
1572
|
-
data,
|
|
1573
|
-
params: matched.params,
|
|
1574
|
-
queryClient,
|
|
1575
|
-
}, appRequest, trackedRequest),
|
|
1576
|
-
slots: renderedPage.slots,
|
|
1577
|
-
serverModules: options.serverModules,
|
|
1578
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1579
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1580
|
-
clientRouteInferenceCache,
|
|
1581
|
-
timing,
|
|
1582
|
-
vitePlugins: options.vitePlugins,
|
|
1583
|
-
}),
|
|
1584
|
-
);
|
|
1585
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "layoutRenderMs");
|
|
1586
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1587
|
-
const metadata = await loadComposedRouteMetadata({
|
|
1588
|
-
appDir: options.appDir,
|
|
1589
|
-
code: originalCode,
|
|
1590
|
-
context: withTrackedRequest({
|
|
1591
|
-
data,
|
|
1592
|
-
params: matched.params,
|
|
1593
|
-
}, appRequest, trackedRequest),
|
|
1594
|
-
filename: matched.route.file,
|
|
1595
|
-
importPolicy: options.importPolicy,
|
|
1596
|
-
routes,
|
|
1597
|
-
serverModules: options.serverModules,
|
|
1598
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1599
|
-
devServerModuleCacheVersion: options.devServerModuleCacheVersion,
|
|
1600
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1601
|
-
define: options.define,
|
|
1602
|
-
vitePlugins: options.vitePlugins,
|
|
1603
|
-
});
|
|
1604
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "metadataMs");
|
|
1605
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1606
|
-
html = injectHeadMetadata(html, metadata);
|
|
1607
|
-
warnIfCspNonceWouldBlockInlineTags({
|
|
1608
|
-
html,
|
|
1609
|
-
logger: options.logger,
|
|
1610
|
-
metadata,
|
|
1611
|
-
request: options.request,
|
|
1612
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1613
|
-
});
|
|
1614
|
-
html = injectAuthSessionClaims(
|
|
1615
|
-
html,
|
|
1616
|
-
originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined,
|
|
1617
|
-
);
|
|
1618
|
-
html = injectQueryState(html, dehydrate(queryClient));
|
|
1619
|
-
|
|
1620
|
-
const response = withOptionalActionCookie(
|
|
1621
|
-
htmlResponse(
|
|
1622
|
-
applyActionHtmlReplacements(
|
|
1623
|
-
`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
1624
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1625
|
-
currentStyleSheets: clientStyleSheets,
|
|
1626
|
-
currentScript: clientRoute ? clientScript : undefined,
|
|
1627
|
-
currentNavigationScript: navigationScript,
|
|
1628
|
-
routeScripts: options.clientScripts,
|
|
1629
|
-
})}${html}`,
|
|
1630
|
-
preparedActions.htmlReplacements,
|
|
1631
|
-
),
|
|
1632
|
-
{
|
|
1633
|
-
headers: responseHeadersForMetadata(metadata, options.request),
|
|
1634
|
-
},
|
|
1635
|
-
),
|
|
1636
|
-
preparedActions.csrfToken,
|
|
1637
|
-
preparedActions.csrfTokenIsNew === true,
|
|
1638
|
-
);
|
|
1639
|
-
|
|
1640
|
-
const effectiveCachePolicy = cachePolicy ?? activeRouteCacheContext()?.cachePolicy;
|
|
1641
|
-
const configuredHsts = configuredHstsHeader(metadata?.security);
|
|
1642
|
-
invalidConfiguredHsts = hasInvalidConfiguredHsts(metadata?.security);
|
|
1643
|
-
|
|
1644
|
-
const finalResponse = preparedActions.hasFormActions
|
|
1645
|
-
? withRouteCacheHeader(response, effectiveCachePolicy)
|
|
1646
|
-
: await cacheRouteResponse({
|
|
1647
|
-
key: cacheKey,
|
|
1648
|
-
cache: options.routeCache,
|
|
1649
|
-
// A policy can still arrive from a runtime cacheControl() call made
|
|
1650
|
-
// outside the page source, which is what `mayUseRouteCache` scans.
|
|
1651
|
-
// Without a tracker there is no evidence the render ignored request
|
|
1652
|
-
// headers, so the entry is treated as header dependent.
|
|
1653
|
-
headerDependent:
|
|
1780
|
+
} finally {
|
|
1781
|
+
// Reported from every return path, including the streaming ones, so that a
|
|
1782
|
+
// caller storing by path alone never mistakes an unreported render for a
|
|
1783
|
+
// shareable one. The value stays lazy because a streamed render can still
|
|
1784
|
+
// read request headers while its deferred parts resolve; callers read it
|
|
1785
|
+
// after draining the body.
|
|
1786
|
+
if (options.renderSignals !== undefined) {
|
|
1787
|
+
const tracked = trackedRequest;
|
|
1788
|
+
options.renderSignals.headerDependent = () =>
|
|
1654
1789
|
sourceUsesRequestInput ||
|
|
1655
1790
|
invalidConfiguredHsts ||
|
|
1656
|
-
(
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
});
|
|
1663
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "responseBuildMs");
|
|
1664
|
-
emitRenderTiming(options, timing, finalResponse.status);
|
|
1665
|
-
return finalResponse;
|
|
1666
|
-
} catch (error) {
|
|
1667
|
-
if (isRedirectError(error)) {
|
|
1668
|
-
const response = new Response(null, {
|
|
1669
|
-
headers: { location: error.location },
|
|
1670
|
-
status: error.status,
|
|
1671
|
-
});
|
|
1672
|
-
emitRenderTiming(options, timing, response.status);
|
|
1673
|
-
return response;
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
if (isNotFoundError(error)) {
|
|
1677
|
-
const notFoundFile = await nearestBoundaryFileForPage({
|
|
1678
|
-
appDir: options.appDir,
|
|
1679
|
-
filename: "not-found.mreact.tsx",
|
|
1680
|
-
pageFile: matched.route.file,
|
|
1681
|
-
});
|
|
1682
|
-
|
|
1683
|
-
const response = await renderSpecialRoute({
|
|
1684
|
-
appDir: options.appDir,
|
|
1685
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1686
|
-
currentStyleSheets: options.clientStylesByFile?.get(notFoundFile),
|
|
1687
|
-
error: undefined,
|
|
1688
|
-
request: options.request,
|
|
1689
|
-
routePath: matched.route.path,
|
|
1690
|
-
routeFile: notFoundFile,
|
|
1691
|
-
routeScripts: options.clientScripts,
|
|
1692
|
-
serverModules: options.serverModules,
|
|
1693
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1694
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1695
|
-
vitePlugins: options.vitePlugins,
|
|
1696
|
-
navigation: recoveryRoute,
|
|
1697
|
-
status: 404,
|
|
1698
|
-
textFallback: "Not Found",
|
|
1699
|
-
});
|
|
1700
|
-
emitRenderTiming(options, timing, response.status);
|
|
1701
|
-
return response;
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
const errorFile = await nearestBoundaryFileForPage({
|
|
1705
|
-
appDir: options.appDir,
|
|
1706
|
-
filename: "error.mreact.tsx",
|
|
1707
|
-
pageFile: matched.route.file,
|
|
1708
|
-
});
|
|
1709
|
-
options.onRenderError?.(error);
|
|
1710
|
-
|
|
1711
|
-
const response = await renderSpecialRoute({
|
|
1712
|
-
appDir: options.appDir,
|
|
1713
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1714
|
-
currentStyleSheets: options.clientStylesByFile?.get(errorFile),
|
|
1715
|
-
error,
|
|
1716
|
-
request: options.request,
|
|
1717
|
-
routePath: matched.route.path,
|
|
1718
|
-
routeFile: errorFile,
|
|
1719
|
-
routeScripts: options.clientScripts,
|
|
1720
|
-
serverModules: options.serverModules,
|
|
1721
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1722
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1723
|
-
vitePlugins: options.vitePlugins,
|
|
1724
|
-
navigation: recoveryRoute,
|
|
1725
|
-
status: 500,
|
|
1726
|
-
textFallback: "Internal Server Error",
|
|
1727
|
-
});
|
|
1728
|
-
emitRenderTiming(options, timing, response.status);
|
|
1729
|
-
return response;
|
|
1730
|
-
} finally {
|
|
1731
|
-
// Reported from every return path, including the streaming ones, so that a
|
|
1732
|
-
// caller storing by path alone never mistakes an unreported render for a
|
|
1733
|
-
// shareable one. The value stays lazy because a streamed render can still
|
|
1734
|
-
// read request headers while its deferred parts resolve; callers read it
|
|
1735
|
-
// after draining the body.
|
|
1736
|
-
if (options.renderSignals !== undefined) {
|
|
1737
|
-
const tracked = trackedRequest;
|
|
1738
|
-
options.renderSignals.headerDependent = () =>
|
|
1739
|
-
sourceUsesRequestInput || invalidConfiguredHsts || (tracked?.requestDependent() ?? true);
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1742
|
-
})).value;
|
|
1791
|
+
(tracked?.requestDependent() ?? true);
|
|
1792
|
+
options.renderSignals.strictTransportSecurity = () => renderedStrictTransportSecurity;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
})
|
|
1796
|
+
).value;
|
|
1743
1797
|
}
|
|
1744
1798
|
|
|
1745
1799
|
async function applyAppRouterResponseHook(
|
|
@@ -1966,6 +2020,48 @@ function isNavigationRequest(request: Request): boolean {
|
|
|
1966
2020
|
return request.headers.get("x-mreact-navigation") === "1";
|
|
1967
2021
|
}
|
|
1968
2022
|
|
|
2023
|
+
function withServerOnlyRouteMarkers(
|
|
2024
|
+
html: string,
|
|
2025
|
+
routePath: string,
|
|
2026
|
+
request: Request,
|
|
2027
|
+
prerenderVariantCapture: boolean,
|
|
2028
|
+
): string {
|
|
2029
|
+
const marker = serverOnlyRouteMarkerParts(routePath, request, prerenderVariantCapture);
|
|
2030
|
+
return marker === undefined ? html : `${marker.prefix}${html}${marker.suffix}`;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
function serverOnlyRouteMarkerParts(
|
|
2034
|
+
routePath: string,
|
|
2035
|
+
request: Request,
|
|
2036
|
+
prerenderVariantCapture: boolean,
|
|
2037
|
+
): { prefix: string; suffix: string } | undefined {
|
|
2038
|
+
if (prerenderVariantCapture) {
|
|
2039
|
+
return prerenderVariantMarkerParts(routePath);
|
|
2040
|
+
}
|
|
2041
|
+
return isNavigationRequest(request) ? routeMarkerParts(routePath) : undefined;
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
export function prerenderVariantMarkerParts(routePath: string): {
|
|
2045
|
+
prefix: string;
|
|
2046
|
+
suffix: string;
|
|
2047
|
+
} {
|
|
2048
|
+
const routeId = routeIdForPath(routePath);
|
|
2049
|
+
return {
|
|
2050
|
+
prefix: `<!--mreact-route-variant-start:${routeId}-->`,
|
|
2051
|
+
suffix: `<!--mreact-route-variant-end:${routeId}-->`,
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
function appendNavigationVary(headers: Headers): void {
|
|
2056
|
+
const vary = headers.get("vary");
|
|
2057
|
+
if (
|
|
2058
|
+
vary?.split(",").some((value) => value.trim().toLowerCase() === "x-mreact-navigation") === true
|
|
2059
|
+
) {
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
headers.append("vary", "x-mreact-navigation");
|
|
2063
|
+
}
|
|
2064
|
+
|
|
1969
2065
|
function isNavigationRouteCacheReloadRequest(request: Request): boolean {
|
|
1970
2066
|
return (
|
|
1971
2067
|
isNavigationRequest(request) && request.headers.get("x-mreact-navigation-cache") === "reload"
|
|
@@ -2148,15 +2244,7 @@ async function renderSpecialRoute(options: {
|
|
|
2148
2244
|
}
|
|
2149
2245
|
|
|
2150
2246
|
const props = {
|
|
2151
|
-
|
|
2152
|
-
debug: errorDebugContext(options.error, options.routePath),
|
|
2153
|
-
error: normalizeErrorForProps(options.error),
|
|
2154
|
-
params: {},
|
|
2155
|
-
queryClient: createQueryClient(),
|
|
2156
|
-
request: options.request,
|
|
2157
|
-
requestId: requestIdForErrorContext(options.request),
|
|
2158
|
-
routeId: routeIdForPath(options.routePath ?? new URL(options.request.url).pathname),
|
|
2159
|
-
traceId: traceContextFromRequest(options.request)?.traceId,
|
|
2247
|
+
...errorBoundaryProps(options.error, options.request, options.routePath),
|
|
2160
2248
|
};
|
|
2161
2249
|
const pageHtml = await renderServerFileToHtml(
|
|
2162
2250
|
options.routeFile,
|
|
@@ -2205,6 +2293,20 @@ async function renderSpecialRoute(options: {
|
|
|
2205
2293
|
);
|
|
2206
2294
|
}
|
|
2207
2295
|
|
|
2296
|
+
function errorBoundaryProps(error: unknown, request: Request, routePath: string | undefined) {
|
|
2297
|
+
return {
|
|
2298
|
+
data: undefined,
|
|
2299
|
+
debug: errorDebugContext(error, routePath),
|
|
2300
|
+
error: normalizeErrorForProps(error),
|
|
2301
|
+
params: {},
|
|
2302
|
+
queryClient: createQueryClient(),
|
|
2303
|
+
request,
|
|
2304
|
+
requestId: requestIdForErrorContext(request),
|
|
2305
|
+
routeId: routeIdForPath(routePath ?? new URL(request.url).pathname),
|
|
2306
|
+
traceId: traceContextFromRequest(request)?.traceId,
|
|
2307
|
+
};
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2208
2310
|
async function renderServerFileToHtml(
|
|
2209
2311
|
file: string,
|
|
2210
2312
|
props: ServerComponentProps,
|
|
@@ -3399,8 +3501,11 @@ function runServerStreamModule(
|
|
|
3399
3501
|
appDir: string;
|
|
3400
3502
|
assetBaseUrl?: string | undefined;
|
|
3401
3503
|
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
3504
|
+
markerRequest: Request;
|
|
3505
|
+
prerenderVariantCapture?: boolean | undefined;
|
|
3402
3506
|
pageFile: string;
|
|
3403
3507
|
props: ServerComponentProps;
|
|
3508
|
+
requestUrl: string;
|
|
3404
3509
|
routePath: string;
|
|
3405
3510
|
routeScripts?: ReadonlyMap<string, string> | undefined;
|
|
3406
3511
|
clientRoute: boolean;
|
|
@@ -3445,11 +3550,15 @@ function runServerStreamModule(
|
|
|
3445
3550
|
script: options.script,
|
|
3446
3551
|
props: {
|
|
3447
3552
|
params: options.props.params,
|
|
3448
|
-
request: { url: options.
|
|
3553
|
+
request: { url: options.requestUrl },
|
|
3449
3554
|
data: options.props.data,
|
|
3450
3555
|
},
|
|
3451
3556
|
})
|
|
3452
|
-
:
|
|
3557
|
+
: serverOnlyRouteMarkerParts(
|
|
3558
|
+
options.routePath,
|
|
3559
|
+
options.markerRequest,
|
|
3560
|
+
options.prerenderVariantCapture === true,
|
|
3561
|
+
);
|
|
3453
3562
|
|
|
3454
3563
|
sink.append("<!DOCTYPE html>");
|
|
3455
3564
|
sink.append(
|
|
@@ -3665,12 +3774,18 @@ async function runServerStreamModuleWithLoading(
|
|
|
3665
3774
|
clientRoute: boolean;
|
|
3666
3775
|
clientReferenceManifest?: readonly ClientReferenceMetadata[] | undefined;
|
|
3667
3776
|
data: Promise<unknown>;
|
|
3777
|
+
dataSettlement: () => ObservedPromiseSettlement<unknown>;
|
|
3668
3778
|
define?: UserConfig["define"] | undefined;
|
|
3779
|
+
errorFile?: string | undefined;
|
|
3669
3780
|
loadingFile: string;
|
|
3781
|
+
markerRequest: Request;
|
|
3782
|
+
onRenderError?: ((error: unknown) => void) | undefined;
|
|
3783
|
+
prerenderVariantCapture?: boolean | undefined;
|
|
3670
3784
|
pageFile: string;
|
|
3671
3785
|
params: RouteParams;
|
|
3672
3786
|
queryClient: QueryClient;
|
|
3673
3787
|
request: Request;
|
|
3788
|
+
requestUrl: string;
|
|
3674
3789
|
trackedRequest?: TrackedHeaderRequest | undefined;
|
|
3675
3790
|
routePath: string;
|
|
3676
3791
|
routeScripts?: ReadonlyMap<string, string> | undefined;
|
|
@@ -3681,12 +3796,16 @@ async function runServerStreamModuleWithLoading(
|
|
|
3681
3796
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
3682
3797
|
importPolicy?: AppRouterImportPolicy | undefined;
|
|
3683
3798
|
},
|
|
3684
|
-
): Promise<ReadableStream<Uint8Array
|
|
3685
|
-
const loadingProps = withTrackedRequest(
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3799
|
+
): Promise<ReadableStream<Uint8Array> | Response> {
|
|
3800
|
+
const loadingProps = withTrackedRequest(
|
|
3801
|
+
{
|
|
3802
|
+
data: undefined,
|
|
3803
|
+
params: options.params,
|
|
3804
|
+
queryClient: options.queryClient,
|
|
3805
|
+
},
|
|
3806
|
+
options.request,
|
|
3807
|
+
options.trackedRequest,
|
|
3808
|
+
);
|
|
3690
3809
|
const layoutShells = await layoutShellsForPage(
|
|
3691
3810
|
options.appDir,
|
|
3692
3811
|
options.pageFile,
|
|
@@ -3717,10 +3836,24 @@ async function runServerStreamModuleWithLoading(
|
|
|
3717
3836
|
script: options.script,
|
|
3718
3837
|
props: {
|
|
3719
3838
|
params: options.params,
|
|
3720
|
-
request: { url: options.
|
|
3839
|
+
request: { url: options.requestUrl },
|
|
3721
3840
|
},
|
|
3722
3841
|
})
|
|
3723
|
-
:
|
|
3842
|
+
: serverOnlyRouteMarkerParts(
|
|
3843
|
+
options.routePath,
|
|
3844
|
+
options.markerRequest,
|
|
3845
|
+
options.prerenderVariantCapture === true,
|
|
3846
|
+
);
|
|
3847
|
+
|
|
3848
|
+
const dataSettlement = options.dataSettlement();
|
|
3849
|
+
|
|
3850
|
+
if (dataSettlement.status === "rejected") {
|
|
3851
|
+
throw dataSettlement.error;
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
if (dataSettlement.status === "fulfilled" && dataSettlement.value instanceof Response) {
|
|
3855
|
+
return dataSettlement.value;
|
|
3856
|
+
}
|
|
3724
3857
|
|
|
3725
3858
|
return renderToReadableStream((sink) => {
|
|
3726
3859
|
sink.append("<!DOCTYPE html>");
|
|
@@ -3738,7 +3871,7 @@ async function runServerStreamModuleWithLoading(
|
|
|
3738
3871
|
|
|
3739
3872
|
sink.append(marker?.prefix ?? "");
|
|
3740
3873
|
|
|
3741
|
-
|
|
3874
|
+
renderOutOfOrderBoundary(
|
|
3742
3875
|
sink,
|
|
3743
3876
|
"mreact-route",
|
|
3744
3877
|
options.data,
|
|
@@ -3746,11 +3879,15 @@ async function runServerStreamModuleWithLoading(
|
|
|
3746
3879
|
await appendServerStreamModule(
|
|
3747
3880
|
code,
|
|
3748
3881
|
boundarySink,
|
|
3749
|
-
withTrackedRequest(
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3882
|
+
withTrackedRequest(
|
|
3883
|
+
{
|
|
3884
|
+
data,
|
|
3885
|
+
params: options.params,
|
|
3886
|
+
queryClient: options.queryClient,
|
|
3887
|
+
},
|
|
3888
|
+
options.request,
|
|
3889
|
+
options.trackedRequest,
|
|
3890
|
+
),
|
|
3754
3891
|
options.pageFile,
|
|
3755
3892
|
options.serverModules,
|
|
3756
3893
|
options.serverModuleCacheVersion,
|
|
@@ -3760,6 +3897,34 @@ async function runServerStreamModuleWithLoading(
|
|
|
3760
3897
|
);
|
|
3761
3898
|
},
|
|
3762
3899
|
{
|
|
3900
|
+
async catch(boundarySink, error) {
|
|
3901
|
+
try {
|
|
3902
|
+
options.onRenderError?.(error);
|
|
3903
|
+
} catch {
|
|
3904
|
+
// Error reporting is best-effort and must not truncate a response that already flushed.
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
if (options.errorFile === undefined) {
|
|
3908
|
+
boundarySink.append("Internal Server Error");
|
|
3909
|
+
return;
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
try {
|
|
3913
|
+
boundarySink.append(
|
|
3914
|
+
await renderServerFileToHtml(
|
|
3915
|
+
options.errorFile,
|
|
3916
|
+
errorBoundaryProps(error, options.request, options.routePath),
|
|
3917
|
+
options.serverModules,
|
|
3918
|
+
options.serverModuleCacheVersion,
|
|
3919
|
+
options.serverSourceFiles,
|
|
3920
|
+
options.define,
|
|
3921
|
+
options.vitePlugins,
|
|
3922
|
+
),
|
|
3923
|
+
);
|
|
3924
|
+
} catch {
|
|
3925
|
+
boundarySink.append("Internal Server Error");
|
|
3926
|
+
}
|
|
3927
|
+
},
|
|
3763
3928
|
placeholder(boundarySink) {
|
|
3764
3929
|
boundarySink.append(loadingHtml);
|
|
3765
3930
|
},
|
|
@@ -3777,70 +3942,6 @@ async function runServerStreamModuleWithLoading(
|
|
|
3777
3942
|
});
|
|
3778
3943
|
}
|
|
3779
3944
|
|
|
3780
|
-
function renderVisibleOutOfOrderBoundary<T>(
|
|
3781
|
-
sink: HtmlSink,
|
|
3782
|
-
id: string,
|
|
3783
|
-
value: T,
|
|
3784
|
-
render: (sink: HtmlSink, value: Awaited<T>) => void | PromiseLike<void>,
|
|
3785
|
-
options: {
|
|
3786
|
-
catch?: (sink: HtmlSink, error: unknown) => void | PromiseLike<void>;
|
|
3787
|
-
placeholder?: (sink: HtmlSink) => void | PromiseLike<void>;
|
|
3788
|
-
placeholderTag?: string;
|
|
3789
|
-
} = {},
|
|
3790
|
-
): void {
|
|
3791
|
-
const placeholderSink = createStringSink();
|
|
3792
|
-
void options.placeholder?.(placeholderSink);
|
|
3793
|
-
const placeholderTag = normalizeVisibleOutOfOrderPlaceholderTag(options.placeholderTag);
|
|
3794
|
-
sink.append(
|
|
3795
|
-
`<${placeholderTag} data-mreact-oob-placeholder="${escapeHtmlAttribute(id)}">${placeholderSink.toString()}</${placeholderTag}>`,
|
|
3796
|
-
);
|
|
3797
|
-
|
|
3798
|
-
const task = renderVisibleOutOfOrderFragment(sink, id, value, render, options);
|
|
3799
|
-
|
|
3800
|
-
if (sink.defer === undefined) {
|
|
3801
|
-
void task;
|
|
3802
|
-
return;
|
|
3803
|
-
}
|
|
3804
|
-
|
|
3805
|
-
sink.defer(task);
|
|
3806
|
-
}
|
|
3807
|
-
|
|
3808
|
-
function normalizeVisibleOutOfOrderPlaceholderTag(tag: unknown): string {
|
|
3809
|
-
if (typeof tag !== "string") {
|
|
3810
|
-
return "span";
|
|
3811
|
-
}
|
|
3812
|
-
|
|
3813
|
-
const normalized = tag.trim().toLowerCase();
|
|
3814
|
-
return /^[a-z][a-z0-9-]*$/.test(normalized) ? normalized : "span";
|
|
3815
|
-
}
|
|
3816
|
-
|
|
3817
|
-
async function renderVisibleOutOfOrderFragment<T>(
|
|
3818
|
-
sink: HtmlSink,
|
|
3819
|
-
id: string,
|
|
3820
|
-
value: T,
|
|
3821
|
-
render: (sink: HtmlSink, value: Awaited<T>) => void | PromiseLike<void>,
|
|
3822
|
-
options: {
|
|
3823
|
-
catch?: (sink: HtmlSink, error: unknown) => void | PromiseLike<void>;
|
|
3824
|
-
},
|
|
3825
|
-
): Promise<void> {
|
|
3826
|
-
const fragmentSink = createStringSink();
|
|
3827
|
-
|
|
3828
|
-
await renderAsyncBoundary(
|
|
3829
|
-
fragmentSink,
|
|
3830
|
-
value,
|
|
3831
|
-
render,
|
|
3832
|
-
options.catch === undefined ? {} : { catch: options.catch },
|
|
3833
|
-
);
|
|
3834
|
-
await fragmentSink.drain();
|
|
3835
|
-
|
|
3836
|
-
sink.append(renderVisibleOutOfOrderFragmentHtml(id, fragmentSink.toString()));
|
|
3837
|
-
}
|
|
3838
|
-
|
|
3839
|
-
function renderVisibleOutOfOrderFragmentHtml(id: string, html: string): string {
|
|
3840
|
-
const escapedId = escapeHtmlAttribute(id);
|
|
3841
|
-
return `<template data-mreact-oob-fragment="${escapedId}">${html}</template><mreact-oob-complete hidden data-mreact-oob-complete="${escapedId}"></mreact-oob-complete>`;
|
|
3842
|
-
}
|
|
3843
|
-
|
|
3844
3945
|
async function appendServerStreamModule(
|
|
3845
3946
|
code: string,
|
|
3846
3947
|
sink: HtmlSink,
|
|
@@ -5053,7 +5154,12 @@ async function loadComposedRouteMetadata(options: {
|
|
|
5053
5154
|
composedRouteMetadataCacheCounters,
|
|
5054
5155
|
);
|
|
5055
5156
|
if (cached !== undefined) {
|
|
5056
|
-
|
|
5157
|
+
const entry = await cached;
|
|
5158
|
+
return applyFileConventionMetadataTemplate(
|
|
5159
|
+
entry.metadata,
|
|
5160
|
+
entry.template,
|
|
5161
|
+
options.context.params,
|
|
5162
|
+
);
|
|
5057
5163
|
}
|
|
5058
5164
|
}
|
|
5059
5165
|
|
|
@@ -5063,13 +5169,17 @@ async function loadComposedRouteMetadata(options: {
|
|
|
5063
5169
|
setBoundedCacheEntry(
|
|
5064
5170
|
composedRouteMetadataCache,
|
|
5065
5171
|
cacheKey,
|
|
5066
|
-
Promise.resolve(loaded.metadata),
|
|
5172
|
+
Promise.resolve({ metadata: loaded.metadata, template: loaded.template }),
|
|
5067
5173
|
maxComposedRouteMetadataCacheEntries,
|
|
5068
5174
|
composedRouteMetadataCacheCounters,
|
|
5069
5175
|
);
|
|
5070
5176
|
}
|
|
5071
5177
|
|
|
5072
|
-
return
|
|
5178
|
+
return applyFileConventionMetadataTemplate(
|
|
5179
|
+
loaded.metadata,
|
|
5180
|
+
loaded.template,
|
|
5181
|
+
options.context.params,
|
|
5182
|
+
);
|
|
5073
5183
|
} catch (error) {
|
|
5074
5184
|
if (cacheKey !== undefined) {
|
|
5075
5185
|
composedRouteMetadataCache.delete(cacheKey);
|
|
@@ -5111,7 +5221,11 @@ async function loadComposedRouteMetadataUncached(options: {
|
|
|
5111
5221
|
serverModuleCacheVersion?: string | undefined;
|
|
5112
5222
|
serverSourceFiles?: ReadonlyMap<string, string> | undefined;
|
|
5113
5223
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
5114
|
-
}): Promise<{
|
|
5224
|
+
}): Promise<{
|
|
5225
|
+
dynamic: boolean;
|
|
5226
|
+
metadata: RouteMetadata | undefined;
|
|
5227
|
+
template: FileConventionMetadataTemplate;
|
|
5228
|
+
}> {
|
|
5115
5229
|
const layoutFiles = await shellFilesForPage(
|
|
5116
5230
|
options.appDir,
|
|
5117
5231
|
options.filename,
|
|
@@ -5174,12 +5288,8 @@ async function loadComposedRouteMetadataUncached(options: {
|
|
|
5174
5288
|
|
|
5175
5289
|
return {
|
|
5176
5290
|
dynamic,
|
|
5177
|
-
metadata:
|
|
5178
|
-
|
|
5179
|
-
options.routes,
|
|
5180
|
-
options.filename,
|
|
5181
|
-
options.context.params,
|
|
5182
|
-
),
|
|
5291
|
+
metadata: mergeRouteMetadata(metadata),
|
|
5292
|
+
template: fileConventionMetadataTemplate(options.routes, options.filename),
|
|
5183
5293
|
};
|
|
5184
5294
|
}
|
|
5185
5295
|
|