@rangojs/router 0.0.0-experimental.8678bb02 → 0.0.0-experimental.87
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 +126 -38
- package/dist/bin/rango.js +130 -47
- package/dist/vite/index.js +847 -384
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +5 -5
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +28 -20
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +91 -17
- package/skills/loader/SKILL.md +35 -2
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +59 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/rango/SKILL.md +24 -22
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +24 -0
- package/skills/router-setup/SKILL.md +35 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +3 -1
- package/src/__internal.ts +1 -1
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/navigation-bridge.ts +87 -6
- package/src/browser/navigation-client.ts +128 -77
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/partial-update.ts +60 -7
- package/src/browser/prefetch/cache.ts +129 -21
- package/src/browser/prefetch/fetch.ts +156 -18
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +72 -8
- package/src/browser/react/NavigationProvider.tsx +57 -11
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +11 -1
- package/src/browser/react/use-router.ts +29 -9
- package/src/browser/rsc-router.tsx +60 -9
- package/src/browser/scroll-restoration.ts +10 -8
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/server-action-bridge.ts +8 -18
- package/src/browser/types.ts +33 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +50 -24
- 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/cf/cf-cache-store.ts +5 -7
- package/src/client.tsx +84 -230
- package/src/deps/browser.ts +0 -1
- package/src/handle.ts +40 -0
- package/src/index.rsc.ts +6 -1
- package/src/index.ts +49 -6
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +138 -77
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +27 -2
- package/src/route-definition/dsl-helpers.ts +210 -35
- package/src/route-definition/helpers-types.ts +61 -14
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +9 -1
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/handler-context.ts +70 -17
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/lazy-includes.ts +6 -6
- package/src/router/loader-resolution.ts +153 -21
- package/src/router/manifest.ts +22 -13
- package/src/router/match-api.ts +127 -192
- package/src/router/match-middleware/cache-lookup.ts +28 -8
- package/src/router/match-middleware/segment-resolution.ts +53 -0
- package/src/router/match-result.ts +82 -4
- package/src/router/middleware-types.ts +2 -28
- package/src/router/middleware.ts +32 -7
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +60 -9
- 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-interfaces.ts +36 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +70 -5
- package/src/router/segment-resolution/revalidation.ts +87 -9
- package/src/router/trie-matching.ts +10 -4
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +54 -7
- package/src/rsc/handler.ts +478 -399
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +14 -3
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +15 -2
- package/src/rsc/server-action.ts +10 -2
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +6 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +11 -61
- package/src/server/context.ts +65 -5
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +142 -55
- 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 +17 -43
- package/src/types/loader-types.ts +37 -11
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +12 -1
- package/src/types/segments.ts +1 -1
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +47 -12
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +18 -16
- package/src/use-loader.tsx +77 -5
- 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 +128 -74
- 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/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-id-utils.ts +12 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-internal-ids.ts +257 -40
- package/src/vite/plugins/performance-tracks.ts +64 -206
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +40 -18
- package/src/vite/router-discovery.ts +237 -37
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/package-resolution.ts +1 -1
- package/src/vite/utils/prerender-utils.ts +37 -5
- package/src/vite/utils/shared-utils.ts +3 -2
- package/src/browser/debug-channel.ts +0 -93
|
@@ -41,8 +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 {
|
|
45
|
-
|
|
44
|
+
import {
|
|
45
|
+
track,
|
|
46
|
+
RSCRouterContext,
|
|
47
|
+
runInsideLoaderScope,
|
|
48
|
+
} from "../../server/context.js";
|
|
46
49
|
|
|
47
50
|
// ---------------------------------------------------------------------------
|
|
48
51
|
// Telemetry helpers
|
|
@@ -233,7 +236,9 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
233
236
|
params: ctx.params,
|
|
234
237
|
loaderId: loader.$$id,
|
|
235
238
|
loaderData: deps.wrapLoaderPromise(
|
|
236
|
-
|
|
239
|
+
runInsideLoaderScope(() =>
|
|
240
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
241
|
+
),
|
|
237
242
|
entry,
|
|
238
243
|
segmentId,
|
|
239
244
|
ctx.pathname,
|
|
@@ -314,6 +319,39 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
314
319
|
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
315
320
|
for (const layoutEntry of entry.layout) {
|
|
316
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
|
+
seg._inherited = true;
|
|
350
|
+
allLoaderSegments.push(seg);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
allMatchedIds.push(...inherited.matchedIds);
|
|
354
|
+
}
|
|
317
355
|
}
|
|
318
356
|
}
|
|
319
357
|
|
|
@@ -683,13 +721,20 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
683
721
|
return staticComponent;
|
|
684
722
|
}
|
|
685
723
|
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
724
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
725
|
+
// the build handler. At build time (context.build === true), always
|
|
726
|
+
// use the build handler from routeEntry.handler.
|
|
727
|
+
const handler =
|
|
728
|
+
!context.build && routeEntry.liveHandler
|
|
729
|
+
? routeEntry.liveHandler
|
|
730
|
+
: routeEntry.handler;
|
|
686
731
|
if (!routeEntry.loading) {
|
|
687
|
-
const result = handleHandlerResult(await
|
|
732
|
+
const result = handleHandlerResult(await handler(context));
|
|
688
733
|
doneHandler();
|
|
689
734
|
return result;
|
|
690
735
|
}
|
|
691
736
|
if (!actionContext) {
|
|
692
|
-
const result = handleHandlerResult(
|
|
737
|
+
const result = handleHandlerResult(handler(context));
|
|
693
738
|
if (result instanceof Promise) {
|
|
694
739
|
result.finally(doneHandler).catch(() => {});
|
|
695
740
|
const tracked = deps.trackHandler(result, {
|
|
@@ -712,9 +757,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
712
757
|
debugLog("segment.action", "resolving action route with awaited value", {
|
|
713
758
|
entryId: entry.id,
|
|
714
759
|
});
|
|
715
|
-
const actionResult = handleHandlerResult(
|
|
716
|
-
await routeEntry.handler(context),
|
|
717
|
-
);
|
|
760
|
+
const actionResult = handleHandlerResult(await handler(context));
|
|
718
761
|
doneHandler();
|
|
719
762
|
return {
|
|
720
763
|
content: Promise.resolve(actionResult),
|
|
@@ -830,6 +873,7 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
830
873
|
deps,
|
|
831
874
|
actionContext,
|
|
832
875
|
stale,
|
|
876
|
+
entry,
|
|
833
877
|
);
|
|
834
878
|
segments.push(...orphanResult.segments);
|
|
835
879
|
matchedIds.push(...orphanResult.matchedIds);
|
|
@@ -941,6 +985,8 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
941
985
|
deps: SegmentResolutionDeps<TEnv>,
|
|
942
986
|
actionContext?: ActionContext,
|
|
943
987
|
stale?: boolean,
|
|
988
|
+
/** Parent route entry — its loaders are inherited so parallel slots can access them. */
|
|
989
|
+
parentRouteEntry?: EntryData,
|
|
944
990
|
): Promise<SegmentRevalidationResult> {
|
|
945
991
|
invariant(
|
|
946
992
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -968,6 +1014,37 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
968
1014
|
segments.push(...loaderResult.segments);
|
|
969
1015
|
matchedIds.push(...loaderResult.matchedIds);
|
|
970
1016
|
|
|
1017
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
1018
|
+
// can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
|
|
1019
|
+
if (
|
|
1020
|
+
parentRouteEntry &&
|
|
1021
|
+
parentRouteEntry.loader &&
|
|
1022
|
+
parentRouteEntry.loader.length > 0 &&
|
|
1023
|
+
Object.keys(orphan.parallel).length > 0
|
|
1024
|
+
) {
|
|
1025
|
+
const inheritedResult = await resolveLoadersWithRevalidation(
|
|
1026
|
+
parentRouteEntry,
|
|
1027
|
+
context,
|
|
1028
|
+
belongsToRoute,
|
|
1029
|
+
clientSegmentIds,
|
|
1030
|
+
prevParams,
|
|
1031
|
+
request,
|
|
1032
|
+
prevUrl,
|
|
1033
|
+
nextUrl,
|
|
1034
|
+
routeKey,
|
|
1035
|
+
deps,
|
|
1036
|
+
actionContext,
|
|
1037
|
+
orphan.shortCode,
|
|
1038
|
+
stale,
|
|
1039
|
+
);
|
|
1040
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
1041
|
+
for (const s of inheritedResult.segments) {
|
|
1042
|
+
s._inherited = true;
|
|
1043
|
+
}
|
|
1044
|
+
segments.push(...inheritedResult.segments);
|
|
1045
|
+
matchedIds.push(...inheritedResult.matchedIds);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
971
1048
|
// Handler-first: resolve orphan layout handler before its parallels
|
|
972
1049
|
// so ctx.set() values are visible to parallel children.
|
|
973
1050
|
matchedIds.push(orphan.shortCode);
|
|
@@ -1054,6 +1131,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1054
1131
|
);
|
|
1055
1132
|
|
|
1056
1133
|
if (!resolvedParallelEntries.has(parallelEntry.id)) {
|
|
1134
|
+
// shortCodeOverride must match the parent layout, not the parallel entry.
|
|
1057
1135
|
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1058
1136
|
parallelEntry,
|
|
1059
1137
|
context,
|
|
@@ -1066,7 +1144,7 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1066
1144
|
routeKey,
|
|
1067
1145
|
deps,
|
|
1068
1146
|
actionContext,
|
|
1069
|
-
|
|
1147
|
+
orphan.shortCode,
|
|
1070
1148
|
stale,
|
|
1071
1149
|
);
|
|
1072
1150
|
segments.push(...loaderResult.segments);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { TrieNode, TrieLeaf } from "../build/route-trie.js";
|
|
9
|
+
import { safeDecodeURIComponent } from "./url-params.js";
|
|
9
10
|
|
|
10
11
|
export interface TrieMatchResult {
|
|
11
12
|
/** Route name */
|
|
@@ -173,20 +174,25 @@ function validateAndBuild(
|
|
|
173
174
|
originalPathname: string,
|
|
174
175
|
pathnameHasTrailingSlash: boolean,
|
|
175
176
|
): TrieMatchResult | null {
|
|
176
|
-
// Build named params by zipping leaf.pa with positional paramValues
|
|
177
|
+
// Build named params by zipping leaf.pa with positional paramValues.
|
|
178
|
+
// Params are URL-decoded at this boundary so ctx.params holds the values
|
|
179
|
+
// apps expect (matching Express/React Router) and round-trip cleanly
|
|
180
|
+
// through ctx.reverse.
|
|
177
181
|
const params: Record<string, string> = {};
|
|
178
182
|
if (leaf.pa) {
|
|
179
183
|
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
180
|
-
params[leaf.pa[i]] = paramValues[i];
|
|
184
|
+
params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
// Add wildcard param (wildcard leaves have pn from TrieNode.w type)
|
|
185
189
|
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
186
|
-
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
190
|
+
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
191
|
+
safeDecodeURIComponent(wildcardValue);
|
|
187
192
|
}
|
|
188
193
|
|
|
189
|
-
// Validate constraints
|
|
194
|
+
// Validate constraints against decoded values so constraint lists can be
|
|
195
|
+
// written in decoded form (e.g. ["en-GB", "en US"]).
|
|
190
196
|
if (leaf.cv) {
|
|
191
197
|
for (const paramName in leaf.cv) {
|
|
192
198
|
const allowed = leaf.cv[paramName]!;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL param encode/decode at the route boundary.
|
|
3
|
+
*
|
|
4
|
+
* Extraction (decode): regex/trie matchers keep param values URL-encoded;
|
|
5
|
+
* `safeDecodeURIComponent` turns them back into raw strings so `ctx.params`
|
|
6
|
+
* matches the contract apps expect (Express/React Router/Fastify/Koa) and
|
|
7
|
+
* round-trips through reverse stay stable. Malformed %-encoding is
|
|
8
|
+
* preserved as-is so a broken URL doesn't crash matching.
|
|
9
|
+
*
|
|
10
|
+
* Reversal (encode): `encodePathSegment` escapes only what RFC 3986
|
|
11
|
+
* requires for a path segment — `/`, `?`, `#`, space, control chars,
|
|
12
|
+
* non-ASCII — and leaves pchar sub-delims (`@ : $ & + , ; =` and friends)
|
|
13
|
+
* readable. `encodeURIComponent` over-encodes for path segments, which
|
|
14
|
+
* makes generated URLs harder for humans to read in the address bar
|
|
15
|
+
* (e.g. mailbox IDs like `ivo@example.com` would become
|
|
16
|
+
* `ivo%40example.com` even though `@` is path-legal).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export function safeDecodeURIComponent(raw: string): string {
|
|
20
|
+
if (raw === "" || raw.indexOf("%") === -1) return raw;
|
|
21
|
+
try {
|
|
22
|
+
return decodeURIComponent(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// encodeURIComponent over-encodes for path segments. After running it,
|
|
29
|
+
// un-encode the pchar sub-delims + (`:` / `@`) so the resulting URL
|
|
30
|
+
// keeps human-readable characters that are legal in a path segment.
|
|
31
|
+
// Everything dangerous — `/ ? # %` and space/control/non-ASCII — stays
|
|
32
|
+
// encoded.
|
|
33
|
+
const PATH_SAFE_ESCAPES: Record<string, string> = {
|
|
34
|
+
"%3A": ":",
|
|
35
|
+
"%40": "@",
|
|
36
|
+
"%24": "$",
|
|
37
|
+
"%26": "&",
|
|
38
|
+
"%2B": "+",
|
|
39
|
+
"%2C": ",",
|
|
40
|
+
"%3B": ";",
|
|
41
|
+
"%3D": "=",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function encodePathSegment(value: string): string {
|
|
45
|
+
return encodeURIComponent(value).replace(
|
|
46
|
+
/%(?:3A|40|24|26|2B|2C|3B|3D)/gi,
|
|
47
|
+
(match) => PATH_SAFE_ESCAPES[match.toUpperCase()] ?? match,
|
|
48
|
+
);
|
|
49
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -19,9 +19,10 @@ 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
|
-
EntryData,
|
|
24
|
-
InterceptSelectorContext,
|
|
25
|
+
type EntryData,
|
|
25
26
|
getContext,
|
|
26
27
|
RSCRouterContext,
|
|
27
28
|
type MetricsStore,
|
|
@@ -133,6 +134,7 @@ export function createRouter<TEnv = any>(
|
|
|
133
134
|
const {
|
|
134
135
|
id: userProvidedId,
|
|
135
136
|
$$id: injectedId,
|
|
137
|
+
basename: basenameOption,
|
|
136
138
|
debugPerformance = false,
|
|
137
139
|
document: documentOption,
|
|
138
140
|
defaultErrorBoundary,
|
|
@@ -158,6 +160,13 @@ export function createRouter<TEnv = any>(
|
|
|
158
160
|
originCheck: originCheckOption,
|
|
159
161
|
} = options;
|
|
160
162
|
|
|
163
|
+
// Normalize basename: ensure leading slash, strip trailing slash.
|
|
164
|
+
// A bare "/" is equivalent to no basename.
|
|
165
|
+
const basename =
|
|
166
|
+
basenameOption && basenameOption.replace(/^\/+|\/+$/g, "")
|
|
167
|
+
? "/" + basenameOption.replace(/^\/+|\/+$/g, "")
|
|
168
|
+
: undefined;
|
|
169
|
+
|
|
161
170
|
// Resolve telemetry sink (no-op when not configured)
|
|
162
171
|
const telemetry = resolveSink(telemetrySink);
|
|
163
172
|
|
|
@@ -615,6 +624,8 @@ export function createRouter<TEnv = any>(
|
|
|
615
624
|
params: Record<string, string>,
|
|
616
625
|
buildVars?: Record<string, any>,
|
|
617
626
|
isPassthroughRoute?: boolean,
|
|
627
|
+
buildEnv?: TEnv,
|
|
628
|
+
devMode?: boolean,
|
|
618
629
|
) {
|
|
619
630
|
return _matchForPrerender(
|
|
620
631
|
pathname,
|
|
@@ -622,6 +633,8 @@ export function createRouter<TEnv = any>(
|
|
|
622
633
|
prerenderDeps,
|
|
623
634
|
buildVars,
|
|
624
635
|
isPassthroughRoute,
|
|
636
|
+
buildEnv,
|
|
637
|
+
devMode,
|
|
625
638
|
);
|
|
626
639
|
}
|
|
627
640
|
|
|
@@ -629,12 +642,16 @@ export function createRouter<TEnv = any>(
|
|
|
629
642
|
handler: Function,
|
|
630
643
|
handlerId: string,
|
|
631
644
|
routeName?: string,
|
|
645
|
+
buildEnv?: TEnv,
|
|
646
|
+
devMode?: boolean,
|
|
632
647
|
) {
|
|
633
648
|
return _renderStaticSegment<TEnv>(
|
|
634
649
|
handler,
|
|
635
650
|
handlerId,
|
|
636
651
|
mergedRouteMap,
|
|
637
652
|
routeName,
|
|
653
|
+
buildEnv,
|
|
654
|
+
devMode,
|
|
638
655
|
);
|
|
639
656
|
}
|
|
640
657
|
|
|
@@ -659,8 +676,15 @@ export function createRouter<TEnv = any>(
|
|
|
659
676
|
const router: RSCRouterInternal<TEnv, {}> = {
|
|
660
677
|
__brand: RSC_ROUTER_BRAND,
|
|
661
678
|
id: routerId,
|
|
679
|
+
basename,
|
|
680
|
+
|
|
681
|
+
routes(patternsOrBuilder: UrlPatterns<TEnv> | UrlBuilder<TEnv>): any {
|
|
682
|
+
// Wrap builder functions in urls() automatically
|
|
683
|
+
const urlPatterns: UrlPatterns<TEnv> =
|
|
684
|
+
typeof patternsOrBuilder === "function"
|
|
685
|
+
? (urls(patternsOrBuilder) as UrlPatterns<TEnv>)
|
|
686
|
+
: patternsOrBuilder;
|
|
662
687
|
|
|
663
|
-
routes(urlPatterns: UrlPatterns<TEnv>): any {
|
|
664
688
|
// Store reference for runtime manifest generation
|
|
665
689
|
storedUrlPatterns = urlPatterns;
|
|
666
690
|
const currentMountIndex = mountIndex++;
|
|
@@ -708,6 +732,10 @@ export function createRouter<TEnv = any>(
|
|
|
708
732
|
counters: {},
|
|
709
733
|
mountIndex: currentMountIndex,
|
|
710
734
|
cacheProfiles: resolvedCacheProfiles,
|
|
735
|
+
// basename sets the initial URL prefix so all path() patterns
|
|
736
|
+
// are registered with the prefix (e.g. "/admin" + "/users" = "/admin/users").
|
|
737
|
+
// No namePrefix — route names stay unprefixed.
|
|
738
|
+
...(basename ? { urlPrefix: basename } : {}),
|
|
711
739
|
},
|
|
712
740
|
() => {
|
|
713
741
|
handlerResult = urlPatterns.handler() as AllUseItems[];
|
|
@@ -727,7 +755,7 @@ export function createRouter<TEnv = any>(
|
|
|
727
755
|
if (entry.type === "route" && entry.isPrerender) {
|
|
728
756
|
if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
|
|
729
757
|
prerenderRouteKeys.add(name);
|
|
730
|
-
if (entry.
|
|
758
|
+
if (entry.isPassthrough === true) {
|
|
731
759
|
if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
|
|
732
760
|
passthroughRouteKeys.add(name);
|
|
733
761
|
}
|
|
@@ -856,8 +884,18 @@ export function createRouter<TEnv = any>(
|
|
|
856
884
|
patternOrMiddleware: string | MiddlewareFn<TEnv>,
|
|
857
885
|
middleware?: MiddlewareFn<TEnv>,
|
|
858
886
|
): any {
|
|
859
|
-
//
|
|
860
|
-
|
|
887
|
+
// Auto-prefix pattern with basename so router-level middleware
|
|
888
|
+
// patterns are router-relative (e.g. "/users/*" matches "/app/users/*").
|
|
889
|
+
if (basename && typeof patternOrMiddleware === "string") {
|
|
890
|
+
const pattern = patternOrMiddleware;
|
|
891
|
+
const prefixed =
|
|
892
|
+
pattern === "/*" || pattern === "*"
|
|
893
|
+
? `${basename}/*`
|
|
894
|
+
: `${basename}${pattern}`;
|
|
895
|
+
addMiddleware(prefixed, middleware, null);
|
|
896
|
+
} else {
|
|
897
|
+
addMiddleware(patternOrMiddleware, middleware, null);
|
|
898
|
+
}
|
|
861
899
|
return router;
|
|
862
900
|
},
|
|
863
901
|
|
|
@@ -958,6 +996,9 @@ export function createRouter<TEnv = any>(
|
|
|
958
996
|
// Expose source file for per-router type generation
|
|
959
997
|
__sourceFile,
|
|
960
998
|
|
|
999
|
+
// Expose basename for runtime manifest generation
|
|
1000
|
+
__basename: basename,
|
|
1001
|
+
|
|
961
1002
|
// RSC request handler (lazily created on first call)
|
|
962
1003
|
fetch: (() => {
|
|
963
1004
|
// Handler is created on first call and reused
|
|
@@ -991,6 +1032,10 @@ export function createRouter<TEnv = any>(
|
|
|
991
1032
|
};
|
|
992
1033
|
})(),
|
|
993
1034
|
|
|
1035
|
+
// Low-level route matching for request classification
|
|
1036
|
+
findMatch: (pathname: string, metricsStore?: any) =>
|
|
1037
|
+
findMatch(pathname, metricsStore),
|
|
1038
|
+
|
|
994
1039
|
// Debug utility for manifest inspection
|
|
995
1040
|
debugManifest: () => buildDebugManifest<TEnv>(routesEntries),
|
|
996
1041
|
};
|
|
@@ -999,7 +1044,9 @@ export function createRouter<TEnv = any>(
|
|
|
999
1044
|
RouterRegistry.set(routerId, router);
|
|
1000
1045
|
|
|
1001
1046
|
// If urls option was provided, auto-register them
|
|
1002
|
-
if (urlsOption) {
|
|
1047
|
+
if (typeof urlsOption === "function") {
|
|
1048
|
+
return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
|
|
1049
|
+
} else if (urlsOption) {
|
|
1003
1050
|
return router.routes(urlsOption) as RSCRouter<TEnv, {}>;
|
|
1004
1051
|
}
|
|
1005
1052
|
|