@reckona/mreact-router 0.0.152 → 0.0.153
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/actions.d.ts +10 -0
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +6 -0
- package/dist/actions.js.map +1 -1
- package/dist/adapters/aws-lambda.d.ts +18 -0
- package/dist/adapters/aws-lambda.d.ts.map +1 -1
- package/dist/adapters/aws-lambda.js +18 -0
- package/dist/adapters/aws-lambda.js.map +1 -1
- package/dist/adapters/cloudflare.d.ts +33 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +33 -0
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/edge.d.ts +5 -0
- package/dist/adapters/edge.d.ts.map +1 -1
- package/dist/adapters/edge.js +5 -0
- package/dist/adapters/edge.js.map +1 -1
- package/dist/adapters/node.d.ts +5 -0
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +5 -0
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/static.d.ts +5 -0
- package/dist/adapters/static.d.ts.map +1 -1
- package/dist/adapters/static.js +5 -0
- package/dist/adapters/static.js.map +1 -1
- package/dist/build.d.ts +15 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +19 -0
- package/dist/build.js.map +1 -1
- package/dist/cache.d.ts +15 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +15 -0
- package/dist/cache.js.map +1 -1
- package/dist/cookies.d.ts +16 -0
- package/dist/cookies.d.ts.map +1 -1
- package/dist/cookies.js +16 -0
- package/dist/cookies.js.map +1 -1
- package/dist/dev-server.d.ts +5 -0
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +5 -0
- package/dist/dev-server.js.map +1 -1
- package/dist/link.d.ts +10 -0
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +5 -0
- package/dist/link.js.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +39 -4
- package/dist/middleware.js.map +1 -1
- package/dist/navigation.d.ts +53 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +48 -0
- package/dist/navigation.js.map +1 -1
- package/dist/render.d.ts +28 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +127 -27
- package/dist/render.js.map +1 -1
- package/dist/route-module-loader.d.ts +6 -1
- package/dist/route-module-loader.d.ts.map +1 -1
- package/dist/route-module-loader.js +8 -1
- package/dist/route-module-loader.js.map +1 -1
- package/dist/serve.d.ts +5 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +5 -0
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts +19 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +19 -0
- package/dist/session.js.map +1 -1
- package/dist/typed-routes.d.ts +5 -0
- package/dist/typed-routes.d.ts.map +1 -1
- package/dist/typed-routes.js +5 -0
- package/dist/typed-routes.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -1
- package/package.json +11 -11
- package/src/actions.ts +18 -0
- package/src/adapters/aws-lambda.ts +18 -0
- package/src/adapters/cloudflare.ts +33 -0
- package/src/adapters/edge.ts +5 -0
- package/src/adapters/node.ts +5 -0
- package/src/adapters/static.ts +5 -0
- package/src/build.ts +19 -0
- package/src/cache.ts +15 -0
- package/src/cookies.ts +16 -0
- package/src/dev-server.ts +5 -0
- package/src/link.ts +10 -0
- package/src/middleware.ts +54 -4
- package/src/navigation.ts +53 -0
- package/src/render.ts +164 -25
- package/src/route-module-loader.ts +15 -0
- package/src/serve.ts +5 -0
- package/src/session.ts +19 -0
- package/src/typed-routes.ts +5 -0
- package/src/types.ts +15 -0
package/src/render.ts
CHANGED
|
@@ -137,6 +137,7 @@ import {
|
|
|
137
137
|
prebuiltRouteLoaderModuleArtifact,
|
|
138
138
|
prebuiltServerComponentModuleCode,
|
|
139
139
|
prebuiltServerModuleOutputMatches,
|
|
140
|
+
prebuiltServerModuleOutputOptionsMatch,
|
|
140
141
|
type BuiltServerModuleOutputLike,
|
|
141
142
|
} from "./route-module-loader.js";
|
|
142
143
|
|
|
@@ -353,6 +354,8 @@ async function preloadBuiltPageRouteModules(options: {
|
|
|
353
354
|
options.serverModuleCacheVersion,
|
|
354
355
|
options.define,
|
|
355
356
|
options.vitePlugins,
|
|
357
|
+
undefined,
|
|
358
|
+
{ serverAwaitHydration: options.analysis.clientInference.client },
|
|
356
359
|
);
|
|
357
360
|
}
|
|
358
361
|
|
|
@@ -476,6 +479,7 @@ type StreamModuleExports = Record<string, unknown> & {
|
|
|
476
479
|
|
|
477
480
|
const serverTransformCache = new Map<string, TransformOutput>();
|
|
478
481
|
const serverTransformCacheCounters = createRouterRuntimeCacheCounters();
|
|
482
|
+
let productionDynamicServerTransformWarned = false;
|
|
479
483
|
const serverSourceFileCache = new Map<string, Promise<string>>();
|
|
480
484
|
const serverSourceFileCacheCounters = createRouterRuntimeCacheCounters();
|
|
481
485
|
const routeSourceAnalysisCache = new Map<string, Promise<RouteSourceAnalysis>>();
|
|
@@ -640,6 +644,17 @@ function finishRenderTimingPhase(
|
|
|
640
644
|
timing.phases[phaseName] = logDurationMs(startedAt);
|
|
641
645
|
}
|
|
642
646
|
|
|
647
|
+
async function readSettledPromiseAtNextTask<T>(
|
|
648
|
+
promise: Promise<T>,
|
|
649
|
+
): Promise<{ settled: true; value: T } | { settled: false }> {
|
|
650
|
+
return await Promise.race([
|
|
651
|
+
promise.then((value) => ({ settled: true as const, value })),
|
|
652
|
+
new Promise<{ settled: false }>((resolve) => {
|
|
653
|
+
setTimeout(() => resolve({ settled: false }), 0);
|
|
654
|
+
}),
|
|
655
|
+
]);
|
|
656
|
+
}
|
|
657
|
+
|
|
643
658
|
function addRenderTimingPhaseDuration(
|
|
644
659
|
timing: RenderTiming | undefined,
|
|
645
660
|
startedAt: number | undefined,
|
|
@@ -652,6 +667,12 @@ function addRenderTimingPhaseDuration(
|
|
|
652
667
|
timing.phases[phaseName] = (timing.phases[phaseName] ?? 0) + logDurationMs(startedAt);
|
|
653
668
|
}
|
|
654
669
|
|
|
670
|
+
function routeLoaderMayReturnControlResponse(code: string): boolean {
|
|
671
|
+
return /\b(?:redirect|redirectExternal|rewrite|notFound|throwNotFound)\s*\(/.test(code) ||
|
|
672
|
+
/\bnew\s+Response\s*\(/.test(code) ||
|
|
673
|
+
/\bResponse\.(?:redirect|json)\s*\(/.test(code);
|
|
674
|
+
}
|
|
675
|
+
|
|
655
676
|
async function waitForRenderPreload(
|
|
656
677
|
options: Pick<RenderAppRequestOptions, "preload">,
|
|
657
678
|
timing: RenderTiming | undefined,
|
|
@@ -835,6 +856,7 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
835
856
|
appDir: options.appDir,
|
|
836
857
|
filename: "not-found.mreact.tsx",
|
|
837
858
|
pathname: url.pathname,
|
|
859
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
838
860
|
});
|
|
839
861
|
|
|
840
862
|
const response = await renderSpecialRoute({
|
|
@@ -1208,8 +1230,9 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1208
1230
|
}
|
|
1209
1231
|
|
|
1210
1232
|
let streamData: unknown;
|
|
1211
|
-
|
|
1212
|
-
|
|
1233
|
+
let streamDataPromise = dataPromise ?? Promise.resolve(undefined);
|
|
1234
|
+
phaseStartedAt = renderTimingPhaseStartedAt(timing);
|
|
1235
|
+
if (loadingFile === undefined || routeLoaderMayReturnControlResponse(code)) {
|
|
1213
1236
|
try {
|
|
1214
1237
|
streamData = dataPromise === undefined ? undefined : await dataPromise;
|
|
1215
1238
|
} finally {
|
|
@@ -1219,6 +1242,20 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1219
1242
|
emitRenderTiming(options, timing, streamData.status);
|
|
1220
1243
|
return streamData;
|
|
1221
1244
|
}
|
|
1245
|
+
streamDataPromise = Promise.resolve(streamData);
|
|
1246
|
+
} else {
|
|
1247
|
+
try {
|
|
1248
|
+
const settledData = await readSettledPromiseAtNextTask(streamDataPromise);
|
|
1249
|
+
if (settledData.settled) {
|
|
1250
|
+
if (settledData.value instanceof Response) {
|
|
1251
|
+
emitRenderTiming(options, timing, settledData.value.status);
|
|
1252
|
+
return settledData.value;
|
|
1253
|
+
}
|
|
1254
|
+
streamDataPromise = Promise.resolve(settledData.value);
|
|
1255
|
+
}
|
|
1256
|
+
} finally {
|
|
1257
|
+
finishRenderTimingPhase(timing, phaseStartedAt, "loaderWaitMs");
|
|
1258
|
+
}
|
|
1222
1259
|
}
|
|
1223
1260
|
|
|
1224
1261
|
await waitForRenderPreload(options, timing);
|
|
@@ -1249,7 +1286,7 @@ async function renderAppRequestInternal(options: RenderAppRequestOptions): Promi
|
|
|
1249
1286
|
appDir: options.appDir,
|
|
1250
1287
|
assetBaseUrl: options.assetBaseUrl,
|
|
1251
1288
|
clientRoute,
|
|
1252
|
-
data:
|
|
1289
|
+
data: streamDataPromise,
|
|
1253
1290
|
define: options.define,
|
|
1254
1291
|
loadingFile,
|
|
1255
1292
|
pageFile: matched.route.file,
|
|
@@ -1737,16 +1774,18 @@ async function nearestBoundaryFileForPath(options: {
|
|
|
1737
1774
|
appDir: string;
|
|
1738
1775
|
filename: string;
|
|
1739
1776
|
pathname: string;
|
|
1777
|
+
serverSourceFiles?: ReadonlyMap<string, string> | undefined;
|
|
1740
1778
|
}): Promise<string> {
|
|
1741
1779
|
const parts = options.pathname
|
|
1742
1780
|
.replace(/^\/+|\/+$/g, "")
|
|
1743
1781
|
.split("/")
|
|
1744
|
-
.filter((part) => part.length > 0);
|
|
1782
|
+
.filter((part) => part.length > 0 && part !== "." && part !== "..");
|
|
1745
1783
|
|
|
1746
|
-
return nearestBoundaryFileFromParts({
|
|
1784
|
+
return await nearestBoundaryFileFromParts({
|
|
1747
1785
|
appDir: options.appDir,
|
|
1748
1786
|
filename: options.filename,
|
|
1749
1787
|
parts,
|
|
1788
|
+
serverSourceFiles: options.serverSourceFiles,
|
|
1750
1789
|
});
|
|
1751
1790
|
}
|
|
1752
1791
|
|
|
@@ -1754,11 +1793,19 @@ async function nearestBoundaryFileFromParts(options: {
|
|
|
1754
1793
|
appDir: string;
|
|
1755
1794
|
filename: string;
|
|
1756
1795
|
parts: string[];
|
|
1796
|
+
serverSourceFiles?: ReadonlyMap<string, string> | undefined;
|
|
1757
1797
|
}): Promise<string> {
|
|
1758
1798
|
for (let count = options.parts.length; count >= 0; count -= 1) {
|
|
1759
1799
|
for (const filename of boundaryFilenameCandidates(options.filename)) {
|
|
1760
1800
|
const candidate = join(options.appDir, ...options.parts.slice(0, count), filename);
|
|
1761
1801
|
|
|
1802
|
+
if (options.serverSourceFiles !== undefined) {
|
|
1803
|
+
if (options.serverSourceFiles.has(candidate)) {
|
|
1804
|
+
return candidate;
|
|
1805
|
+
}
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1762
1809
|
try {
|
|
1763
1810
|
await access(candidate);
|
|
1764
1811
|
return candidate;
|
|
@@ -1835,10 +1882,16 @@ async function renderSpecialRoute(options: {
|
|
|
1835
1882
|
textFallback: string;
|
|
1836
1883
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
1837
1884
|
}): Promise<Response> {
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1885
|
+
if (options.serverSourceFiles !== undefined) {
|
|
1886
|
+
if (!options.serverSourceFiles.has(options.routeFile)) {
|
|
1887
|
+
return new Response(options.textFallback, { status: options.status });
|
|
1888
|
+
}
|
|
1889
|
+
} else {
|
|
1890
|
+
try {
|
|
1891
|
+
await access(options.routeFile);
|
|
1892
|
+
} catch {
|
|
1893
|
+
return new Response(options.textFallback, { status: options.status });
|
|
1894
|
+
}
|
|
1842
1895
|
}
|
|
1843
1896
|
|
|
1844
1897
|
const props = {
|
|
@@ -1982,7 +2035,10 @@ async function dispatchServerRoute(options: {
|
|
|
1982
2035
|
const handler = module[options.request.method] ?? module.ALL ?? module.default;
|
|
1983
2036
|
|
|
1984
2037
|
if (typeof handler !== "function") {
|
|
1985
|
-
return new Response("Method Not Allowed", {
|
|
2038
|
+
return new Response("Method Not Allowed", {
|
|
2039
|
+
headers: { allow: allowedServerRouteMethods(module).join(", ") },
|
|
2040
|
+
status: 405,
|
|
2041
|
+
});
|
|
1986
2042
|
}
|
|
1987
2043
|
|
|
1988
2044
|
let response: unknown;
|
|
@@ -2007,6 +2063,12 @@ async function dispatchServerRoute(options: {
|
|
|
2007
2063
|
: new Response("Invalid route response", { status: 500 });
|
|
2008
2064
|
}
|
|
2009
2065
|
|
|
2066
|
+
function allowedServerRouteMethods(module: Record<string, unknown>): string[] {
|
|
2067
|
+
return ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"].filter(
|
|
2068
|
+
(method) => typeof module[method] === "function",
|
|
2069
|
+
);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2010
2072
|
async function dispatchConventionAssetRoute(options: {
|
|
2011
2073
|
file: string;
|
|
2012
2074
|
request: Request;
|
|
@@ -2544,7 +2606,10 @@ function transformServerModule(options: {
|
|
|
2544
2606
|
if (
|
|
2545
2607
|
artifact !== undefined &&
|
|
2546
2608
|
artifact.sourceHash === sourceHash &&
|
|
2547
|
-
|
|
2609
|
+
prebuiltServerModuleOutputOptionsMatch(
|
|
2610
|
+
artifact,
|
|
2611
|
+
options.serverAwaitHydration === true ? { serverAwaitHydration: true } : {},
|
|
2612
|
+
)
|
|
2548
2613
|
) {
|
|
2549
2614
|
return {
|
|
2550
2615
|
code: artifact.code,
|
|
@@ -2578,6 +2643,8 @@ function transformServerModule(options: {
|
|
|
2578
2643
|
return cached;
|
|
2579
2644
|
}
|
|
2580
2645
|
|
|
2646
|
+
warnProductionDynamicServerTransform();
|
|
2647
|
+
|
|
2581
2648
|
const output = transform({
|
|
2582
2649
|
code: options.code,
|
|
2583
2650
|
...(options.clientBoundaryImports === undefined
|
|
@@ -2605,6 +2672,19 @@ function transformServerModule(options: {
|
|
|
2605
2672
|
return output;
|
|
2606
2673
|
}
|
|
2607
2674
|
|
|
2675
|
+
export const __transformServerModuleForTesting = transformServerModule;
|
|
2676
|
+
|
|
2677
|
+
function warnProductionDynamicServerTransform(): void {
|
|
2678
|
+
if (process.env.NODE_ENV !== "production" || productionDynamicServerTransformWarned) {
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
productionDynamicServerTransformWarned = true;
|
|
2683
|
+
console.warn(
|
|
2684
|
+
"mreact router: dynamic server transform path ran in production. Production deployments should provide prebuilt serverModules; otherwise request-time transforms use development compiler settings.",
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2608
2688
|
async function analyzeRouteSource(options: {
|
|
2609
2689
|
appDir: string;
|
|
2610
2690
|
artifact?: BuiltRouteSourceAnalysisSummary | undefined;
|
|
@@ -3442,12 +3522,14 @@ async function loadServerStreamModule(
|
|
|
3442
3522
|
define?: UserConfig["define"] | undefined,
|
|
3443
3523
|
vitePlugins?: readonly PluginOption[] | undefined,
|
|
3444
3524
|
importPolicy?: AppRouterImportPolicy | undefined,
|
|
3525
|
+
outputOptions: { serverAwaitHydration?: boolean } = {},
|
|
3445
3526
|
): Promise<StreamModuleExports> {
|
|
3446
3527
|
const artifactCode = serverModules?.get(sourcefile)?.stream;
|
|
3447
3528
|
const codeHash = memoizedHashText(code);
|
|
3448
|
-
const prebuiltCode = prebuiltServerComponentModuleCode(artifactCode, code, codeHash);
|
|
3529
|
+
const prebuiltCode = prebuiltServerComponentModuleCode(artifactCode, code, codeHash, outputOptions);
|
|
3449
3530
|
if (
|
|
3450
3531
|
artifactCode !== undefined &&
|
|
3532
|
+
prebuiltServerModuleOutputOptionsMatch(artifactCode, outputOptions) &&
|
|
3451
3533
|
prebuiltServerModuleOutputMatches(artifactCode, code, codeHash) &&
|
|
3452
3534
|
artifactCode.moduleFile !== undefined
|
|
3453
3535
|
) {
|
|
@@ -4539,10 +4621,7 @@ async function loadComposedRouteMetadata(options: {
|
|
|
4539
4621
|
serverSourceFiles?: ReadonlyMap<string, string> | undefined;
|
|
4540
4622
|
vitePlugins?: readonly PluginOption[] | undefined;
|
|
4541
4623
|
}): Promise<RouteMetadata | undefined> {
|
|
4542
|
-
const cacheKey =
|
|
4543
|
-
options.serverModuleCacheVersion === undefined
|
|
4544
|
-
? undefined
|
|
4545
|
-
: `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}\0${viteDefineCacheKey(options.define)}\0${vitePluginsCacheKey(options.vitePlugins)}`;
|
|
4624
|
+
const cacheKey = composedRouteMetadataCacheKey(options);
|
|
4546
4625
|
if (cacheKey !== undefined) {
|
|
4547
4626
|
const cached = readRouterRuntimeCacheEntry(
|
|
4548
4627
|
composedRouteMetadataCache,
|
|
@@ -4575,6 +4654,22 @@ async function loadComposedRouteMetadata(options: {
|
|
|
4575
4654
|
}
|
|
4576
4655
|
}
|
|
4577
4656
|
|
|
4657
|
+
function composedRouteMetadataCacheKey(options: {
|
|
4658
|
+
appDir: string;
|
|
4659
|
+
code: string;
|
|
4660
|
+
define?: UserConfig["define"] | undefined;
|
|
4661
|
+
filename: string;
|
|
4662
|
+
importPolicy?: AppRouterImportPolicy | undefined;
|
|
4663
|
+
serverModuleCacheVersion?: string | undefined;
|
|
4664
|
+
vitePlugins?: readonly PluginOption[] | undefined;
|
|
4665
|
+
}): string | undefined {
|
|
4666
|
+
return options.serverModuleCacheVersion === undefined
|
|
4667
|
+
? undefined
|
|
4668
|
+
: `${options.appDir}\0${options.filename}\0${options.serverModuleCacheVersion}\0${memoizedHashText(options.code)}\0${importPolicyCacheKey(options.importPolicy)}\0${viteDefineCacheKey(options.define)}\0${vitePluginsCacheKey(options.vitePlugins)}`;
|
|
4669
|
+
}
|
|
4670
|
+
|
|
4671
|
+
export const __composedRouteMetadataCacheKeyForTesting = composedRouteMetadataCacheKey;
|
|
4672
|
+
|
|
4578
4673
|
async function loadComposedRouteMetadataUncached(options: {
|
|
4579
4674
|
appDir: string;
|
|
4580
4675
|
code: string;
|
|
@@ -4775,26 +4870,59 @@ interface InlineCspTag {
|
|
|
4775
4870
|
|
|
4776
4871
|
function inlineCspTags(html: string): InlineCspTag[] {
|
|
4777
4872
|
const tags: InlineCspTag[] = [];
|
|
4778
|
-
const
|
|
4779
|
-
let
|
|
4873
|
+
const lowerHtml = html.toLowerCase();
|
|
4874
|
+
let position = 0;
|
|
4875
|
+
|
|
4876
|
+
while (position < html.length) {
|
|
4877
|
+
const scriptIndex = lowerHtml.indexOf("<script", position);
|
|
4878
|
+
const styleIndex = lowerHtml.indexOf("<style", position);
|
|
4879
|
+
const openerIndex =
|
|
4880
|
+
scriptIndex === -1
|
|
4881
|
+
? styleIndex
|
|
4882
|
+
: styleIndex === -1
|
|
4883
|
+
? scriptIndex
|
|
4884
|
+
: Math.min(scriptIndex, styleIndex);
|
|
4885
|
+
|
|
4886
|
+
if (openerIndex === -1) {
|
|
4887
|
+
break;
|
|
4888
|
+
}
|
|
4780
4889
|
|
|
4781
|
-
|
|
4782
|
-
const
|
|
4890
|
+
const name = lowerHtml.startsWith("<script", openerIndex) ? "script" : "style";
|
|
4891
|
+
const tagEnd = html.indexOf(">", openerIndex + 1);
|
|
4892
|
+
if (tagEnd === -1) {
|
|
4893
|
+
break;
|
|
4894
|
+
}
|
|
4783
4895
|
|
|
4784
|
-
|
|
4896
|
+
const closeToken = `</${name}>`;
|
|
4897
|
+
const closeIndex = lowerHtml.indexOf(closeToken, tagEnd + 1);
|
|
4898
|
+
const nextScriptIndex = lowerHtml.indexOf("<script", tagEnd + 1);
|
|
4899
|
+
const nextStyleIndex = lowerHtml.indexOf("<style", tagEnd + 1);
|
|
4900
|
+
const nextOpenerIndex =
|
|
4901
|
+
nextScriptIndex === -1
|
|
4902
|
+
? nextStyleIndex
|
|
4903
|
+
: nextStyleIndex === -1
|
|
4904
|
+
? nextScriptIndex
|
|
4905
|
+
: Math.min(nextScriptIndex, nextStyleIndex);
|
|
4906
|
+
|
|
4907
|
+
if (closeIndex === -1 || (nextOpenerIndex !== -1 && nextOpenerIndex < closeIndex)) {
|
|
4908
|
+
position = tagEnd + 1;
|
|
4785
4909
|
continue;
|
|
4786
4910
|
}
|
|
4787
4911
|
|
|
4788
4912
|
tags.push({
|
|
4789
|
-
attributes: parseTagAttributes(
|
|
4790
|
-
content:
|
|
4913
|
+
attributes: parseTagAttributes(html.slice(openerIndex + name.length + 1, tagEnd)),
|
|
4914
|
+
content: html.slice(tagEnd + 1, closeIndex),
|
|
4791
4915
|
name,
|
|
4792
4916
|
});
|
|
4917
|
+
|
|
4918
|
+
position = closeIndex + closeToken.length;
|
|
4793
4919
|
}
|
|
4794
4920
|
|
|
4795
4921
|
return tags;
|
|
4796
4922
|
}
|
|
4797
4923
|
|
|
4924
|
+
export const __inlineCspTagsForTesting = inlineCspTags;
|
|
4925
|
+
|
|
4798
4926
|
function parseTagAttributes(source: string): ReadonlyMap<string, string> {
|
|
4799
4927
|
const attributes = new Map<string, string>();
|
|
4800
4928
|
const pattern = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
@@ -4842,7 +4970,7 @@ function injectQueryState(html: string, state: DehydratedQueryClient): string {
|
|
|
4842
4970
|
)}</script>`;
|
|
4843
4971
|
|
|
4844
4972
|
return /<\/body>/i.test(html)
|
|
4845
|
-
? html
|
|
4973
|
+
? replaceFinalBodyCloseTag(html, script)
|
|
4846
4974
|
: `${html}${script}`;
|
|
4847
4975
|
}
|
|
4848
4976
|
|
|
@@ -4856,10 +4984,21 @@ function injectAuthSessionClaims(html: string, claims: unknown): string {
|
|
|
4856
4984
|
)}</script>`;
|
|
4857
4985
|
|
|
4858
4986
|
return /<\/body>/i.test(html)
|
|
4859
|
-
? html
|
|
4987
|
+
? replaceFinalBodyCloseTag(html, script)
|
|
4860
4988
|
: `${html}${script}`;
|
|
4861
4989
|
}
|
|
4862
4990
|
|
|
4991
|
+
function replaceFinalBodyCloseTag(html: string, insertion: string): string {
|
|
4992
|
+
const matches = [...html.matchAll(/<\/body>/gi)];
|
|
4993
|
+
const last = matches.at(-1);
|
|
4994
|
+
|
|
4995
|
+
if (last?.index === undefined) {
|
|
4996
|
+
return `${html}${insertion}`;
|
|
4997
|
+
}
|
|
4998
|
+
|
|
4999
|
+
return `${html.slice(0, last.index)}${insertion}${html.slice(last.index)}`;
|
|
5000
|
+
}
|
|
5001
|
+
|
|
4863
5002
|
function authIncludesClaims(code: string): boolean {
|
|
4864
5003
|
return /\bexport\s+const\s+auth\s*=\s*["']include-claims["']\s*;?/.test(code);
|
|
4865
5004
|
}
|
|
@@ -58,11 +58,16 @@ export function prebuiltServerComponentModuleCode(
|
|
|
58
58
|
artifact: BuiltServerModuleArtifact["string"] | BuiltServerModuleArtifact["stream"] | undefined,
|
|
59
59
|
code: string,
|
|
60
60
|
codeHash: string,
|
|
61
|
+
options: { serverAwaitHydration?: boolean } = {},
|
|
61
62
|
): string | undefined {
|
|
62
63
|
if (artifact === undefined) {
|
|
63
64
|
return undefined;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
if (!prebuiltServerModuleOutputOptionsMatch(artifact, options)) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
66
71
|
if (!prebuiltServerModuleOutputMatches(artifact, code, codeHash)) {
|
|
67
72
|
return undefined;
|
|
68
73
|
}
|
|
@@ -70,6 +75,16 @@ export function prebuiltServerComponentModuleCode(
|
|
|
70
75
|
return artifact.bundleCode;
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
export function prebuiltServerModuleOutputOptionsMatch(
|
|
79
|
+
artifact: NonNullable<BuiltServerModuleArtifact["string"] | BuiltServerModuleArtifact["stream"]>,
|
|
80
|
+
options: { serverAwaitHydration?: boolean },
|
|
81
|
+
): boolean {
|
|
82
|
+
return (
|
|
83
|
+
(artifact.metadata?.serverAwaitHydration === true) ===
|
|
84
|
+
(options.serverAwaitHydration === true)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
73
88
|
export function prebuiltServerModuleOutputMatches(
|
|
74
89
|
artifact: BuiltServerModuleOutputLike,
|
|
75
90
|
code: string,
|
package/src/serve.ts
CHANGED
|
@@ -577,6 +577,11 @@ async function materializeResponseAsBuffer(response: Response): Promise<Response
|
|
|
577
577
|
});
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Starts a Node HTTP server for an already built app-router output directory.
|
|
582
|
+
*
|
|
583
|
+
* Use this for production-like local serving or custom Node deployments. It loads `.mreact/server` artifacts, applies the generated import policy when configured, enforces host trust options, and returns a `close()` method for orderly shutdown.
|
|
584
|
+
*/
|
|
580
585
|
export async function startServer(
|
|
581
586
|
options: StartServerOptions,
|
|
582
587
|
): Promise<{ close(): Promise<void>; server: Server; url: string }> {
|
package/src/session.ts
CHANGED
|
@@ -72,6 +72,11 @@ function createSessionId(): string {
|
|
|
72
72
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Creates a process-local session store backed by an in-memory LRU-like map.
|
|
77
|
+
*
|
|
78
|
+
* Use it for development, tests, or single-process deployments; production multi-instance deployments should provide a shared `SessionStore`.
|
|
79
|
+
*/
|
|
75
80
|
export function createMemorySessionStore<TData>(
|
|
76
81
|
options: MemorySessionStoreOptions = {},
|
|
77
82
|
): SessionStore<TData> {
|
|
@@ -152,6 +157,9 @@ function nonNegativeIntegerOrDefault(value: number | undefined, fallback: number
|
|
|
152
157
|
return value === undefined || !Number.isInteger(value) || value < 0 ? fallback : value;
|
|
153
158
|
}
|
|
154
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Reads the current session cookie, loads the matching session record, and deletes expired records.
|
|
162
|
+
*/
|
|
155
163
|
export async function getSession<TData>(
|
|
156
164
|
request: Request,
|
|
157
165
|
store: SessionStore<TData>,
|
|
@@ -177,6 +185,9 @@ export async function getSession<TData>(
|
|
|
177
185
|
return record;
|
|
178
186
|
}
|
|
179
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Creates a new session record, stores it, and appends the session cookie to the response.
|
|
190
|
+
*/
|
|
180
191
|
export async function createSession<TData>(
|
|
181
192
|
response: Response,
|
|
182
193
|
store: SessionStore<TData>,
|
|
@@ -200,6 +211,9 @@ export async function createSession<TData>(
|
|
|
200
211
|
return record;
|
|
201
212
|
}
|
|
202
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Deletes the current session record when present and appends an expiring session cookie to the response.
|
|
216
|
+
*/
|
|
203
217
|
export async function destroySession<TData>(
|
|
204
218
|
request: Request,
|
|
205
219
|
response: Response,
|
|
@@ -219,6 +233,11 @@ export async function destroySession<TData>(
|
|
|
219
233
|
});
|
|
220
234
|
}
|
|
221
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Replaces the current session id while preserving the stored session data.
|
|
238
|
+
*
|
|
239
|
+
* Use this after authentication or privilege changes to reduce session fixation risk.
|
|
240
|
+
*/
|
|
222
241
|
export async function rotateSession<TData>(
|
|
223
242
|
request: Request,
|
|
224
243
|
response: Response,
|
package/src/typed-routes.ts
CHANGED
|
@@ -54,6 +54,11 @@ export type AppRouteLinkSegment<Segment extends string> = Segment extends `:${st
|
|
|
54
54
|
? string
|
|
55
55
|
: Segment;
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Builds an internal app-route URL from a route pattern and typed params/search options.
|
|
59
|
+
*
|
|
60
|
+
* Dynamic `:param` and `:...catchAll` segments are URL-encoded, search values are serialized with `URLSearchParams`, and protocol-relative or external paths are rejected.
|
|
61
|
+
*/
|
|
57
62
|
export function href<const Path extends `/${string}`>(
|
|
58
63
|
path: Path,
|
|
59
64
|
...args: HasRouteParams<Path> extends true
|
package/src/types.ts
CHANGED
|
@@ -25,6 +25,11 @@ export interface LoaderContext<TParams extends RouteParams = RouteParams> {
|
|
|
25
25
|
request: Request;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Context passed to a route `generateMetadata` export.
|
|
30
|
+
*
|
|
31
|
+
* Metadata is evaluated on the server after loader data is available, so it can derive head tags, robots data, sitemap entries, and security header directives from the same request and route params as the page.
|
|
32
|
+
*/
|
|
28
33
|
export interface GenerateMetadataContext<
|
|
29
34
|
TData = unknown,
|
|
30
35
|
TParams extends RouteParams = RouteParams,
|
|
@@ -49,6 +54,11 @@ export type PageComponent<TLoader extends RouteLoader> = (
|
|
|
49
54
|
props: PageProps<InferLoaderData<TLoader>, InferLoaderParams<TLoader>>,
|
|
50
55
|
) => ReactCompatNode;
|
|
51
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Preserves the relationship between a page component and its loader when authoring route files.
|
|
59
|
+
*
|
|
60
|
+
* Wrap a default page export with `definePage<typeof loader>(...)` to infer `data` and `params` props from the route loader without changing runtime behavior.
|
|
61
|
+
*/
|
|
52
62
|
export function definePage<TLoader extends RouteLoader>(
|
|
53
63
|
component: PageComponent<TLoader>,
|
|
54
64
|
): PageComponent<TLoader> {
|
|
@@ -73,6 +83,11 @@ export interface MetadataImage {
|
|
|
73
83
|
width?: MetadataScalar;
|
|
74
84
|
}
|
|
75
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Declarative metadata returned by route metadata exports.
|
|
88
|
+
*
|
|
89
|
+
* Values are rendered into the document head and response headers by the app router; use `generateMetadata` when the values depend on loader data, params, or the current request.
|
|
90
|
+
*/
|
|
76
91
|
export interface RouteMetadata {
|
|
77
92
|
alternates?: {
|
|
78
93
|
canonical?: MetadataScalar;
|