@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
|
@@ -9,8 +9,8 @@ import { isPlaceholder } from "../types/segments.js";
|
|
|
9
9
|
import { setAppVersion } from "./app-version.js";
|
|
10
10
|
import { isActionFenceActive } from "./action-fence.js";
|
|
11
11
|
import { getRangoState } from "./rango-state.js";
|
|
12
|
-
import * as React from "react";
|
|
13
12
|
import { startTransition } from "react";
|
|
13
|
+
import { flushSync } from "react-dom";
|
|
14
14
|
import {
|
|
15
15
|
createNavigationTransaction,
|
|
16
16
|
resolveNavigationState,
|
|
@@ -29,15 +29,12 @@ import {
|
|
|
29
29
|
ensureHistoryKey,
|
|
30
30
|
} from "./scroll-restoration.js";
|
|
31
31
|
|
|
32
|
-
// addTransitionType is only available in React experimental
|
|
33
|
-
const addTransitionType: ((type: string) => void) | undefined =
|
|
34
|
-
"addTransitionType" in React ? (React as any).addTransitionType : undefined;
|
|
35
|
-
|
|
36
32
|
import {
|
|
37
33
|
setupDelegatedLinkPrefetch,
|
|
38
34
|
setupLinkInterception,
|
|
39
35
|
} from "./link-interceptor.js";
|
|
40
36
|
import {
|
|
37
|
+
addTransitionType,
|
|
41
38
|
createPartialUpdater,
|
|
42
39
|
shouldStartViewTransition,
|
|
43
40
|
} from "./partial-update.js";
|
|
@@ -168,6 +165,8 @@ export function createNavigationBridge(
|
|
|
168
165
|
basename,
|
|
169
166
|
} = config;
|
|
170
167
|
let version = config.version;
|
|
168
|
+
// The latest back/forward: an earlier one's deferred restore yields to it.
|
|
169
|
+
let popGeneration = 0;
|
|
171
170
|
|
|
172
171
|
// Create shared partial updater
|
|
173
172
|
const fetchPartialUpdate = createPartialUpdater({
|
|
@@ -188,7 +187,8 @@ export function createNavigationBridge(
|
|
|
188
187
|
handles: (metadata) => {
|
|
189
188
|
if (metadata.handles) consumeHandles(metadata, eventController, store);
|
|
190
189
|
},
|
|
191
|
-
|
|
190
|
+
// Committed before the page is handed over (PartialUpdateConfig.fill).
|
|
191
|
+
flush: () => flushSync(eventController.flushRouteState),
|
|
192
192
|
},
|
|
193
193
|
});
|
|
194
194
|
|
|
@@ -304,7 +304,7 @@ export function createNavigationBridge(
|
|
|
304
304
|
// Save current scroll position before changing URL
|
|
305
305
|
handleNavigationStart();
|
|
306
306
|
|
|
307
|
-
const
|
|
307
|
+
const shallowShown = store.getHistoryEntryMemory();
|
|
308
308
|
|
|
309
309
|
// Update browser URL (carry intercept context into history state)
|
|
310
310
|
const historyState = buildHistoryState(
|
|
@@ -320,7 +320,7 @@ export function createNavigationBridge(
|
|
|
320
320
|
// Ensure new history entry has a scroll restoration key
|
|
321
321
|
ensureHistoryKey();
|
|
322
322
|
// Same route, new entry (transition({ when }) back/forward source).
|
|
323
|
-
store.rememberDisplayedEntry(
|
|
323
|
+
store.rememberDisplayedEntry(shallowShown);
|
|
324
324
|
// Treeless: readers take the entry's state with setLocation below.
|
|
325
325
|
eventController.commitLocationState(historyState, true);
|
|
326
326
|
|
|
@@ -457,20 +457,13 @@ export function createNavigationBridge(
|
|
|
457
457
|
).filter((segment) => segment.clientGroup === undefined),
|
|
458
458
|
options?.replace ? "replace" : "push",
|
|
459
459
|
{
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return {
|
|
468
|
-
url: targetUrl,
|
|
469
|
-
params: { ...params, ...destination.params },
|
|
470
|
-
routeName: destination.routeName,
|
|
471
|
-
state: destination.state,
|
|
472
|
-
};
|
|
473
|
-
},
|
|
460
|
+
// The destination's params include the mount's.
|
|
461
|
+
to: () => ({
|
|
462
|
+
url: targetUrl,
|
|
463
|
+
params: destination.params,
|
|
464
|
+
routeName: destination.routeName,
|
|
465
|
+
state: destination.state,
|
|
466
|
+
}),
|
|
474
467
|
extra: destination.when,
|
|
475
468
|
},
|
|
476
469
|
),
|
|
@@ -635,7 +628,8 @@ export function createNavigationBridge(
|
|
|
635
628
|
* Handle browser back/forward navigation
|
|
636
629
|
* Uses cached segments when available for instant restoration
|
|
637
630
|
*/
|
|
638
|
-
async handlePopstate(): Promise<void> {
|
|
631
|
+
async handlePopstate(event?: PopStateEvent): Promise<void> {
|
|
632
|
+
const generation = ++popGeneration;
|
|
639
633
|
// Abort any pending navigation to prevent race conditions, and the
|
|
640
634
|
// fill of the page being left (prefetch: false), which its committed
|
|
641
635
|
// navigation still owns.
|
|
@@ -745,18 +739,6 @@ export function createNavigationBridge(
|
|
|
745
739
|
forceAwait: true,
|
|
746
740
|
transitionGatedOff: gatedOff,
|
|
747
741
|
});
|
|
748
|
-
store.rememberDisplayedEntry();
|
|
749
|
-
// The restored entry's location and params, set before onUpdate so
|
|
750
|
-
// the hooks take both in the batch that renders its tree. Not at
|
|
751
|
-
// the popstate event: the page being left is on screen until here
|
|
752
|
-
// (#1031). A restore that has to fetch gets its location from
|
|
753
|
-
// tx.commit() instead (navigation-transaction.ts).
|
|
754
|
-
eventController.setLocation(new URL(url));
|
|
755
|
-
eventController.setParams(cachedParams);
|
|
756
|
-
// The entry this handler found at the event, which the cached tree
|
|
757
|
-
// was built for: history may have moved during the render above.
|
|
758
|
-
eventController.commitLocationState(historyState);
|
|
759
|
-
|
|
760
742
|
const popstateUpdate = {
|
|
761
743
|
root,
|
|
762
744
|
metadata: {
|
|
@@ -778,68 +760,106 @@ export function createNavigationBridge(
|
|
|
778
760
|
!isIntercept &&
|
|
779
761
|
!isLeavingIntercept &&
|
|
780
762
|
shouldStartViewTransition(cachedSegments, gatedOff);
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
763
|
+
// Not when the browser animated the swipe itself, nor when it
|
|
764
|
+
// restores this entry's scroll itself ("auto": no
|
|
765
|
+
// <Html.ScrollRestoration>), which it does right after the event,
|
|
766
|
+
// on whatever page is then on screen.
|
|
767
|
+
const animate =
|
|
768
|
+
hasTransition &&
|
|
769
|
+
!event?.hasUAVisualTransition &&
|
|
770
|
+
window.history.scrollRestoration === "manual";
|
|
771
|
+
const commit = (): void => {
|
|
772
|
+
// A later back/forward, or a navigation that committed first
|
|
773
|
+
// (a shallow one from an app's popstate listener), owns the
|
|
774
|
+
// screen now.
|
|
775
|
+
if (
|
|
776
|
+
generation !== popGeneration ||
|
|
777
|
+
store.getHistoryKey() !== historyKey
|
|
778
|
+
) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
store.rememberDisplayedEntry();
|
|
782
|
+
// The restored entry's location and params, set here so the hooks
|
|
783
|
+
// take both in the batch that renders its tree. Not at the
|
|
784
|
+
// popstate event: the page being left is on screen until here
|
|
785
|
+
// (#1031, #1164). A restore that has to fetch gets its location
|
|
786
|
+
// from tx.commit() instead (navigation-transaction.ts).
|
|
787
|
+
eventController.setLocation(new URL(url));
|
|
788
|
+
eventController.setParams(cachedParams);
|
|
789
|
+
// The entry this handler found at the event, which the cached
|
|
790
|
+
// tree was built for: history may have moved during the render.
|
|
791
|
+
eventController.commitLocationState(historyState);
|
|
792
|
+
if (process.env.NODE_ENV !== "production") {
|
|
793
|
+
auditTreeCause("popstate");
|
|
794
|
+
}
|
|
795
|
+
if (!hasTransition) onUpdate(popstateUpdate);
|
|
796
|
+
else {
|
|
797
|
+
startTransition(() => {
|
|
798
|
+
if (animate) {
|
|
799
|
+
addTransitionType?.("pop");
|
|
800
|
+
addTransitionType?.("navigation-back");
|
|
801
|
+
}
|
|
802
|
+
onUpdate({ ...popstateUpdate, inTransition: true });
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
// SWR: If stale, trigger background revalidation
|
|
806
|
+
if (isStale) {
|
|
807
|
+
debugLog("[Browser] Cache is stale, background revalidating...");
|
|
808
|
+
// Background revalidation - don't await, just fire and forget.
|
|
809
|
+
// When the entry's handles are incomplete (a deferred Meta was still
|
|
810
|
+
// pending when the user navigated away — see handlesPending), send NO
|
|
811
|
+
// segment IDs so the server returns a FULL re-render with the handle
|
|
812
|
+
// stream. A normal stale revalidation sends the cached IDs and the
|
|
813
|
+
// server returns a diff-only payload that omits unchanged segments'
|
|
814
|
+
// handles, so a deferred Meta would never re-stream and the title
|
|
815
|
+
// would stay the pre-resolution carry. handlesPending is set only for
|
|
816
|
+
// the deferred-Meta-aborted case, so action/cross-tab SWR keeps the
|
|
817
|
+
// cheap diff path.
|
|
818
|
+
const segmentIds = cached?.handlesPending
|
|
819
|
+
? []
|
|
820
|
+
: cachedSegments.map((s) => s.id);
|
|
821
|
+
|
|
822
|
+
const tx = createNavigationTransaction(
|
|
823
|
+
store,
|
|
824
|
+
eventController,
|
|
825
|
+
url,
|
|
826
|
+
{ skipLoadingState: true, replace: true },
|
|
827
|
+
);
|
|
816
828
|
|
|
817
|
-
|
|
818
|
-
url,
|
|
819
|
-
segmentIds,
|
|
820
|
-
false,
|
|
821
|
-
tx.handle.signal,
|
|
822
|
-
tx.with({
|
|
829
|
+
fetchPartialUpdate(
|
|
823
830
|
url,
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
)
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
831
|
+
segmentIds,
|
|
832
|
+
false,
|
|
833
|
+
tx.handle.signal,
|
|
834
|
+
tx.with({
|
|
835
|
+
url,
|
|
836
|
+
replace: true,
|
|
837
|
+
scroll: false,
|
|
838
|
+
intercept: isIntercept,
|
|
839
|
+
interceptSourceUrl,
|
|
840
|
+
cacheOnly: true,
|
|
841
|
+
}),
|
|
842
|
+
{ type: "stale-revalidation", interceptSourceUrl },
|
|
843
|
+
)
|
|
844
|
+
.catch((error) => {
|
|
845
|
+
if (isBackgroundSuppressible(error)) return;
|
|
846
|
+
console.error(
|
|
847
|
+
"[Browser] Background revalidation failed:",
|
|
848
|
+
error,
|
|
849
|
+
);
|
|
850
|
+
})
|
|
851
|
+
.finally(() => {
|
|
852
|
+
tx[Symbol.dispose]();
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
// React commits a transition started while window.event is the
|
|
857
|
+
// popstate event synchronously, with no view transition, and leaves
|
|
858
|
+
// its types queued for the next transition commit (react-dom
|
|
859
|
+
// shouldAttemptEagerTransition). An animated restore commits on the
|
|
860
|
+
// next task: the page being left stays on screen one task longer.
|
|
861
|
+
if (animate) setTimeout(commit, 0);
|
|
862
|
+
else commit();
|
|
843
863
|
return;
|
|
844
864
|
} catch (error) {
|
|
845
865
|
console.warn(
|
|
@@ -905,8 +925,9 @@ export function createNavigationBridge(
|
|
|
905
925
|
// tx.commit() never ran, so the location is still the page being
|
|
906
926
|
// left. The error boundary below replaces that page, and history is
|
|
907
927
|
// already on the entry that failed: the hooks report that entry, in
|
|
908
|
-
// the boundary's batch.
|
|
928
|
+
// the boundary's batch, and scroll restoration tracks it as shown.
|
|
909
929
|
eventController.setLocation(new URL(url));
|
|
930
|
+
ensureHistoryKey();
|
|
910
931
|
|
|
911
932
|
const networkError = toNetworkError(error, {
|
|
912
933
|
url,
|
|
@@ -939,8 +960,8 @@ export function createNavigationBridge(
|
|
|
939
960
|
this.navigate(url, options);
|
|
940
961
|
});
|
|
941
962
|
|
|
942
|
-
const handlePopstate = () => {
|
|
943
|
-
this.handlePopstate();
|
|
963
|
+
const handlePopstate = (event: PopStateEvent) => {
|
|
964
|
+
this.handlePopstate(event);
|
|
944
965
|
};
|
|
945
966
|
|
|
946
967
|
// When the browser restores a page from bfcache (back-forward cache),
|
|
@@ -79,6 +79,7 @@ export function createNavigationClient(
|
|
|
79
79
|
targetUrl,
|
|
80
80
|
segmentIds,
|
|
81
81
|
previousUrl,
|
|
82
|
+
rewriteTarget,
|
|
82
83
|
signal,
|
|
83
84
|
staleRevalidation,
|
|
84
85
|
interceptSourceUrl,
|
|
@@ -115,6 +116,11 @@ export function createNavigationClient(
|
|
|
115
116
|
if (routerId) {
|
|
116
117
|
fetchUrl.searchParams.set("_rsc_rid", routerId);
|
|
117
118
|
}
|
|
119
|
+
// The page left's rewrite target (#1144), in both prefetch keys like
|
|
120
|
+
// the params above: buildPrefetchUrl sets it in the same place.
|
|
121
|
+
if (rewriteTarget) {
|
|
122
|
+
fetchUrl.searchParams.set("_rsc_rt", rewriteTarget);
|
|
123
|
+
}
|
|
118
124
|
// A param, not a header: the document cache decides from the raw URL
|
|
119
125
|
// (cache/document-cache.ts), and it keeps the fill out of every
|
|
120
126
|
// prefetch key below.
|
|
@@ -317,18 +323,15 @@ export function createNavigationClient(
|
|
|
317
323
|
// so only a fully-warmed prefetch commits in a transition (no fallback flash).
|
|
318
324
|
let fullyPrefetched = false;
|
|
319
325
|
|
|
320
|
-
|
|
326
|
+
// The prefetch entry this navigation serves.
|
|
327
|
+
let adopted = cachedEntry;
|
|
328
|
+
if (adopted) {
|
|
321
329
|
if (tx) {
|
|
322
330
|
browserDebugLog(tx, "prefetch cache hit (warm)", {
|
|
323
331
|
key: hitKey,
|
|
324
332
|
wildcard: hitKey === wildcardKey,
|
|
325
333
|
});
|
|
326
334
|
}
|
|
327
|
-
payloadPromise = cachedEntry.payload;
|
|
328
|
-
streamCompletePromise = cachedEntry.streamComplete;
|
|
329
|
-
// Only a hit whose stream already fully drained is "fully prefetched";
|
|
330
|
-
// a still-streaming hit must keep streaming its fallbacks like a cold load.
|
|
331
|
-
fullyPrefetched = cachedEntry.complete;
|
|
332
335
|
} else if (inflightEntryPromise) {
|
|
333
336
|
if (tx) {
|
|
334
337
|
browserDebugLog(tx, "reusing inflight prefetch", {
|
|
@@ -342,8 +345,6 @@ export function createNavigationClient(
|
|
|
342
345
|
if (tx) {
|
|
343
346
|
browserDebugLog(tx, "inflight prefetch unavailable, refetching");
|
|
344
347
|
}
|
|
345
|
-
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
346
|
-
freshResult());
|
|
347
348
|
} else if (adoptedViaWildcard && entry.scope === "source") {
|
|
348
349
|
// A wildcard-adopted inflight that turned out source-scoped was
|
|
349
350
|
// built for a different source page. Discard and refetch.
|
|
@@ -353,15 +354,19 @@ export function createNavigationClient(
|
|
|
353
354
|
"wildcard inflight turned out source-scoped, refetching",
|
|
354
355
|
);
|
|
355
356
|
}
|
|
356
|
-
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
357
|
-
freshResult());
|
|
358
357
|
} else {
|
|
359
|
-
|
|
360
|
-
streamCompletePromise = entry.streamComplete;
|
|
361
|
-
// Adopted inflight is normally still streaming (false), but read the
|
|
362
|
-
// flag in case it completed between publish and adoption.
|
|
363
|
-
fullyPrefetched = entry.complete;
|
|
358
|
+
adopted = entry;
|
|
364
359
|
}
|
|
360
|
+
}
|
|
361
|
+
if (adopted) {
|
|
362
|
+
// The click owns the stream from here on (DecodedPrefetch.stallTimer).
|
|
363
|
+
clearTimeout(adopted.stallTimer);
|
|
364
|
+
payloadPromise = adopted.payload;
|
|
365
|
+
streamCompletePromise = adopted.streamComplete;
|
|
366
|
+
// Only an entry whose stream already fully drained is "fully
|
|
367
|
+
// prefetched"; a still-streaming one keeps streaming its fallbacks like
|
|
368
|
+
// a cold load.
|
|
369
|
+
fullyPrefetched = adopted.complete;
|
|
365
370
|
} else {
|
|
366
371
|
({ payload: payloadPromise, streamComplete: streamCompletePromise } =
|
|
367
372
|
freshResult());
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
UpdateSubscriber,
|
|
8
8
|
ResolvedSegment,
|
|
9
9
|
HandleData,
|
|
10
|
+
ShownEntry,
|
|
10
11
|
} from "./types.js";
|
|
11
12
|
import type { EventController } from "./event-controller.js";
|
|
12
13
|
import { clearPrefetchCache } from "./prefetch/cache.js";
|
|
@@ -466,13 +467,14 @@ export function createNavigationStore(
|
|
|
466
467
|
currentHistoryKey = key;
|
|
467
468
|
},
|
|
468
469
|
|
|
469
|
-
rememberDisplayedEntry(
|
|
470
|
+
rememberDisplayedEntry(shown?: ShownEntry): void {
|
|
470
471
|
if (typeof window === "undefined") return;
|
|
471
472
|
const key = getHistoryStateKey();
|
|
472
473
|
const previous = entryMemory.get(key);
|
|
473
474
|
entryMemory.delete(key);
|
|
474
475
|
entryMemory.set(key, {
|
|
475
|
-
routeName: routeName ?? previous?.routeName,
|
|
476
|
+
routeName: shown?.routeName ?? previous?.routeName,
|
|
477
|
+
rewriteTarget: (shown ?? previous)?.rewriteTarget,
|
|
476
478
|
state: window.history.state,
|
|
477
479
|
});
|
|
478
480
|
if (entryMemory.size > HISTORY_ENTRY_MEMORY_SIZE) {
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
NavigateOptionsInternal,
|
|
3
3
|
NavigationStore,
|
|
4
4
|
ResolvedSegment,
|
|
5
|
+
ShownEntry,
|
|
5
6
|
StreamingToken,
|
|
6
7
|
} from "./types.js";
|
|
7
8
|
import {
|
|
@@ -53,8 +54,8 @@ interface CommitOptions {
|
|
|
53
54
|
* merged in. The page left was saved by handleTraversalStart.
|
|
54
55
|
*/
|
|
55
56
|
traversal?: boolean;
|
|
56
|
-
/** The
|
|
57
|
-
|
|
57
|
+
/** The payload's metadata: its route name and rewrite target, remembered per history entry. */
|
|
58
|
+
metadata?: ShownEntry;
|
|
58
59
|
/**
|
|
59
60
|
* transition({ when }) decision already made at this navigation's first
|
|
60
61
|
* presentation (the optimistic clientUrls() swap). Not a commit input: the
|
|
@@ -86,8 +87,8 @@ interface BoundCommitOverrides {
|
|
|
86
87
|
cacheOnly?: boolean;
|
|
87
88
|
/** Server-set location state to merge into history.pushState */
|
|
88
89
|
serverState?: Record<string, unknown>;
|
|
89
|
-
/** The
|
|
90
|
-
|
|
90
|
+
/** The payload's metadata: its route name and rewrite target, remembered per history entry. */
|
|
91
|
+
metadata?: ShownEntry;
|
|
91
92
|
/** No payload follows this commit (CommitOptions.treeless). */
|
|
92
93
|
treeless?: boolean;
|
|
93
94
|
}
|
|
@@ -210,7 +211,7 @@ export function createNavigationTransaction(
|
|
|
210
211
|
if (storeOnly) {
|
|
211
212
|
// Same entry (an action refetch): refresh its state, keep its name
|
|
212
213
|
// unless the payload names it.
|
|
213
|
-
store.rememberDisplayedEntry(opts.
|
|
214
|
+
store.rememberDisplayedEntry(opts.metadata);
|
|
214
215
|
debugLog("[Browser] Store updated (action)");
|
|
215
216
|
handle.complete(parsedUrl);
|
|
216
217
|
return {};
|
|
@@ -222,7 +223,7 @@ export function createNavigationTransaction(
|
|
|
222
223
|
serverState && Object.keys(serverState).length > 0
|
|
223
224
|
? mergeLocationState(serverState)
|
|
224
225
|
: window.history.state;
|
|
225
|
-
store.rememberDisplayedEntry(opts.
|
|
226
|
+
store.rememberDisplayedEntry(opts.metadata);
|
|
226
227
|
eventController.commitLocationState(entryState, opts.treeless);
|
|
227
228
|
handle.complete(parsedUrl);
|
|
228
229
|
debugLog("[Browser] Traversal committed, historyKey:", historyKey);
|
|
@@ -237,7 +238,7 @@ export function createNavigationTransaction(
|
|
|
237
238
|
|
|
238
239
|
pushHistoryWithIdx(historyState, url, replace ?? false);
|
|
239
240
|
ensureHistoryKey();
|
|
240
|
-
store.rememberDisplayedEntry(opts.
|
|
241
|
+
store.rememberDisplayedEntry(opts.metadata);
|
|
241
242
|
eventController.commitLocationState(historyState, opts.treeless);
|
|
242
243
|
|
|
243
244
|
handle.complete(parsedUrl);
|
|
@@ -296,7 +297,7 @@ export function createNavigationTransaction(
|
|
|
296
297
|
interceptSourceUrl,
|
|
297
298
|
cacheOnly,
|
|
298
299
|
serverState,
|
|
299
|
-
|
|
300
|
+
metadata: overrides?.metadata ?? opts.metadata,
|
|
300
301
|
treeless: overrides?.treeless,
|
|
301
302
|
});
|
|
302
303
|
},
|