@rangojs/router 0.0.0-experimental.a769fbe7 → 0.0.0-experimental.b02a2fec
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 +689 -366
- package/dist/vite/index.js.bak +5448 -0
- package/package.json +2 -2
- package/skills/links/SKILL.md +3 -1
- package/skills/middleware/SKILL.md +2 -0
- package/skills/prerender/SKILL.md +110 -68
- package/skills/router-setup/SKILL.md +35 -0
- package/src/__internal.ts +1 -1
- package/src/browser/app-version.ts +14 -0
- package/src/browser/navigation-bridge.ts +19 -4
- package/src/browser/navigation-client.ts +64 -64
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/partial-update.ts +27 -5
- package/src/browser/prefetch/fetch.ts +8 -2
- package/src/browser/react/Link.tsx +44 -8
- package/src/browser/react/NavigationProvider.tsx +8 -1
- 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 -18
- package/src/browser/types.ts +20 -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/client.tsx +2 -56
- package/src/deps/browser.ts +0 -1
- 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 +4 -1
- 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 +48 -15
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/loader-resolution.ts +150 -21
- package/src/router/match-api.ts +124 -189
- 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 +0 -6
- package/src/router/middleware.ts +0 -3
- 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-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.ts +53 -5
- package/src/rsc/handler.ts +472 -398
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +12 -3
- package/src/rsc/rsc-rendering.ts +8 -2
- package/src/rsc/server-action.ts +8 -2
- package/src/rsc/ssr-setup.ts +2 -2
- package/src/rsc/types.ts +6 -4
- package/src/server/context.ts +39 -2
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +132 -13
- 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 -11
- package/src/types/loader-types.ts +32 -5
- package/src/types/route-entry.ts +1 -1
- package/src/types/segments.ts +1 -0
- 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 +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/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 -207
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +18 -5
- 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
- package/src/browser/debug-channel.ts +0 -93
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useContext, useMemo } from "react";
|
|
4
4
|
import { NavigationStoreContext } from "./context.js";
|
|
5
5
|
import { prefetchDirect } from "../prefetch/fetch.js";
|
|
6
|
+
import { getAppVersion } from "../app-version.js";
|
|
6
7
|
import type { RouterInstance, RouterNavigateOptions } from "../types.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -29,14 +30,22 @@ export function useRouter(): RouterInstance {
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
// Stable reference: ctx is itself stable (NavigationProvider memoizes with [])
|
|
32
|
-
return useMemo<RouterInstance>(
|
|
33
|
-
|
|
33
|
+
return useMemo<RouterInstance>(() => {
|
|
34
|
+
/** Prefix a root-relative path with basename if not already prefixed. */
|
|
35
|
+
function withBasename(url: string): string {
|
|
36
|
+
const bn = ctx!.basename;
|
|
37
|
+
if (!bn || !url.startsWith("/") || url.startsWith(bn + "/") || url === bn)
|
|
38
|
+
return url;
|
|
39
|
+
return url === "/" ? bn : bn + url;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
34
43
|
push(url: string, options?: RouterNavigateOptions): Promise<void> {
|
|
35
|
-
return ctx.navigate(url, { ...options, replace: false });
|
|
44
|
+
return ctx.navigate(withBasename(url), { ...options, replace: false });
|
|
36
45
|
},
|
|
37
46
|
|
|
38
47
|
replace(url: string, options?: RouterNavigateOptions): Promise<void> {
|
|
39
|
-
return ctx.navigate(url, { ...options, replace: true });
|
|
48
|
+
return ctx.navigate(withBasename(url), { ...options, replace: true });
|
|
40
49
|
},
|
|
41
50
|
|
|
42
51
|
refresh(): Promise<void> {
|
|
@@ -46,7 +55,12 @@ export function useRouter(): RouterInstance {
|
|
|
46
55
|
prefetch(url: string): void {
|
|
47
56
|
const segmentState = ctx.store?.getSegmentState();
|
|
48
57
|
if (segmentState) {
|
|
49
|
-
prefetchDirect(
|
|
58
|
+
prefetchDirect(
|
|
59
|
+
withBasename(url),
|
|
60
|
+
segmentState.currentSegmentIds,
|
|
61
|
+
getAppVersion(),
|
|
62
|
+
ctx.store?.getRouterId?.(),
|
|
63
|
+
);
|
|
50
64
|
}
|
|
51
65
|
},
|
|
52
66
|
|
|
@@ -57,7 +71,6 @@ export function useRouter(): RouterInstance {
|
|
|
57
71
|
forward(): void {
|
|
58
72
|
window.history.forward();
|
|
59
73
|
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
);
|
|
74
|
+
};
|
|
75
|
+
}, []);
|
|
63
76
|
}
|
|
@@ -23,6 +23,7 @@ import type { EventController } from "./event-controller.js";
|
|
|
23
23
|
import type { ResolvedThemeConfig, Theme } from "../theme/types.js";
|
|
24
24
|
import { initRangoState } from "./rango-state.js";
|
|
25
25
|
import { initPrefetchCache } from "./prefetch/cache.js";
|
|
26
|
+
import { setAppVersion } from "./app-version.js";
|
|
26
27
|
import {
|
|
27
28
|
isInterceptSegment,
|
|
28
29
|
splitInterceptSegments,
|
|
@@ -139,7 +140,6 @@ export async function initBrowserApp(
|
|
|
139
140
|
initialTheme,
|
|
140
141
|
} = options;
|
|
141
142
|
|
|
142
|
-
// Load initial payload from SSR-injected __FLIGHT_DATA__
|
|
143
143
|
const initialPayload =
|
|
144
144
|
await deps.createFromReadableStream<RscPayload>(rscStream);
|
|
145
145
|
|
|
@@ -164,6 +164,12 @@ export async function initBrowserApp(
|
|
|
164
164
|
...(storeOptions?.cacheSize && { cacheSize: storeOptions.cacheSize }),
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
+
// Seed router identity from the initial SSR payload so the first
|
|
168
|
+
// cross-app SPA navigation can detect the app switch.
|
|
169
|
+
if (initialPayload.metadata?.routerId) {
|
|
170
|
+
store.setRouterId?.(initialPayload.metadata.routerId);
|
|
171
|
+
}
|
|
172
|
+
|
|
167
173
|
// Create event controller for reactive state management
|
|
168
174
|
const eventController = createEventController({
|
|
169
175
|
initialLocation: new URL(window.location.href),
|
|
@@ -205,6 +211,7 @@ export async function initBrowserApp(
|
|
|
205
211
|
// Initialize the localStorage state key for cache invalidation.
|
|
206
212
|
// Uses the build version so a new deploy automatically busts all cached prefetches.
|
|
207
213
|
initRangoState(version ?? "0");
|
|
214
|
+
setAppVersion(version);
|
|
208
215
|
|
|
209
216
|
// Initialize the in-memory prefetch cache TTL from server config.
|
|
210
217
|
// A value of 0 disables the cache; undefined falls back to the module default.
|
|
@@ -231,7 +238,6 @@ export async function initBrowserApp(
|
|
|
231
238
|
deps,
|
|
232
239
|
onUpdate: (update) => store.emitUpdate(update),
|
|
233
240
|
renderSegments,
|
|
234
|
-
version,
|
|
235
241
|
onNavigate: (url, options) => {
|
|
236
242
|
if (!navigateFn) {
|
|
237
243
|
window.location.href = url;
|
|
@@ -249,7 +255,7 @@ export async function initBrowserApp(
|
|
|
249
255
|
client,
|
|
250
256
|
onUpdate: (update) => store.emitUpdate(update),
|
|
251
257
|
renderSegments,
|
|
252
|
-
version,
|
|
258
|
+
version: version,
|
|
253
259
|
});
|
|
254
260
|
|
|
255
261
|
// Connect action redirect → navigation bridge (now that both are initialized)
|
|
@@ -316,6 +322,7 @@ export async function initBrowserApp(
|
|
|
316
322
|
segmentIds: [],
|
|
317
323
|
previousUrl: store.getSegmentState().currentUrl,
|
|
318
324
|
interceptSourceUrl: interceptSourceUrl || undefined,
|
|
325
|
+
routerId: store.getRouterId?.(),
|
|
319
326
|
hmr: true,
|
|
320
327
|
signal: abort.signal,
|
|
321
328
|
});
|
|
@@ -329,6 +336,21 @@ export async function initBrowserApp(
|
|
|
329
336
|
throw new Error("HMR refetch returned invalid payload");
|
|
330
337
|
}
|
|
331
338
|
|
|
339
|
+
// Update version BEFORE rebuilding state so that
|
|
340
|
+
// clearHistoryCache() runs first, then the fresh segment
|
|
341
|
+
// cache entry we create below survives.
|
|
342
|
+
const newVersion = payload.metadata.version;
|
|
343
|
+
if (newVersion && newVersion !== version) {
|
|
344
|
+
console.log(
|
|
345
|
+
"[RSCRouter] HMR: version changed",
|
|
346
|
+
version,
|
|
347
|
+
"→",
|
|
348
|
+
newVersion,
|
|
349
|
+
"clearing caches",
|
|
350
|
+
);
|
|
351
|
+
navigationBridge.updateVersion(newVersion);
|
|
352
|
+
}
|
|
353
|
+
|
|
332
354
|
if (payload.metadata?.isPartial) {
|
|
333
355
|
const segments = payload.metadata.segments || [];
|
|
334
356
|
const matched = payload.metadata.matched || [];
|
|
@@ -478,6 +500,7 @@ export function RSCRouter(_props: RSCRouterProps): React.ReactElement {
|
|
|
478
500
|
initialTheme={initialTheme}
|
|
479
501
|
warmupEnabled={warmupEnabled}
|
|
480
502
|
version={version}
|
|
503
|
+
basename={initialPayload.metadata?.basename}
|
|
481
504
|
/>
|
|
482
505
|
);
|
|
483
506
|
}
|
|
@@ -356,19 +356,18 @@ export function handleNavigationEnd(options: {
|
|
|
356
356
|
scroll?: boolean;
|
|
357
357
|
isStreaming?: () => boolean;
|
|
358
358
|
}): void {
|
|
359
|
-
if (!initialized) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
359
|
const { restore = false, scroll = true, isStreaming } = options;
|
|
364
360
|
|
|
365
|
-
// Don't scroll if explicitly disabled
|
|
366
|
-
if (scroll === false) {
|
|
361
|
+
// Don't scroll if explicitly disabled or not in a browser
|
|
362
|
+
if (scroll === false || typeof window === "undefined") {
|
|
367
363
|
return;
|
|
368
364
|
}
|
|
369
365
|
|
|
370
|
-
//
|
|
371
|
-
|
|
366
|
+
// Save/restore requires initialization (sessionStorage, history state).
|
|
367
|
+
// But basic scroll-to-top and hash scrolling work without it — this
|
|
368
|
+
// matters during cross-app navigation where ScrollRestoration unmounts
|
|
369
|
+
// and remounts, creating a brief window where initialized is false.
|
|
370
|
+
if (restore && initialized) {
|
|
372
371
|
if (restoreScrollPosition({ retryIfStreaming: true, isStreaming })) {
|
|
373
372
|
return;
|
|
374
373
|
}
|
|
@@ -378,6 +377,9 @@ export function handleNavigationEnd(options: {
|
|
|
378
377
|
// Defer hash and scroll-to-top to after React paints the new content,
|
|
379
378
|
// so the user doesn't see the current page jump before the new route appears.
|
|
380
379
|
deferToNextPaint(() => {
|
|
380
|
+
// Re-check: the deferred callback may fire after environment teardown
|
|
381
|
+
if (typeof window === "undefined") return;
|
|
382
|
+
|
|
381
383
|
// Try hash scrolling first
|
|
382
384
|
if (scrollToHash()) {
|
|
383
385
|
return;
|
|
@@ -4,8 +4,6 @@ import type {
|
|
|
4
4
|
RscPayload,
|
|
5
5
|
} from "./types.js";
|
|
6
6
|
import { createPartialUpdater } from "./partial-update.js";
|
|
7
|
-
import { createClientDebugChannel, DEBUG_ID_HEADER } from "./debug-channel.js";
|
|
8
|
-
import { findSourceMapURL } from "../deps/browser.js";
|
|
9
7
|
import { createNavigationTransaction } from "./navigation-transaction.js";
|
|
10
8
|
import {
|
|
11
9
|
reconcileSegments,
|
|
@@ -31,6 +29,7 @@ import {
|
|
|
31
29
|
} from "./response-adapter.js";
|
|
32
30
|
import { mergeLocationState } from "./history-state.js";
|
|
33
31
|
import { classifyActionOutcome } from "./action-coordinator.js";
|
|
32
|
+
import { getAppVersion } from "./app-version.js";
|
|
34
33
|
|
|
35
34
|
// Polyfill Symbol.dispose/asyncDispose for Safari and older browsers
|
|
36
35
|
if (typeof Symbol.dispose === "undefined") {
|
|
@@ -45,8 +44,6 @@ if (typeof Symbol.asyncDispose === "undefined") {
|
|
|
45
44
|
*/
|
|
46
45
|
export interface ServerActionBridgeConfigWithController extends ServerActionBridgeConfig {
|
|
47
46
|
eventController: EventController;
|
|
48
|
-
/** RSC version from initial payload metadata */
|
|
49
|
-
version?: string;
|
|
50
47
|
/** Callback to trigger SPA navigation (for action redirects) */
|
|
51
48
|
onNavigate?: (
|
|
52
49
|
url: string,
|
|
@@ -77,7 +74,6 @@ export function createServerActionBridge(
|
|
|
77
74
|
deps,
|
|
78
75
|
onUpdate,
|
|
79
76
|
renderSegments,
|
|
80
|
-
version,
|
|
81
77
|
onNavigate,
|
|
82
78
|
} = config;
|
|
83
79
|
|
|
@@ -88,7 +84,7 @@ export function createServerActionBridge(
|
|
|
88
84
|
client,
|
|
89
85
|
onUpdate,
|
|
90
86
|
renderSegments,
|
|
91
|
-
|
|
87
|
+
getVersion: getAppVersion,
|
|
92
88
|
});
|
|
93
89
|
|
|
94
90
|
/**
|
|
@@ -167,9 +163,15 @@ export function createServerActionBridge(
|
|
|
167
163
|
segmentState.currentSegmentIds.join(","),
|
|
168
164
|
);
|
|
169
165
|
// Add version param for version mismatch detection
|
|
166
|
+
const version = getAppVersion();
|
|
170
167
|
if (version) {
|
|
171
168
|
url.searchParams.set("_rsc_v", version);
|
|
172
169
|
}
|
|
170
|
+
// Add router ID for app switch detection
|
|
171
|
+
const rid = store.getRouterId?.();
|
|
172
|
+
if (rid) {
|
|
173
|
+
url.searchParams.set("_rsc_rid", rid);
|
|
174
|
+
}
|
|
173
175
|
|
|
174
176
|
// Encode arguments
|
|
175
177
|
const encodedBody = await deps.encodeReply(args, { temporaryReferences });
|
|
@@ -201,14 +203,6 @@ export function createServerActionBridge(
|
|
|
201
203
|
const onHandleAbort = () => fetchAbort.abort();
|
|
202
204
|
handle.signal.addEventListener("abort", onHandleAbort, { once: true });
|
|
203
205
|
|
|
204
|
-
// Dev-only: create debug channel for React Performance Tracks
|
|
205
|
-
const debugId = (import.meta as any).hot
|
|
206
|
-
? crypto.randomUUID()
|
|
207
|
-
: undefined;
|
|
208
|
-
const debugChannel = debugId
|
|
209
|
-
? createClientDebugChannel(debugId)
|
|
210
|
-
: undefined;
|
|
211
|
-
|
|
212
206
|
// Send action request with stream tracking
|
|
213
207
|
const responsePromise = fetch(url, {
|
|
214
208
|
method: "POST",
|
|
@@ -216,11 +210,9 @@ export function createServerActionBridge(
|
|
|
216
210
|
"rsc-action": id,
|
|
217
211
|
"X-RSC-Router-Client-Path": segmentState.currentUrl,
|
|
218
212
|
...(tx && { "X-RSC-Router-Request-Id": tx.requestId }),
|
|
219
|
-
// Send intercept source URL so server can maintain intercept context
|
|
220
213
|
...(interceptSourceUrl && {
|
|
221
214
|
"X-RSC-Router-Intercept-Source": interceptSourceUrl,
|
|
222
215
|
}),
|
|
223
|
-
...(debugId && { [DEBUG_ID_HEADER]: debugId }),
|
|
224
216
|
},
|
|
225
217
|
body: encodedBody,
|
|
226
218
|
signal: fetchAbort.signal,
|
|
@@ -283,7 +275,6 @@ export function createServerActionBridge(
|
|
|
283
275
|
try {
|
|
284
276
|
payload = await deps.createFromFetch<RscPayload>(responsePromise, {
|
|
285
277
|
temporaryReferences,
|
|
286
|
-
...(debugChannel && { debugChannel, findSourceMapURL }),
|
|
287
278
|
});
|
|
288
279
|
} catch (error) {
|
|
289
280
|
// Clean up streaming token on error (may be null if fetch failed before .then() ran)
|
|
@@ -321,7 +312,6 @@ export function createServerActionBridge(
|
|
|
321
312
|
matchedCount: payload.metadata?.matched?.length ?? 0,
|
|
322
313
|
diffCount: payload.metadata?.diff?.length ?? 0,
|
|
323
314
|
});
|
|
324
|
-
|
|
325
315
|
// Guard: if the action was aborted while streaming (e.g., user navigated
|
|
326
316
|
// away or abortAllActions fired), bail out before any reconcile/render/cache
|
|
327
317
|
// writes to avoid overwriting the current UI with stale action results.
|
package/src/browser/types.ts
CHANGED
|
@@ -32,6 +32,9 @@ export type HandleData = Record<string, Record<string, unknown[]>>;
|
|
|
32
32
|
export interface RscMetadata {
|
|
33
33
|
pathname: string;
|
|
34
34
|
segments: ResolvedSegment[];
|
|
35
|
+
/** Router instance ID. When this changes between navigations, the client
|
|
36
|
+
* forces a full tree replacement (app switch via host router). */
|
|
37
|
+
routerId?: string;
|
|
35
38
|
isPartial?: boolean;
|
|
36
39
|
isError?: boolean;
|
|
37
40
|
matched?: string[];
|
|
@@ -70,6 +73,8 @@ export interface RscMetadata {
|
|
|
70
73
|
* Included when theme is enabled in router config.
|
|
71
74
|
*/
|
|
72
75
|
initialTheme?: Theme;
|
|
76
|
+
/** URL prefix for all routes (from createRouter({ basename })). */
|
|
77
|
+
basename?: string;
|
|
73
78
|
/** Whether connection warmup is enabled */
|
|
74
79
|
warmupEnabled?: boolean;
|
|
75
80
|
/** Server-side redirect with optional state (for partial requests) */
|
|
@@ -343,7 +348,6 @@ export interface RscBrowserDependencies {
|
|
|
343
348
|
response: Promise<Response>,
|
|
344
349
|
options?: {
|
|
345
350
|
temporaryReferences?: any;
|
|
346
|
-
debugChannel?: { readable?: ReadableStream; writable?: WritableStream };
|
|
347
351
|
findSourceMapURL?: (
|
|
348
352
|
filename: string,
|
|
349
353
|
environmentName: string,
|
|
@@ -410,10 +414,13 @@ export interface NavigationStore {
|
|
|
410
414
|
segments: ResolvedSegment[],
|
|
411
415
|
handleData?: HandleData,
|
|
412
416
|
): void;
|
|
413
|
-
getCachedSegments(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
+
getCachedSegments(historyKey: string):
|
|
418
|
+
| {
|
|
419
|
+
segments: ResolvedSegment[];
|
|
420
|
+
stale: boolean;
|
|
421
|
+
handleData?: HandleData;
|
|
422
|
+
routerId?: string;
|
|
423
|
+
}
|
|
417
424
|
| undefined;
|
|
418
425
|
hasHistoryCache(historyKey: string): boolean;
|
|
419
426
|
updateCacheHandleData(historyKey: string, handleData: HandleData): void;
|
|
@@ -429,6 +436,10 @@ export interface NavigationStore {
|
|
|
429
436
|
getInterceptSourceUrl(): string | null;
|
|
430
437
|
setInterceptSourceUrl(url: string | null): void;
|
|
431
438
|
|
|
439
|
+
// Router identity tracking (for cross-app navigation detection)
|
|
440
|
+
getRouterId?(): string | undefined;
|
|
441
|
+
setRouterId?(id: string): void;
|
|
442
|
+
|
|
432
443
|
// UI update notifications
|
|
433
444
|
onUpdate(callback: UpdateSubscriber): () => void;
|
|
434
445
|
emitUpdate(update: NavigationUpdate): void;
|
|
@@ -459,6 +470,8 @@ export interface FetchPartialOptions {
|
|
|
459
470
|
interceptSourceUrl?: string;
|
|
460
471
|
/** RSC version for cache invalidation detection */
|
|
461
472
|
version?: string;
|
|
473
|
+
/** Current router ID — server detects app switch and returns full response */
|
|
474
|
+
routerId?: string;
|
|
462
475
|
/** If true, this is an HMR refetch - server should invalidate manifest cache */
|
|
463
476
|
hmr?: boolean;
|
|
464
477
|
}
|
|
@@ -527,6 +540,8 @@ export interface NavigationBridge {
|
|
|
527
540
|
refresh(): Promise<void>;
|
|
528
541
|
handlePopstate(): Promise<void>;
|
|
529
542
|
registerLinkInterception(): () => void;
|
|
543
|
+
/** Update the RSC version (e.g. after HMR). Clears prefetch cache. */
|
|
544
|
+
updateVersion(newVersion: string): void;
|
|
530
545
|
}
|
|
531
546
|
|
|
532
547
|
/**
|
|
@@ -45,7 +45,7 @@ export interface GeneratedManifest {
|
|
|
45
45
|
routeTrailingSlash?: Record<string, string>;
|
|
46
46
|
/** Route names using Prerender (for dev-mode Node.js delegation) */
|
|
47
47
|
prerenderRoutes?: string[];
|
|
48
|
-
/** Route names with
|
|
48
|
+
/** Route names wrapped with Passthrough() (live handler for runtime fallback) */
|
|
49
49
|
passthroughRoutes?: string[];
|
|
50
50
|
/** Route name → response type for non-RSC routes */
|
|
51
51
|
responseTypeRoutes?: Record<string, string>;
|
|
@@ -150,10 +150,7 @@ function buildPrefixTreeNode(
|
|
|
150
150
|
if (prerenderDefs && entry.prerenderDef) {
|
|
151
151
|
prerenderDefs[name] = entry.prerenderDef;
|
|
152
152
|
}
|
|
153
|
-
if (
|
|
154
|
-
passthroughRoutes &&
|
|
155
|
-
entry.prerenderDef?.options?.passthrough === true
|
|
156
|
-
) {
|
|
153
|
+
if (passthroughRoutes && entry.isPassthrough === true) {
|
|
157
154
|
passthroughRoutes.push(name);
|
|
158
155
|
}
|
|
159
156
|
}
|
|
@@ -285,6 +282,7 @@ export function generateManifest<TEnv>(
|
|
|
285
282
|
export function generateManifestFull<TEnv>(
|
|
286
283
|
urlpatterns: UrlPatterns<TEnv, any>,
|
|
287
284
|
mountIndex: number = 0,
|
|
285
|
+
options?: { urlPrefix?: string },
|
|
288
286
|
): FullManifest {
|
|
289
287
|
const routeManifest: Record<string, string> = {};
|
|
290
288
|
const routeAncestry: Record<string, string[]> = {};
|
|
@@ -310,6 +308,8 @@ export function generateManifestFull<TEnv>(
|
|
|
310
308
|
counters: {},
|
|
311
309
|
mountIndex,
|
|
312
310
|
trackedIncludes, // Enable include tracking
|
|
311
|
+
// basename sets the initial URL prefix for all path() registrations
|
|
312
|
+
...(options?.urlPrefix ? { urlPrefix: options.urlPrefix } : {}),
|
|
313
313
|
},
|
|
314
314
|
() => {
|
|
315
315
|
const helpers = createRouteHelpers();
|
|
@@ -347,7 +347,7 @@ export function generateManifestFull<TEnv>(
|
|
|
347
347
|
if (entry.prerenderDef) {
|
|
348
348
|
prerenderDefs[name] = entry.prerenderDef;
|
|
349
349
|
}
|
|
350
|
-
if (entry.
|
|
350
|
+
if (entry.isPassthrough === true) {
|
|
351
351
|
passthroughRoutes.push(name);
|
|
352
352
|
}
|
|
353
353
|
}
|
|
@@ -25,6 +25,9 @@ export {
|
|
|
25
25
|
} from "./route-types/include-resolution.js";
|
|
26
26
|
export {
|
|
27
27
|
extractUrlsVariableFromRouter,
|
|
28
|
+
extractUrlsFromRouter,
|
|
29
|
+
extractBasenameFromRouter,
|
|
30
|
+
type UrlsExtractionResult,
|
|
28
31
|
buildCombinedRouteMapForRouterFile,
|
|
29
32
|
detectUnresolvableIncludes,
|
|
30
33
|
detectUnresolvableIncludesForUrlsFile,
|
|
@@ -357,12 +357,17 @@ function buildRouteMapFromBlock(
|
|
|
357
357
|
/**
|
|
358
358
|
* Build route map and search schemas together.
|
|
359
359
|
* Internal helper used by the include resolution path.
|
|
360
|
+
*
|
|
361
|
+
* @param inlineBlock - Optional pre-extracted code block (e.g. from an inline
|
|
362
|
+
* builder function). When provided, variableName is ignored and the block
|
|
363
|
+
* is parsed directly for path()/include() calls.
|
|
360
364
|
*/
|
|
361
365
|
export function buildCombinedRouteMapWithSearch(
|
|
362
366
|
filePath: string,
|
|
363
367
|
variableName?: string,
|
|
364
368
|
visited?: Set<string>,
|
|
365
369
|
diagnosticsOut?: UnresolvableInclude[],
|
|
370
|
+
inlineBlock?: string,
|
|
366
371
|
): {
|
|
367
372
|
routes: Record<string, string>;
|
|
368
373
|
searchSchemas: Record<string, Record<string, string>>;
|
|
@@ -384,7 +389,9 @@ export function buildCombinedRouteMapWithSearch(
|
|
|
384
389
|
}
|
|
385
390
|
|
|
386
391
|
let block: string;
|
|
387
|
-
if (
|
|
392
|
+
if (inlineBlock) {
|
|
393
|
+
block = inlineBlock;
|
|
394
|
+
} else if (variableName) {
|
|
388
395
|
const extracted = extractUrlsBlockForVariable(source, variableName);
|
|
389
396
|
if (!extracted) return { routes: {}, searchSchemas: {} };
|
|
390
397
|
block = extracted;
|