@rangojs/router 0.20.0 → 0.22.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/dist/bin/rango.js +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +29 -4
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +12 -6
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +55 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/plugin-types.d.ts +27 -0
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +167 -22
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugin-types.ts +28 -0
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/src/vite/utils/client-chunks.ts +5 -1
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
filterRouteSegmentIds,
|
|
16
16
|
} from "./react/filter-segment-order.js";
|
|
17
17
|
import { notifyListeners } from "./notify-listeners.js";
|
|
18
|
+
import { cloneHandleData } from "./navigation-store.js";
|
|
19
|
+
import {
|
|
20
|
+
locationStateSnapshot,
|
|
21
|
+
type LocationStateSnapshot,
|
|
22
|
+
} from "./react/location-state-shared.js";
|
|
18
23
|
|
|
19
24
|
// Polyfill Symbol.dispose for Safari and older browsers
|
|
20
25
|
if (typeof Symbol.dispose === "undefined") {
|
|
@@ -137,6 +142,16 @@ export interface HandleState {
|
|
|
137
142
|
routeSegmentIds: string[];
|
|
138
143
|
}
|
|
139
144
|
|
|
145
|
+
/**
|
|
146
|
+
* What the document's HTML was rendered with, kept for the hydration window
|
|
147
|
+
* (EventController.lockHydration).
|
|
148
|
+
*/
|
|
149
|
+
export interface HydrationSnapshot {
|
|
150
|
+
state: DerivedNavigationState;
|
|
151
|
+
params: Record<string, string>;
|
|
152
|
+
handles: HandleState;
|
|
153
|
+
}
|
|
154
|
+
|
|
140
155
|
/**
|
|
141
156
|
* Result from starting a navigation
|
|
142
157
|
* Implements Disposable for use with `using` keyword
|
|
@@ -224,7 +239,8 @@ export interface EventController {
|
|
|
224
239
|
getActionState(actionId: string): TrackedActionState;
|
|
225
240
|
getLocation(): NavigationLocation;
|
|
226
241
|
|
|
227
|
-
// Location
|
|
242
|
+
// Location of a commit no transaction completes: a shallow navigation, a
|
|
243
|
+
// back/forward restored from the history cache, a back/forward that failed.
|
|
228
244
|
setLocation(location: NavigationLocation): void;
|
|
229
245
|
|
|
230
246
|
// Subscriptions
|
|
@@ -256,6 +272,31 @@ export interface EventController {
|
|
|
256
272
|
resolvedIds?: string[],
|
|
257
273
|
): void;
|
|
258
274
|
getHandleState(): HandleState;
|
|
275
|
+
/**
|
|
276
|
+
* Open the hydration window. Until releaseHydrationLock(), getHandleState()
|
|
277
|
+
* returns the state the document's HTML was rendered with, and handle writes
|
|
278
|
+
* (setHandleData, setRouteSegmentIds) wait in order. initBrowserApp opens it
|
|
279
|
+
* before hydrateRoot, once the document's handle snapshot is applied: a hook
|
|
280
|
+
* reading the handle state when its boundary hydrates, however late, reads
|
|
281
|
+
* what SSR rendered (issue #1035).
|
|
282
|
+
*/
|
|
283
|
+
lockHydration(): void;
|
|
284
|
+
/**
|
|
285
|
+
* While the hydration window is open, the state the document's HTML was
|
|
286
|
+
* rendered with; undefined once it closes. A router hook's first render
|
|
287
|
+
* reads it: a component hydrating in a boundary that streamed in late
|
|
288
|
+
* renders what SSR rendered, even after a navigation or an action started.
|
|
289
|
+
* The hook's effect then catches up with the live state, and its
|
|
290
|
+
* subscription stays live, so pending UI is not held back.
|
|
291
|
+
*/
|
|
292
|
+
getHydrationSnapshot(): HydrationSnapshot | undefined;
|
|
293
|
+
/**
|
|
294
|
+
* Close the hydration window: apply the writes that waited, in order, and
|
|
295
|
+
* notify handle subscribers. NavigationProvider calls it after the commit
|
|
296
|
+
* that made React hydrate every boundary still dehydrated (its barrier).
|
|
297
|
+
* Idempotent.
|
|
298
|
+
*/
|
|
299
|
+
releaseHydrationLock(): void;
|
|
259
300
|
/**
|
|
260
301
|
* Update ONLY `routeSegmentIds` (what `useSegments` reads) from `matched`,
|
|
261
302
|
* leaving `data` and `segmentOrder` (what `useHandle` collects over) untouched.
|
|
@@ -269,6 +310,21 @@ export interface EventController {
|
|
|
269
310
|
setParams(params: Record<string, string>): void;
|
|
270
311
|
getParams(): Record<string, string>;
|
|
271
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Record the committed history entry's location state from `entryState`:
|
|
315
|
+
* the state object the commit pushed, restored or merged into the entry,
|
|
316
|
+
* so the entry and its readers have one source. Notifies nobody:
|
|
317
|
+
* NavigationProvider takes the snapshot with the entry's payload
|
|
318
|
+
* (LocationStateContext). `treeless` marks a commit no payload follows (a
|
|
319
|
+
* shallow navigation, a commit that keeps every segment, an action's
|
|
320
|
+
* state): the provider takes it with this commit's state notification, in
|
|
321
|
+
* the batch of the location.
|
|
322
|
+
*/
|
|
323
|
+
commitLocationState(entryState: unknown, treeless?: boolean): void;
|
|
324
|
+
getLocationState(): LocationStateSnapshot;
|
|
325
|
+
/** True once after a `treeless` commit. */
|
|
326
|
+
takeTreelessLocationState(): boolean;
|
|
327
|
+
|
|
272
328
|
// Direct state access for advanced use
|
|
273
329
|
getCurrentNavigation(): NavigationEntry | null;
|
|
274
330
|
getInflightActions(): Map<string, ActionEntry>;
|
|
@@ -460,9 +516,16 @@ export function createEventController(
|
|
|
460
516
|
let handleData: HandleData = {};
|
|
461
517
|
let handleSegmentOrder: string[] = [];
|
|
462
518
|
let routeSegmentIds: string[] = [];
|
|
519
|
+
// The hydration window (lockHydration): what the document's HTML was
|
|
520
|
+
// rendered with, and the handle writes waiting for its release.
|
|
521
|
+
let hydrationSnapshot: HydrationSnapshot | undefined;
|
|
522
|
+
let writesAfterHydration: (() => void)[] = [];
|
|
463
523
|
|
|
464
524
|
let routeParams: Record<string, string> = {};
|
|
465
525
|
|
|
526
|
+
let locationState: LocationStateSnapshot;
|
|
527
|
+
let treelessLocationState = false;
|
|
528
|
+
|
|
466
529
|
const stateListeners = new Set<StateListener>();
|
|
467
530
|
const actionListeners = new Map<string, Set<ActionStateListener>>();
|
|
468
531
|
const handleListeners = new Set<HandleListener>();
|
|
@@ -985,6 +1048,12 @@ export function createEventController(
|
|
|
985
1048
|
isPartial?: boolean,
|
|
986
1049
|
resolvedIds?: string[],
|
|
987
1050
|
): void {
|
|
1051
|
+
if (hydrationSnapshot) {
|
|
1052
|
+
writesAfterHydration.push(() =>
|
|
1053
|
+
setHandleData(data, matched, isPartial, resolvedIds),
|
|
1054
|
+
);
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
988
1057
|
const rawMatched = matched ?? [];
|
|
989
1058
|
const newSegmentOrder = filterSegmentOrder(rawMatched);
|
|
990
1059
|
// Separate list for useSegments(): "layouts and routes only" — strip
|
|
@@ -992,7 +1061,10 @@ export function createEventController(
|
|
|
992
1061
|
const newRouteSegmentIds = filterRouteSegmentIds(rawMatched);
|
|
993
1062
|
|
|
994
1063
|
if (isPartial && newSegmentOrder.length > 0) {
|
|
995
|
-
// Partial update: merge new data with existing
|
|
1064
|
+
// Partial update: merge new data with existing, into a copy of the
|
|
1065
|
+
// containers. The object a reader was handed is never written to: a
|
|
1066
|
+
// history entry restored from the store's cache can be that object.
|
|
1067
|
+
handleData = cloneHandleData(handleData);
|
|
996
1068
|
for (const handleName of Object.keys(data)) {
|
|
997
1069
|
if (!handleData[handleName]) {
|
|
998
1070
|
handleData[handleName] = {};
|
|
@@ -1029,6 +1101,7 @@ export function createEventController(
|
|
|
1029
1101
|
}
|
|
1030
1102
|
|
|
1031
1103
|
function getHandleState(): HandleState {
|
|
1104
|
+
if (hydrationSnapshot) return hydrationSnapshot.handles;
|
|
1032
1105
|
return {
|
|
1033
1106
|
data: handleData,
|
|
1034
1107
|
segmentOrder: handleSegmentOrder,
|
|
@@ -1036,7 +1109,29 @@ export function createEventController(
|
|
|
1036
1109
|
};
|
|
1037
1110
|
}
|
|
1038
1111
|
|
|
1112
|
+
function lockHydration(): void {
|
|
1113
|
+
if (hydrationSnapshot) return;
|
|
1114
|
+
hydrationSnapshot = {
|
|
1115
|
+
state: getState(),
|
|
1116
|
+
params: routeParams,
|
|
1117
|
+
handles: getHandleState(),
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function releaseHydrationLock(): void {
|
|
1122
|
+
if (!hydrationSnapshot) return;
|
|
1123
|
+
hydrationSnapshot = undefined;
|
|
1124
|
+
const writes = writesAfterHydration;
|
|
1125
|
+
writesAfterHydration = [];
|
|
1126
|
+
for (const write of writes) write();
|
|
1127
|
+
notifyHandles.flush();
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1039
1130
|
function setRouteSegmentIds(matched: string[]): void {
|
|
1131
|
+
if (hydrationSnapshot) {
|
|
1132
|
+
writesAfterHydration.push(() => setRouteSegmentIds(matched));
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1040
1135
|
const next = filterRouteSegmentIds(matched);
|
|
1041
1136
|
if (
|
|
1042
1137
|
next.length === routeSegmentIds.length &&
|
|
@@ -1094,6 +1189,20 @@ export function createEventController(
|
|
|
1094
1189
|
return routeParams;
|
|
1095
1190
|
}
|
|
1096
1191
|
|
|
1192
|
+
function commitLocationState(entryState: unknown, treeless?: boolean): void {
|
|
1193
|
+
locationState = locationStateSnapshot(entryState, locationState);
|
|
1194
|
+
if (treeless) {
|
|
1195
|
+
treelessLocationState = true;
|
|
1196
|
+
notify();
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
function takeTreelessLocationState(): boolean {
|
|
1201
|
+
const taken = treelessLocationState;
|
|
1202
|
+
treelessLocationState = false;
|
|
1203
|
+
return taken;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1097
1206
|
// ========================================================================
|
|
1098
1207
|
// Return Controller
|
|
1099
1208
|
// ========================================================================
|
|
@@ -1116,12 +1225,20 @@ export function createEventController(
|
|
|
1116
1225
|
// Handles
|
|
1117
1226
|
setHandleData,
|
|
1118
1227
|
getHandleState,
|
|
1228
|
+
lockHydration,
|
|
1229
|
+
getHydrationSnapshot: () => hydrationSnapshot,
|
|
1230
|
+
releaseHydrationLock,
|
|
1119
1231
|
setRouteSegmentIds,
|
|
1120
1232
|
|
|
1121
1233
|
// Params
|
|
1122
1234
|
setParams,
|
|
1123
1235
|
getParams,
|
|
1124
1236
|
|
|
1237
|
+
// Location state
|
|
1238
|
+
commitLocationState,
|
|
1239
|
+
getLocationState: () => locationState,
|
|
1240
|
+
takeTreelessLocationState,
|
|
1241
|
+
|
|
1125
1242
|
// Subscriptions
|
|
1126
1243
|
subscribe,
|
|
1127
1244
|
subscribeToAction,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
addLocationState,
|
|
3
|
+
CLEAR_ON_RELOAD_KEY_SUFFIX,
|
|
2
4
|
isLocationStateDefinition,
|
|
3
5
|
isLocationStateEntry,
|
|
6
|
+
isLocationStateKey,
|
|
4
7
|
peekLocationStateKey,
|
|
5
8
|
replaceCurrentHistoryState,
|
|
6
9
|
resolveLocationStateEntries,
|
|
10
|
+
stampLocationState,
|
|
7
11
|
} from "./react/location-state-shared.js";
|
|
8
12
|
|
|
9
13
|
/**
|
|
@@ -13,7 +17,7 @@ function isTypedLocationState(
|
|
|
13
17
|
state: unknown,
|
|
14
18
|
): state is Record<string, unknown> {
|
|
15
19
|
if (state === null || typeof state !== "object") return false;
|
|
16
|
-
return Object.keys(state).some(
|
|
20
|
+
return Object.keys(state).some(isLocationStateKey);
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
/**
|
|
@@ -100,16 +104,15 @@ export function buildHistoryState(
|
|
|
100
104
|
Object.assign(result, serverState);
|
|
101
105
|
}
|
|
102
106
|
|
|
107
|
+
if (hasLocationState(result)) stampLocationState(result);
|
|
108
|
+
|
|
103
109
|
return Object.keys(result).length > 0 ? result : null;
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
/** Check if a history state object contains location state keys. */
|
|
107
113
|
export function hasLocationState(state: unknown): boolean {
|
|
108
114
|
if (!state || typeof state !== "object") return false;
|
|
109
|
-
return (
|
|
110
|
-
"state" in state ||
|
|
111
|
-
Object.keys(state).some((k) => k.startsWith("__rsc_ls_"))
|
|
112
|
-
);
|
|
115
|
+
return "state" in state || Object.keys(state).some(isLocationStateKey);
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
/**
|
|
@@ -157,19 +160,38 @@ export function stripShellMissMarker(): void {
|
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
/**
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* so
|
|
163
|
+
* Remove the `clearOnReload` slots of the entry a document load starts on:
|
|
164
|
+
* the server rendered this document without them. One pass before hydration
|
|
165
|
+
* (initBrowserApp; renderRoute's `hydrate` mode), so no reader needs to know
|
|
166
|
+
* the page began as a document load, and a slot nobody reads is removed too.
|
|
167
|
+
* No server-set state exists yet to lose: a document response carries none
|
|
168
|
+
* (rsc-rendering.ts attaches it to partial payloads only).
|
|
169
|
+
*
|
|
170
|
+
* A slot is recognised by its key alone (the `~r` suffix): at start-up the
|
|
171
|
+
* module that defines it may not be loaded.
|
|
172
|
+
*/
|
|
173
|
+
export function clearLocationStateOnDocumentLoad(): void {
|
|
174
|
+
const state: unknown = window.history.state;
|
|
175
|
+
if (state === null || typeof state !== "object") return;
|
|
176
|
+
let next: Record<string, unknown> | undefined;
|
|
177
|
+
for (const key in state) {
|
|
178
|
+
if (isLocationStateKey(key) && key.endsWith(CLEAR_ON_RELOAD_KEY_SUFFIX)) {
|
|
179
|
+
next ??= { ...state };
|
|
180
|
+
delete next[key];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (next) window.history.replaceState(next, "");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Merge server-set location state into the current history entry and return
|
|
188
|
+
* the entry state written. Readers see it when the caller commits it
|
|
189
|
+
* (EventController.commitLocationState).
|
|
163
190
|
*/
|
|
164
191
|
export function mergeLocationState(
|
|
165
192
|
locationState: Record<string, unknown>,
|
|
166
|
-
):
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
};
|
|
171
|
-
replaceCurrentHistoryState(merged);
|
|
172
|
-
if (Object.keys(locationState).some((k) => k.startsWith("__rsc_ls_"))) {
|
|
173
|
-
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
174
|
-
}
|
|
193
|
+
): Record<string, unknown> {
|
|
194
|
+
const entryState = addLocationState(window.history.state, locationState);
|
|
195
|
+
replaceCurrentHistoryState(entryState);
|
|
196
|
+
return entryState;
|
|
175
197
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
type EventController,
|
|
5
5
|
} from "./event-controller.js";
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
ENVIRONMENT_DEFAULT_PREFETCH,
|
|
8
8
|
resolveAdaptiveStrategy,
|
|
9
9
|
subscribeToAdaptiveStrategyChange,
|
|
10
10
|
} from "./prefetch/default-strategy.js";
|
|
@@ -383,7 +383,7 @@ export function setupDelegatedLinkPrefetch(
|
|
|
383
383
|
options: DelegatedPrefetchOptions,
|
|
384
384
|
): () => void {
|
|
385
385
|
const defaultStrategy =
|
|
386
|
-
options.defaultPrefetch ??
|
|
386
|
+
options.defaultPrefetch ?? ENVIRONMENT_DEFAULT_PREFETCH;
|
|
387
387
|
if (defaultStrategy === "none") return () => {};
|
|
388
388
|
|
|
389
389
|
const basename = canonicalizeBasenamePathname(options.basename);
|
|
@@ -14,11 +14,7 @@ import {
|
|
|
14
14
|
createNavigationTransaction,
|
|
15
15
|
resolveNavigationState,
|
|
16
16
|
} from "./navigation-transaction.js";
|
|
17
|
-
import {
|
|
18
|
-
buildHistoryState,
|
|
19
|
-
hasLocationState,
|
|
20
|
-
pushHistoryWithIdx,
|
|
21
|
-
} from "./history-state.js";
|
|
17
|
+
import { buildHistoryState, pushHistoryWithIdx } from "./history-state.js";
|
|
22
18
|
import {
|
|
23
19
|
handleNavigationStart,
|
|
24
20
|
handleNavigationEnd,
|
|
@@ -39,7 +35,10 @@ import {
|
|
|
39
35
|
shouldStartViewTransition,
|
|
40
36
|
} from "./partial-update.js";
|
|
41
37
|
import { decideCommitGatedOff, mergeSegmentParams } from "./transition-when.js";
|
|
42
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
generateHistoryKey,
|
|
40
|
+
liveHandleDataForCache,
|
|
41
|
+
} from "./navigation-store.js";
|
|
43
42
|
import type { EventController } from "./event-controller.js";
|
|
44
43
|
import { isInterceptOnlyCache } from "./intercept-utils.js";
|
|
45
44
|
import {
|
|
@@ -58,6 +57,7 @@ import {
|
|
|
58
57
|
} from "./prefetch/loader.js";
|
|
59
58
|
import type { PrefetchStrategy } from "../router/prefetch-default.js";
|
|
60
59
|
import { beginClientUrlNavigation } from "../client-urls/navigation.js";
|
|
60
|
+
import { auditTreeCause } from "../suspense-audit.js";
|
|
61
61
|
|
|
62
62
|
// Polyfill Symbol.dispose for Safari and older browsers
|
|
63
63
|
if (typeof Symbol.dispose === "undefined") {
|
|
@@ -66,6 +66,9 @@ if (typeof Symbol.dispose === "undefined") {
|
|
|
66
66
|
|
|
67
67
|
export { createNavigationTransaction };
|
|
68
68
|
|
|
69
|
+
// Mirrors the redirect-chain limit browsers apply to document navigations.
|
|
70
|
+
const MAX_REDIRECT_HOPS = 20;
|
|
71
|
+
|
|
69
72
|
/**
|
|
70
73
|
* Extended configuration for navigation bridge with event controller
|
|
71
74
|
*/
|
|
@@ -153,6 +156,45 @@ export function createNavigationBridge(
|
|
|
153
156
|
getVersion: () => version,
|
|
154
157
|
});
|
|
155
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Re-navigates to a server redirect target, counting hops so a target that
|
|
161
|
+
* redirects back ends in the error boundary instead of looping.
|
|
162
|
+
*/
|
|
163
|
+
async function followServerRedirect(
|
|
164
|
+
self: {
|
|
165
|
+
navigate: (
|
|
166
|
+
url: string,
|
|
167
|
+
options?: NavigateOptionsInternal,
|
|
168
|
+
) => Promise<void>;
|
|
169
|
+
},
|
|
170
|
+
error: ServerRedirect,
|
|
171
|
+
url: string,
|
|
172
|
+
hops: number,
|
|
173
|
+
options: { replace?: boolean; transition?: boolean },
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
const redirectUrl = validateRedirectOrigin(
|
|
176
|
+
error.url,
|
|
177
|
+
window.location.origin,
|
|
178
|
+
);
|
|
179
|
+
if (!redirectUrl) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (hops >= MAX_REDIRECT_HOPS) {
|
|
183
|
+
const loopError = new Error(
|
|
184
|
+
`[rango] Server redirect loop: stopped after following ${hops} redirects, at ${url}`,
|
|
185
|
+
);
|
|
186
|
+
console.error(loopError.message);
|
|
187
|
+
emitNavigationError(onUpdate, loopError, url);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
return self.navigate(redirectUrl, {
|
|
191
|
+
state: error.state,
|
|
192
|
+
...options,
|
|
193
|
+
_skipCache: true,
|
|
194
|
+
_redirectHops: hops + 1,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
156
198
|
return {
|
|
157
199
|
/**
|
|
158
200
|
* Navigate to a URL
|
|
@@ -209,7 +251,11 @@ export function createNavigationBridge(
|
|
|
209
251
|
const currentKey = store.getHistoryKey();
|
|
210
252
|
const currentCache = store.getCachedSegments(currentKey);
|
|
211
253
|
if (currentCache?.segments) {
|
|
212
|
-
const currentHandleData =
|
|
254
|
+
const currentHandleData = liveHandleDataForCache(
|
|
255
|
+
eventController,
|
|
256
|
+
store,
|
|
257
|
+
currentKey,
|
|
258
|
+
);
|
|
213
259
|
store.cacheSegmentsForHistory(
|
|
214
260
|
historyKey,
|
|
215
261
|
currentCache.segments,
|
|
@@ -220,8 +266,6 @@ export function createNavigationBridge(
|
|
|
220
266
|
// Save current scroll position before changing URL
|
|
221
267
|
handleNavigationStart();
|
|
222
268
|
|
|
223
|
-
// Snapshot old state before pushState/replaceState overwrites it
|
|
224
|
-
const oldState = window.history.state;
|
|
225
269
|
const shallowRouteName = store.getHistoryEntryMemory()?.routeName;
|
|
226
270
|
|
|
227
271
|
// Update browser URL (carry intercept context into history state)
|
|
@@ -239,11 +283,8 @@ export function createNavigationBridge(
|
|
|
239
283
|
ensureHistoryKey();
|
|
240
284
|
// Same route, new entry (transition({ when }) back/forward source).
|
|
241
285
|
store.rememberDisplayedEntry(shallowRouteName);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (hasLocationState(oldState) || hasLocationState(historyState)) {
|
|
245
|
-
window.dispatchEvent(new Event("__rsc_locationstate"));
|
|
246
|
-
}
|
|
286
|
+
// Treeless: readers take the entry's state with setLocation below.
|
|
287
|
+
eventController.commitLocationState(historyState, true);
|
|
247
288
|
|
|
248
289
|
// Update store history key so future navigations reference the right cache
|
|
249
290
|
store.setHistoryKey(historyKey);
|
|
@@ -292,7 +333,11 @@ export function createNavigationBridge(
|
|
|
292
333
|
const sourceHistoryKey = store.getHistoryKey();
|
|
293
334
|
const sourceCached = store.getCachedSegments(sourceHistoryKey);
|
|
294
335
|
if (sourceCached?.segments && sourceCached.segments.length > 0) {
|
|
295
|
-
const currentHandleData =
|
|
336
|
+
const currentHandleData = liveHandleDataForCache(
|
|
337
|
+
eventController,
|
|
338
|
+
store,
|
|
339
|
+
sourceHistoryKey,
|
|
340
|
+
);
|
|
296
341
|
store.updateCacheHandleData(sourceHistoryKey, currentHandleData);
|
|
297
342
|
}
|
|
298
343
|
|
|
@@ -357,6 +402,8 @@ export function createNavigationBridge(
|
|
|
357
402
|
const clientUrlPresentation = beginClientUrlNavigation(
|
|
358
403
|
targetUrl,
|
|
359
404
|
tx.handle.signal,
|
|
405
|
+
// The entry the commit will push, without what the server adds.
|
|
406
|
+
() => buildHistoryState(resolvedState),
|
|
360
407
|
transitionOptOut
|
|
361
408
|
? () => true
|
|
362
409
|
: (destination) =>
|
|
@@ -378,7 +425,7 @@ export function createNavigationBridge(
|
|
|
378
425
|
url: targetUrl,
|
|
379
426
|
params: { ...params, ...destination.params },
|
|
380
427
|
routeName: destination.routeName,
|
|
381
|
-
state:
|
|
428
|
+
state: destination.state,
|
|
382
429
|
};
|
|
383
430
|
},
|
|
384
431
|
extra: destination.when,
|
|
@@ -420,29 +467,12 @@ export function createNavigationBridge(
|
|
|
420
467
|
},
|
|
421
468
|
);
|
|
422
469
|
} catch (error) {
|
|
423
|
-
// Server-side redirect with location state: the current transaction's
|
|
424
|
-
// cleanup resets loading state. Re-navigate to the redirect
|
|
425
|
-
// target carrying the server-set state into history.pushState.
|
|
426
|
-
if (error instanceof ServerRedirect) {
|
|
427
|
-
const redirectUrl = validateRedirectOrigin(
|
|
428
|
-
error.url,
|
|
429
|
-
window.location.origin,
|
|
430
|
-
);
|
|
431
|
-
if (!redirectUrl) {
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
return this.navigate(redirectUrl, {
|
|
435
|
-
state: error.state,
|
|
436
|
-
replace: options?.replace,
|
|
437
|
-
transition: options?.transition,
|
|
438
|
-
_skipCache: true,
|
|
439
|
-
} as NavigateOptionsInternal);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
470
|
// Aborted, or superseded by a newer navigation. A superseded nav may
|
|
443
471
|
// reject with a non-AbortError (e.g. a Flight decode that fails after its
|
|
444
472
|
// signal was aborted), so check the signal too -- otherwise we would
|
|
445
|
-
// render a boundary that clobbers the newer navigation's content.
|
|
473
|
+
// render a boundary that clobbers the newer navigation's content. Before
|
|
474
|
+
// the redirect branch: a superseded nav must not follow its redirect
|
|
475
|
+
// over the navigation that replaced it.
|
|
446
476
|
if (
|
|
447
477
|
(error instanceof DOMException && error.name === "AbortError") ||
|
|
448
478
|
tx.handle.signal.aborted
|
|
@@ -451,6 +481,19 @@ export function createNavigationBridge(
|
|
|
451
481
|
return;
|
|
452
482
|
}
|
|
453
483
|
|
|
484
|
+
// Server-side redirect with location state: the current transaction's
|
|
485
|
+
// cleanup resets loading state. Re-navigate to the redirect
|
|
486
|
+
// target carrying the server-set state into history.pushState.
|
|
487
|
+
if (error instanceof ServerRedirect) {
|
|
488
|
+
return followServerRedirect(
|
|
489
|
+
this,
|
|
490
|
+
error,
|
|
491
|
+
url,
|
|
492
|
+
options?._redirectHops ?? 0,
|
|
493
|
+
{ replace: options?.replace, transition: options?.transition },
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
454
497
|
const networkError = toNetworkError(error, {
|
|
455
498
|
url,
|
|
456
499
|
operation: "navigation",
|
|
@@ -578,9 +621,6 @@ export function createNavigationBridge(
|
|
|
578
621
|
historyKey,
|
|
579
622
|
);
|
|
580
623
|
|
|
581
|
-
// Update location in event controller
|
|
582
|
-
eventController.setLocation(new URL(url));
|
|
583
|
-
|
|
584
624
|
// If this is an intercept, restore the intercept context
|
|
585
625
|
if (isIntercept && interceptSourceUrl) {
|
|
586
626
|
store.setInterceptSourceUrl(interceptSourceUrl);
|
|
@@ -648,9 +688,16 @@ export function createNavigationBridge(
|
|
|
648
688
|
transitionGatedOff: gatedOff,
|
|
649
689
|
});
|
|
650
690
|
store.rememberDisplayedEntry();
|
|
651
|
-
//
|
|
652
|
-
//
|
|
691
|
+
// The restored entry's location and params, set before onUpdate so
|
|
692
|
+
// the hooks take both in the batch that renders its tree. Not at
|
|
693
|
+
// the popstate event: the page being left is on screen until here
|
|
694
|
+
// (#1031). A restore that has to fetch gets its location from
|
|
695
|
+
// tx.commit() instead (navigation-transaction.ts).
|
|
696
|
+
eventController.setLocation(new URL(url));
|
|
653
697
|
eventController.setParams(cachedParams);
|
|
698
|
+
// The entry this handler found at the event, which the cached tree
|
|
699
|
+
// was built for: history may have moved during the render above.
|
|
700
|
+
eventController.commitLocationState(historyState);
|
|
654
701
|
|
|
655
702
|
const popstateUpdate = {
|
|
656
703
|
root,
|
|
@@ -673,6 +720,7 @@ export function createNavigationBridge(
|
|
|
673
720
|
!isIntercept &&
|
|
674
721
|
!isLeavingIntercept &&
|
|
675
722
|
shouldStartViewTransition(cachedSegments, gatedOff);
|
|
723
|
+
if (process.env.NODE_ENV !== "production") auditTreeCause("popstate");
|
|
676
724
|
if (hasTransition) {
|
|
677
725
|
startTransition(() => {
|
|
678
726
|
if (addTransitionType) {
|
|
@@ -783,6 +831,21 @@ export function createNavigationBridge(
|
|
|
783
831
|
return;
|
|
784
832
|
}
|
|
785
833
|
|
|
834
|
+
// The browser is already on the entry that redirected. Replacing makes
|
|
835
|
+
// that entry the redirect target, so Back from the target reaches the
|
|
836
|
+
// entry before it; a push would leave the redirecting entry in history
|
|
837
|
+
// and Back would hit the redirect again. Before setLocation below, or
|
|
838
|
+
// the hooks would report the redirecting URL first.
|
|
839
|
+
if (error instanceof ServerRedirect) {
|
|
840
|
+
return followServerRedirect(this, error, url, 0, { replace: true });
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// tx.commit() never ran, so the location is still the page being
|
|
844
|
+
// left. The error boundary below replaces that page, and history is
|
|
845
|
+
// already on the entry that failed: the hooks report that entry, in
|
|
846
|
+
// the boundary's batch.
|
|
847
|
+
eventController.setLocation(new URL(url));
|
|
848
|
+
|
|
786
849
|
const networkError = toNetworkError(error, {
|
|
787
850
|
url,
|
|
788
851
|
operation: "navigation",
|
|
@@ -8,7 +8,9 @@ import type {
|
|
|
8
8
|
ResolvedSegment,
|
|
9
9
|
HandleData,
|
|
10
10
|
} from "./types.js";
|
|
11
|
+
import type { EventController } from "./event-controller.js";
|
|
11
12
|
import { clearPrefetchCache } from "./prefetch/cache.js";
|
|
13
|
+
import { LoaderStore } from "../loader-store.js";
|
|
12
14
|
import {
|
|
13
15
|
adoptRangoState,
|
|
14
16
|
getRangoState,
|
|
@@ -73,6 +75,28 @@ export function cloneHandleData(handleData: HandleData): HandleData {
|
|
|
73
75
|
return cloned;
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Handle data to write into the history cache for the page on screen. During
|
|
80
|
+
* the hydration window getHandleState() is the document snapshot while late
|
|
81
|
+
* pushes sit queued in the controller (they are already in the cache entry,
|
|
82
|
+
* written by rsc-router), so writing the snapshot back would drop them; the
|
|
83
|
+
* entry's own data is the live view then.
|
|
84
|
+
*/
|
|
85
|
+
export function liveHandleDataForCache(
|
|
86
|
+
eventController: Pick<
|
|
87
|
+
EventController,
|
|
88
|
+
"getHandleState" | "getHydrationSnapshot"
|
|
89
|
+
>,
|
|
90
|
+
store: Pick<NavigationStore, "getCachedSegments">,
|
|
91
|
+
historyKey: string,
|
|
92
|
+
): HandleData {
|
|
93
|
+
if (eventController.getHydrationSnapshot()) {
|
|
94
|
+
const cached = store.getCachedSegments(historyKey)?.handleData;
|
|
95
|
+
if (cached) return cached;
|
|
96
|
+
}
|
|
97
|
+
return eventController.getHandleState().data;
|
|
98
|
+
}
|
|
99
|
+
|
|
76
100
|
// BroadcastChannel for cross-tab cache invalidation
|
|
77
101
|
const CACHE_INVALIDATION_CHANNEL = "rsc-router-cache-invalidation";
|
|
78
102
|
|
|
@@ -390,6 +414,8 @@ export function createNavigationStore(
|
|
|
390
414
|
}
|
|
391
415
|
|
|
392
416
|
const store: NavigationStore = {
|
|
417
|
+
loaders: new LoaderStore(),
|
|
418
|
+
|
|
393
419
|
// ========================================================================
|
|
394
420
|
// Internal Segment State (for bridges)
|
|
395
421
|
// ========================================================================
|