@rangojs/router 0.0.0-experimental.fa8a383a → 0.0.0-experimental.fad716ff
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 +76 -18
- package/dist/bin/rango.js +130 -47
- package/dist/vite/index.js +526 -168
- package/dist/vite/index.js.bak +5448 -0
- package/package.json +2 -2
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +8 -0
- package/skills/links/SKILL.md +3 -1
- package/skills/loader/SKILL.md +53 -43
- package/skills/middleware/SKILL.md +2 -0
- package/skills/parallel/SKILL.md +67 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/route/SKILL.md +31 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/typesafety/SKILL.md +10 -0
- package/src/__internal.ts +1 -1
- package/src/browser/app-version.ts +14 -0
- package/src/browser/navigation-bridge.ts +16 -3
- package/src/browser/navigation-client.ts +64 -40
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/partial-update.ts +37 -4
- package/src/browser/prefetch/fetch.ts +8 -2
- package/src/browser/prefetch/queue.ts +61 -29
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/react/Link.tsx +44 -8
- package/src/browser/react/NavigationProvider.tsx +13 -4
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-router.ts +21 -8
- package/src/browser/rsc-router.tsx +26 -3
- package/src/browser/scroll-restoration.ts +10 -8
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +27 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +211 -72
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +46 -5
- package/src/cache/taint.ts +55 -0
- package/src/client.tsx +2 -56
- package/src/context-var.ts +72 -2
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +3 -1
- package/src/index.ts +12 -0
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/reverse.ts +22 -1
- package/src/route-definition/dsl-helpers.ts +42 -19
- package/src/route-definition/helpers-types.ts +10 -6
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +9 -1
- package/src/route-definition/resolve-handler-use.ts +149 -0
- package/src/route-types.ts +11 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/handler-context.ts +79 -23
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/loader-resolution.ts +156 -21
- package/src/router/match-api.ts +124 -189
- package/src/router/match-middleware/background-revalidation.ts +12 -1
- package/src/router/match-middleware/cache-lookup.ts +72 -13
- package/src/router/match-middleware/cache-store.ts +21 -4
- package/src/router/match-middleware/segment-resolution.ts +53 -0
- package/src/router/match-result.ts +11 -5
- package/src/router/metrics.ts +6 -1
- package/src/router/middleware-types.ts +6 -8
- package/src/router/middleware.ts +2 -5
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +101 -18
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/revalidation.ts +122 -26
- package/src/router/types.ts +1 -0
- package/src/router.ts +54 -5
- package/src/rsc/handler.ts +464 -377
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +14 -2
- package/src/rsc/rsc-rendering.ts +10 -1
- package/src/rsc/server-action.ts +8 -0
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +9 -1
- package/src/server/context.ts +50 -1
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +175 -15
- package/src/ssr/index.tsx +3 -0
- package/src/static-handler.ts +18 -6
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +137 -33
- package/src/types/loader-types.ts +36 -9
- package/src/types/route-entry.ts +1 -1
- package/src/urls/path-helper-types.ts +9 -2
- package/src/urls/path-helper.ts +47 -12
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +16 -6
- package/src/use-loader.tsx +73 -4
- package/src/vite/discovery/bundle-postprocess.ts +30 -33
- package/src/vite/discovery/discover-routers.ts +5 -1
- package/src/vite/discovery/prerender-collection.ts +14 -1
- package/src/vite/discovery/state.ts +13 -4
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +60 -5
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-internal-ids.ts +118 -39
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +19 -2
- package/src/vite/router-discovery.ts +178 -37
- package/src/vite/utils/prerender-utils.ts +18 -0
- package/src/vite/utils/shared-utils.ts +3 -2
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Error boundary segment creation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type
|
|
11
|
+
import { createElement, type ReactNode } from "react";
|
|
12
12
|
import { DataNotFoundError } from "../../errors";
|
|
13
13
|
import {
|
|
14
14
|
createErrorInfo,
|
|
@@ -180,34 +180,39 @@ export function catchSegmentError<TEnv>(
|
|
|
180
180
|
|
|
181
181
|
if (error instanceof DataNotFoundError) {
|
|
182
182
|
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
183
|
+
// Fall back to router's notFound component, then a plain default
|
|
184
|
+
const notFoundOption = deps.notFoundComponent;
|
|
185
|
+
const defaultFallback =
|
|
186
|
+
typeof notFoundOption === "function"
|
|
187
|
+
? notFoundOption({ pathname: pathname ?? "" })
|
|
188
|
+
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
189
|
+
const effectiveNotFoundFallback = notFoundFallback ?? defaultFallback;
|
|
183
190
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
191
|
+
const notFoundInfo = createNotFoundInfo(
|
|
192
|
+
error,
|
|
193
|
+
entry.shortCode,
|
|
194
|
+
entry.type,
|
|
195
|
+
pathname,
|
|
196
|
+
);
|
|
191
197
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
198
|
+
reportError(true, {
|
|
199
|
+
notFound: true,
|
|
200
|
+
message: notFoundInfo.message,
|
|
201
|
+
});
|
|
196
202
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
debugLog("segment", "notFound boundary handled error", {
|
|
204
|
+
segmentId: entry.shortCode,
|
|
205
|
+
message: notFoundInfo.message,
|
|
206
|
+
});
|
|
201
207
|
|
|
202
|
-
|
|
208
|
+
setResponseStatus(404);
|
|
203
209
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
210
|
+
return createNotFoundSegment(
|
|
211
|
+
notFoundInfo,
|
|
212
|
+
effectiveNotFoundFallback,
|
|
213
|
+
entry,
|
|
214
|
+
params,
|
|
215
|
+
);
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
const fallback = deps.findNearestErrorBoundary(entry);
|
|
@@ -41,7 +41,11 @@ import {
|
|
|
41
41
|
} from "./helpers.js";
|
|
42
42
|
import { getRouterContext } from "../router-context.js";
|
|
43
43
|
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
track,
|
|
46
|
+
RSCRouterContext,
|
|
47
|
+
runInsideLoaderScope,
|
|
48
|
+
} from "../../server/context.js";
|
|
45
49
|
|
|
46
50
|
// ---------------------------------------------------------------------------
|
|
47
51
|
// Telemetry helpers
|
|
@@ -232,7 +236,9 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
232
236
|
params: ctx.params,
|
|
233
237
|
loaderId: loader.$$id,
|
|
234
238
|
loaderData: deps.wrapLoaderPromise(
|
|
235
|
-
|
|
239
|
+
runInsideLoaderScope(() =>
|
|
240
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
241
|
+
),
|
|
236
242
|
entry,
|
|
237
243
|
segmentId,
|
|
238
244
|
ctx.pathname,
|
|
@@ -262,29 +268,46 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
262
268
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
263
269
|
const allLoaderSegments: ResolvedSegment[] = [];
|
|
264
270
|
const allMatchedIds: string[] = [];
|
|
271
|
+
const seenIds = new Set<string>();
|
|
265
272
|
|
|
266
273
|
async function collectEntryLoaders(
|
|
267
274
|
entry: EntryData,
|
|
268
275
|
belongsToRoute: boolean,
|
|
269
276
|
shortCodeOverride?: string,
|
|
270
277
|
): Promise<void> {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
278
|
+
// Skip if all loaders from this entry have already been resolved
|
|
279
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
280
|
+
const loaderEntries = entry.loader ?? [];
|
|
281
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
282
|
+
const allAlreadySeen =
|
|
283
|
+
loaderEntries.length > 0 &&
|
|
284
|
+
loaderEntries.every((le, i) =>
|
|
285
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
286
|
+
);
|
|
287
|
+
if (!allAlreadySeen) {
|
|
288
|
+
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
289
|
+
entry,
|
|
290
|
+
context,
|
|
291
|
+
belongsToRoute,
|
|
292
|
+
clientSegmentIds,
|
|
293
|
+
prevParams,
|
|
294
|
+
request,
|
|
295
|
+
prevUrl,
|
|
296
|
+
nextUrl,
|
|
297
|
+
routeKey,
|
|
298
|
+
deps,
|
|
299
|
+
actionContext,
|
|
300
|
+
shortCodeOverride,
|
|
301
|
+
stale,
|
|
302
|
+
);
|
|
303
|
+
for (const seg of segments) {
|
|
304
|
+
if (!seenIds.has(seg.id)) {
|
|
305
|
+
seenIds.add(seg.id);
|
|
306
|
+
allLoaderSegments.push(seg);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
allMatchedIds.push(...matchedIds);
|
|
310
|
+
}
|
|
288
311
|
|
|
289
312
|
const seenParallelEntryIds = new Set<string>();
|
|
290
313
|
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
@@ -296,6 +319,38 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
296
319
|
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
297
320
|
for (const layoutEntry of entry.layout) {
|
|
298
321
|
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
322
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
323
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
324
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
325
|
+
if (
|
|
326
|
+
entry.type === "route" &&
|
|
327
|
+
entry.loader &&
|
|
328
|
+
entry.loader.length > 0 &&
|
|
329
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
330
|
+
) {
|
|
331
|
+
const inherited = await resolveLoadersWithRevalidation(
|
|
332
|
+
entry,
|
|
333
|
+
context,
|
|
334
|
+
childBelongsToRoute,
|
|
335
|
+
clientSegmentIds,
|
|
336
|
+
prevParams,
|
|
337
|
+
request,
|
|
338
|
+
prevUrl,
|
|
339
|
+
nextUrl,
|
|
340
|
+
routeKey,
|
|
341
|
+
deps,
|
|
342
|
+
actionContext,
|
|
343
|
+
layoutEntry.shortCode,
|
|
344
|
+
stale,
|
|
345
|
+
);
|
|
346
|
+
for (const seg of inherited.segments) {
|
|
347
|
+
if (!seenIds.has(seg.id)) {
|
|
348
|
+
seenIds.add(seg.id);
|
|
349
|
+
allLoaderSegments.push(seg);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
allMatchedIds.push(...inherited.matchedIds);
|
|
353
|
+
}
|
|
299
354
|
}
|
|
300
355
|
}
|
|
301
356
|
|
|
@@ -665,13 +720,20 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
665
720
|
return staticComponent;
|
|
666
721
|
}
|
|
667
722
|
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
723
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
724
|
+
// the build handler. At build time (context.build === true), always
|
|
725
|
+
// use the build handler from routeEntry.handler.
|
|
726
|
+
const handler =
|
|
727
|
+
!context.build && routeEntry.liveHandler
|
|
728
|
+
? routeEntry.liveHandler
|
|
729
|
+
: routeEntry.handler;
|
|
668
730
|
if (!routeEntry.loading) {
|
|
669
|
-
const result = handleHandlerResult(await
|
|
731
|
+
const result = handleHandlerResult(await handler(context));
|
|
670
732
|
doneHandler();
|
|
671
733
|
return result;
|
|
672
734
|
}
|
|
673
735
|
if (!actionContext) {
|
|
674
|
-
const result = handleHandlerResult(
|
|
736
|
+
const result = handleHandlerResult(handler(context));
|
|
675
737
|
if (result instanceof Promise) {
|
|
676
738
|
result.finally(doneHandler).catch(() => {});
|
|
677
739
|
const tracked = deps.trackHandler(result, {
|
|
@@ -694,9 +756,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
694
756
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
695
757
|
entryId: entry.id,
|
|
696
758
|
});
|
|
697
|
-
const actionResult = handleHandlerResult(
|
|
698
|
-
await routeEntry.handler(context),
|
|
699
|
-
);
|
|
759
|
+
const actionResult = handleHandlerResult(await handler(context));
|
|
700
760
|
doneHandler();
|
|
701
761
|
return {
|
|
702
762
|
content: Promise.resolve(actionResult),
|
|
@@ -705,10 +765,12 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
705
765
|
() => null,
|
|
706
766
|
);
|
|
707
767
|
|
|
768
|
+
// Normalize void handlers (undefined) to null so the reconciler's
|
|
769
|
+
// component === null checks work consistently for both void and explicit null.
|
|
708
770
|
const resolvedComponent =
|
|
709
771
|
component && typeof component === "object" && "content" in component
|
|
710
|
-
? (component as { content: ReactNode }).content
|
|
711
|
-
: component;
|
|
772
|
+
? ((component as { content: ReactNode }).content ?? null)
|
|
773
|
+
: (component ?? null);
|
|
712
774
|
|
|
713
775
|
const segment: ResolvedSegment = {
|
|
714
776
|
id: entry.shortCode,
|
|
@@ -810,6 +872,7 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
810
872
|
deps,
|
|
811
873
|
actionContext,
|
|
812
874
|
stale,
|
|
875
|
+
entry,
|
|
813
876
|
);
|
|
814
877
|
segments.push(...orphanResult.segments);
|
|
815
878
|
matchedIds.push(...orphanResult.matchedIds);
|
|
@@ -921,6 +984,8 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
921
984
|
deps: SegmentResolutionDeps<TEnv>,
|
|
922
985
|
actionContext?: ActionContext,
|
|
923
986
|
stale?: boolean,
|
|
987
|
+
/** Parent route entry — its loaders are inherited so parallel slots can access them. */
|
|
988
|
+
parentRouteEntry?: EntryData,
|
|
924
989
|
): Promise<SegmentRevalidationResult> {
|
|
925
990
|
invariant(
|
|
926
991
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -948,6 +1013,33 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
948
1013
|
segments.push(...loaderResult.segments);
|
|
949
1014
|
matchedIds.push(...loaderResult.matchedIds);
|
|
950
1015
|
|
|
1016
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
1017
|
+
// can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
|
|
1018
|
+
if (
|
|
1019
|
+
parentRouteEntry &&
|
|
1020
|
+
parentRouteEntry.loader &&
|
|
1021
|
+
parentRouteEntry.loader.length > 0 &&
|
|
1022
|
+
Object.keys(orphan.parallel).length > 0
|
|
1023
|
+
) {
|
|
1024
|
+
const inheritedResult = await resolveLoadersWithRevalidation(
|
|
1025
|
+
parentRouteEntry,
|
|
1026
|
+
context,
|
|
1027
|
+
belongsToRoute,
|
|
1028
|
+
clientSegmentIds,
|
|
1029
|
+
prevParams,
|
|
1030
|
+
request,
|
|
1031
|
+
prevUrl,
|
|
1032
|
+
nextUrl,
|
|
1033
|
+
routeKey,
|
|
1034
|
+
deps,
|
|
1035
|
+
actionContext,
|
|
1036
|
+
orphan.shortCode,
|
|
1037
|
+
stale,
|
|
1038
|
+
);
|
|
1039
|
+
segments.push(...inheritedResult.segments);
|
|
1040
|
+
matchedIds.push(...inheritedResult.matchedIds);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
951
1043
|
// Handler-first: resolve orphan layout handler before its parallels
|
|
952
1044
|
// so ctx.set() values are visible to parallel children.
|
|
953
1045
|
matchedIds.push(orphan.shortCode);
|
|
@@ -1229,6 +1321,10 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1229
1321
|
}
|
|
1230
1322
|
|
|
1231
1323
|
const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
|
|
1324
|
+
if (entry.type === "cache") {
|
|
1325
|
+
const store = RSCRouterContext.getStore();
|
|
1326
|
+
if (store) store.insideCacheScope = true;
|
|
1327
|
+
}
|
|
1232
1328
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
1233
1329
|
const resolved = await resolveWithErrorBoundary(
|
|
1234
1330
|
nonParallelEntry,
|
package/src/router/types.ts
CHANGED
|
@@ -96,6 +96,7 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
96
96
|
findNearestNotFoundBoundary: (
|
|
97
97
|
entry: EntryData | null,
|
|
98
98
|
) => ReactNode | NotFoundBoundaryHandler | null;
|
|
99
|
+
notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
99
100
|
callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
|
|
100
101
|
}
|
|
101
102
|
|
package/src/router.ts
CHANGED
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
import MapRootLayout from "./server/root-layout.js";
|
|
20
20
|
import type { AllUseItems } from "./route-types.js";
|
|
21
21
|
import type { UrlPatterns } from "./urls.js";
|
|
22
|
+
import type { UrlBuilder } from "./urls/pattern-types.js";
|
|
23
|
+
import { urls } from "./urls.js";
|
|
22
24
|
import {
|
|
23
25
|
EntryData,
|
|
24
26
|
InterceptSelectorContext,
|
|
@@ -133,6 +135,7 @@ export function createRouter<TEnv = any>(
|
|
|
133
135
|
const {
|
|
134
136
|
id: userProvidedId,
|
|
135
137
|
$$id: injectedId,
|
|
138
|
+
basename: basenameOption,
|
|
136
139
|
debugPerformance = false,
|
|
137
140
|
document: documentOption,
|
|
138
141
|
defaultErrorBoundary,
|
|
@@ -158,6 +161,13 @@ export function createRouter<TEnv = any>(
|
|
|
158
161
|
originCheck: originCheckOption,
|
|
159
162
|
} = options;
|
|
160
163
|
|
|
164
|
+
// Normalize basename: ensure leading slash, strip trailing slash.
|
|
165
|
+
// A bare "/" is equivalent to no basename.
|
|
166
|
+
const basename =
|
|
167
|
+
basenameOption && basenameOption.replace(/^\/+|\/+$/g, "")
|
|
168
|
+
? "/" + basenameOption.replace(/^\/+|\/+$/g, "")
|
|
169
|
+
: undefined;
|
|
170
|
+
|
|
161
171
|
// Resolve telemetry sink (no-op when not configured)
|
|
162
172
|
const telemetry = resolveSink(telemetrySink);
|
|
163
173
|
|
|
@@ -526,6 +536,7 @@ export function createRouter<TEnv = any>(
|
|
|
526
536
|
trackHandler,
|
|
527
537
|
findNearestErrorBoundary,
|
|
528
538
|
findNearestNotFoundBoundary,
|
|
539
|
+
notFoundComponent: notFound,
|
|
529
540
|
callOnError,
|
|
530
541
|
};
|
|
531
542
|
|
|
@@ -614,6 +625,8 @@ export function createRouter<TEnv = any>(
|
|
|
614
625
|
params: Record<string, string>,
|
|
615
626
|
buildVars?: Record<string, any>,
|
|
616
627
|
isPassthroughRoute?: boolean,
|
|
628
|
+
buildEnv?: TEnv,
|
|
629
|
+
devMode?: boolean,
|
|
617
630
|
) {
|
|
618
631
|
return _matchForPrerender(
|
|
619
632
|
pathname,
|
|
@@ -621,6 +634,8 @@ export function createRouter<TEnv = any>(
|
|
|
621
634
|
prerenderDeps,
|
|
622
635
|
buildVars,
|
|
623
636
|
isPassthroughRoute,
|
|
637
|
+
buildEnv,
|
|
638
|
+
devMode,
|
|
624
639
|
);
|
|
625
640
|
}
|
|
626
641
|
|
|
@@ -628,12 +643,16 @@ export function createRouter<TEnv = any>(
|
|
|
628
643
|
handler: Function,
|
|
629
644
|
handlerId: string,
|
|
630
645
|
routeName?: string,
|
|
646
|
+
buildEnv?: TEnv,
|
|
647
|
+
devMode?: boolean,
|
|
631
648
|
) {
|
|
632
649
|
return _renderStaticSegment<TEnv>(
|
|
633
650
|
handler,
|
|
634
651
|
handlerId,
|
|
635
652
|
mergedRouteMap,
|
|
636
653
|
routeName,
|
|
654
|
+
buildEnv,
|
|
655
|
+
devMode,
|
|
637
656
|
);
|
|
638
657
|
}
|
|
639
658
|
|
|
@@ -658,8 +677,15 @@ export function createRouter<TEnv = any>(
|
|
|
658
677
|
const router: RSCRouterInternal<TEnv, {}> = {
|
|
659
678
|
__brand: RSC_ROUTER_BRAND,
|
|
660
679
|
id: routerId,
|
|
680
|
+
basename,
|
|
681
|
+
|
|
682
|
+
routes(patternsOrBuilder: UrlPatterns<TEnv> | UrlBuilder<TEnv>): any {
|
|
683
|
+
// Wrap builder functions in urls() automatically
|
|
684
|
+
const urlPatterns: UrlPatterns<TEnv> =
|
|
685
|
+
typeof patternsOrBuilder === "function"
|
|
686
|
+
? (urls(patternsOrBuilder) as UrlPatterns<TEnv>)
|
|
687
|
+
: patternsOrBuilder;
|
|
661
688
|
|
|
662
|
-
routes(urlPatterns: UrlPatterns<TEnv>): any {
|
|
663
689
|
// Store reference for runtime manifest generation
|
|
664
690
|
storedUrlPatterns = urlPatterns;
|
|
665
691
|
const currentMountIndex = mountIndex++;
|
|
@@ -707,6 +733,10 @@ export function createRouter<TEnv = any>(
|
|
|
707
733
|
counters: {},
|
|
708
734
|
mountIndex: currentMountIndex,
|
|
709
735
|
cacheProfiles: resolvedCacheProfiles,
|
|
736
|
+
// basename sets the initial URL prefix so all path() patterns
|
|
737
|
+
// are registered with the prefix (e.g. "/admin" + "/users" = "/admin/users").
|
|
738
|
+
// No namePrefix — route names stay unprefixed.
|
|
739
|
+
...(basename ? { urlPrefix: basename } : {}),
|
|
710
740
|
},
|
|
711
741
|
() => {
|
|
712
742
|
handlerResult = urlPatterns.handler() as AllUseItems[];
|
|
@@ -726,7 +756,7 @@ export function createRouter<TEnv = any>(
|
|
|
726
756
|
if (entry.type === "route" && entry.isPrerender) {
|
|
727
757
|
if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
|
|
728
758
|
prerenderRouteKeys.add(name);
|
|
729
|
-
if (entry.
|
|
759
|
+
if (entry.isPassthrough === true) {
|
|
730
760
|
if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
|
|
731
761
|
passthroughRouteKeys.add(name);
|
|
732
762
|
}
|
|
@@ -855,8 +885,18 @@ export function createRouter<TEnv = any>(
|
|
|
855
885
|
patternOrMiddleware: string | MiddlewareFn<TEnv>,
|
|
856
886
|
middleware?: MiddlewareFn<TEnv>,
|
|
857
887
|
): any {
|
|
858
|
-
//
|
|
859
|
-
|
|
888
|
+
// Auto-prefix pattern with basename so router-level middleware
|
|
889
|
+
// patterns are router-relative (e.g. "/users/*" matches "/app/users/*").
|
|
890
|
+
if (basename && typeof patternOrMiddleware === "string") {
|
|
891
|
+
const pattern = patternOrMiddleware;
|
|
892
|
+
const prefixed =
|
|
893
|
+
pattern === "/*" || pattern === "*"
|
|
894
|
+
? `${basename}/*`
|
|
895
|
+
: `${basename}${pattern}`;
|
|
896
|
+
addMiddleware(prefixed, middleware, null);
|
|
897
|
+
} else {
|
|
898
|
+
addMiddleware(patternOrMiddleware, middleware, null);
|
|
899
|
+
}
|
|
860
900
|
return router;
|
|
861
901
|
},
|
|
862
902
|
|
|
@@ -957,6 +997,9 @@ export function createRouter<TEnv = any>(
|
|
|
957
997
|
// Expose source file for per-router type generation
|
|
958
998
|
__sourceFile,
|
|
959
999
|
|
|
1000
|
+
// Expose basename for runtime manifest generation
|
|
1001
|
+
__basename: basename,
|
|
1002
|
+
|
|
960
1003
|
// RSC request handler (lazily created on first call)
|
|
961
1004
|
fetch: (() => {
|
|
962
1005
|
// Handler is created on first call and reused
|
|
@@ -990,6 +1033,10 @@ export function createRouter<TEnv = any>(
|
|
|
990
1033
|
};
|
|
991
1034
|
})(),
|
|
992
1035
|
|
|
1036
|
+
// Low-level route matching for request classification
|
|
1037
|
+
findMatch: (pathname: string, metricsStore?: any) =>
|
|
1038
|
+
findMatch(pathname, metricsStore),
|
|
1039
|
+
|
|
993
1040
|
// Debug utility for manifest inspection
|
|
994
1041
|
debugManifest: () => buildDebugManifest<TEnv>(routesEntries),
|
|
995
1042
|
};
|
|
@@ -998,7 +1045,9 @@ export function createRouter<TEnv = any>(
|
|
|
998
1045
|
RouterRegistry.set(routerId, router);
|
|
999
1046
|
|
|
1000
1047
|
// If urls option was provided, auto-register them
|
|
1001
|
-
if (urlsOption) {
|
|
1048
|
+
if (typeof urlsOption === "function") {
|
|
1049
|
+
return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
|
|
1050
|
+
} else if (urlsOption) {
|
|
1002
1051
|
return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
|
|
1003
1052
|
}
|
|
1004
1053
|
|