@reckona/mreact-router 0.0.203 → 0.0.205
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 +53 -9
- package/dist/actions.d.ts +6 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +70 -10
- 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 +2 -1
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +18 -10
- 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 +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +91 -5
- 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 +3 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +211 -13
- package/dist/cache.js.map +1 -1
- package/dist/cli.js +12 -1
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +32 -1
- 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 +11 -6
- 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.map +1 -1
- package/dist/navigation-marker.js +72 -9
- package/dist/navigation-marker.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 +14 -0
- package/dist/prerender-entry.d.ts.map +1 -1
- package/dist/prerender-entry.js +40 -3
- package/dist/prerender-entry.js.map +1 -1
- package/dist/render.d.ts +9 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +102 -123
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +8 -2
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +22 -20
- 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 +81 -11
- package/src/adapters/aws-lambda.ts +426 -115
- package/src/adapters/cloudflare.ts +29 -13
- 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 +117 -10
- package/src/built-runtime.ts +28 -0
- package/src/cache.ts +262 -15
- package/src/cli.ts +14 -1
- package/src/client.ts +32 -1
- package/src/config.ts +43 -6
- package/src/csp.ts +14 -11
- 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 +79 -9
- package/src/navigation.ts +1 -1
- package/src/node-server.ts +128 -22
- package/src/prerender-entry.ts +52 -3
- package/src/render.ts +807 -753
- package/src/serve.ts +49 -56
- 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
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
createStringSink,
|
|
24
24
|
type HtmlSink,
|
|
25
|
-
|
|
25
|
+
renderOutOfOrderBoundary,
|
|
26
26
|
renderOutOfOrderReorderScript,
|
|
27
27
|
renderReactNodeToString,
|
|
28
28
|
renderToReadableStream,
|
|
@@ -67,11 +67,9 @@ import {
|
|
|
67
67
|
type TrackedHeaderRequest,
|
|
68
68
|
withTrackedRequest,
|
|
69
69
|
} from "./request-header-tracking.js";
|
|
70
|
-
import {
|
|
71
|
-
configuredHstsHeader,
|
|
72
|
-
hasInvalidConfiguredHsts,
|
|
73
|
-
} from "./security-headers.js";
|
|
70
|
+
import { configuredHstsHeader, hasInvalidConfiguredHsts } from "./security-headers.js";
|
|
74
71
|
import { resolveRouterCacheLimit } from "./cache-config.js";
|
|
72
|
+
import { protectNonceBearingResponse } from "./prerender-entry.js";
|
|
75
73
|
import {
|
|
76
74
|
importAppRouterBuiltFileModule,
|
|
77
75
|
importAppRouterFileModule,
|
|
@@ -130,13 +128,15 @@ import {
|
|
|
130
128
|
type RouteMiddlewareControl,
|
|
131
129
|
} from "./middleware.js";
|
|
132
130
|
import {
|
|
133
|
-
|
|
131
|
+
applyFileConventionMetadataTemplate,
|
|
132
|
+
fileConventionMetadataTemplate,
|
|
134
133
|
injectHeadMetadata,
|
|
135
134
|
mergeRouteMetadata,
|
|
136
135
|
responseHeadersForMetadata,
|
|
137
136
|
serializeRobots,
|
|
138
137
|
serializeSitemap,
|
|
139
138
|
validateRouteMetadata,
|
|
139
|
+
type FileConventionMetadataTemplate,
|
|
140
140
|
} from "./metadata.js";
|
|
141
141
|
import {
|
|
142
142
|
createSlotRenderContext,
|
|
@@ -219,6 +219,7 @@ export interface RenderAppRequestOptions {
|
|
|
219
219
|
routeMatcher?: RouteMatcher | undefined;
|
|
220
220
|
routes?: readonly AppRoute[] | undefined;
|
|
221
221
|
serverModules?: ReadonlyMap<string, BuiltServerModuleArtifact> | undefined;
|
|
222
|
+
serverRenderArtifactLoader?: AppRouterServerRenderArtifactLoader | undefined;
|
|
222
223
|
serverModuleCacheVersion?: string | undefined;
|
|
223
224
|
devServerModuleCacheVersion?: string | undefined;
|
|
224
225
|
// True when serving through a dev server: request-time server transforms
|
|
@@ -235,6 +236,14 @@ export interface RenderAppRequestOptions {
|
|
|
235
236
|
}
|
|
236
237
|
|
|
237
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;
|
|
238
247
|
/**
|
|
239
248
|
* Receives whether the render depended on the incoming request headers.
|
|
240
249
|
*
|
|
@@ -251,7 +260,6 @@ export interface RenderAppRequestRuntimeOptions extends RenderAppRequestOptions
|
|
|
251
260
|
strictTransportSecurity: () => string | undefined;
|
|
252
261
|
}
|
|
253
262
|
| undefined;
|
|
254
|
-
serverRenderArtifactLoader?: AppRouterServerRenderArtifactLoader | undefined;
|
|
255
263
|
}
|
|
256
264
|
|
|
257
265
|
export interface AppRouterRenderPreload {
|
|
@@ -572,7 +580,12 @@ const middlewareModuleCache = new Map<string, Promise<MiddlewareModule>>();
|
|
|
572
580
|
const middlewareModuleCacheCounters = createRouterRuntimeCacheCounters();
|
|
573
581
|
const serverRouteModuleCache = new Map<string, Promise<Record<string, unknown>>>();
|
|
574
582
|
const serverRouteModuleCacheCounters = createRouterRuntimeCacheCounters();
|
|
575
|
-
|
|
583
|
+
interface ComposedRouteMetadataCacheEntry {
|
|
584
|
+
metadata: RouteMetadata | undefined;
|
|
585
|
+
template: FileConventionMetadataTemplate;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const composedRouteMetadataCache = new Map<string, Promise<ComposedRouteMetadataCacheEntry>>();
|
|
576
589
|
const composedRouteMetadataCacheCounters = createRouterRuntimeCacheCounters();
|
|
577
590
|
const maxServerTransformCacheEntries = resolveRouterCacheLimit("SERVER_TRANSFORM", 512);
|
|
578
591
|
const maxServerSourceFileCacheEntries = resolveRouterCacheLimit("SERVER_SOURCE_FILE", 512);
|
|
@@ -695,7 +708,9 @@ export async function renderAppRequest(options: RenderAppRequestOptions): Promis
|
|
|
695
708
|
};
|
|
696
709
|
invokeRouterInstrumentation(options.instrumentation?.onRequestStart, requestEvent);
|
|
697
710
|
const response = await renderAppRequestInternal({ ...options, requestUrl: url });
|
|
698
|
-
const finalResponse =
|
|
711
|
+
const finalResponse = protectNonceBearingResponse(
|
|
712
|
+
await applyAppRouterResponseHook(response, options),
|
|
713
|
+
);
|
|
699
714
|
invokeRouterInstrumentation(options.instrumentation?.onRequestEnd, {
|
|
700
715
|
...requestEvent,
|
|
701
716
|
status: finalResponse.status,
|
|
@@ -724,43 +739,24 @@ function finishRenderTimingPhase(
|
|
|
724
739
|
timing.phases[phaseName] = logDurationMs(startedAt);
|
|
725
740
|
}
|
|
726
741
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
promise.then((value) => ({ settled: true as const, value })),
|
|
732
|
-
new Promise<{ settled: false }>((resolve) => {
|
|
733
|
-
setTimeout(() => resolve({ settled: false }), 0);
|
|
734
|
-
}),
|
|
735
|
-
]);
|
|
736
|
-
}
|
|
742
|
+
type ObservedPromiseSettlement<T> =
|
|
743
|
+
| { status: "pending" }
|
|
744
|
+
| { status: "fulfilled"; value: T }
|
|
745
|
+
| { error: unknown; status: "rejected" };
|
|
737
746
|
|
|
738
|
-
function
|
|
739
|
-
|
|
740
|
-
resolve(): void;
|
|
741
|
-
} {
|
|
742
|
-
let resolve: (() => void) | undefined;
|
|
743
|
-
const promise = new Promise<void>((innerResolve) => {
|
|
744
|
-
resolve = innerResolve;
|
|
745
|
-
});
|
|
747
|
+
function observePromiseSettlement<T>(promise: Promise<T>): () => ObservedPromiseSettlement<T> {
|
|
748
|
+
let settlement: ObservedPromiseSettlement<T> = { status: "pending" };
|
|
746
749
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
resolve?.();
|
|
750
|
+
void promise.then(
|
|
751
|
+
(value) => {
|
|
752
|
+
settlement = { status: "fulfilled", value };
|
|
751
753
|
},
|
|
752
|
-
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
dataPromise: Promise<unknown>,
|
|
757
|
-
loaderReady: Promise<void> | undefined,
|
|
758
|
-
): Promise<void> {
|
|
759
|
-
if (loaderReady === undefined) {
|
|
760
|
-
return;
|
|
761
|
-
}
|
|
754
|
+
(error: unknown) => {
|
|
755
|
+
settlement = { error, status: "rejected" };
|
|
756
|
+
},
|
|
757
|
+
);
|
|
762
758
|
|
|
763
|
-
|
|
759
|
+
return () => settlement;
|
|
764
760
|
}
|
|
765
761
|
|
|
766
762
|
function addRenderTimingPhaseDuration(
|
|
@@ -775,14 +771,6 @@ function addRenderTimingPhaseDuration(
|
|
|
775
771
|
timing.phases[phaseName] = (timing.phases[phaseName] ?? 0) + logDurationMs(startedAt);
|
|
776
772
|
}
|
|
777
773
|
|
|
778
|
-
function routeLoaderMayReturnControlResponse(code: string): boolean {
|
|
779
|
-
return (
|
|
780
|
-
/\b(?:redirect|redirectExternal|rewrite|notFound|throwNotFound)\s*\(/.test(code) ||
|
|
781
|
-
/\bnew\s+Response\s*\(/.test(code) ||
|
|
782
|
-
/\bResponse\.(?:redirect|json)\s*\(/.test(code)
|
|
783
|
-
);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
774
|
async function waitForRenderPreload(
|
|
787
775
|
options: Pick<RenderAppRequestOptions, "preload">,
|
|
788
776
|
timing: RenderTiming | undefined,
|
|
@@ -1008,217 +996,525 @@ async function renderAppRequestInternal(
|
|
|
1008
996
|
let sourceUsesRequestInput = true;
|
|
1009
997
|
let invalidConfiguredHsts = false;
|
|
1010
998
|
let renderedStrictTransportSecurity: string | undefined;
|
|
1011
|
-
return (
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
if (matched.route.kind === "metadata") {
|
|
1021
|
-
return await dispatchMetadataRoute({
|
|
1022
|
-
appDir: options.appDir,
|
|
1023
|
-
file: matched.route.file,
|
|
1024
|
-
importPolicy: options.importPolicy,
|
|
1025
|
-
params: matched.params,
|
|
1026
|
-
request: options.request,
|
|
1027
|
-
route: matched.route,
|
|
1028
|
-
serverModules: options.serverModules,
|
|
1029
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1030
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1031
|
-
vitePlugins: options.vitePlugins,
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
if (matched.route.kind === "server") {
|
|
1036
|
-
return await dispatchServerRoute({
|
|
1037
|
-
appDir: options.appDir,
|
|
1038
|
-
env: options.env,
|
|
1039
|
-
file: matched.route.file,
|
|
1040
|
-
importPolicy: options.importPolicy,
|
|
1041
|
-
params: matched.params,
|
|
1042
|
-
request: options.request,
|
|
1043
|
-
route: matched.route,
|
|
1044
|
-
serverModules: options.serverModules,
|
|
1045
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1046
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1047
|
-
vitePlugins: options.vitePlugins,
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
// Issue 080: page routes render HTML for GET / HEAD only. Other
|
|
1052
|
-
// methods (PUT, PATCH, DELETE, PROPFIND, ...) get 405 with an
|
|
1053
|
-
// Allow header so the response shape complies with RFC 9110 §9
|
|
1054
|
-
// and so caching intermediaries do not cross-cache method results.
|
|
1055
|
-
const methodResponse = pageRouteMethodResponse(options.request.method);
|
|
1056
|
-
if (methodResponse !== undefined) {
|
|
1057
|
-
return methodResponse;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
const clientScript = options.clientScripts?.get(matched.route.path);
|
|
1061
|
-
const clientStyleSheets = options.clientStyles?.get(matched.route.path);
|
|
1062
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1063
|
-
const originalCode = await readServerSourceFile(
|
|
1064
|
-
matched.route.file,
|
|
1065
|
-
options.serverModuleCacheVersion,
|
|
1066
|
-
options.serverSourceFiles,
|
|
1067
|
-
);
|
|
1068
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "readSourceMs");
|
|
1069
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1070
|
-
const originalAnalysis = await analyzeRouteSource({
|
|
1071
|
-
appDir: options.appDir,
|
|
1072
|
-
artifact: options.serverModules?.get(matched.route.file)?.analysis,
|
|
1073
|
-
code: originalCode,
|
|
1074
|
-
filename: matched.route.file,
|
|
1075
|
-
routePath: matched.route.path,
|
|
1076
|
-
clientRouteInferenceCache,
|
|
1077
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1078
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1079
|
-
define: options.define,
|
|
1080
|
-
timing,
|
|
1081
|
-
vitePlugins: options.vitePlugins,
|
|
1082
|
-
});
|
|
1083
|
-
sourceUsesRequestInput = originalAnalysis.usesRequestInput;
|
|
1084
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "sourceAnalysisMs");
|
|
1085
|
-
const cachePolicy = originalAnalysis.cachePolicy;
|
|
1086
|
-
const navigationScript = options.navigationScripts?.get(matched.route.path);
|
|
1087
|
-
const navigationRequest = isNavigationRequest(options.request);
|
|
1088
|
-
const cacheKey = routeCacheKey(
|
|
1089
|
-
options.appDir,
|
|
1090
|
-
matched.route.path,
|
|
1091
|
-
url,
|
|
1092
|
-
navigationRequest ? "navigation" : "document",
|
|
1093
|
-
);
|
|
1094
|
-
const mayUseRouteCache =
|
|
1095
|
-
!sourceUsesRequestInput &&
|
|
1096
|
-
(cachePolicy === undefined
|
|
1097
|
-
? originalAnalysis.usesRuntimeCacheControl
|
|
1098
|
-
: cachePolicy.revalidateSeconds !== 0);
|
|
1099
|
-
const reloadRouteCache = isNavigationRouteCacheReloadRequest(options.request);
|
|
1100
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1101
|
-
const cachedResponse =
|
|
1102
|
-
!mayUseRouteCache || reloadRouteCache
|
|
1103
|
-
? undefined
|
|
1104
|
-
: await cachedRouteResponse({
|
|
1105
|
-
cache: options.routeCache,
|
|
1106
|
-
key: cacheKey,
|
|
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,
|
|
1107
1005
|
request: options.request,
|
|
1108
1006
|
});
|
|
1109
|
-
|
|
1007
|
+
}
|
|
1110
1008
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
+
}
|
|
1114
1023
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
// headers through this request so that a render depending on one is never
|
|
1118
|
-
// stored under a key that ignores it. Callers that store by path alone ask
|
|
1119
|
-
// for tracking through `renderSignals` even when the route itself declares
|
|
1120
|
-
// no cache policy.
|
|
1121
|
-
trackedRequest =
|
|
1122
|
-
mayUseRouteCache || options.renderSignals !== undefined
|
|
1123
|
-
? trackRequestHeaderReads(options.request)
|
|
1124
|
-
: undefined;
|
|
1125
|
-
const appRequest = trackedRequest?.request ?? options.request;
|
|
1126
|
-
|
|
1127
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1128
|
-
const preparedActions = await prepareRouteServerActions({
|
|
1129
|
-
appDir: options.appDir,
|
|
1130
|
-
code: originalCode,
|
|
1131
|
-
formActionReferences: options.serverActionReferencesByFile?.get(matched.route.file),
|
|
1132
|
-
placeholders: true,
|
|
1133
|
-
pageFile: matched.route.file,
|
|
1134
|
-
request: options.request,
|
|
1135
|
-
});
|
|
1136
|
-
for (const diagnostic of preparedActions.diagnostics ?? []) {
|
|
1137
|
-
console.warn(`${diagnostic.code}: ${diagnostic.message}`);
|
|
1138
|
-
}
|
|
1139
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "serverActionsMs");
|
|
1140
|
-
const code = preparedActions.code;
|
|
1141
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1142
|
-
const routeAnalysis =
|
|
1143
|
-
code === originalCode
|
|
1144
|
-
? originalAnalysis
|
|
1145
|
-
: await analyzeRouteSource({
|
|
1024
|
+
if (matched.route.kind === "server") {
|
|
1025
|
+
return await dispatchServerRoute({
|
|
1146
1026
|
appDir: options.appDir,
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
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,
|
|
1153
1035
|
serverSourceFiles: options.serverSourceFiles,
|
|
1154
|
-
define: options.define,
|
|
1155
1036
|
vitePlugins: options.vitePlugins,
|
|
1156
1037
|
});
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
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({
|
|
1166
1060
|
appDir: options.appDir,
|
|
1061
|
+
artifact: options.serverModules?.get(matched.route.file)?.analysis,
|
|
1167
1062
|
code: originalCode,
|
|
1168
|
-
context: withTrackedRequest({
|
|
1169
|
-
env: options.env,
|
|
1170
|
-
params: matched.params,
|
|
1171
|
-
queryClient,
|
|
1172
|
-
}, appRequest, trackedRequest),
|
|
1173
1063
|
filename: matched.route.file,
|
|
1174
|
-
importPolicy: options.importPolicy,
|
|
1175
|
-
instrumentation: options.instrumentation,
|
|
1176
|
-
devServerModuleCacheVersion: options.devServerModuleCacheVersion,
|
|
1177
|
-
request: options.request,
|
|
1178
|
-
routeId: routeIdForPath(matched.route.path),
|
|
1179
1064
|
routePath: matched.route.path,
|
|
1180
|
-
|
|
1065
|
+
clientRouteInferenceCache,
|
|
1181
1066
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1067
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1182
1068
|
define: options.define,
|
|
1183
|
-
vitePlugins: options.vitePlugins,
|
|
1184
1069
|
timing,
|
|
1185
|
-
|
|
1186
|
-
})
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
+
}
|
|
1220
1517
|
|
|
1221
|
-
if (loadingFile === undefined && !mayRenderOutOfOrder) {
|
|
1222
1518
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1223
1519
|
let data: unknown;
|
|
1224
1520
|
try {
|
|
@@ -1230,10 +1526,16 @@ async function renderAppRequestInternal(
|
|
|
1230
1526
|
emitRenderTiming(options, timing, data.status);
|
|
1231
1527
|
return data;
|
|
1232
1528
|
}
|
|
1529
|
+
sourceUsesRequestInput ||= await routeShellsMayUseRequestInput({
|
|
1530
|
+
appDir: options.appDir,
|
|
1531
|
+
pageFile: matched.route.file,
|
|
1532
|
+
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1533
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1534
|
+
});
|
|
1233
1535
|
await waitForRenderPreload(options, timing);
|
|
1234
1536
|
await loadServerRenderArtifacts(options, matched.route.file, timing);
|
|
1235
1537
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1236
|
-
const
|
|
1538
|
+
const output = transformServerModule({
|
|
1237
1539
|
code: routeCode,
|
|
1238
1540
|
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
1239
1541
|
clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
|
|
@@ -1242,38 +1544,49 @@ async function renderAppRequestInternal(
|
|
|
1242
1544
|
serverOutput: "string",
|
|
1243
1545
|
});
|
|
1244
1546
|
finishRenderTimingPhase(timing, phaseStartedAt, "stringTransformMs");
|
|
1245
|
-
const
|
|
1547
|
+
const fatalDiagnostics = fatalServerDiagnostics(output.diagnostics);
|
|
1246
1548
|
|
|
1247
|
-
if (
|
|
1248
|
-
return new Response(formatServerDiagnostics(matched.route.file,
|
|
1549
|
+
if (fatalDiagnostics.length > 0) {
|
|
1550
|
+
return new Response(formatServerDiagnostics(matched.route.file, fatalDiagnostics), {
|
|
1249
1551
|
status: 500,
|
|
1250
1552
|
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
1251
1553
|
});
|
|
1252
1554
|
}
|
|
1253
1555
|
|
|
1556
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1254
1557
|
const renderedPage = await runWithQueryClient(queryClient, () =>
|
|
1255
1558
|
runServerModuleWithSlots(
|
|
1256
|
-
|
|
1257
|
-
withTrackedRequest(
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1559
|
+
output.code,
|
|
1560
|
+
withTrackedRequest(
|
|
1561
|
+
{
|
|
1562
|
+
data,
|
|
1563
|
+
params: matched.params,
|
|
1564
|
+
queryClient,
|
|
1565
|
+
},
|
|
1566
|
+
appRequest,
|
|
1567
|
+
trackedRequest,
|
|
1568
|
+
),
|
|
1262
1569
|
matched.route.file,
|
|
1263
1570
|
options.serverModules,
|
|
1264
1571
|
options.serverModuleCacheVersion,
|
|
1265
1572
|
options.define,
|
|
1266
1573
|
options.vitePlugins,
|
|
1267
|
-
|
|
1574
|
+
timing,
|
|
1268
1575
|
options.importPolicy,
|
|
1269
1576
|
options.devServerModuleCacheVersion,
|
|
1270
1577
|
),
|
|
1271
1578
|
);
|
|
1579
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "pageRenderMs");
|
|
1272
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.
|
|
1273
1586
|
const pageHtmlForLayout = clientRoute
|
|
1274
1587
|
? withHydrationMarkers({
|
|
1275
1588
|
assetBaseUrl: options.assetBaseUrl,
|
|
1276
|
-
clientReferenceManifest:
|
|
1589
|
+
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1277
1590
|
html: pageHtml,
|
|
1278
1591
|
routePath: matched.route.path,
|
|
1279
1592
|
script: clientScript,
|
|
@@ -1283,17 +1596,27 @@ async function renderAppRequestInternal(
|
|
|
1283
1596
|
data,
|
|
1284
1597
|
},
|
|
1285
1598
|
})
|
|
1286
|
-
: withServerOnlyRouteMarkers(
|
|
1599
|
+
: withServerOnlyRouteMarkers(
|
|
1600
|
+
pageHtml,
|
|
1601
|
+
matched.route.path,
|
|
1602
|
+
options.request,
|
|
1603
|
+
options.prerenderVariantCapture === true,
|
|
1604
|
+
);
|
|
1605
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1287
1606
|
let html = await runWithQueryClient(queryClient, () =>
|
|
1288
1607
|
applyLayouts({
|
|
1289
1608
|
appDir: options.appDir,
|
|
1290
1609
|
pageFile: matched.route.file,
|
|
1291
1610
|
html: pageHtmlForLayout,
|
|
1292
|
-
props: withTrackedRequest(
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1611
|
+
props: withTrackedRequest(
|
|
1612
|
+
{
|
|
1613
|
+
data,
|
|
1614
|
+
params: matched.params,
|
|
1615
|
+
queryClient,
|
|
1616
|
+
},
|
|
1617
|
+
appRequest,
|
|
1618
|
+
trackedRequest,
|
|
1619
|
+
),
|
|
1297
1620
|
slots: renderedPage.slots,
|
|
1298
1621
|
serverModules: options.serverModules,
|
|
1299
1622
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
@@ -1301,16 +1624,21 @@ async function renderAppRequestInternal(
|
|
|
1301
1624
|
clientRouteInferenceCache,
|
|
1302
1625
|
timing,
|
|
1303
1626
|
vitePlugins: options.vitePlugins,
|
|
1304
|
-
importPolicy: options.importPolicy,
|
|
1305
1627
|
}),
|
|
1306
1628
|
);
|
|
1629
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "layoutRenderMs");
|
|
1630
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1307
1631
|
const metadata = await loadComposedRouteMetadata({
|
|
1308
1632
|
appDir: options.appDir,
|
|
1309
1633
|
code: originalCode,
|
|
1310
|
-
context: withTrackedRequest(
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1634
|
+
context: withTrackedRequest(
|
|
1635
|
+
{
|
|
1636
|
+
data,
|
|
1637
|
+
params: matched.params,
|
|
1638
|
+
},
|
|
1639
|
+
appRequest,
|
|
1640
|
+
trackedRequest,
|
|
1641
|
+
),
|
|
1314
1642
|
filename: matched.route.file,
|
|
1315
1643
|
importPolicy: options.importPolicy,
|
|
1316
1644
|
routes,
|
|
@@ -1321,6 +1649,8 @@ async function renderAppRequestInternal(
|
|
|
1321
1649
|
define: options.define,
|
|
1322
1650
|
vitePlugins: options.vitePlugins,
|
|
1323
1651
|
});
|
|
1652
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "metadataMs");
|
|
1653
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1324
1654
|
html = injectHeadMetadata(html, metadata);
|
|
1325
1655
|
warnIfCspNonceWouldBlockInlineTags({
|
|
1326
1656
|
html,
|
|
@@ -1334,6 +1664,7 @@ async function renderAppRequestInternal(
|
|
|
1334
1664
|
originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined,
|
|
1335
1665
|
);
|
|
1336
1666
|
html = injectQueryState(html, dehydrate(queryClient, options.dehydrateOptions));
|
|
1667
|
+
|
|
1337
1668
|
const response = withOptionalActionCookie(
|
|
1338
1669
|
htmlResponse(
|
|
1339
1670
|
applyActionHtmlReplacements(
|
|
@@ -1347,413 +1678,122 @@ async function renderAppRequestInternal(
|
|
|
1347
1678
|
preparedActions.htmlReplacements,
|
|
1348
1679
|
),
|
|
1349
1680
|
{
|
|
1350
|
-
headers: responseHeadersForMetadata(metadata, options.request,
|
|
1351
|
-
"x-mreact-stream": "1",
|
|
1352
|
-
}),
|
|
1681
|
+
headers: responseHeadersForMetadata(metadata, options.request),
|
|
1353
1682
|
},
|
|
1354
1683
|
),
|
|
1355
1684
|
preparedActions.csrfToken,
|
|
1356
1685
|
preparedActions.csrfTokenIsNew === true,
|
|
1357
1686
|
);
|
|
1358
1687
|
appendNavigationVary(response.headers);
|
|
1359
|
-
emitRenderTiming(options, timing, response.status);
|
|
1360
|
-
return response;
|
|
1361
|
-
}
|
|
1362
1688
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
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;
|
|
1391
1724
|
}
|
|
1392
|
-
}
|
|
1393
1725
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
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);
|
|
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
|
+
});
|
|
1408
1732
|
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
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,
|
|
1413
1758
|
});
|
|
1414
|
-
|
|
1759
|
+
options.onRenderError?.(error);
|
|
1415
1760
|
|
|
1416
|
-
|
|
1417
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1418
|
-
const stream = await runServerStreamModuleWithLoading(output.code, {
|
|
1761
|
+
const response = await renderSpecialRoute({
|
|
1419
1762
|
appDir: options.appDir,
|
|
1420
1763
|
assetBaseUrl: options.assetBaseUrl,
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
loadingFile,
|
|
1425
|
-
markerRequest: options.request,
|
|
1426
|
-
pageFile: matched.route.file,
|
|
1427
|
-
params: matched.params,
|
|
1428
|
-
queryClient,
|
|
1429
|
-
request: appRequest,
|
|
1430
|
-
requestUrl: url.pathname,
|
|
1431
|
-
trackedRequest,
|
|
1764
|
+
currentStyleSheets: options.clientStylesByFile?.get(errorFile),
|
|
1765
|
+
error,
|
|
1766
|
+
request: options.request,
|
|
1432
1767
|
routePath: matched.route.path,
|
|
1768
|
+
routeFile: errorFile,
|
|
1433
1769
|
routeScripts: options.clientScripts,
|
|
1434
1770
|
serverModules: options.serverModules,
|
|
1435
1771
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1436
1772
|
serverSourceFiles: options.serverSourceFiles,
|
|
1437
1773
|
vitePlugins: options.vitePlugins,
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1774
|
+
navigation: recoveryRoute,
|
|
1775
|
+
status: 500,
|
|
1776
|
+
textFallback: "Internal Server Error",
|
|
1442
1777
|
});
|
|
1443
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
1444
|
-
|
|
1445
|
-
const response = withOptionalActionCookie(
|
|
1446
|
-
new Response(stream, {
|
|
1447
|
-
headers: streamShellResponseHeaders,
|
|
1448
|
-
}),
|
|
1449
|
-
preparedActions.csrfToken,
|
|
1450
|
-
preparedActions.csrfTokenIsNew === true,
|
|
1451
|
-
);
|
|
1452
|
-
appendNavigationVary(response.headers);
|
|
1453
1778
|
emitRenderTiming(options, timing, response.status);
|
|
1454
1779
|
return response;
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
const stream = runServerStreamModule(output.code, {
|
|
1465
|
-
appDir: options.appDir,
|
|
1466
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1467
|
-
pageFile: matched.route.file,
|
|
1468
|
-
markerRequest: options.request,
|
|
1469
|
-
props,
|
|
1470
|
-
requestUrl: url.pathname,
|
|
1471
|
-
routePath: matched.route.path,
|
|
1472
|
-
routeScripts: options.clientScripts,
|
|
1473
|
-
serverModules: options.serverModules,
|
|
1474
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1475
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1476
|
-
define: options.define,
|
|
1477
|
-
vitePlugins: options.vitePlugins,
|
|
1478
|
-
clientRouteInferenceCache,
|
|
1479
|
-
importPolicy: options.importPolicy,
|
|
1480
|
-
clientRoute,
|
|
1481
|
-
script: clientScript,
|
|
1482
|
-
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1483
|
-
});
|
|
1484
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
1485
|
-
|
|
1486
|
-
const response = withOptionalActionCookie(
|
|
1487
|
-
new Response(stream, {
|
|
1488
|
-
headers: streamShellResponseHeaders,
|
|
1489
|
-
}),
|
|
1490
|
-
preparedActions.csrfToken,
|
|
1491
|
-
preparedActions.csrfTokenIsNew === true,
|
|
1492
|
-
);
|
|
1493
|
-
appendNavigationVary(response.headers);
|
|
1494
|
-
emitRenderTiming(options, timing, response.status);
|
|
1495
|
-
return response;
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1499
|
-
let data: unknown;
|
|
1500
|
-
try {
|
|
1501
|
-
data = dataPromise === undefined ? undefined : await dataPromise;
|
|
1502
|
-
} finally {
|
|
1503
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
|
|
1504
|
-
}
|
|
1505
|
-
if (data instanceof Response) {
|
|
1506
|
-
emitRenderTiming(options, timing, data.status);
|
|
1507
|
-
return data;
|
|
1508
|
-
}
|
|
1509
|
-
sourceUsesRequestInput ||= await routeShellsMayUseRequestInput({
|
|
1510
|
-
appDir: options.appDir,
|
|
1511
|
-
pageFile: matched.route.file,
|
|
1512
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1513
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1514
|
-
});
|
|
1515
|
-
await waitForRenderPreload(options, timing);
|
|
1516
|
-
await loadServerRenderArtifacts(options, matched.route.file, timing);
|
|
1517
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1518
|
-
const output = transformServerModule({
|
|
1519
|
-
code: routeCode,
|
|
1520
|
-
clientBoundaryImports: clientInference.clientBoundaryImports,
|
|
1521
|
-
clientBoundaryFallbackImports: clientInference.clientBoundaryFallbackImports,
|
|
1522
|
-
filename: matched.route.file,
|
|
1523
|
-
serverModules: options.serverModules,
|
|
1524
|
-
serverOutput: "string",
|
|
1525
|
-
});
|
|
1526
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "stringTransformMs");
|
|
1527
|
-
const fatalDiagnostics = fatalServerDiagnostics(output.diagnostics);
|
|
1528
|
-
|
|
1529
|
-
if (fatalDiagnostics.length > 0) {
|
|
1530
|
-
return new Response(formatServerDiagnostics(matched.route.file, fatalDiagnostics), {
|
|
1531
|
-
status: 500,
|
|
1532
|
-
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1537
|
-
const renderedPage = await runWithQueryClient(queryClient, () =>
|
|
1538
|
-
runServerModuleWithSlots(
|
|
1539
|
-
output.code,
|
|
1540
|
-
withTrackedRequest({
|
|
1541
|
-
data,
|
|
1542
|
-
params: matched.params,
|
|
1543
|
-
queryClient,
|
|
1544
|
-
}, appRequest, trackedRequest),
|
|
1545
|
-
matched.route.file,
|
|
1546
|
-
options.serverModules,
|
|
1547
|
-
options.serverModuleCacheVersion,
|
|
1548
|
-
options.define,
|
|
1549
|
-
options.vitePlugins,
|
|
1550
|
-
timing,
|
|
1551
|
-
options.importPolicy,
|
|
1552
|
-
options.devServerModuleCacheVersion,
|
|
1553
|
-
),
|
|
1554
|
-
);
|
|
1555
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "pageRenderMs");
|
|
1556
|
-
const pageHtml = renderedPage.html;
|
|
1557
|
-
// Wrap the page (not the full document) with the hydration marker so
|
|
1558
|
-
// the marker sits inside <body>, not around <html>. Wrapping <html>
|
|
1559
|
-
// forces the browser HTML parser to strip the wrappers and promote
|
|
1560
|
-
// <head> / <body> children up to the marker, which flattens the
|
|
1561
|
-
// layout into the marker and breaks the hydration target lookup.
|
|
1562
|
-
const pageHtmlForLayout = clientRoute
|
|
1563
|
-
? withHydrationMarkers({
|
|
1564
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1565
|
-
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
1566
|
-
html: pageHtml,
|
|
1567
|
-
routePath: matched.route.path,
|
|
1568
|
-
script: clientScript,
|
|
1569
|
-
props: {
|
|
1570
|
-
params: matched.params,
|
|
1571
|
-
request: { url: url.pathname },
|
|
1572
|
-
data,
|
|
1573
|
-
},
|
|
1574
|
-
})
|
|
1575
|
-
: withServerOnlyRouteMarkers(pageHtml, matched.route.path, options.request);
|
|
1576
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1577
|
-
let html = await runWithQueryClient(queryClient, () =>
|
|
1578
|
-
applyLayouts({
|
|
1579
|
-
appDir: options.appDir,
|
|
1580
|
-
pageFile: matched.route.file,
|
|
1581
|
-
html: pageHtmlForLayout,
|
|
1582
|
-
props: withTrackedRequest({
|
|
1583
|
-
data,
|
|
1584
|
-
params: matched.params,
|
|
1585
|
-
queryClient,
|
|
1586
|
-
}, appRequest, trackedRequest),
|
|
1587
|
-
slots: renderedPage.slots,
|
|
1588
|
-
serverModules: options.serverModules,
|
|
1589
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1590
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1591
|
-
clientRouteInferenceCache,
|
|
1592
|
-
timing,
|
|
1593
|
-
vitePlugins: options.vitePlugins,
|
|
1594
|
-
}),
|
|
1595
|
-
);
|
|
1596
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "layoutRenderMs");
|
|
1597
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1598
|
-
const metadata = await loadComposedRouteMetadata({
|
|
1599
|
-
appDir: options.appDir,
|
|
1600
|
-
code: originalCode,
|
|
1601
|
-
context: withTrackedRequest({
|
|
1602
|
-
data,
|
|
1603
|
-
params: matched.params,
|
|
1604
|
-
}, appRequest, trackedRequest),
|
|
1605
|
-
filename: matched.route.file,
|
|
1606
|
-
importPolicy: options.importPolicy,
|
|
1607
|
-
routes,
|
|
1608
|
-
serverModules: options.serverModules,
|
|
1609
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1610
|
-
devServerModuleCacheVersion: options.devServerModuleCacheVersion,
|
|
1611
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1612
|
-
define: options.define,
|
|
1613
|
-
vitePlugins: options.vitePlugins,
|
|
1614
|
-
});
|
|
1615
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "metadataMs");
|
|
1616
|
-
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1617
|
-
html = injectHeadMetadata(html, metadata);
|
|
1618
|
-
warnIfCspNonceWouldBlockInlineTags({
|
|
1619
|
-
html,
|
|
1620
|
-
logger: options.logger,
|
|
1621
|
-
metadata,
|
|
1622
|
-
request: options.request,
|
|
1623
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1624
|
-
});
|
|
1625
|
-
html = injectAuthSessionClaims(
|
|
1626
|
-
html,
|
|
1627
|
-
originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined,
|
|
1628
|
-
);
|
|
1629
|
-
html = injectQueryState(html, dehydrate(queryClient, options.dehydrateOptions));
|
|
1630
|
-
|
|
1631
|
-
const response = withOptionalActionCookie(
|
|
1632
|
-
htmlResponse(
|
|
1633
|
-
applyActionHtmlReplacements(
|
|
1634
|
-
`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
1635
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1636
|
-
currentStyleSheets: clientStyleSheets,
|
|
1637
|
-
currentScript: clientRoute ? clientScript : undefined,
|
|
1638
|
-
currentNavigationScript: navigationScript,
|
|
1639
|
-
routeScripts: options.clientScripts,
|
|
1640
|
-
})}${html}`,
|
|
1641
|
-
preparedActions.htmlReplacements,
|
|
1642
|
-
),
|
|
1643
|
-
{
|
|
1644
|
-
headers: responseHeadersForMetadata(metadata, options.request),
|
|
1645
|
-
},
|
|
1646
|
-
),
|
|
1647
|
-
preparedActions.csrfToken,
|
|
1648
|
-
preparedActions.csrfTokenIsNew === true,
|
|
1649
|
-
);
|
|
1650
|
-
appendNavigationVary(response.headers);
|
|
1651
|
-
|
|
1652
|
-
const effectiveCachePolicy = cachePolicy ?? activeRouteCacheContext()?.cachePolicy;
|
|
1653
|
-
const configuredHsts = configuredHstsHeader(metadata?.security);
|
|
1654
|
-
renderedStrictTransportSecurity = configuredHsts;
|
|
1655
|
-
invalidConfiguredHsts = hasInvalidConfiguredHsts(metadata?.security);
|
|
1656
|
-
|
|
1657
|
-
const finalResponse = preparedActions.hasFormActions
|
|
1658
|
-
? withRouteCacheHeader(response, effectiveCachePolicy)
|
|
1659
|
-
: await cacheRouteResponse({
|
|
1660
|
-
key: cacheKey,
|
|
1661
|
-
cache: options.routeCache,
|
|
1662
|
-
// A policy can still arrive from a runtime cacheControl() call made
|
|
1663
|
-
// outside the page source, which is what `mayUseRouteCache` scans.
|
|
1664
|
-
// Without a tracker there is no evidence the render ignored request
|
|
1665
|
-
// headers, so the entry is treated as header dependent.
|
|
1666
|
-
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 = () =>
|
|
1667
1789
|
sourceUsesRequestInput ||
|
|
1668
1790
|
invalidConfiguredHsts ||
|
|
1669
|
-
(
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
});
|
|
1676
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "responseBuildMs");
|
|
1677
|
-
emitRenderTiming(options, timing, finalResponse.status);
|
|
1678
|
-
return finalResponse;
|
|
1679
|
-
} catch (error) {
|
|
1680
|
-
if (isRedirectError(error)) {
|
|
1681
|
-
const response = new Response(null, {
|
|
1682
|
-
headers: { location: error.location },
|
|
1683
|
-
status: error.status,
|
|
1684
|
-
});
|
|
1685
|
-
emitRenderTiming(options, timing, response.status);
|
|
1686
|
-
return response;
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1689
|
-
if (isNotFoundError(error)) {
|
|
1690
|
-
const notFoundFile = await nearestBoundaryFileForPage({
|
|
1691
|
-
appDir: options.appDir,
|
|
1692
|
-
filename: "not-found.mreact.tsx",
|
|
1693
|
-
pageFile: matched.route.file,
|
|
1694
|
-
});
|
|
1695
|
-
|
|
1696
|
-
const response = await renderSpecialRoute({
|
|
1697
|
-
appDir: options.appDir,
|
|
1698
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1699
|
-
currentStyleSheets: options.clientStylesByFile?.get(notFoundFile),
|
|
1700
|
-
error: undefined,
|
|
1701
|
-
request: options.request,
|
|
1702
|
-
routePath: matched.route.path,
|
|
1703
|
-
routeFile: notFoundFile,
|
|
1704
|
-
routeScripts: options.clientScripts,
|
|
1705
|
-
serverModules: options.serverModules,
|
|
1706
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1707
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1708
|
-
vitePlugins: options.vitePlugins,
|
|
1709
|
-
navigation: recoveryRoute,
|
|
1710
|
-
status: 404,
|
|
1711
|
-
textFallback: "Not Found",
|
|
1712
|
-
});
|
|
1713
|
-
emitRenderTiming(options, timing, response.status);
|
|
1714
|
-
return response;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
const errorFile = await nearestBoundaryFileForPage({
|
|
1718
|
-
appDir: options.appDir,
|
|
1719
|
-
filename: "error.mreact.tsx",
|
|
1720
|
-
pageFile: matched.route.file,
|
|
1721
|
-
});
|
|
1722
|
-
options.onRenderError?.(error);
|
|
1723
|
-
|
|
1724
|
-
const response = await renderSpecialRoute({
|
|
1725
|
-
appDir: options.appDir,
|
|
1726
|
-
assetBaseUrl: options.assetBaseUrl,
|
|
1727
|
-
currentStyleSheets: options.clientStylesByFile?.get(errorFile),
|
|
1728
|
-
error,
|
|
1729
|
-
request: options.request,
|
|
1730
|
-
routePath: matched.route.path,
|
|
1731
|
-
routeFile: errorFile,
|
|
1732
|
-
routeScripts: options.clientScripts,
|
|
1733
|
-
serverModules: options.serverModules,
|
|
1734
|
-
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1735
|
-
serverSourceFiles: options.serverSourceFiles,
|
|
1736
|
-
vitePlugins: options.vitePlugins,
|
|
1737
|
-
navigation: recoveryRoute,
|
|
1738
|
-
status: 500,
|
|
1739
|
-
textFallback: "Internal Server Error",
|
|
1740
|
-
});
|
|
1741
|
-
emitRenderTiming(options, timing, response.status);
|
|
1742
|
-
return response;
|
|
1743
|
-
} finally {
|
|
1744
|
-
// Reported from every return path, including the streaming ones, so that a
|
|
1745
|
-
// caller storing by path alone never mistakes an unreported render for a
|
|
1746
|
-
// shareable one. The value stays lazy because a streamed render can still
|
|
1747
|
-
// read request headers while its deferred parts resolve; callers read it
|
|
1748
|
-
// after draining the body.
|
|
1749
|
-
if (options.renderSignals !== undefined) {
|
|
1750
|
-
const tracked = trackedRequest;
|
|
1751
|
-
options.renderSignals.headerDependent = () =>
|
|
1752
|
-
sourceUsesRequestInput || invalidConfiguredHsts || (tracked?.requestDependent() ?? true);
|
|
1753
|
-
options.renderSignals.strictTransportSecurity = () => renderedStrictTransportSecurity;
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
})).value;
|
|
1791
|
+
(tracked?.requestDependent() ?? true);
|
|
1792
|
+
options.renderSignals.strictTransportSecurity = () => renderedStrictTransportSecurity;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
})
|
|
1796
|
+
).value;
|
|
1757
1797
|
}
|
|
1758
1798
|
|
|
1759
1799
|
async function applyAppRouterResponseHook(
|
|
@@ -1980,16 +2020,22 @@ function isNavigationRequest(request: Request): boolean {
|
|
|
1980
2020
|
return request.headers.get("x-mreact-navigation") === "1";
|
|
1981
2021
|
}
|
|
1982
2022
|
|
|
1983
|
-
function withServerOnlyRouteMarkers(
|
|
1984
|
-
|
|
2023
|
+
function withServerOnlyRouteMarkers(
|
|
2024
|
+
html: string,
|
|
2025
|
+
routePath: string,
|
|
2026
|
+
request: Request,
|
|
2027
|
+
prerenderVariantCapture: boolean,
|
|
2028
|
+
): string {
|
|
2029
|
+
const marker = serverOnlyRouteMarkerParts(routePath, request, prerenderVariantCapture);
|
|
1985
2030
|
return marker === undefined ? html : `${marker.prefix}${html}${marker.suffix}`;
|
|
1986
2031
|
}
|
|
1987
2032
|
|
|
1988
2033
|
function serverOnlyRouteMarkerParts(
|
|
1989
2034
|
routePath: string,
|
|
1990
2035
|
request: Request,
|
|
2036
|
+
prerenderVariantCapture: boolean,
|
|
1991
2037
|
): { prefix: string; suffix: string } | undefined {
|
|
1992
|
-
if (
|
|
2038
|
+
if (prerenderVariantCapture) {
|
|
1993
2039
|
return prerenderVariantMarkerParts(routePath);
|
|
1994
2040
|
}
|
|
1995
2041
|
return isNavigationRequest(request) ? routeMarkerParts(routePath) : undefined;
|
|
@@ -2009,9 +2055,7 @@ export function prerenderVariantMarkerParts(routePath: string): {
|
|
|
2009
2055
|
function appendNavigationVary(headers: Headers): void {
|
|
2010
2056
|
const vary = headers.get("vary");
|
|
2011
2057
|
if (
|
|
2012
|
-
vary
|
|
2013
|
-
?.split(",")
|
|
2014
|
-
.some((value) => value.trim().toLowerCase() === "x-mreact-navigation") === true
|
|
2058
|
+
vary?.split(",").some((value) => value.trim().toLowerCase() === "x-mreact-navigation") === true
|
|
2015
2059
|
) {
|
|
2016
2060
|
return;
|
|
2017
2061
|
}
|
|
@@ -2200,15 +2244,7 @@ async function renderSpecialRoute(options: {
|
|
|
2200
2244
|
}
|
|
2201
2245
|
|
|
2202
2246
|
const props = {
|
|
2203
|
-
|
|
2204
|
-
debug: errorDebugContext(options.error, options.routePath),
|
|
2205
|
-
error: normalizeErrorForProps(options.error),
|
|
2206
|
-
params: {},
|
|
2207
|
-
queryClient: createQueryClient(),
|
|
2208
|
-
request: options.request,
|
|
2209
|
-
requestId: requestIdForErrorContext(options.request),
|
|
2210
|
-
routeId: routeIdForPath(options.routePath ?? new URL(options.request.url).pathname),
|
|
2211
|
-
traceId: traceContextFromRequest(options.request)?.traceId,
|
|
2247
|
+
...errorBoundaryProps(options.error, options.request, options.routePath),
|
|
2212
2248
|
};
|
|
2213
2249
|
const pageHtml = await renderServerFileToHtml(
|
|
2214
2250
|
options.routeFile,
|
|
@@ -2257,6 +2293,20 @@ async function renderSpecialRoute(options: {
|
|
|
2257
2293
|
);
|
|
2258
2294
|
}
|
|
2259
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
|
+
|
|
2260
2310
|
async function renderServerFileToHtml(
|
|
2261
2311
|
file: string,
|
|
2262
2312
|
props: ServerComponentProps,
|
|
@@ -3452,6 +3502,7 @@ function runServerStreamModule(
|
|
|
3452
3502
|
assetBaseUrl?: string | undefined;
|
|
3453
3503
|
clientRouteInferenceCache: ClientRouteInferenceCache;
|
|
3454
3504
|
markerRequest: Request;
|
|
3505
|
+
prerenderVariantCapture?: boolean | undefined;
|
|
3455
3506
|
pageFile: string;
|
|
3456
3507
|
props: ServerComponentProps;
|
|
3457
3508
|
requestUrl: string;
|
|
@@ -3503,7 +3554,11 @@ function runServerStreamModule(
|
|
|
3503
3554
|
data: options.props.data,
|
|
3504
3555
|
},
|
|
3505
3556
|
})
|
|
3506
|
-
: serverOnlyRouteMarkerParts(
|
|
3557
|
+
: serverOnlyRouteMarkerParts(
|
|
3558
|
+
options.routePath,
|
|
3559
|
+
options.markerRequest,
|
|
3560
|
+
options.prerenderVariantCapture === true,
|
|
3561
|
+
);
|
|
3507
3562
|
|
|
3508
3563
|
sink.append("<!DOCTYPE html>");
|
|
3509
3564
|
sink.append(
|
|
@@ -3719,9 +3774,13 @@ async function runServerStreamModuleWithLoading(
|
|
|
3719
3774
|
clientRoute: boolean;
|
|
3720
3775
|
clientReferenceManifest?: readonly ClientReferenceMetadata[] | undefined;
|
|
3721
3776
|
data: Promise<unknown>;
|
|
3777
|
+
dataSettlement: () => ObservedPromiseSettlement<unknown>;
|
|
3722
3778
|
define?: UserConfig["define"] | undefined;
|
|
3779
|
+
errorFile?: string | undefined;
|
|
3723
3780
|
loadingFile: string;
|
|
3724
3781
|
markerRequest: Request;
|
|
3782
|
+
onRenderError?: ((error: unknown) => void) | undefined;
|
|
3783
|
+
prerenderVariantCapture?: boolean | undefined;
|
|
3725
3784
|
pageFile: string;
|
|
3726
3785
|
params: RouteParams;
|
|
3727
3786
|
queryClient: QueryClient;
|
|
@@ -3737,12 +3796,16 @@ async function runServerStreamModuleWithLoading(
|
|
|
3737
3796
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
3738
3797
|
importPolicy?: AppRouterImportPolicy | undefined;
|
|
3739
3798
|
},
|
|
3740
|
-
): Promise<ReadableStream<Uint8Array
|
|
3741
|
-
const loadingProps = withTrackedRequest(
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
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
|
+
);
|
|
3746
3809
|
const layoutShells = await layoutShellsForPage(
|
|
3747
3810
|
options.appDir,
|
|
3748
3811
|
options.pageFile,
|
|
@@ -3776,7 +3839,21 @@ async function runServerStreamModuleWithLoading(
|
|
|
3776
3839
|
request: { url: options.requestUrl },
|
|
3777
3840
|
},
|
|
3778
3841
|
})
|
|
3779
|
-
: serverOnlyRouteMarkerParts(
|
|
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
|
+
}
|
|
3780
3857
|
|
|
3781
3858
|
return renderToReadableStream((sink) => {
|
|
3782
3859
|
sink.append("<!DOCTYPE html>");
|
|
@@ -3794,7 +3871,7 @@ async function runServerStreamModuleWithLoading(
|
|
|
3794
3871
|
|
|
3795
3872
|
sink.append(marker?.prefix ?? "");
|
|
3796
3873
|
|
|
3797
|
-
|
|
3874
|
+
renderOutOfOrderBoundary(
|
|
3798
3875
|
sink,
|
|
3799
3876
|
"mreact-route",
|
|
3800
3877
|
options.data,
|
|
@@ -3802,11 +3879,15 @@ async function runServerStreamModuleWithLoading(
|
|
|
3802
3879
|
await appendServerStreamModule(
|
|
3803
3880
|
code,
|
|
3804
3881
|
boundarySink,
|
|
3805
|
-
withTrackedRequest(
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3882
|
+
withTrackedRequest(
|
|
3883
|
+
{
|
|
3884
|
+
data,
|
|
3885
|
+
params: options.params,
|
|
3886
|
+
queryClient: options.queryClient,
|
|
3887
|
+
},
|
|
3888
|
+
options.request,
|
|
3889
|
+
options.trackedRequest,
|
|
3890
|
+
),
|
|
3810
3891
|
options.pageFile,
|
|
3811
3892
|
options.serverModules,
|
|
3812
3893
|
options.serverModuleCacheVersion,
|
|
@@ -3816,6 +3897,34 @@ async function runServerStreamModuleWithLoading(
|
|
|
3816
3897
|
);
|
|
3817
3898
|
},
|
|
3818
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
|
+
},
|
|
3819
3928
|
placeholder(boundarySink) {
|
|
3820
3929
|
boundarySink.append(loadingHtml);
|
|
3821
3930
|
},
|
|
@@ -3833,70 +3942,6 @@ async function runServerStreamModuleWithLoading(
|
|
|
3833
3942
|
});
|
|
3834
3943
|
}
|
|
3835
3944
|
|
|
3836
|
-
function renderVisibleOutOfOrderBoundary<T>(
|
|
3837
|
-
sink: HtmlSink,
|
|
3838
|
-
id: string,
|
|
3839
|
-
value: T,
|
|
3840
|
-
render: (sink: HtmlSink, value: Awaited<T>) => void | PromiseLike<void>,
|
|
3841
|
-
options: {
|
|
3842
|
-
catch?: (sink: HtmlSink, error: unknown) => void | PromiseLike<void>;
|
|
3843
|
-
placeholder?: (sink: HtmlSink) => void | PromiseLike<void>;
|
|
3844
|
-
placeholderTag?: string;
|
|
3845
|
-
} = {},
|
|
3846
|
-
): void {
|
|
3847
|
-
const placeholderSink = createStringSink();
|
|
3848
|
-
void options.placeholder?.(placeholderSink);
|
|
3849
|
-
const placeholderTag = normalizeVisibleOutOfOrderPlaceholderTag(options.placeholderTag);
|
|
3850
|
-
sink.append(
|
|
3851
|
-
`<${placeholderTag} data-mreact-oob-placeholder="${escapeHtmlAttribute(id)}">${placeholderSink.toString()}</${placeholderTag}>`,
|
|
3852
|
-
);
|
|
3853
|
-
|
|
3854
|
-
const task = renderVisibleOutOfOrderFragment(sink, id, value, render, options);
|
|
3855
|
-
|
|
3856
|
-
if (sink.defer === undefined) {
|
|
3857
|
-
void task;
|
|
3858
|
-
return;
|
|
3859
|
-
}
|
|
3860
|
-
|
|
3861
|
-
sink.defer(task);
|
|
3862
|
-
}
|
|
3863
|
-
|
|
3864
|
-
function normalizeVisibleOutOfOrderPlaceholderTag(tag: unknown): string {
|
|
3865
|
-
if (typeof tag !== "string") {
|
|
3866
|
-
return "span";
|
|
3867
|
-
}
|
|
3868
|
-
|
|
3869
|
-
const normalized = tag.trim().toLowerCase();
|
|
3870
|
-
return /^[a-z][a-z0-9-]*$/.test(normalized) ? normalized : "span";
|
|
3871
|
-
}
|
|
3872
|
-
|
|
3873
|
-
async function renderVisibleOutOfOrderFragment<T>(
|
|
3874
|
-
sink: HtmlSink,
|
|
3875
|
-
id: string,
|
|
3876
|
-
value: T,
|
|
3877
|
-
render: (sink: HtmlSink, value: Awaited<T>) => void | PromiseLike<void>,
|
|
3878
|
-
options: {
|
|
3879
|
-
catch?: (sink: HtmlSink, error: unknown) => void | PromiseLike<void>;
|
|
3880
|
-
},
|
|
3881
|
-
): Promise<void> {
|
|
3882
|
-
const fragmentSink = createStringSink();
|
|
3883
|
-
|
|
3884
|
-
await renderAsyncBoundary(
|
|
3885
|
-
fragmentSink,
|
|
3886
|
-
value,
|
|
3887
|
-
render,
|
|
3888
|
-
options.catch === undefined ? {} : { catch: options.catch },
|
|
3889
|
-
);
|
|
3890
|
-
await fragmentSink.drain();
|
|
3891
|
-
|
|
3892
|
-
sink.append(renderVisibleOutOfOrderFragmentHtml(id, fragmentSink.toString()));
|
|
3893
|
-
}
|
|
3894
|
-
|
|
3895
|
-
function renderVisibleOutOfOrderFragmentHtml(id: string, html: string): string {
|
|
3896
|
-
const escapedId = escapeHtmlAttribute(id);
|
|
3897
|
-
return `<template data-mreact-oob-fragment="${escapedId}">${html}</template><mreact-oob-complete hidden data-mreact-oob-complete="${escapedId}"></mreact-oob-complete>`;
|
|
3898
|
-
}
|
|
3899
|
-
|
|
3900
3945
|
async function appendServerStreamModule(
|
|
3901
3946
|
code: string,
|
|
3902
3947
|
sink: HtmlSink,
|
|
@@ -5109,7 +5154,12 @@ async function loadComposedRouteMetadata(options: {
|
|
|
5109
5154
|
composedRouteMetadataCacheCounters,
|
|
5110
5155
|
);
|
|
5111
5156
|
if (cached !== undefined) {
|
|
5112
|
-
|
|
5157
|
+
const entry = await cached;
|
|
5158
|
+
return applyFileConventionMetadataTemplate(
|
|
5159
|
+
entry.metadata,
|
|
5160
|
+
entry.template,
|
|
5161
|
+
options.context.params,
|
|
5162
|
+
);
|
|
5113
5163
|
}
|
|
5114
5164
|
}
|
|
5115
5165
|
|
|
@@ -5119,13 +5169,17 @@ async function loadComposedRouteMetadata(options: {
|
|
|
5119
5169
|
setBoundedCacheEntry(
|
|
5120
5170
|
composedRouteMetadataCache,
|
|
5121
5171
|
cacheKey,
|
|
5122
|
-
Promise.resolve(loaded.metadata),
|
|
5172
|
+
Promise.resolve({ metadata: loaded.metadata, template: loaded.template }),
|
|
5123
5173
|
maxComposedRouteMetadataCacheEntries,
|
|
5124
5174
|
composedRouteMetadataCacheCounters,
|
|
5125
5175
|
);
|
|
5126
5176
|
}
|
|
5127
5177
|
|
|
5128
|
-
return
|
|
5178
|
+
return applyFileConventionMetadataTemplate(
|
|
5179
|
+
loaded.metadata,
|
|
5180
|
+
loaded.template,
|
|
5181
|
+
options.context.params,
|
|
5182
|
+
);
|
|
5129
5183
|
} catch (error) {
|
|
5130
5184
|
if (cacheKey !== undefined) {
|
|
5131
5185
|
composedRouteMetadataCache.delete(cacheKey);
|
|
@@ -5167,7 +5221,11 @@ async function loadComposedRouteMetadataUncached(options: {
|
|
|
5167
5221
|
serverModuleCacheVersion?: string | undefined;
|
|
5168
5222
|
serverSourceFiles?: ReadonlyMap<string, string> | undefined;
|
|
5169
5223
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
5170
|
-
}): Promise<{
|
|
5224
|
+
}): Promise<{
|
|
5225
|
+
dynamic: boolean;
|
|
5226
|
+
metadata: RouteMetadata | undefined;
|
|
5227
|
+
template: FileConventionMetadataTemplate;
|
|
5228
|
+
}> {
|
|
5171
5229
|
const layoutFiles = await shellFilesForPage(
|
|
5172
5230
|
options.appDir,
|
|
5173
5231
|
options.filename,
|
|
@@ -5230,12 +5288,8 @@ async function loadComposedRouteMetadataUncached(options: {
|
|
|
5230
5288
|
|
|
5231
5289
|
return {
|
|
5232
5290
|
dynamic,
|
|
5233
|
-
metadata:
|
|
5234
|
-
|
|
5235
|
-
options.routes,
|
|
5236
|
-
options.filename,
|
|
5237
|
-
options.context.params,
|
|
5238
|
-
),
|
|
5291
|
+
metadata: mergeRouteMetadata(metadata),
|
|
5292
|
+
template: fileConventionMetadataTemplate(options.routes, options.filename),
|
|
5239
5293
|
};
|
|
5240
5294
|
}
|
|
5241
5295
|
|