@rangojs/router 0.0.0-experimental.cb54cbba → 0.0.0-experimental.ea6d5eec
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/AGENTS.md +4 -0
- package/dist/bin/rango.js +8 -3
- package/dist/vite/index.js +136 -197
- package/package.json +15 -14
- package/skills/caching/SKILL.md +37 -4
- package/src/browser/navigation-bridge.ts +1 -3
- package/src/browser/navigation-client.ts +77 -24
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +39 -9
- package/src/browser/prefetch/cache.ts +54 -2
- package/src/browser/prefetch/fetch.ts +22 -12
- package/src/browser/prefetch/queue.ts +53 -13
- package/src/browser/react/Link.tsx +9 -1
- package/src/browser/react/NavigationProvider.tsx +27 -0
- package/src/browser/rsc-router.tsx +90 -57
- package/src/browser/scroll-restoration.ts +31 -34
- package/src/browser/types.ts +9 -0
- package/src/build/route-types/router-processing.ts +12 -2
- package/src/cache/cache-scope.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +453 -11
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/index.ts +1 -0
- package/src/route-definition/redirect.ts +2 -2
- package/src/route-map-builder.ts +7 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/intercept-resolution.ts +2 -0
- package/src/router/lazy-includes.ts +2 -0
- package/src/router/logging.ts +4 -1
- package/src/router/manifest.ts +3 -1
- package/src/router/match-middleware/segment-resolution.ts +1 -0
- package/src/router/middleware.ts +2 -1
- package/src/router/router-context.ts +5 -1
- package/src/router/segment-resolution/revalidation.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router.ts +4 -0
- package/src/server/request-context.ts +10 -4
- package/src/types/route-entry.ts +7 -0
- package/src/vite/discovery/state.ts +0 -2
- package/src/vite/plugin-types.ts +0 -83
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +144 -209
- package/src/vite/router-discovery.ts +0 -8
|
@@ -188,6 +188,7 @@ export async function resolveInterceptEntry<TEnv>(
|
|
|
188
188
|
context,
|
|
189
189
|
actionContext,
|
|
190
190
|
stale,
|
|
191
|
+
traceSource: "intercept-loader",
|
|
191
192
|
});
|
|
192
193
|
|
|
193
194
|
if (!shouldRevalidate) {
|
|
@@ -355,6 +356,7 @@ export async function resolveInterceptLoadersOnly<TEnv>(
|
|
|
355
356
|
context,
|
|
356
357
|
actionContext,
|
|
357
358
|
stale,
|
|
359
|
+
traceSource: "intercept-loader",
|
|
358
360
|
});
|
|
359
361
|
|
|
360
362
|
if (!shouldRevalidate) {
|
|
@@ -14,6 +14,7 @@ export interface LazyEvalDeps<TEnv = any> {
|
|
|
14
14
|
mergedRouteMap: Record<string, string>;
|
|
15
15
|
nextMountIndex: () => number;
|
|
16
16
|
getPrecomputedByPrefix: () => Map<string, Record<string, string>> | null;
|
|
17
|
+
routerId?: string;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
// Detect lazy includes in handler result and create placeholder entries
|
|
@@ -200,6 +201,7 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
200
201
|
trailingSlash: entry.trailingSlash,
|
|
201
202
|
handler: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
|
|
202
203
|
mountIndex: deps.nextMountIndex(),
|
|
204
|
+
routerId: deps.routerId,
|
|
203
205
|
// Lazy evaluation fields
|
|
204
206
|
lazy: true,
|
|
205
207
|
lazyPatterns: lazyInclude.patterns,
|
package/src/router/logging.ts
CHANGED
|
@@ -12,7 +12,10 @@ export interface RevalidationTraceEntry {
|
|
|
12
12
|
| "cache-hit"
|
|
13
13
|
| "loader"
|
|
14
14
|
| "parallel"
|
|
15
|
-
| "orphan-layout"
|
|
15
|
+
| "orphan-layout"
|
|
16
|
+
| "route-handler"
|
|
17
|
+
| "layout-handler"
|
|
18
|
+
| "intercept-loader";
|
|
16
19
|
defaultShouldRevalidate: boolean;
|
|
17
20
|
finalShouldRevalidate: boolean;
|
|
18
21
|
reason: string;
|
package/src/router/manifest.ts
CHANGED
|
@@ -65,7 +65,9 @@ export async function loadManifest(
|
|
|
65
65
|
const mountIndex = entry.mountIndex;
|
|
66
66
|
|
|
67
67
|
// Check module-level cache (persists across requests within same isolate)
|
|
68
|
-
|
|
68
|
+
// Include routerId so multi-router setups (host routing) don't share cached
|
|
69
|
+
// EntryData across routers with overlapping mountIndex + routeKey combinations.
|
|
70
|
+
const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
|
|
69
71
|
const cached = manifestModuleCache.get(cacheKey);
|
|
70
72
|
if (cached) {
|
|
71
73
|
const cacheStart = performance.now();
|
package/src/router/middleware.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
import { _getRequestContext } from "../server/request-context.js";
|
|
22
22
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
23
23
|
import { appendMetric, createMetricsStore } from "./metrics.js";
|
|
24
|
+
import { stripInternalParams } from "./handler-context.js";
|
|
24
25
|
|
|
25
26
|
// Re-export types and cookie utilities for backward compatibility
|
|
26
27
|
export type {
|
|
@@ -147,7 +148,7 @@ export function createMiddlewareContext<TEnv>(
|
|
|
147
148
|
search?: Record<string, unknown>,
|
|
148
149
|
) => string,
|
|
149
150
|
): MiddlewareContext<TEnv> {
|
|
150
|
-
const url = new URL(request.url);
|
|
151
|
+
const url = stripInternalParams(new URL(request.url));
|
|
151
152
|
|
|
152
153
|
// Track the initial response to detect pre/post-next() phase.
|
|
153
154
|
// Before next(): responseHolder.response === initialResponse (the stub).
|
|
@@ -138,6 +138,7 @@ export interface RouterContext<TEnv = any> {
|
|
|
138
138
|
interceptResult: InterceptResult | null,
|
|
139
139
|
localRouteName: string,
|
|
140
140
|
pathname: string,
|
|
141
|
+
stale?: boolean,
|
|
141
142
|
) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
|
|
142
143
|
|
|
143
144
|
// Generator-based segment resolution (for pipeline)
|
|
@@ -188,7 +189,10 @@ export interface RouterContext<TEnv = any> {
|
|
|
188
189
|
| "cache-hit"
|
|
189
190
|
| "loader"
|
|
190
191
|
| "parallel"
|
|
191
|
-
| "orphan-layout"
|
|
192
|
+
| "orphan-layout"
|
|
193
|
+
| "route-handler"
|
|
194
|
+
| "layout-handler"
|
|
195
|
+
| "intercept-loader";
|
|
192
196
|
}) => Promise<boolean>;
|
|
193
197
|
|
|
194
198
|
// Request context
|
|
@@ -608,6 +608,8 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
608
608
|
context,
|
|
609
609
|
actionContext,
|
|
610
610
|
stale,
|
|
611
|
+
traceSource:
|
|
612
|
+
entry.type === "route" ? "route-handler" : "layout-handler",
|
|
611
613
|
});
|
|
612
614
|
emitRevalidationDecision(
|
|
613
615
|
entry.shortCode,
|
|
@@ -1165,6 +1167,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1165
1167
|
localRouteName: string,
|
|
1166
1168
|
pathname: string,
|
|
1167
1169
|
deps: SegmentResolutionDeps<TEnv>,
|
|
1170
|
+
stale?: boolean,
|
|
1168
1171
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
1169
1172
|
const allSegments: ResolvedSegment[] = [];
|
|
1170
1173
|
const matchedIds: string[] = [];
|
|
@@ -1209,7 +1212,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1209
1212
|
loaderPromises,
|
|
1210
1213
|
deps,
|
|
1211
1214
|
actionContext,
|
|
1212
|
-
|
|
1215
|
+
stale,
|
|
1213
1216
|
),
|
|
1214
1217
|
(seg) => ({ segments: [seg], matchedIds: [seg.id] }),
|
|
1215
1218
|
deps,
|
|
@@ -204,6 +204,7 @@ export function createSegmentWrappers<TEnv = any>(
|
|
|
204
204
|
interceptResult: { intercept: InterceptEntry; entry: EntryData } | null,
|
|
205
205
|
localRouteName: string,
|
|
206
206
|
pathname: string,
|
|
207
|
+
stale?: boolean,
|
|
207
208
|
): ReturnType<typeof _resolveAllSegmentsWithRevalidation> {
|
|
208
209
|
return _resolveAllSegmentsWithRevalidation(
|
|
209
210
|
entries,
|
|
@@ -221,6 +222,7 @@ export function createSegmentWrappers<TEnv = any>(
|
|
|
221
222
|
localRouteName,
|
|
222
223
|
pathname,
|
|
223
224
|
segmentDeps,
|
|
225
|
+
stale,
|
|
224
226
|
);
|
|
225
227
|
}
|
|
226
228
|
|
package/src/router.ts
CHANGED
|
@@ -560,6 +560,7 @@ export function createRouter<TEnv = any>(
|
|
|
560
560
|
mergedRouteMap,
|
|
561
561
|
nextMountIndex: () => mountIndex++,
|
|
562
562
|
getPrecomputedByPrefix,
|
|
563
|
+
routerId,
|
|
563
564
|
};
|
|
564
565
|
|
|
565
566
|
function evaluateLazyEntry(entry: RouteEntry<TEnv>): void {
|
|
@@ -751,6 +752,7 @@ export function createRouter<TEnv = any>(
|
|
|
751
752
|
trailingSlash: trailingSlashConfig,
|
|
752
753
|
handler: urlPatterns.handler,
|
|
753
754
|
mountIndex: currentMountIndex,
|
|
755
|
+
routerId,
|
|
754
756
|
cacheProfiles: resolvedCacheProfiles,
|
|
755
757
|
...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
|
|
756
758
|
...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
|
|
@@ -770,6 +772,7 @@ export function createRouter<TEnv = any>(
|
|
|
770
772
|
trailingSlash: trailingSlashConfig,
|
|
771
773
|
handler: urlPatterns.handler,
|
|
772
774
|
mountIndex: currentMountIndex,
|
|
775
|
+
routerId,
|
|
773
776
|
cacheProfiles: resolvedCacheProfiles,
|
|
774
777
|
...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
|
|
775
778
|
...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
|
|
@@ -813,6 +816,7 @@ export function createRouter<TEnv = any>(
|
|
|
813
816
|
trailingSlash: trailingSlashConfig,
|
|
814
817
|
handler: urlPatterns.handler,
|
|
815
818
|
mountIndex: mountIndex++,
|
|
819
|
+
routerId,
|
|
816
820
|
// Lazy evaluation fields
|
|
817
821
|
lazy: true,
|
|
818
822
|
lazyPatterns: lazyInclude.patterns,
|
|
@@ -30,7 +30,10 @@ import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
|
30
30
|
import { THEME_COOKIE } from "../theme/constants.js";
|
|
31
31
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
32
32
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
createReverseFunction,
|
|
35
|
+
stripInternalParams,
|
|
36
|
+
} from "../router/handler-context.js";
|
|
34
37
|
import { getGlobalRouteMap, isRouteRootScoped } from "../route-map-builder.js";
|
|
35
38
|
import { invariant } from "../errors.js";
|
|
36
39
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
@@ -58,7 +61,7 @@ export interface RequestContext<
|
|
|
58
61
|
originalUrl: URL;
|
|
59
62
|
/** URL pathname */
|
|
60
63
|
pathname: string;
|
|
61
|
-
/** URL search params (
|
|
64
|
+
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
62
65
|
searchParams: URLSearchParams;
|
|
63
66
|
/** Variables set by middleware (same as ctx.var) */
|
|
64
67
|
var: Record<string, any>;
|
|
@@ -555,14 +558,17 @@ export function createRequestContext<TEnv>(
|
|
|
555
558
|
invalidateResponseCookieCache();
|
|
556
559
|
};
|
|
557
560
|
|
|
561
|
+
// Strip internal _rsc* params so userland sees a clean URL.
|
|
562
|
+
const cleanUrl = stripInternalParams(url);
|
|
563
|
+
|
|
558
564
|
// Build the context object first (without use), then add use
|
|
559
565
|
const ctx: RequestContext<TEnv> = {
|
|
560
566
|
env,
|
|
561
567
|
request,
|
|
562
|
-
url,
|
|
568
|
+
url: cleanUrl,
|
|
563
569
|
originalUrl: new URL(request.url),
|
|
564
570
|
pathname: url.pathname,
|
|
565
|
-
searchParams:
|
|
571
|
+
searchParams: cleanUrl.searchParams,
|
|
566
572
|
var: variables,
|
|
567
573
|
get: ((keyOrVar: any) =>
|
|
568
574
|
contextGet(variables, keyOrVar)) as RequestContext<TEnv>["get"],
|
package/src/types/route-entry.ts
CHANGED
|
@@ -55,6 +55,13 @@ export interface RouteEntry<TEnv = any> {
|
|
|
55
55
|
| Promise<() => Array<AllUseItems>>;
|
|
56
56
|
mountIndex: number;
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Router ID that owns this entry. Used to namespace the manifest cache
|
|
60
|
+
* so multi-router setups (host routing) don't share cached EntryData
|
|
61
|
+
* across routers with overlapping mountIndex + routeKey combinations.
|
|
62
|
+
*/
|
|
63
|
+
routerId?: string;
|
|
64
|
+
|
|
58
65
|
/**
|
|
59
66
|
* Route keys in this entry that have pre-render handlers.
|
|
60
67
|
* Used by the non-trie match path to set the `pr` flag.
|
|
@@ -13,8 +13,6 @@ export const VIRTUAL_ROUTES_MANIFEST_ID = "virtual:rsc-router/routes-manifest";
|
|
|
13
13
|
export interface PluginOptions {
|
|
14
14
|
enableBuildPrerender?: boolean;
|
|
15
15
|
staticRouteTypesGeneration?: boolean;
|
|
16
|
-
include?: string[];
|
|
17
|
-
exclude?: string[];
|
|
18
16
|
// Mutable ref for deferred auto-discovery (node preset).
|
|
19
17
|
// The auto-discover config() hook populates this before configResolved.
|
|
20
18
|
routerPathRef?: { path?: string };
|
package/src/vite/plugin-types.ts
CHANGED
|
@@ -1,39 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RSC plugin entry points configuration.
|
|
3
|
-
* All entries use virtual modules by default. Specify a path to use a custom entry file.
|
|
4
|
-
*/
|
|
5
|
-
export interface RscEntries {
|
|
6
|
-
/**
|
|
7
|
-
* Path to a custom browser/client entry file.
|
|
8
|
-
* If not specified, a default virtual entry is used.
|
|
9
|
-
*/
|
|
10
|
-
client?: string;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Path to a custom SSR entry file.
|
|
14
|
-
* If not specified, a default virtual entry is used.
|
|
15
|
-
*/
|
|
16
|
-
ssr?: string;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Path to a custom RSC entry file.
|
|
20
|
-
* If not specified, a default virtual entry is used that imports the router from the `entry` option.
|
|
21
|
-
*/
|
|
22
|
-
rsc?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Options for @vitejs/plugin-rsc integration
|
|
27
|
-
*/
|
|
28
|
-
export interface RscPluginOptions {
|
|
29
|
-
/**
|
|
30
|
-
* Entry points for client, ssr, and rsc environments.
|
|
31
|
-
* All entries use virtual modules by default.
|
|
32
|
-
* Specify paths only when you need custom entry files.
|
|
33
|
-
*/
|
|
34
|
-
entries?: RscEntries;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
1
|
/**
|
|
38
2
|
* Base options shared by all presets
|
|
39
3
|
*/
|
|
@@ -51,21 +15,6 @@ interface RangoBaseOptions {
|
|
|
51
15
|
* @default true
|
|
52
16
|
*/
|
|
53
17
|
staticRouteTypesGeneration?: boolean;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Glob patterns for files to include in route type scanning.
|
|
57
|
-
* Only files matching at least one pattern will be scanned.
|
|
58
|
-
* Patterns are relative to the project root.
|
|
59
|
-
* When unset, all .ts/.tsx files are scanned.
|
|
60
|
-
*/
|
|
61
|
-
include?: string[];
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Glob patterns for files to exclude from route type scanning.
|
|
65
|
-
* Takes precedence over `include`. Patterns are relative to the project root.
|
|
66
|
-
* Defaults to common test/build directories.
|
|
67
|
-
*/
|
|
68
|
-
exclude?: string[];
|
|
69
18
|
}
|
|
70
19
|
|
|
71
20
|
/**
|
|
@@ -76,38 +25,6 @@ export interface RangoNodeOptions extends RangoBaseOptions {
|
|
|
76
25
|
* Deployment preset. Defaults to 'node' when not specified.
|
|
77
26
|
*/
|
|
78
27
|
preset?: "node";
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Path to your router configuration file that exports the route tree.
|
|
82
|
-
* This file must export a `router` object created with `createRouter()`.
|
|
83
|
-
*
|
|
84
|
-
* When omitted, auto-discovers the router by scanning for files containing
|
|
85
|
-
* `createRouter`. If exactly one is found, it is used automatically.
|
|
86
|
-
* If multiple are found, an error is thrown with the list of candidates.
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```ts
|
|
90
|
-
* rango({ router: './src/router.tsx' })
|
|
91
|
-
* // or simply:
|
|
92
|
-
* rango()
|
|
93
|
-
* ```
|
|
94
|
-
*/
|
|
95
|
-
router?: string;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* RSC plugin configuration. By default, rsc-router includes @vitejs/plugin-rsc
|
|
99
|
-
* with sensible defaults.
|
|
100
|
-
*
|
|
101
|
-
* Entry files (browser, ssr, rsc) are optional - if they don't exist,
|
|
102
|
-
* virtual defaults are used.
|
|
103
|
-
*
|
|
104
|
-
* - Omit or pass `true`/`{}` to use defaults (recommended)
|
|
105
|
-
* - Pass `{ entries: {...} }` to customize entry paths
|
|
106
|
-
* - Pass `false` to disable (for manual @vitejs/plugin-rsc configuration)
|
|
107
|
-
*
|
|
108
|
-
* @default true
|
|
109
|
-
*/
|
|
110
|
-
rsc?: boolean | RscPluginOptions;
|
|
111
28
|
}
|
|
112
29
|
|
|
113
30
|
/**
|
|
@@ -278,9 +278,7 @@ export function exposeActionId(): Plugin {
|
|
|
278
278
|
if (!rscPluginApi) {
|
|
279
279
|
throw new Error(
|
|
280
280
|
"[rsc-router] Could not find @vitejs/plugin-rsc. " +
|
|
281
|
-
"@rangojs/router requires the Vite RSC plugin
|
|
282
|
-
"The RSC plugin should be included automatically. If you disabled it with\n" +
|
|
283
|
-
"rango({ rsc: false }), add rsc() before rango() in your config.",
|
|
281
|
+
"@rangojs/router requires the Vite RSC plugin, which is included automatically by rango().",
|
|
284
282
|
);
|
|
285
283
|
}
|
|
286
284
|
|
|
@@ -135,8 +135,11 @@ export function createVersionPlugin(): Plugin {
|
|
|
135
135
|
let server: any = null;
|
|
136
136
|
const clientModuleSignatures = new Map<string, ClientModuleSignature>();
|
|
137
137
|
|
|
138
|
+
let versionCounter = 0;
|
|
138
139
|
const bumpVersion = (reason: string) => {
|
|
139
|
-
|
|
140
|
+
// Use timestamp + counter to guarantee uniqueness even when multiple
|
|
141
|
+
// bumps happen within the same millisecond (e.g. cascading HMR events).
|
|
142
|
+
currentVersion = Date.now().toString(16) + String(++versionCounter);
|
|
140
143
|
console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
|
|
141
144
|
|
|
142
145
|
const rscEnv = server?.environments?.rsc;
|
|
@@ -211,6 +214,15 @@ export function createVersionPlugin(): Plugin {
|
|
|
211
214
|
|
|
212
215
|
if (!isRscModule) return;
|
|
213
216
|
|
|
217
|
+
// Skip re-bumping when the version virtual module itself is invalidated
|
|
218
|
+
// (our own bumpVersion() invalidates it, which re-triggers hotUpdate).
|
|
219
|
+
if (
|
|
220
|
+
ctx.modules.length === 1 &&
|
|
221
|
+
ctx.modules[0].id === "\0" + VIRTUAL_IDS.version
|
|
222
|
+
) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
214
226
|
if (isCodeModule(ctx.file)) {
|
|
215
227
|
const filePath = normalizeModuleId(ctx.file);
|
|
216
228
|
const previousSignature = clientModuleSignatures.get(filePath);
|