@rangojs/router 0.24.3 → 0.25.0
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 +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/types.d.ts +8 -0
- package/dist/types/client-urls/client-root.d.ts +4 -1
- package/dist/types/client-urls/client-urls.d.ts +28 -2
- package/dist/types/client-urls/navigation.d.ts +6 -8
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +20 -5
- package/dist/types/client-urls/types.d.ts +35 -17
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -6
- package/dist/types/router/router-options.d.ts +4 -7
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +19 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +10 -1
- package/dist/types/rsc/shell-capture.d.ts +7 -0
- package/dist/types/rsc/types.d.ts +5 -0
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/context.d.ts +64 -4
- package/dist/types/server/request-context.d.ts +41 -4
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +41 -4
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +4 -3
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/urls/path-helper.d.ts +5 -0
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6816 -6547
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/caching/SKILL.md +3 -1
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +195 -32
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +32 -10
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +43 -8
- package/skills/prerender/SKILL.md +1 -2
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +85 -21
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +119 -98
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-reverse.ts +4 -17
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +2 -0
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/types.ts +8 -0
- package/src/cache/vercel/vercel-cache-store.ts +6 -1
- package/src/client-urls/client-root.tsx +10 -5
- package/src/client-urls/client-urls.ts +246 -166
- package/src/client-urls/navigation.ts +45 -38
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +286 -75
- package/src/client-urls/types.ts +40 -16
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +27 -7
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +6 -8
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -7
- package/src/router/router-options.ts +4 -7
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +37 -2
- package/src/router/tracing.ts +2 -2
- package/src/router.ts +2 -9
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +83 -41
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +22 -0
- package/src/rsc/shell-capture.ts +11 -0
- package/src/rsc/types.ts +5 -0
- package/src/segment-system.tsx +125 -119
- package/src/server/context.ts +96 -5
- package/src/server/handle-store.ts +3 -2
- package/src/server/request-context.ts +42 -3
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/dispatch.ts +4 -5
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +77 -4
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +4 -3
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/urls/path-helper.ts +16 -3
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
|
@@ -75,7 +75,8 @@ let customGetKey: GetScrollKeyFunction | null = null;
|
|
|
75
75
|
/**
|
|
76
76
|
* Scroll key of the entry on screen. At popstate history.state already belongs
|
|
77
77
|
* to the destination, so this is the only way to save the page being left
|
|
78
|
-
* under its own key (see handleTraversalStart).
|
|
78
|
+
* under its own key (see handleTraversalStart). A traversal moves it when its
|
|
79
|
+
* restore commits (handleNavigationEnd), not at its event.
|
|
79
80
|
*/
|
|
80
81
|
let displayedKey: string | null = null;
|
|
81
82
|
|
|
@@ -283,10 +284,12 @@ export function cancelScrollRestorationPolling(): void {
|
|
|
283
284
|
*
|
|
284
285
|
* @param options.retryIfStreaming - If true, poll while streaming until we can scroll to target
|
|
285
286
|
* @param options.isStreaming - Function to check if streaming is in progress
|
|
287
|
+
* @param options.committed - React has committed the restored tree (a layout effect)
|
|
286
288
|
*/
|
|
287
289
|
export function restoreScrollPosition(options?: {
|
|
288
290
|
retryIfStreaming?: boolean;
|
|
289
291
|
isStreaming?: () => boolean;
|
|
292
|
+
committed?: boolean;
|
|
290
293
|
}): boolean {
|
|
291
294
|
// Clear any pending polling
|
|
292
295
|
cancelScrollRestorationPolling();
|
|
@@ -298,6 +301,11 @@ export function restoreScrollPosition(options?: {
|
|
|
298
301
|
return false;
|
|
299
302
|
}
|
|
300
303
|
|
|
304
|
+
// Once React committed the restored tree, scroll now, so a view
|
|
305
|
+
// transition's new snapshot is taken at the restored position, while the
|
|
306
|
+
// page still streams too.
|
|
307
|
+
if (options?.committed) window.scrollTo(0, savedY);
|
|
308
|
+
|
|
301
309
|
// If streaming, poll until streaming ends then scroll to saved position
|
|
302
310
|
if (options?.retryIfStreaming && options?.isStreaming?.()) {
|
|
303
311
|
const startTime = Date.now();
|
|
@@ -319,9 +327,8 @@ export function restoreScrollPosition(options?: {
|
|
|
319
327
|
return true;
|
|
320
328
|
}
|
|
321
329
|
|
|
322
|
-
// Not streaming
|
|
323
|
-
//
|
|
324
|
-
// would be overwritten when React replaces the content.
|
|
330
|
+
// Not streaming: scroll (again) on the next frame, for layout that settles
|
|
331
|
+
// after the commit and for a caller that ran before it.
|
|
325
332
|
deferToNextPaint(() => {
|
|
326
333
|
window.scrollTo(0, savedY);
|
|
327
334
|
debugLog("[Scroll] Restored position:", savedY, "for key:", key);
|
|
@@ -374,18 +381,18 @@ export function handleNavigationStart(): void {
|
|
|
374
381
|
|
|
375
382
|
/**
|
|
376
383
|
* Handle scroll for a back/forward traversal, at popstate before anything
|
|
377
|
-
* renders: save the page
|
|
378
|
-
* so saveCurrentScrollPosition would use the destination's key)
|
|
379
|
-
*
|
|
384
|
+
* renders: save the page on screen under ITS key (history has already moved,
|
|
385
|
+
* so saveCurrentScrollPosition would use the destination's key). A second
|
|
386
|
+
* traversal before the first one's restore commits (a rapid double Back, a
|
|
387
|
+
* deferred animated restore) saves the page still on screen, never an entry
|
|
388
|
+
* it did not show.
|
|
380
389
|
*/
|
|
381
390
|
export function handleTraversalStart(): void {
|
|
382
391
|
if (!initialized) return;
|
|
383
|
-
const leftKey = displayedKey;
|
|
384
|
-
trackDisplayedEntry();
|
|
385
392
|
// A key shared with the destination (a getKey that collapses entries, e.g.
|
|
386
393
|
// by pathname) keeps the destination's saved position.
|
|
387
|
-
if (
|
|
388
|
-
saveScrollPosition(
|
|
394
|
+
if (displayedKey !== null && displayedKey !== getScrollKey()) {
|
|
395
|
+
saveScrollPosition(displayedKey, window.scrollY);
|
|
389
396
|
}
|
|
390
397
|
}
|
|
391
398
|
|
|
@@ -394,13 +401,15 @@ export function handleTraversalStart(): void {
|
|
|
394
401
|
* @param options.restore - If true, restore saved position (for popstate)
|
|
395
402
|
* @param options.scroll - If false, don't scroll at all
|
|
396
403
|
* @param options.isStreaming - Function to check if streaming is in progress (for retry logic)
|
|
404
|
+
* @param options.committed - Called after React committed the new tree
|
|
397
405
|
*/
|
|
398
406
|
export function handleNavigationEnd(options: {
|
|
399
407
|
restore?: boolean;
|
|
400
408
|
scroll?: boolean;
|
|
401
409
|
isStreaming?: () => boolean;
|
|
410
|
+
committed?: boolean;
|
|
402
411
|
}): void {
|
|
403
|
-
const { restore = false, scroll = true, isStreaming } = options;
|
|
412
|
+
const { restore = false, scroll = true, isStreaming, committed } = options;
|
|
404
413
|
|
|
405
414
|
// Don't scroll if explicitly disabled or not in a browser
|
|
406
415
|
if (scroll === false || typeof window === "undefined") {
|
|
@@ -412,11 +421,17 @@ export function handleNavigationEnd(options: {
|
|
|
412
421
|
// matters during cross-app navigation where ScrollRestoration unmounts
|
|
413
422
|
// and remounts, creating a brief window where initialized is false.
|
|
414
423
|
if (restore) {
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
424
|
+
if (initialized) {
|
|
425
|
+
trackDisplayedEntry();
|
|
426
|
+
if (
|
|
427
|
+
restoreScrollPosition({
|
|
428
|
+
retryIfStreaming: true,
|
|
429
|
+
isStreaming,
|
|
430
|
+
committed,
|
|
431
|
+
})
|
|
432
|
+
) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
420
435
|
}
|
|
421
436
|
// An "auto" entry (no <Html.ScrollRestoration>, or created before it mounted)
|
|
422
437
|
// is restored by the browser. A scrollTo here races that restore, and
|
|
@@ -438,9 +453,8 @@ export function handleNavigationEnd(options: {
|
|
|
438
453
|
// against unmeasured layout, so sync scroll is correct here even
|
|
439
454
|
// before the new tree's scrollHeight settles.
|
|
440
455
|
//
|
|
441
|
-
// (The restore branch above
|
|
442
|
-
// depends on the new tree's max scroll
|
|
443
|
-
// unmeasured DOM would clamp savedY to whatever the old/zero max was.)
|
|
456
|
+
// (The restore branch above also scrolls again on the next frame: savedY
|
|
457
|
+
// depends on the new tree's max scroll, which can grow after the commit.)
|
|
444
458
|
if (scrollToHash()) {
|
|
445
459
|
return;
|
|
446
460
|
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import type { ResolvedSegment } from "./types.js";
|
|
2
|
-
import {
|
|
3
|
-
mergeSegmentLoaders,
|
|
4
|
-
needsLoaderMerge,
|
|
5
|
-
insertMissingDiffSegments,
|
|
6
|
-
} from "./merge-segment-loaders.js";
|
|
2
|
+
import { insertMissingDiffSegments } from "./merge-segment-loaders.js";
|
|
7
3
|
import { assertSegmentStructure } from "./segment-structure-assert.js";
|
|
8
4
|
import { splitInterceptSegments } from "./intercept-utils.js";
|
|
9
5
|
import { debugLog, IS_BROWSER_DEBUG } from "./logging.js";
|
|
@@ -11,15 +7,17 @@ import { debugLog, IS_BROWSER_DEBUG } from "./logging.js";
|
|
|
11
7
|
/**
|
|
12
8
|
* Determines the merging behavior for segment reconciliation.
|
|
13
9
|
*
|
|
14
|
-
* - 'action': From server-action-bridge's own merge. Always
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* on cached segments not in server diff.
|
|
10
|
+
* - 'action': From server-action-bridge's own merge. Always preserves cached
|
|
11
|
+
* loading (even undefined), never clears cached segment loading.
|
|
12
|
+
* - 'navigation': From partial-update during normal navigation. Preserves
|
|
13
|
+
* cached loading only when defined, clears truthy loading on cached
|
|
14
|
+
* segments not in server diff.
|
|
20
15
|
* - 'stale-revalidation': From partial-update during stale revalidation or
|
|
21
|
-
* action-triggered refetch.
|
|
22
|
-
*
|
|
16
|
+
* action-triggered refetch. Always preserves cached loading (same as
|
|
17
|
+
* action), clears truthy loading on cached segments not in server diff.
|
|
18
|
+
*
|
|
19
|
+
* No actor merges loader data: a loader the server skipped, an intercept's
|
|
20
|
+
* included, is its own cached segment in `matched`.
|
|
23
21
|
*/
|
|
24
22
|
export type ReconcileActor = "navigation" | "action" | "stale-revalidation";
|
|
25
23
|
|
|
@@ -64,11 +62,6 @@ export interface ReconcileResult {
|
|
|
64
62
|
* (not undefined). When cached is undefined, lets server value through
|
|
65
63
|
* because we're building a new tree.
|
|
66
64
|
*
|
|
67
|
-
* Loader merging:
|
|
68
|
-
* - action/stale-revalidation: Merges partial loader data when server returns
|
|
69
|
-
* fewer loaders than cached (revalidation only updated some loaders).
|
|
70
|
-
* - navigation: Does not merge (full navigation fetches complete data).
|
|
71
|
-
*
|
|
72
65
|
* Cached segment handling (segments in matched but not in server response):
|
|
73
66
|
* - action: Returns cached segment as-is (preserve tree structure).
|
|
74
67
|
* - navigation/stale-revalidation: Clears truthy loading to undefined
|
|
@@ -77,7 +70,6 @@ export interface ReconcileResult {
|
|
|
77
70
|
*/
|
|
78
71
|
export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
79
72
|
const { actor, matched, diff, insertMissingDiff } = input;
|
|
80
|
-
const shouldMergeLoaders = actor !== "navigation";
|
|
81
73
|
const context = actor === "action" ? "action-bridge" : "partial-update";
|
|
82
74
|
|
|
83
75
|
// Build lookup maps from arrays
|
|
@@ -119,16 +111,6 @@ export function reconcileSegments(input: ReconcileInput): ReconcileResult {
|
|
|
119
111
|
|
|
120
112
|
if (fromServer) {
|
|
121
113
|
const inDiff = diffSet.has(segId);
|
|
122
|
-
// Merge partial loader data when server returns fewer loaders than cached
|
|
123
|
-
if (shouldMergeLoaders && needsLoaderMerge(fromServer, fromCache)) {
|
|
124
|
-
if (IS_BROWSER_DEBUG) {
|
|
125
|
-
debugLog(
|
|
126
|
-
`[reconcile] ${segId}: MERGE loaders (server partial, ${inDiff ? "in diff" : "not in diff"})`,
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
return mergeSegmentLoaders(fromServer, fromCache);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
114
|
// Preserve cached structural properties to maintain consistent React tree.
|
|
133
115
|
// Changing these between renders alters the element nesting
|
|
134
116
|
// (with/without RouteContentWrapper, MountContextProvider, etc.),
|
|
@@ -3,8 +3,10 @@ import type {
|
|
|
3
3
|
ServerActionBridgeConfig,
|
|
4
4
|
RscPayload,
|
|
5
5
|
ResolvedSegment,
|
|
6
|
+
NavigationUpdate,
|
|
7
|
+
UpdateSubscriber,
|
|
6
8
|
} from "./types.js";
|
|
7
|
-
import { createPartialUpdater } from "./partial-update.js";
|
|
9
|
+
import { addTransitionType, createPartialUpdater } from "./partial-update.js";
|
|
8
10
|
import { enterActionFence, exitActionFence } from "./action-fence.js";
|
|
9
11
|
import { KEEP_CACHE_HEADER } from "./cookie-name.js";
|
|
10
12
|
import { createNavigationTransaction } from "./navigation-transaction.js";
|
|
@@ -39,6 +41,7 @@ import {
|
|
|
39
41
|
import { mergeLocationState } from "./history-state.js";
|
|
40
42
|
import { stampLocationState } from "./react/location-state-shared.js";
|
|
41
43
|
import { classifyActionOutcome } from "./action-coordinator.js";
|
|
44
|
+
import { revalidationErrorOf } from "../decode-loader-results.js";
|
|
42
45
|
import { getAppVersion } from "./app-version.js";
|
|
43
46
|
import {
|
|
44
47
|
decideCommitGatedOff,
|
|
@@ -99,6 +102,24 @@ function applyActionLocationState(
|
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
|
|
105
|
+
/**
|
|
106
|
+
* An action's commit: urgent when transition({ when }) gated it off, like
|
|
107
|
+
* every gated-off commit, else a transition tagged `action` (#1165).
|
|
108
|
+
*/
|
|
109
|
+
function commitAction(
|
|
110
|
+
onUpdate: UpdateSubscriber,
|
|
111
|
+
update: NavigationUpdate,
|
|
112
|
+
gatedOff: boolean,
|
|
113
|
+
): void {
|
|
114
|
+
if (gatedOff) onUpdate(update);
|
|
115
|
+
else
|
|
116
|
+
startTransition(() => {
|
|
117
|
+
addTransitionType?.("action");
|
|
118
|
+
update.inTransition = true;
|
|
119
|
+
onUpdate(update);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
102
123
|
/**
|
|
103
124
|
* Create a server action bridge for handling RSC server actions
|
|
104
125
|
*
|
|
@@ -173,6 +194,7 @@ export function createServerActionBridge(
|
|
|
173
194
|
* Refetch current route via a navigation transaction.
|
|
174
195
|
* Encapsulates the repeated pattern of creating a navTx + fetchPartialUpdate
|
|
175
196
|
* used by navigated-away, hmr-missing, and consolidation-needed scenarios.
|
|
197
|
+
* Resolves with the error of a loader that threw on the refetch.
|
|
176
198
|
*/
|
|
177
199
|
async function refetchRoute(opts?: {
|
|
178
200
|
segments?: string[];
|
|
@@ -180,7 +202,7 @@ export function createServerActionBridge(
|
|
|
180
202
|
actionId?: string;
|
|
181
203
|
/** transition({ when }) `action` fields for the refetch commit. */
|
|
182
204
|
action?: TransitionWhenActionInput;
|
|
183
|
-
}): Promise<
|
|
205
|
+
}): Promise<Error | undefined> {
|
|
184
206
|
const src = opts?.interceptSourceUrl ?? null;
|
|
185
207
|
const navTx = createNavigationTransaction(
|
|
186
208
|
store,
|
|
@@ -189,7 +211,7 @@ export function createServerActionBridge(
|
|
|
189
211
|
{ replace: true, skipLoadingState: true },
|
|
190
212
|
);
|
|
191
213
|
try {
|
|
192
|
-
await fetchPartialUpdate(
|
|
214
|
+
return await fetchPartialUpdate(
|
|
193
215
|
window.location.href,
|
|
194
216
|
opts?.segments ?? [],
|
|
195
217
|
false,
|
|
@@ -302,6 +324,11 @@ export function createServerActionBridge(
|
|
|
302
324
|
if (rid) {
|
|
303
325
|
url.searchParams.set("_rsc_rid", rid);
|
|
304
326
|
}
|
|
327
|
+
// The page's rewrite target: the server diffs the re-render against it.
|
|
328
|
+
const rewriteTarget = store.getHistoryEntryMemory()?.rewriteTarget;
|
|
329
|
+
if (rewriteTarget) {
|
|
330
|
+
url.searchParams.set("_rsc_rt", rewriteTarget);
|
|
331
|
+
}
|
|
305
332
|
|
|
306
333
|
// Encode arguments
|
|
307
334
|
const encodedBody = await deps.encodeReply(args, { temporaryReferences });
|
|
@@ -618,12 +645,13 @@ export function createServerActionBridge(
|
|
|
618
645
|
);
|
|
619
646
|
store.rememberDisplayedEntry();
|
|
620
647
|
|
|
621
|
-
// Update UI with error boundary
|
|
622
|
-
// gated it off, like every gated-off commit.
|
|
623
|
-
const errorUpdate = { root: errorTree, metadata: metadata! };
|
|
648
|
+
// Update UI with error boundary.
|
|
624
649
|
if (process.env.NODE_ENV !== "production") auditTreeCause("action");
|
|
625
|
-
|
|
626
|
-
|
|
650
|
+
commitAction(
|
|
651
|
+
onUpdate,
|
|
652
|
+
{ root: errorTree, metadata: metadata! },
|
|
653
|
+
errorGatedOff,
|
|
654
|
+
);
|
|
627
655
|
|
|
628
656
|
// Throw the error so the action promise rejects
|
|
629
657
|
if (returnValue && !returnValue.ok) {
|
|
@@ -677,6 +705,8 @@ export function createServerActionBridge(
|
|
|
677
705
|
const fullSegments = reconciled.segments;
|
|
678
706
|
|
|
679
707
|
const returnData = returnValue?.data;
|
|
708
|
+
// A loader that threw while it re-ran: reported, not thrown.
|
|
709
|
+
let revalidationError: Error | undefined;
|
|
680
710
|
|
|
681
711
|
if (returnValue && !returnValue.ok) {
|
|
682
712
|
handle.fail(returnValue.data);
|
|
@@ -754,7 +784,7 @@ export function createServerActionBridge(
|
|
|
754
784
|
// route. Invalidation is deferred to finalizeAction(); here we only
|
|
755
785
|
// trigger the revalidation refetch (suppressed on keep).
|
|
756
786
|
if (!keepCache) {
|
|
757
|
-
await refetchRoute({
|
|
787
|
+
revalidationError = await refetchRoute({
|
|
758
788
|
interceptSourceUrl: store.getInterceptSourceUrl(),
|
|
759
789
|
actionId: id,
|
|
760
790
|
action: whenAction,
|
|
@@ -771,7 +801,7 @@ export function createServerActionBridge(
|
|
|
771
801
|
// resolving last must discharge a directive-free sibling's repair.
|
|
772
802
|
// See the keep row in docs/design/rango-state-cookie.md (the all-keep
|
|
773
803
|
// edge, and the benign re-mark-stale-after-refetch end-state delta).
|
|
774
|
-
await refetchRoute({
|
|
804
|
+
revalidationError = await refetchRoute({
|
|
775
805
|
interceptSourceUrl,
|
|
776
806
|
actionId: id,
|
|
777
807
|
action: whenAction,
|
|
@@ -794,7 +824,7 @@ export function createServerActionBridge(
|
|
|
794
824
|
handle.clearConsolidation();
|
|
795
825
|
|
|
796
826
|
// Ungated on keepCache, same as hmr-missing above (see the keep row).
|
|
797
|
-
await refetchRoute({
|
|
827
|
+
revalidationError = await refetchRoute({
|
|
798
828
|
segments: segmentsToSend,
|
|
799
829
|
interceptSourceUrl,
|
|
800
830
|
actionId: id,
|
|
@@ -824,6 +854,9 @@ export function createServerActionBridge(
|
|
|
824
854
|
currentHandleData,
|
|
825
855
|
);
|
|
826
856
|
}
|
|
857
|
+
// Not rendered: the sibling's refetch reruns these loaders and
|
|
858
|
+
// reports its own. Resolve when the metadata lands, without waiting
|
|
859
|
+
// for this answer's loader chunks.
|
|
827
860
|
break;
|
|
828
861
|
}
|
|
829
862
|
|
|
@@ -854,6 +887,7 @@ export function createServerActionBridge(
|
|
|
854
887
|
? reconciled.interceptSegments
|
|
855
888
|
: undefined,
|
|
856
889
|
});
|
|
890
|
+
revalidationError = await revalidationErrorOf(segments);
|
|
857
891
|
|
|
858
892
|
// Re-check if user navigated away (could happen during async renderSegments)
|
|
859
893
|
if (window.location.pathname !== actionStartPathname) {
|
|
@@ -891,12 +925,14 @@ export function createServerActionBridge(
|
|
|
891
925
|
fullSegments,
|
|
892
926
|
currentHandleData,
|
|
893
927
|
);
|
|
894
|
-
store.rememberDisplayedEntry(metadata
|
|
928
|
+
store.rememberDisplayedEntry(metadata);
|
|
895
929
|
|
|
896
|
-
const actionUpdate = { root: newTree, metadata: metadata! };
|
|
897
930
|
if (process.env.NODE_ENV !== "production") auditTreeCause("action");
|
|
898
|
-
|
|
899
|
-
|
|
931
|
+
commitAction(
|
|
932
|
+
onUpdate,
|
|
933
|
+
{ root: newTree, metadata: metadata! },
|
|
934
|
+
gatedOff,
|
|
935
|
+
);
|
|
900
936
|
// Invalidation deferred to finalizeAction() (runs after this caches
|
|
901
937
|
// the fresh segments), suppressed when the action called
|
|
902
938
|
// keepClientCache().
|
|
@@ -904,7 +940,7 @@ export function createServerActionBridge(
|
|
|
904
940
|
}
|
|
905
941
|
}
|
|
906
942
|
|
|
907
|
-
handle.complete(returnData);
|
|
943
|
+
handle.complete(returnData, revalidationError);
|
|
908
944
|
return returnData;
|
|
909
945
|
} catch (error) {
|
|
910
946
|
// What the call rejects with (a revalidation render that threw, a
|
package/src/browser/types.ts
CHANGED
|
@@ -36,6 +36,11 @@ export type HandleData = Record<string, Record<string, unknown[]>>;
|
|
|
36
36
|
*/
|
|
37
37
|
export interface RscMetadata {
|
|
38
38
|
pathname: string;
|
|
39
|
+
/**
|
|
40
|
+
* Path and search of the URL the route rendered, when middleware rewrote
|
|
41
|
+
* the request there. Kept per history entry and sent back as `_rsc_rt`.
|
|
42
|
+
*/
|
|
43
|
+
rewriteTarget?: string;
|
|
39
44
|
segments: ResolvedSegment[];
|
|
40
45
|
/** Router instance ID — the current app's identity. A mismatch with the
|
|
41
46
|
* client's id (sent as _rsc_rid) is detected server-side and answered with
|
|
@@ -248,6 +253,13 @@ export interface TrackedActionState {
|
|
|
248
253
|
|
|
249
254
|
/** Result data from the action (preserved after completion) */
|
|
250
255
|
result: unknown | null;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The error of a loader that threw while it re-ran after this successful
|
|
259
|
+
* action (the first, when several did). The loader also throws at its
|
|
260
|
+
* useLoader read, where the nearest error boundary catches it.
|
|
261
|
+
*/
|
|
262
|
+
revalidationError: unknown | null;
|
|
251
263
|
}
|
|
252
264
|
|
|
253
265
|
/**
|
|
@@ -292,13 +304,24 @@ export interface SegmentState {
|
|
|
292
304
|
currentSegmentIds: string[];
|
|
293
305
|
}
|
|
294
306
|
|
|
307
|
+
/**
|
|
308
|
+
* What a committed payload says about the page it shows, remembered per
|
|
309
|
+
* history entry (NavigationStore.rememberDisplayedEntry).
|
|
310
|
+
*
|
|
311
|
+
* @internal This type is an implementation detail and may change without notice.
|
|
312
|
+
*/
|
|
313
|
+
export interface ShownEntry {
|
|
314
|
+
readonly routeName?: string;
|
|
315
|
+
readonly rewriteTarget?: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
295
318
|
/**
|
|
296
319
|
* What the router remembers about one history entry for transition({ when })
|
|
297
|
-
* (NavigationStore.rememberDisplayedEntry).
|
|
320
|
+
* and the requests made from it (NavigationStore.rememberDisplayedEntry).
|
|
298
321
|
*
|
|
299
322
|
* @internal This type is an implementation detail and may change without notice.
|
|
300
323
|
*/
|
|
301
|
-
export interface HistoryEntryMemory {
|
|
324
|
+
export interface HistoryEntryMemory extends ShownEntry {
|
|
302
325
|
readonly routeName: string | undefined;
|
|
303
326
|
/** The entry's `history.state` as last recorded. */
|
|
304
327
|
readonly state: unknown;
|
|
@@ -326,6 +349,12 @@ export interface NavigationUpdate {
|
|
|
326
349
|
/** Function to check if streaming is in progress */
|
|
327
350
|
isStreaming?: () => boolean;
|
|
328
351
|
};
|
|
352
|
+
/**
|
|
353
|
+
* Handed to React inside startTransition (commitInTransition, an action's
|
|
354
|
+
* commit, a cached back/forward's): NavigationProvider joins the route
|
|
355
|
+
* state that arrives while React holds it to its commit.
|
|
356
|
+
*/
|
|
357
|
+
inTransition?: true;
|
|
329
358
|
}
|
|
330
359
|
|
|
331
360
|
/**
|
|
@@ -587,12 +616,12 @@ export interface NavigationStore {
|
|
|
587
616
|
/**
|
|
588
617
|
* Per-history-entry memory for transition({ when }) sources: record the
|
|
589
618
|
* entry on screen (keyed by its `history.state.key`) with its current
|
|
590
|
-
* `history.state` and, when given, its route name
|
|
591
|
-
* record otherwise; every push/replace
|
|
592
|
-
* never carries to another entry).
|
|
593
|
-
* state merge.
|
|
619
|
+
* `history.state` and, when `shown` is given, its route name and rewrite
|
|
620
|
+
* target (both kept from the last record otherwise; every push/replace
|
|
621
|
+
* creates a new key, so a stale record never carries to another entry).
|
|
622
|
+
* Called after every commit, restore and state merge.
|
|
594
623
|
*/
|
|
595
|
-
rememberDisplayedEntry(
|
|
624
|
+
rememberDisplayedEntry(shown?: ShownEntry): void;
|
|
596
625
|
/**
|
|
597
626
|
* The memory of the entry on screen, or of `entryKey`. At popstate
|
|
598
627
|
* history.state already belongs to the destination, so back/forward reads
|
|
@@ -683,6 +712,8 @@ export interface FetchPartialOptions {
|
|
|
683
712
|
targetUrl: string;
|
|
684
713
|
segmentIds: string[];
|
|
685
714
|
previousUrl: string;
|
|
715
|
+
/** The page's RscMetadata.rewriteTarget, sent as `_rsc_rt`. */
|
|
716
|
+
rewriteTarget?: string;
|
|
686
717
|
signal?: AbortSignal;
|
|
687
718
|
/** If true, this is a stale cache revalidation request - server should force revalidators */
|
|
688
719
|
staleRevalidation?: boolean;
|
|
@@ -772,7 +803,8 @@ export interface ServerActionBridgeConfig {
|
|
|
772
803
|
export interface NavigationBridge {
|
|
773
804
|
navigate(url: string, options?: NavigateOptionsInternal): Promise<void>;
|
|
774
805
|
refresh(): Promise<void>;
|
|
775
|
-
|
|
806
|
+
/** `event`: the popstate event; a traversal the browser animated itself (hasUAVisualTransition) is not animated again. */
|
|
807
|
+
handlePopstate(event?: PopStateEvent): Promise<void>;
|
|
776
808
|
registerLinkInterception(): () => void;
|
|
777
809
|
registerDelegatedPrefetch(): () => void;
|
|
778
810
|
/** Current RSC version (live, reflects the latest updateVersion). */
|
|
@@ -16,7 +16,11 @@ import {
|
|
|
16
16
|
RangoContext,
|
|
17
17
|
runWithPrefixes,
|
|
18
18
|
} from "../server/context.js";
|
|
19
|
-
import type {
|
|
19
|
+
import type {
|
|
20
|
+
EntryData,
|
|
21
|
+
IncludeScopePosition,
|
|
22
|
+
TrackedInclude,
|
|
23
|
+
} from "../server/context.js";
|
|
20
24
|
import type { TrailingSlashMode } from "../types.js";
|
|
21
25
|
import { createRouteHelpers } from "../route-definition.js";
|
|
22
26
|
import MapRootLayout from "../server/root-layout.js";
|
|
@@ -179,6 +183,7 @@ async function buildPrefixTreeNode(
|
|
|
179
183
|
routeSearchSchemas?: Record<string, Record<string, string>>,
|
|
180
184
|
routerId?: string,
|
|
181
185
|
validateTransitionWhen?: boolean,
|
|
186
|
+
position?: IncludeScopePosition,
|
|
182
187
|
): Promise<PrefixTreeNode> {
|
|
183
188
|
// Resolve an async include provider (`() => import("./routes")`) so its routes
|
|
184
189
|
// are walked into the build-time manifest/types/href. Runtime matching still
|
|
@@ -222,6 +227,10 @@ async function buildPrefixTreeNode(
|
|
|
222
227
|
mountIndex,
|
|
223
228
|
...(routerId ? { routerId } : {}),
|
|
224
229
|
trackedIncludes, // Enable nested include tracking
|
|
230
|
+
// The same position runtime expansion runs this include at
|
|
231
|
+
// (lazy-includes.ts), so nested hidden names agree across realms.
|
|
232
|
+
scopePath: position?.scopePath,
|
|
233
|
+
hiddenScopePath: position?.hiddenScopePath,
|
|
225
234
|
},
|
|
226
235
|
() => {
|
|
227
236
|
const helpers = createRouteHelpers();
|
|
@@ -304,6 +313,7 @@ async function buildPrefixTreeNode(
|
|
|
304
313
|
routeSearchSchemas,
|
|
305
314
|
routerId,
|
|
306
315
|
validateTransitionWhen,
|
|
316
|
+
include,
|
|
307
317
|
),
|
|
308
318
|
);
|
|
309
319
|
|
|
@@ -505,6 +515,7 @@ export async function generateManifestFull<TEnv>(
|
|
|
505
515
|
routeSearchSchemas,
|
|
506
516
|
options?.routerId,
|
|
507
517
|
options?.validateTransitionWhen,
|
|
518
|
+
include,
|
|
508
519
|
),
|
|
509
520
|
);
|
|
510
521
|
|
|
@@ -48,7 +48,11 @@ export function generatePerModuleTypesSource(
|
|
|
48
48
|
}
|
|
49
49
|
deduped.set(name, { pattern, params, search });
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
// Locale pinned: the machine's default locale reorders names (Czech sorts
|
|
52
|
+
// "ch" after "h"), and generated files must be identical on every machine.
|
|
53
|
+
const sorted = [...deduped.entries()].sort(([a], [b]) =>
|
|
54
|
+
a.localeCompare(b, "en"),
|
|
55
|
+
);
|
|
52
56
|
const body = sorted
|
|
53
57
|
.map(([name, { pattern, params, search }]) => {
|
|
54
58
|
const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
|
|
@@ -69,7 +73,7 @@ export function generateRouteTypesSource(
|
|
|
69
73
|
): string {
|
|
70
74
|
const entries = Object.entries(routeManifest)
|
|
71
75
|
.filter(([name]) => !isAutoGeneratedRouteName(name))
|
|
72
|
-
.sort(([a], [b]) => a.localeCompare(b));
|
|
76
|
+
.sort(([a], [b]) => a.localeCompare(b, "en"));
|
|
73
77
|
|
|
74
78
|
const filteredSearchSchemas = searchSchemas
|
|
75
79
|
? Object.fromEntries(
|