@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/dist/render.js
CHANGED
|
@@ -5,9 +5,9 @@ import { access, readFile, stat } from "node:fs/promises";
|
|
|
5
5
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import { formatDiagnostic, transform } from "@reckona/mreact-compiler";
|
|
7
7
|
import { createQueryClient, dehydrate, __MREACT_QUERY_STATE_SCRIPT_ID, installQueryAsyncStorage, runWithQueryClient, } from "@reckona/mreact-query";
|
|
8
|
-
import { createStringSink,
|
|
8
|
+
import { createStringSink, renderOutOfOrderBoundary, renderOutOfOrderReorderScript, renderReactNodeToString, renderToReadableStream, } from "@reckona/mreact-server";
|
|
9
9
|
import { createClientRouteInferenceCache, formatClientRouteInferenceDiagnostic, inferClientRouteModule, } from "./client-route-inference.js";
|
|
10
|
-
import { hydrationMarkerParts, routeIdForPath, withHydrationMarkers,
|
|
10
|
+
import { hydrationMarkerParts, routeMarkerParts, routeIdForPath, withHydrationMarkers, } from "./navigation-runtime.js";
|
|
11
11
|
import { assetPath } from "./assets.js";
|
|
12
12
|
import { escapeHtmlAttribute } from "@reckona/mreact-shared/html-escape";
|
|
13
13
|
import { matchRoute, scanAppRoutes } from "./routes.js";
|
|
@@ -16,8 +16,9 @@ import { dispatchServerActionRequest, prepareRouteServerActionPlaceholders, prep
|
|
|
16
16
|
import { serverActionCookie } from "./csrf.js";
|
|
17
17
|
import { activeRouteCacheContext, withRouteCacheContext, cachedRouteResponse, cacheRouteResponse, routeCacheKey, routeCachePolicyFromSource, } from "./cache.js";
|
|
18
18
|
import { trackRequestHeaderReads, withTrackedRequest, } from "./request-header-tracking.js";
|
|
19
|
-
import { configuredHstsHeader, hasInvalidConfiguredHsts
|
|
19
|
+
import { configuredHstsHeader, hasInvalidConfiguredHsts } from "./security-headers.js";
|
|
20
20
|
import { resolveRouterCacheLimit } from "./cache-config.js";
|
|
21
|
+
import { protectNonceBearingResponse } from "./prerender-entry.js";
|
|
21
22
|
import { importAppRouterBuiltFileModule, importAppRouterFileModule, importAppRouterSourceModule, fileImportMetaUrlPlugin, rewriteCompatVendorPlaceholderImportsForRunner, } from "./module-runner.js";
|
|
22
23
|
import { bytesResponse, htmlResponse } from "./http.js";
|
|
23
24
|
import { isNotFoundError, isRedirectError, rewriteLocation } from "./navigation.js";
|
|
@@ -30,7 +31,7 @@ import { bundleRouterModule } from "./bundle-pipeline.js";
|
|
|
30
31
|
import { viteDefineCacheKey, vitePluginsCacheKey } from "./vite-plugin-cache-key.js";
|
|
31
32
|
import { invokeRouterInstrumentation, traceContextFromRequest, } from "./trace.js";
|
|
32
33
|
import { mergeRouteMiddlewareControls, middlewareMatches, parseRouteMiddlewareControl, parseStaticMiddlewareConfig, shouldSkipMiddleware, validateRouteMiddlewareControl, } from "./middleware.js";
|
|
33
|
-
import {
|
|
34
|
+
import { applyFileConventionMetadataTemplate, fileConventionMetadataTemplate, injectHeadMetadata, mergeRouteMetadata, responseHeadersForMetadata, serializeRobots, serializeSitemap, validateRouteMetadata, } from "./metadata.js";
|
|
34
35
|
import { createSlotRenderContext, markShellBoundary, shellBoundaryId, splitLayoutSlot, warnUnconsumedRouteSlots, } from "./layout-composer.js";
|
|
35
36
|
import { importPolicyCacheKey, memoizedHashText, prebuiltRequestModuleArtifact, prebuiltRouteLoaderModuleArtifact, prebuiltServerComponentModuleCode, prebuiltServerModuleOutputMatches, prebuiltServerModuleOutputOptionsMatch, } from "./route-module-loader.js";
|
|
36
37
|
import { loadRouteDataFromModule, } from "./route-loader-runtime.js";
|
|
@@ -272,7 +273,7 @@ export async function renderAppRequest(options) {
|
|
|
272
273
|
};
|
|
273
274
|
invokeRouterInstrumentation(options.instrumentation?.onRequestStart, requestEvent);
|
|
274
275
|
const response = await renderAppRequestInternal({ ...options, requestUrl: url });
|
|
275
|
-
const finalResponse = await applyAppRouterResponseHook(response, options);
|
|
276
|
+
const finalResponse = protectNonceBearingResponse(await applyAppRouterResponseHook(response, options));
|
|
276
277
|
invokeRouterInstrumentation(options.instrumentation?.onRequestEnd, {
|
|
277
278
|
...requestEvent,
|
|
278
279
|
status: finalResponse.status,
|
|
@@ -291,31 +292,14 @@ function finishRenderTimingPhase(timing, startedAt, phaseName) {
|
|
|
291
292
|
}
|
|
292
293
|
timing.phases[phaseName] = logDurationMs(startedAt);
|
|
293
294
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
]);
|
|
301
|
-
}
|
|
302
|
-
function createDeferredSignal() {
|
|
303
|
-
let resolve;
|
|
304
|
-
const promise = new Promise((innerResolve) => {
|
|
305
|
-
resolve = innerResolve;
|
|
295
|
+
function observePromiseSettlement(promise) {
|
|
296
|
+
let settlement = { status: "pending" };
|
|
297
|
+
void promise.then((value) => {
|
|
298
|
+
settlement = { status: "fulfilled", value };
|
|
299
|
+
}, (error) => {
|
|
300
|
+
settlement = { error, status: "rejected" };
|
|
306
301
|
});
|
|
307
|
-
return
|
|
308
|
-
promise,
|
|
309
|
-
resolve() {
|
|
310
|
-
resolve?.();
|
|
311
|
-
},
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
async function waitForLoaderReadyOrSettled(dataPromise, loaderReady) {
|
|
315
|
-
if (loaderReady === undefined) {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
await Promise.race([loaderReady, dataPromise.then(() => undefined, () => undefined)]);
|
|
302
|
+
return () => settlement;
|
|
319
303
|
}
|
|
320
304
|
function addRenderTimingPhaseDuration(timing, startedAt, phaseName) {
|
|
321
305
|
if (timing === undefined || startedAt === undefined) {
|
|
@@ -323,11 +307,6 @@ function addRenderTimingPhaseDuration(timing, startedAt, phaseName) {
|
|
|
323
307
|
}
|
|
324
308
|
timing.phases[phaseName] = (timing.phases[phaseName] ?? 0) + logDurationMs(startedAt);
|
|
325
309
|
}
|
|
326
|
-
function routeLoaderMayReturnControlResponse(code) {
|
|
327
|
-
return (/\b(?:redirect|redirectExternal|rewrite|notFound|throwNotFound)\s*\(/.test(code) ||
|
|
328
|
-
/\bnew\s+Response\s*\(/.test(code) ||
|
|
329
|
-
/\bResponse\.(?:redirect|json)\s*\(/.test(code));
|
|
330
|
-
}
|
|
331
310
|
async function waitForRenderPreload(options, timing) {
|
|
332
311
|
if (options.preload?.wait !== "before-render") {
|
|
333
312
|
return;
|
|
@@ -493,6 +472,7 @@ async function renderAppRequestInternal(options) {
|
|
|
493
472
|
let trackedRequest;
|
|
494
473
|
let sourceUsesRequestInput = true;
|
|
495
474
|
let invalidConfiguredHsts = false;
|
|
475
|
+
let renderedStrictTransportSecurity;
|
|
496
476
|
return (await withRouteCacheContext(options.routeCache, async () => {
|
|
497
477
|
try {
|
|
498
478
|
if (matched.route.kind === "asset") {
|
|
@@ -561,7 +541,8 @@ async function renderAppRequestInternal(options) {
|
|
|
561
541
|
finishRenderTimingPhase(timing, phaseStartedAt, "sourceAnalysisMs");
|
|
562
542
|
const cachePolicy = originalAnalysis.cachePolicy;
|
|
563
543
|
const navigationScript = options.navigationScripts?.get(matched.route.path);
|
|
564
|
-
const
|
|
544
|
+
const navigationRequest = isNavigationRequest(options.request);
|
|
545
|
+
const cacheKey = routeCacheKey(options.appDir, matched.route.path, url, navigationRequest ? "navigation" : "document");
|
|
565
546
|
const mayUseRouteCache = !sourceUsesRequestInput &&
|
|
566
547
|
(cachePolicy === undefined
|
|
567
548
|
? originalAnalysis.usesRuntimeCacheControl
|
|
@@ -625,7 +606,6 @@ async function renderAppRequestInternal(options) {
|
|
|
625
606
|
const clientInference = routeAnalysis.clientInference;
|
|
626
607
|
const clientRoute = clientInference.client;
|
|
627
608
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
628
|
-
const loaderReady = routeAnalysis.hasLoader ? createDeferredSignal() : undefined;
|
|
629
609
|
const dataPromise = routeAnalysis.hasLoader
|
|
630
610
|
? loadRouteDataWithInstrumentation({
|
|
631
611
|
appDir: options.appDir,
|
|
@@ -647,15 +627,18 @@ async function renderAppRequestInternal(options) {
|
|
|
647
627
|
define: options.define,
|
|
648
628
|
vitePlugins: options.vitePlugins,
|
|
649
629
|
timing,
|
|
650
|
-
onLoaderReady: loaderReady?.resolve,
|
|
651
630
|
})
|
|
652
631
|
: undefined;
|
|
632
|
+
// Route analysis and loading-boundary discovery can continue before the loader is awaited.
|
|
633
|
+
// Observe an early rejection immediately so strict unhandled-rejection runtimes do not
|
|
634
|
+
// terminate before the request-level control/error handler receives the same promise.
|
|
635
|
+
void dataPromise?.catch(() => { });
|
|
653
636
|
finishRenderTimingPhase(timing, phaseStartedAt, "loaderStartMs");
|
|
654
637
|
recoveryRoute = {
|
|
655
638
|
clientRoute,
|
|
656
639
|
props: {
|
|
657
640
|
params: matched.params,
|
|
658
|
-
request: { url:
|
|
641
|
+
request: { url: url.pathname },
|
|
659
642
|
},
|
|
660
643
|
routePath: matched.route.path,
|
|
661
644
|
script: clientScript,
|
|
@@ -728,16 +711,11 @@ async function renderAppRequestInternal(options) {
|
|
|
728
711
|
script: clientScript,
|
|
729
712
|
props: {
|
|
730
713
|
params: matched.params,
|
|
731
|
-
request: { url:
|
|
714
|
+
request: { url: url.pathname },
|
|
732
715
|
data,
|
|
733
716
|
},
|
|
734
717
|
})
|
|
735
|
-
:
|
|
736
|
-
? withRouteMarkers({
|
|
737
|
-
html: pageHtml,
|
|
738
|
-
routePath: matched.route.path,
|
|
739
|
-
})
|
|
740
|
-
: pageHtml;
|
|
718
|
+
: withServerOnlyRouteMarkers(pageHtml, matched.route.path, options.request, options.prerenderVariantCapture === true);
|
|
741
719
|
let html = await runWithQueryClient(queryClient, () => applyLayouts({
|
|
742
720
|
appDir: options.appDir,
|
|
743
721
|
pageFile: matched.route.file,
|
|
@@ -782,7 +760,7 @@ async function renderAppRequestInternal(options) {
|
|
|
782
760
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
783
761
|
});
|
|
784
762
|
html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
|
|
785
|
-
html = injectQueryState(html, dehydrate(queryClient));
|
|
763
|
+
html = injectQueryState(html, dehydrate(queryClient, options.dehydrateOptions));
|
|
786
764
|
const response = withOptionalActionCookie(htmlResponse(applyActionHtmlReplacements(`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
787
765
|
assetBaseUrl: options.assetBaseUrl,
|
|
788
766
|
currentStyleSheets: clientStyleSheets,
|
|
@@ -794,43 +772,24 @@ async function renderAppRequestInternal(options) {
|
|
|
794
772
|
"x-mreact-stream": "1",
|
|
795
773
|
}),
|
|
796
774
|
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
775
|
+
appendNavigationVary(response.headers);
|
|
797
776
|
emitRenderTiming(options, timing, response.status);
|
|
798
777
|
return response;
|
|
799
778
|
}
|
|
800
779
|
let streamData;
|
|
801
|
-
let streamDataPromise = dataPromise ?? Promise.resolve(undefined);
|
|
802
780
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
streamData = dataPromise === undefined ? undefined : await dataPromise;
|
|
806
|
-
}
|
|
807
|
-
finally {
|
|
808
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
|
|
809
|
-
}
|
|
810
|
-
if (streamData instanceof Response) {
|
|
811
|
-
emitRenderTiming(options, timing, streamData.status);
|
|
812
|
-
return streamData;
|
|
813
|
-
}
|
|
814
|
-
streamDataPromise = Promise.resolve(streamData);
|
|
781
|
+
try {
|
|
782
|
+
streamData = dataPromise === undefined ? undefined : await dataPromise;
|
|
815
783
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const settledData = await readSettledPromiseAtNextTask(streamDataPromise);
|
|
819
|
-
if (settledData.settled) {
|
|
820
|
-
if (settledData.value instanceof Response) {
|
|
821
|
-
emitRenderTiming(options, timing, settledData.value.status);
|
|
822
|
-
return settledData.value;
|
|
823
|
-
}
|
|
824
|
-
streamDataPromise = Promise.resolve(settledData.value);
|
|
825
|
-
}
|
|
826
|
-
else {
|
|
827
|
-
await waitForLoaderReadyOrSettled(streamDataPromise, loaderReady?.promise);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
finally {
|
|
831
|
-
finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
|
|
832
|
-
}
|
|
784
|
+
finally {
|
|
785
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
|
|
833
786
|
}
|
|
787
|
+
if (streamData instanceof Response) {
|
|
788
|
+
emitRenderTiming(options, timing, streamData.status);
|
|
789
|
+
return streamData;
|
|
790
|
+
}
|
|
791
|
+
const streamDataPromise = Promise.resolve(streamData);
|
|
792
|
+
const streamDataSettlement = loadingFile === undefined ? undefined : observePromiseSettlement(streamDataPromise);
|
|
834
793
|
await waitForRenderPreload(options, timing);
|
|
835
794
|
await loadServerRenderArtifacts(options, matched.route.file, timing);
|
|
836
795
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
@@ -851,19 +810,31 @@ async function renderAppRequestInternal(options) {
|
|
|
851
810
|
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
852
811
|
});
|
|
853
812
|
}
|
|
854
|
-
if (loadingFile !== undefined) {
|
|
813
|
+
if (loadingFile !== undefined && streamDataSettlement !== undefined) {
|
|
855
814
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
856
|
-
const
|
|
815
|
+
const errorFile = await nearestExistingBoundaryFileForPage({
|
|
816
|
+
appDir: options.appDir,
|
|
817
|
+
filename: "error.mreact.tsx",
|
|
818
|
+
pageFile: matched.route.file,
|
|
819
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
820
|
+
});
|
|
821
|
+
const streamResult = await runServerStreamModuleWithLoading(output.code, {
|
|
857
822
|
appDir: options.appDir,
|
|
858
823
|
assetBaseUrl: options.assetBaseUrl,
|
|
859
824
|
clientRoute,
|
|
860
825
|
data: streamDataPromise,
|
|
826
|
+
dataSettlement: streamDataSettlement,
|
|
861
827
|
define: options.define,
|
|
828
|
+
errorFile,
|
|
862
829
|
loadingFile,
|
|
830
|
+
markerRequest: options.request,
|
|
831
|
+
onRenderError: options.onRenderError,
|
|
832
|
+
prerenderVariantCapture: options.prerenderVariantCapture,
|
|
863
833
|
pageFile: matched.route.file,
|
|
864
834
|
params: matched.params,
|
|
865
835
|
queryClient,
|
|
866
836
|
request: appRequest,
|
|
837
|
+
requestUrl: url.pathname,
|
|
867
838
|
trackedRequest,
|
|
868
839
|
routePath: matched.route.path,
|
|
869
840
|
routeScripts: options.clientScripts,
|
|
@@ -877,9 +848,14 @@ async function renderAppRequestInternal(options) {
|
|
|
877
848
|
clientReferenceManifest: output.metadata.clientReferenceManifest,
|
|
878
849
|
});
|
|
879
850
|
finishRenderTimingPhase(timing, phaseStartedAt, "streamConstructionMs");
|
|
880
|
-
|
|
851
|
+
if (streamResult instanceof Response) {
|
|
852
|
+
emitRenderTiming(options, timing, streamResult.status);
|
|
853
|
+
return streamResult;
|
|
854
|
+
}
|
|
855
|
+
const response = withOptionalActionCookie(new Response(streamResult, {
|
|
881
856
|
headers: streamShellResponseHeaders,
|
|
882
857
|
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
858
|
+
appendNavigationVary(response.headers);
|
|
883
859
|
emitRenderTiming(options, timing, response.status);
|
|
884
860
|
return response;
|
|
885
861
|
}
|
|
@@ -894,7 +870,10 @@ async function renderAppRequestInternal(options) {
|
|
|
894
870
|
appDir: options.appDir,
|
|
895
871
|
assetBaseUrl: options.assetBaseUrl,
|
|
896
872
|
pageFile: matched.route.file,
|
|
873
|
+
markerRequest: options.request,
|
|
874
|
+
prerenderVariantCapture: options.prerenderVariantCapture,
|
|
897
875
|
props,
|
|
876
|
+
requestUrl: url.pathname,
|
|
898
877
|
routePath: matched.route.path,
|
|
899
878
|
routeScripts: options.clientScripts,
|
|
900
879
|
serverModules: options.serverModules,
|
|
@@ -912,6 +891,7 @@ async function renderAppRequestInternal(options) {
|
|
|
912
891
|
const response = withOptionalActionCookie(new Response(stream, {
|
|
913
892
|
headers: streamShellResponseHeaders,
|
|
914
893
|
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
894
|
+
appendNavigationVary(response.headers);
|
|
915
895
|
emitRenderTiming(options, timing, response.status);
|
|
916
896
|
return response;
|
|
917
897
|
}
|
|
@@ -974,16 +954,11 @@ async function renderAppRequestInternal(options) {
|
|
|
974
954
|
script: clientScript,
|
|
975
955
|
props: {
|
|
976
956
|
params: matched.params,
|
|
977
|
-
request: { url:
|
|
957
|
+
request: { url: url.pathname },
|
|
978
958
|
data,
|
|
979
959
|
},
|
|
980
960
|
})
|
|
981
|
-
:
|
|
982
|
-
? withRouteMarkers({
|
|
983
|
-
html: pageHtml,
|
|
984
|
-
routePath: matched.route.path,
|
|
985
|
-
})
|
|
986
|
-
: pageHtml;
|
|
961
|
+
: withServerOnlyRouteMarkers(pageHtml, matched.route.path, options.request, options.prerenderVariantCapture === true);
|
|
987
962
|
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
988
963
|
let html = await runWithQueryClient(queryClient, () => applyLayouts({
|
|
989
964
|
appDir: options.appDir,
|
|
@@ -1032,7 +1007,7 @@ async function renderAppRequestInternal(options) {
|
|
|
1032
1007
|
serverModuleCacheVersion: options.serverModuleCacheVersion,
|
|
1033
1008
|
});
|
|
1034
1009
|
html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
|
|
1035
|
-
html = injectQueryState(html, dehydrate(queryClient));
|
|
1010
|
+
html = injectQueryState(html, dehydrate(queryClient, options.dehydrateOptions));
|
|
1036
1011
|
const response = withOptionalActionCookie(htmlResponse(applyActionHtmlReplacements(`<!DOCTYPE html>${clientNavigationHeadTags({
|
|
1037
1012
|
assetBaseUrl: options.assetBaseUrl,
|
|
1038
1013
|
currentStyleSheets: clientStyleSheets,
|
|
@@ -1042,8 +1017,10 @@ async function renderAppRequestInternal(options) {
|
|
|
1042
1017
|
})}${html}`, preparedActions.htmlReplacements), {
|
|
1043
1018
|
headers: responseHeadersForMetadata(metadata, options.request),
|
|
1044
1019
|
}), preparedActions.csrfToken, preparedActions.csrfTokenIsNew === true);
|
|
1020
|
+
appendNavigationVary(response.headers);
|
|
1045
1021
|
const effectiveCachePolicy = cachePolicy ?? activeRouteCacheContext()?.cachePolicy;
|
|
1046
1022
|
const configuredHsts = configuredHstsHeader(metadata?.security);
|
|
1023
|
+
renderedStrictTransportSecurity = configuredHsts;
|
|
1047
1024
|
invalidConfiguredHsts = hasInvalidConfiguredHsts(metadata?.security);
|
|
1048
1025
|
const finalResponse = preparedActions.hasFormActions
|
|
1049
1026
|
? withRouteCacheHeader(response, effectiveCachePolicy)
|
|
@@ -1136,7 +1113,10 @@ async function renderAppRequestInternal(options) {
|
|
|
1136
1113
|
// after draining the body.
|
|
1137
1114
|
if (options.renderSignals !== undefined) {
|
|
1138
1115
|
const tracked = trackedRequest;
|
|
1139
|
-
options.renderSignals.headerDependent = () => sourceUsesRequestInput ||
|
|
1116
|
+
options.renderSignals.headerDependent = () => sourceUsesRequestInput ||
|
|
1117
|
+
invalidConfiguredHsts ||
|
|
1118
|
+
(tracked?.requestDependent() ?? true);
|
|
1119
|
+
options.renderSignals.strictTransportSecurity = () => renderedStrictTransportSecurity;
|
|
1140
1120
|
}
|
|
1141
1121
|
}
|
|
1142
1122
|
})).value;
|
|
@@ -1274,6 +1254,30 @@ function routePrefetchManifestScript(routeScripts, assetBaseUrl) {
|
|
|
1274
1254
|
function isNavigationRequest(request) {
|
|
1275
1255
|
return request.headers.get("x-mreact-navigation") === "1";
|
|
1276
1256
|
}
|
|
1257
|
+
function withServerOnlyRouteMarkers(html, routePath, request, prerenderVariantCapture) {
|
|
1258
|
+
const marker = serverOnlyRouteMarkerParts(routePath, request, prerenderVariantCapture);
|
|
1259
|
+
return marker === undefined ? html : `${marker.prefix}${html}${marker.suffix}`;
|
|
1260
|
+
}
|
|
1261
|
+
function serverOnlyRouteMarkerParts(routePath, request, prerenderVariantCapture) {
|
|
1262
|
+
if (prerenderVariantCapture) {
|
|
1263
|
+
return prerenderVariantMarkerParts(routePath);
|
|
1264
|
+
}
|
|
1265
|
+
return isNavigationRequest(request) ? routeMarkerParts(routePath) : undefined;
|
|
1266
|
+
}
|
|
1267
|
+
export function prerenderVariantMarkerParts(routePath) {
|
|
1268
|
+
const routeId = routeIdForPath(routePath);
|
|
1269
|
+
return {
|
|
1270
|
+
prefix: `<!--mreact-route-variant-start:${routeId}-->`,
|
|
1271
|
+
suffix: `<!--mreact-route-variant-end:${routeId}-->`,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function appendNavigationVary(headers) {
|
|
1275
|
+
const vary = headers.get("vary");
|
|
1276
|
+
if (vary?.split(",").some((value) => value.trim().toLowerCase() === "x-mreact-navigation") === true) {
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
headers.append("vary", "x-mreact-navigation");
|
|
1280
|
+
}
|
|
1277
1281
|
function isNavigationRouteCacheReloadRequest(request) {
|
|
1278
1282
|
return (isNavigationRequest(request) && request.headers.get("x-mreact-navigation-cache") === "reload");
|
|
1279
1283
|
}
|
|
@@ -1387,15 +1391,7 @@ async function renderSpecialRoute(options) {
|
|
|
1387
1391
|
}
|
|
1388
1392
|
}
|
|
1389
1393
|
const props = {
|
|
1390
|
-
|
|
1391
|
-
debug: errorDebugContext(options.error, options.routePath),
|
|
1392
|
-
error: normalizeErrorForProps(options.error),
|
|
1393
|
-
params: {},
|
|
1394
|
-
queryClient: createQueryClient(),
|
|
1395
|
-
request: options.request,
|
|
1396
|
-
requestId: requestIdForErrorContext(options.request),
|
|
1397
|
-
routeId: routeIdForPath(options.routePath ?? new URL(options.request.url).pathname),
|
|
1398
|
-
traceId: traceContextFromRequest(options.request)?.traceId,
|
|
1394
|
+
...errorBoundaryProps(options.error, options.request, options.routePath),
|
|
1399
1395
|
};
|
|
1400
1396
|
const pageHtml = await renderServerFileToHtml(options.routeFile, props, options.serverModules, options.serverModuleCacheVersion, options.serverSourceFiles, options.define, options.vitePlugins);
|
|
1401
1397
|
const pageHtmlForLayout = options.navigation?.clientRoute === true
|
|
@@ -1429,6 +1425,19 @@ async function renderSpecialRoute(options) {
|
|
|
1429
1425
|
status: options.status,
|
|
1430
1426
|
});
|
|
1431
1427
|
}
|
|
1428
|
+
function errorBoundaryProps(error, request, routePath) {
|
|
1429
|
+
return {
|
|
1430
|
+
data: undefined,
|
|
1431
|
+
debug: errorDebugContext(error, routePath),
|
|
1432
|
+
error: normalizeErrorForProps(error),
|
|
1433
|
+
params: {},
|
|
1434
|
+
queryClient: createQueryClient(),
|
|
1435
|
+
request,
|
|
1436
|
+
requestId: requestIdForErrorContext(request),
|
|
1437
|
+
routeId: routeIdForPath(routePath ?? new URL(request.url).pathname),
|
|
1438
|
+
traceId: traceContextFromRequest(request)?.traceId,
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1432
1441
|
async function renderServerFileToHtml(file, props, serverModules, serverModuleCacheVersion, serverSourceFiles, define, vitePlugins) {
|
|
1433
1442
|
const code = await readServerSourceFile(file, serverModuleCacheVersion, serverSourceFiles);
|
|
1434
1443
|
const output = transformServerModule({
|
|
@@ -2184,11 +2193,11 @@ function runServerStreamModule(code, options) {
|
|
|
2184
2193
|
script: options.script,
|
|
2185
2194
|
props: {
|
|
2186
2195
|
params: options.props.params,
|
|
2187
|
-
request: { url: options.
|
|
2196
|
+
request: { url: options.requestUrl },
|
|
2188
2197
|
data: options.props.data,
|
|
2189
2198
|
},
|
|
2190
2199
|
})
|
|
2191
|
-
:
|
|
2200
|
+
: serverOnlyRouteMarkerParts(options.routePath, options.markerRequest, options.prerenderVariantCapture === true);
|
|
2192
2201
|
sink.append("<!DOCTYPE html>");
|
|
2193
2202
|
sink.append(clientNavigationHeadTags({
|
|
2194
2203
|
assetBaseUrl: options.assetBaseUrl,
|
|
@@ -2317,10 +2326,17 @@ async function runServerStreamModuleWithLoading(code, options) {
|
|
|
2317
2326
|
script: options.script,
|
|
2318
2327
|
props: {
|
|
2319
2328
|
params: options.params,
|
|
2320
|
-
request: { url: options.
|
|
2329
|
+
request: { url: options.requestUrl },
|
|
2321
2330
|
},
|
|
2322
2331
|
})
|
|
2323
|
-
:
|
|
2332
|
+
: serverOnlyRouteMarkerParts(options.routePath, options.markerRequest, options.prerenderVariantCapture === true);
|
|
2333
|
+
const dataSettlement = options.dataSettlement();
|
|
2334
|
+
if (dataSettlement.status === "rejected") {
|
|
2335
|
+
throw dataSettlement.error;
|
|
2336
|
+
}
|
|
2337
|
+
if (dataSettlement.status === "fulfilled" && dataSettlement.value instanceof Response) {
|
|
2338
|
+
return dataSettlement.value;
|
|
2339
|
+
}
|
|
2324
2340
|
return renderToReadableStream((sink) => {
|
|
2325
2341
|
sink.append("<!DOCTYPE html>");
|
|
2326
2342
|
sink.append(clientNavigationHeadTags({
|
|
@@ -2332,13 +2348,31 @@ async function runServerStreamModuleWithLoading(code, options) {
|
|
|
2332
2348
|
sink.append(shell.prefix);
|
|
2333
2349
|
}
|
|
2334
2350
|
sink.append(marker?.prefix ?? "");
|
|
2335
|
-
|
|
2351
|
+
renderOutOfOrderBoundary(sink, "mreact-route", options.data, async (boundarySink, data) => {
|
|
2336
2352
|
await appendServerStreamModule(code, boundarySink, withTrackedRequest({
|
|
2337
2353
|
data,
|
|
2338
2354
|
params: options.params,
|
|
2339
2355
|
queryClient: options.queryClient,
|
|
2340
2356
|
}, options.request, options.trackedRequest), options.pageFile, options.serverModules, options.serverModuleCacheVersion, options.define, options.vitePlugins, options.importPolicy);
|
|
2341
2357
|
}, {
|
|
2358
|
+
async catch(boundarySink, error) {
|
|
2359
|
+
try {
|
|
2360
|
+
options.onRenderError?.(error);
|
|
2361
|
+
}
|
|
2362
|
+
catch {
|
|
2363
|
+
// Error reporting is best-effort and must not truncate a response that already flushed.
|
|
2364
|
+
}
|
|
2365
|
+
if (options.errorFile === undefined) {
|
|
2366
|
+
boundarySink.append("Internal Server Error");
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
try {
|
|
2370
|
+
boundarySink.append(await renderServerFileToHtml(options.errorFile, errorBoundaryProps(error, options.request, options.routePath), options.serverModules, options.serverModuleCacheVersion, options.serverSourceFiles, options.define, options.vitePlugins));
|
|
2371
|
+
}
|
|
2372
|
+
catch {
|
|
2373
|
+
boundarySink.append("Internal Server Error");
|
|
2374
|
+
}
|
|
2375
|
+
},
|
|
2342
2376
|
placeholder(boundarySink) {
|
|
2343
2377
|
boundarySink.append(loadingHtml);
|
|
2344
2378
|
},
|
|
@@ -2351,35 +2385,6 @@ async function runServerStreamModuleWithLoading(code, options) {
|
|
|
2351
2385
|
renderOutOfOrderReorderScript(sink);
|
|
2352
2386
|
});
|
|
2353
2387
|
}
|
|
2354
|
-
function renderVisibleOutOfOrderBoundary(sink, id, value, render, options = {}) {
|
|
2355
|
-
const placeholderSink = createStringSink();
|
|
2356
|
-
void options.placeholder?.(placeholderSink);
|
|
2357
|
-
const placeholderTag = normalizeVisibleOutOfOrderPlaceholderTag(options.placeholderTag);
|
|
2358
|
-
sink.append(`<${placeholderTag} data-mreact-oob-placeholder="${escapeHtmlAttribute(id)}">${placeholderSink.toString()}</${placeholderTag}>`);
|
|
2359
|
-
const task = renderVisibleOutOfOrderFragment(sink, id, value, render, options);
|
|
2360
|
-
if (sink.defer === undefined) {
|
|
2361
|
-
void task;
|
|
2362
|
-
return;
|
|
2363
|
-
}
|
|
2364
|
-
sink.defer(task);
|
|
2365
|
-
}
|
|
2366
|
-
function normalizeVisibleOutOfOrderPlaceholderTag(tag) {
|
|
2367
|
-
if (typeof tag !== "string") {
|
|
2368
|
-
return "span";
|
|
2369
|
-
}
|
|
2370
|
-
const normalized = tag.trim().toLowerCase();
|
|
2371
|
-
return /^[a-z][a-z0-9-]*$/.test(normalized) ? normalized : "span";
|
|
2372
|
-
}
|
|
2373
|
-
async function renderVisibleOutOfOrderFragment(sink, id, value, render, options) {
|
|
2374
|
-
const fragmentSink = createStringSink();
|
|
2375
|
-
await renderAsyncBoundary(fragmentSink, value, render, options.catch === undefined ? {} : { catch: options.catch });
|
|
2376
|
-
await fragmentSink.drain();
|
|
2377
|
-
sink.append(renderVisibleOutOfOrderFragmentHtml(id, fragmentSink.toString()));
|
|
2378
|
-
}
|
|
2379
|
-
function renderVisibleOutOfOrderFragmentHtml(id, html) {
|
|
2380
|
-
const escapedId = escapeHtmlAttribute(id);
|
|
2381
|
-
return `<template data-mreact-oob-fragment="${escapedId}">${html}</template><mreact-oob-complete hidden data-mreact-oob-complete="${escapedId}"></mreact-oob-complete>`;
|
|
2382
|
-
}
|
|
2383
2388
|
async function appendServerStreamModule(code, sink, props, sourcefile, serverModules, serverModuleCacheVersion, define, vitePlugins, importPolicy) {
|
|
2384
2389
|
const module = await loadServerStreamModule(code, sourcefile, serverModules, serverModuleCacheVersion, define, vitePlugins, importPolicy);
|
|
2385
2390
|
const component = selectStreamComponent(module);
|
|
@@ -3089,15 +3094,16 @@ async function loadComposedRouteMetadata(options) {
|
|
|
3089
3094
|
if (cacheKey !== undefined) {
|
|
3090
3095
|
const cached = readRouterRuntimeCacheEntry(composedRouteMetadataCache, cacheKey, composedRouteMetadataCacheCounters);
|
|
3091
3096
|
if (cached !== undefined) {
|
|
3092
|
-
|
|
3097
|
+
const entry = await cached;
|
|
3098
|
+
return applyFileConventionMetadataTemplate(entry.metadata, entry.template, options.context.params);
|
|
3093
3099
|
}
|
|
3094
3100
|
}
|
|
3095
3101
|
try {
|
|
3096
3102
|
const loaded = await loadComposedRouteMetadataUncached(options);
|
|
3097
3103
|
if (cacheKey !== undefined && !loaded.dynamic) {
|
|
3098
|
-
setBoundedCacheEntry(composedRouteMetadataCache, cacheKey, Promise.resolve(loaded.metadata), maxComposedRouteMetadataCacheEntries, composedRouteMetadataCacheCounters);
|
|
3104
|
+
setBoundedCacheEntry(composedRouteMetadataCache, cacheKey, Promise.resolve({ metadata: loaded.metadata, template: loaded.template }), maxComposedRouteMetadataCacheEntries, composedRouteMetadataCacheCounters);
|
|
3099
3105
|
}
|
|
3100
|
-
return loaded.metadata;
|
|
3106
|
+
return applyFileConventionMetadataTemplate(loaded.metadata, loaded.template, options.context.params);
|
|
3101
3107
|
}
|
|
3102
3108
|
catch (error) {
|
|
3103
3109
|
if (cacheKey !== undefined) {
|
|
@@ -3163,7 +3169,8 @@ async function loadComposedRouteMetadataUncached(options) {
|
|
|
3163
3169
|
}
|
|
3164
3170
|
return {
|
|
3165
3171
|
dynamic,
|
|
3166
|
-
metadata:
|
|
3172
|
+
metadata: mergeRouteMetadata(metadata),
|
|
3173
|
+
template: fileConventionMetadataTemplate(options.routes, options.filename),
|
|
3167
3174
|
};
|
|
3168
3175
|
}
|
|
3169
3176
|
function hasMetadataExport(code) {
|