@rangojs/router 0.0.0-experimental.a06372ef → 0.0.0-experimental.a62282ba
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/dist/vite/index.js +1 -1
- package/package.json +1 -1
- package/src/browser/navigation-bridge.ts +5 -1
- package/src/browser/scroll-restoration.ts +19 -7
- package/src/route-map-builder.ts +7 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/lazy-includes.ts +2 -0
- package/src/router/manifest.ts +3 -1
- package/src/router.ts +4 -0
- package/src/rsc/handler.ts +14 -5
- package/src/types/route-entry.ts +7 -0
package/dist/vite/index.js
CHANGED
|
@@ -1745,7 +1745,7 @@ import { resolve } from "node:path";
|
|
|
1745
1745
|
// package.json
|
|
1746
1746
|
var package_default = {
|
|
1747
1747
|
name: "@rangojs/router",
|
|
1748
|
-
version: "0.0.0-experimental.
|
|
1748
|
+
version: "0.0.0-experimental.a62282ba",
|
|
1749
1749
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1750
1750
|
keywords: [
|
|
1751
1751
|
"react",
|
package/package.json
CHANGED
|
@@ -461,6 +461,10 @@ export function createNavigationBridge(
|
|
|
461
461
|
}
|
|
462
462
|
eventController.setParams(cachedParams);
|
|
463
463
|
|
|
464
|
+
// Scroll to top immediately to avoid flicker from the previous
|
|
465
|
+
// page's scroll position while React paints the cached content.
|
|
466
|
+
window.scrollTo(0, 0);
|
|
467
|
+
|
|
464
468
|
const popstateUpdate = {
|
|
465
469
|
root,
|
|
466
470
|
metadata: {
|
|
@@ -485,7 +489,7 @@ export function createNavigationBridge(
|
|
|
485
489
|
onUpdate(popstateUpdate);
|
|
486
490
|
}
|
|
487
491
|
|
|
488
|
-
// Restore scroll position
|
|
492
|
+
// Restore the actual saved scroll position after React paints.
|
|
489
493
|
handleNavigationEnd({ restore: true, isStreaming });
|
|
490
494
|
|
|
491
495
|
// SWR: If stale, trigger background revalidation
|
|
@@ -288,7 +288,11 @@ export function restoreScrollPosition(options?: {
|
|
|
288
288
|
// Not streaming — scroll after React commits and browser paints.
|
|
289
289
|
// startTransition defers the DOM commit, so scrolling synchronously
|
|
290
290
|
// would be overwritten when React replaces the content.
|
|
291
|
-
|
|
291
|
+
const defer =
|
|
292
|
+
typeof requestAnimationFrame === "function"
|
|
293
|
+
? requestAnimationFrame
|
|
294
|
+
: (fn: () => void) => setTimeout(fn, 0);
|
|
295
|
+
defer(() => {
|
|
292
296
|
window.scrollTo(0, savedY);
|
|
293
297
|
debugLog("[Scroll] Restored position:", savedY, "for key:", key);
|
|
294
298
|
});
|
|
@@ -366,13 +370,21 @@ export function handleNavigationEnd(options: {
|
|
|
366
370
|
// Fall through to hash or top if no saved position
|
|
367
371
|
}
|
|
368
372
|
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
+
// Defer hash and scroll-to-top to after React paints the new content,
|
|
374
|
+
// so the user doesn't see the current page jump before the new route appears.
|
|
375
|
+
const defer =
|
|
376
|
+
typeof requestAnimationFrame === "function"
|
|
377
|
+
? requestAnimationFrame
|
|
378
|
+
: (fn: () => void) => setTimeout(fn, 0);
|
|
379
|
+
defer(() => {
|
|
380
|
+
// Try hash scrolling first
|
|
381
|
+
if (scrollToHash()) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
373
384
|
|
|
374
|
-
|
|
375
|
-
|
|
385
|
+
// Default: scroll to top
|
|
386
|
+
scrollToTop();
|
|
387
|
+
});
|
|
376
388
|
}
|
|
377
389
|
|
|
378
390
|
/**
|
package/src/route-map-builder.ts
CHANGED
|
@@ -199,7 +199,13 @@ export function registerRouterManifestLoader(
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
export async function ensureRouterManifest(routerId: string): Promise<void> {
|
|
202
|
-
|
|
202
|
+
// Check both manifest AND trie. The virtual module's setRouterManifest()
|
|
203
|
+
// pre-sets the manifest at startup, but the per-router trie is only
|
|
204
|
+
// available from the lazy loader. Without this, the lazy loader never
|
|
205
|
+
// runs and findMatch falls back to the global merged trie — which
|
|
206
|
+
// contains routes from ALL routers and breaks multi-router setups.
|
|
207
|
+
if (perRouterManifestMap.has(routerId) && perRouterTrieMap.has(routerId))
|
|
208
|
+
return;
|
|
203
209
|
const loader = routerManifestLoaders.get(routerId);
|
|
204
210
|
if (loader) {
|
|
205
211
|
const mod = await loader();
|
package/src/router/find-match.ts
CHANGED
|
@@ -52,8 +52,10 @@ export function createFindMatch<TEnv = any>(
|
|
|
52
52
|
: undefined;
|
|
53
53
|
|
|
54
54
|
// Phase 1: Try trie match (O(path_length))
|
|
55
|
-
//
|
|
56
|
-
|
|
55
|
+
// Only use the per-router trie. The global trie merges routes from ALL
|
|
56
|
+
// routers and must not be used — in multi-router setups (host routing)
|
|
57
|
+
// overlapping paths like "/" would match the wrong app's route.
|
|
58
|
+
const routeTrie = getRouterTrie(deps.routerId);
|
|
57
59
|
if (routeTrie) {
|
|
58
60
|
const trieStart = performance.now();
|
|
59
61
|
const trieResult = tryTrieMatch(routeTrie, pathname);
|
|
@@ -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/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.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,
|
package/src/rsc/handler.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
RscPayload,
|
|
23
23
|
CreateRSCHandlerOptions,
|
|
24
24
|
LoadSSRModule,
|
|
25
|
+
SSRModule,
|
|
25
26
|
} from "./types.js";
|
|
26
27
|
import {
|
|
27
28
|
createResponseWithMergedHeaders,
|
|
@@ -133,13 +134,21 @@ export function createRSCHandler<
|
|
|
133
134
|
} = deps;
|
|
134
135
|
|
|
135
136
|
// Use provided loadSSRModule or default to vite RSC module loader.
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
const loadSSRModule: LoadSSRModule =
|
|
137
|
+
// In production the SSR module is stable across requests, so memoize
|
|
138
|
+
// the dynamic import to avoid repeated module resolution overhead.
|
|
139
|
+
// In dev mode Vite may hot-reload the module, so skip memoization.
|
|
140
|
+
const rawLoadSSRModule: LoadSSRModule =
|
|
141
141
|
options.loadSSRModule ??
|
|
142
142
|
(() => import.meta.viteRsc.loadModule("ssr", "index"));
|
|
143
|
+
let _ssrModulePromise: Promise<SSRModule> | undefined;
|
|
144
|
+
const loadSSRModule: LoadSSRModule =
|
|
145
|
+
process.env.NODE_ENV === "production"
|
|
146
|
+
? () =>
|
|
147
|
+
(_ssrModulePromise ??= rawLoadSSRModule().catch((err) => {
|
|
148
|
+
_ssrModulePromise = undefined;
|
|
149
|
+
throw err;
|
|
150
|
+
}))
|
|
151
|
+
: rawLoadSSRModule;
|
|
143
152
|
|
|
144
153
|
/**
|
|
145
154
|
* Per-request error reporter that deduplicates via the ALS request context.
|
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.
|