@rangojs/router 0.27.0 → 0.29.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 +1 -1
- package/dist/types/__internal.d.ts +7 -1
- package/dist/types/browser/action-fence.d.ts +2 -1
- package/dist/types/browser/event-controller.d.ts +8 -2
- package/dist/types/browser/history-state.d.ts +3 -9
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/react/use-action.d.ts +7 -2
- package/dist/types/browser/react/use-handle.d.ts +19 -16
- package/dist/types/browser/react/use-link-status.d.ts +5 -2
- package/dist/types/browser/segment-reconciler.d.ts +32 -4
- package/dist/types/browser/types.d.ts +24 -5
- package/dist/types/browser/wire-protocol.d.ts +26 -0
- package/dist/types/cache/cache-key-utils.d.ts +11 -5
- package/dist/types/cache/cache-scope.d.ts +34 -22
- package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
- package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
- package/dist/types/cache/handle-snapshot.d.ts +9 -4
- package/dist/types/cache/segment-codec.d.ts +14 -0
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/taint.d.ts +5 -3
- package/dist/types/cache/types.d.ts +7 -3
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
- package/dist/types/prefetch-markers.d.ts +5 -14
- package/dist/types/prerender/store.d.ts +31 -0
- package/dist/types/prerender/warm-request.d.ts +3 -3
- package/dist/types/prerender.d.ts +2 -2
- package/dist/types/route-name.d.ts +5 -0
- package/dist/types/router/error-handling.d.ts +11 -5
- package/dist/types/router/instrument.d.ts +4 -3
- package/dist/types/router/intercept-resolution.d.ts +25 -9
- package/dist/types/router/match-context.d.ts +6 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +13 -22
- package/dist/types/router/match-middleware/cache-lookup.d.ts +34 -86
- package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
- package/dist/types/router/navigation-snapshot.d.ts +7 -0
- package/dist/types/router/prerender-match.d.ts +21 -6
- package/dist/types/router/preview-match.d.ts +2 -7
- package/dist/types/router/request-classification.d.ts +5 -0
- package/dist/types/router/router-context.d.ts +7 -46
- package/dist/types/router/router-interfaces.d.ts +8 -14
- package/dist/types/router/segment-resolution/helpers.d.ts +19 -3
- package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
- package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +95 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
- package/dist/types/router/segment-resolution.d.ts +1 -2
- package/dist/types/router/segment-wrappers.d.ts +11 -48
- package/dist/types/router/telemetry.d.ts +2 -0
- package/dist/types/router/types.d.ts +11 -1
- package/dist/types/rsc/full-payload.d.ts +2 -2
- package/dist/types/rsc/handler-context.d.ts +6 -6
- package/dist/types/rsc/json-route-result.d.ts +5 -9
- package/dist/types/rsc/request-stages.d.ts +203 -0
- package/dist/types/rsc/response-cache-serve.d.ts +7 -12
- package/dist/types/rsc/response-route-handler.d.ts +2 -2
- package/dist/types/rsc/routine-plan.d.ts +1 -1
- package/dist/types/rsc/server-action.d.ts +2 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +1 -1
- package/dist/types/rsc/shell-capture.d.ts +132 -15
- package/dist/types/rsc/shell-serve.d.ts +37 -12
- package/dist/types/server/context.d.ts +9 -8
- package/dist/types/server/handle-store.d.ts +16 -0
- package/dist/types/server/render-pass.d.ts +72 -0
- package/dist/types/server/request-context.d.ts +53 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/render-route.d.ts +43 -8
- package/dist/types/testing/serve-shell-request.d.ts +8 -1
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +30 -3
- package/dist/types/types/handler-context.d.ts +9 -1
- package/dist/types/types/segments.d.ts +1 -1
- package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
- package/dist/types/vite/discovery/build-env.d.ts +22 -0
- package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
- package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
- package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
- package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +12 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
- package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +8 -47
- package/dist/types/vite/temp-rsc-server.d.ts +39 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
- package/dist/types/wire-params.d.ts +39 -0
- package/dist/vite/index.js +3822 -3727
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +8 -5
- package/skills/caching/SKILL.md +37 -18
- package/skills/client-urls/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/hooks/SKILL.md +1 -1
- package/skills/hooks/data.md +8 -1
- package/skills/hooks/handle-and-actions.md +33 -2
- package/skills/hooks/navigation.md +121 -15
- package/skills/hooks/state.md +6 -4
- package/skills/hooks/urls.md +16 -5
- package/skills/intercept/SKILL.md +23 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/observability/SKILL.md +30 -0
- package/skills/ppr/SKILL.md +18 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +5 -1
- package/skills/server-actions/SKILL.md +15 -1
- package/skills/testing/client-components.md +25 -18
- package/skills/testing/response-routes.md +6 -4
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/use-cache/SKILL.md +46 -8
- package/skills/view-transitions/SKILL.md +1 -1
- package/src/__internal.ts +7 -1
- package/src/browser/action-fence.ts +2 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +37 -13
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +59 -26
- package/src/browser/navigation-client.ts +20 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- package/src/browser/network-error-handler.ts +4 -0
- package/src/browser/partial-update.ts +33 -6
- package/src/browser/prefetch/cache.ts +2 -1
- package/src/browser/prefetch/fetch.ts +8 -16
- package/src/browser/prefetch/queue.ts +4 -4
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/filter-segment-order.ts +2 -2
- package/src/browser/react/use-action.ts +24 -48
- package/src/browser/react/use-handle.ts +22 -18
- package/src/browser/react/use-link-status.ts +48 -60
- package/src/browser/react/use-navigation.ts +92 -41
- package/src/browser/rsc-router.tsx +1 -0
- package/src/browser/segment-reconciler.ts +109 -53
- package/src/browser/server-action-bridge.ts +81 -61
- package/src/browser/types.ts +25 -5
- package/src/browser/wire-protocol.ts +43 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +132 -75
- package/src/cache/cache-scope.ts +110 -64
- package/src/cache/cf/cf-cache-constants.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +486 -874
- package/src/cache/cf/cf-kv-utils.ts +1 -1
- package/src/cache/document-cache.ts +64 -46
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/read-through-swr.ts +20 -18
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +6 -2
- package/src/cache/taint.ts +5 -3
- package/src/cache/types.ts +7 -3
- package/src/cache/vercel/vercel-cache-store.ts +243 -310
- package/src/handles/pushed-by.ts +1 -1
- package/src/prefetch-markers.ts +5 -16
- package/src/prerender/store.ts +76 -0
- package/src/prerender/warm-request.ts +8 -4
- package/src/prerender.ts +2 -2
- package/src/route-name.ts +8 -0
- package/src/router/error-handling.ts +22 -10
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +82 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +5 -5
- package/src/router/match-context.ts +23 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +46 -57
- package/src/router/match-middleware/cache-lookup.ts +677 -726
- package/src/router/match-middleware/cache-store.ts +4 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +14 -4
- package/src/router/prerender-match.ts +100 -44
- package/src/router/preview-match.ts +6 -41
- package/src/router/request-classification.ts +13 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +10 -95
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/helpers.ts +49 -13
- package/src/router/segment-resolution/loader-cache.ts +15 -9
- package/src/router/segment-resolution/loader-mask.ts +5 -5
- package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
- package/src/router/segment-resolution/resolution-pass.ts +139 -0
- package/src/router/segment-resolution/revalidation.ts +939 -789
- package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
- package/src/router/segment-resolution.ts +4 -13
- package/src/router/segment-wrappers.ts +51 -269
- package/src/router/telemetry.ts +5 -0
- package/src/router/types.ts +11 -1
- package/src/router.ts +19 -17
- package/src/rsc/full-payload.ts +6 -5
- package/src/rsc/handler-context.ts +8 -14
- package/src/rsc/handler.ts +126 -507
- package/src/rsc/json-route-result.ts +5 -9
- package/src/rsc/loader-fetch.ts +6 -5
- package/src/rsc/progressive-enhancement.ts +5 -0
- package/src/rsc/request-stages.ts +759 -0
- package/src/rsc/response-cache-serve.ts +10 -14
- package/src/rsc/response-route-handler.ts +13 -5
- package/src/rsc/rewrite-passes.ts +2 -1
- package/src/rsc/routine-plan.ts +1 -1
- package/src/rsc/rsc-rendering.ts +164 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +1 -1
- package/src/rsc/shell-capture.ts +275 -227
- package/src/rsc/shell-serve.ts +121 -18
- package/src/rsc/ssr-setup.ts +5 -4
- package/src/segment-system.tsx +3 -3
- package/src/server/context.ts +22 -25
- package/src/server/handle-store.ts +55 -0
- package/src/server/render-pass.ts +291 -0
- package/src/server/request-context.ts +75 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/testing/dispatch.ts +226 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/render-route.tsx +199 -13
- package/src/testing/serve-shell-request.ts +36 -40
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +30 -3
- package/src/types/handler-context.ts +12 -1
- package/src/types/segments.ts +1 -1
- package/src/vite/discovery/build-discovery-phase.ts +204 -0
- package/src/vite/discovery/build-env.ts +148 -0
- package/src/vite/discovery/bundle-scan-phase.ts +128 -0
- package/src/vite/discovery/dev-prerender-cache.ts +8 -3
- package/src/vite/discovery/dev-server-phase.ts +1776 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +1 -1
- package/src/vite/discovery/prerender-collection.ts +24 -39
- package/src/vite/discovery/state.ts +15 -0
- package/src/vite/plugins/version-plugin.ts +1 -1
- package/src/vite/plugins/virtual-entries.ts +1 -1
- package/src/vite/router-discovery.ts +29 -2428
- package/src/vite/temp-rsc-server.ts +223 -0
- package/src/vite/utils/prerender-utils.ts +47 -0
- package/src/wire-params.ts +40 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { ConsoleMessage, Expect, Locator, Page } from "@playwright/test";
|
|
6
6
|
import { PREFETCH_HEADER } from "../../prefetch-markers.js";
|
|
7
|
+
import { WIRE } from "../../wire-params.js";
|
|
7
8
|
|
|
8
9
|
// ============================================================================
|
|
9
10
|
// Standalone helpers (need only a page/locator)
|
|
@@ -177,8 +178,7 @@ export function isPrefetchRequest(req: {
|
|
|
177
178
|
|
|
178
179
|
// Module-level matcher + handler so blockPrefetch/unblockPrefetch share
|
|
179
180
|
// stable references — Playwright's unroute matches by identity.
|
|
180
|
-
const rscPartialUrl = (url: URL): boolean =>
|
|
181
|
-
url.searchParams.has("_rsc_partial");
|
|
181
|
+
const rscPartialUrl = (url: URL): boolean => url.searchParams.has(WIRE.partial);
|
|
182
182
|
async function abortPrefetchRoute(
|
|
183
183
|
route: import("@playwright/test").Route,
|
|
184
184
|
): Promise<void> {
|
|
@@ -26,24 +26,30 @@
|
|
|
26
26
|
* - navigate() drives the navigation lifecycle as a click does:
|
|
27
27
|
* useNavigation() reads loading from the call and idle with the commit,
|
|
28
28
|
* and useLinkStatus().pending is true for a link to the destination until
|
|
29
|
-
* the commit
|
|
29
|
+
* the commit, which React holds with it. A `transition` spec plus a pending
|
|
30
30
|
* Promise seeded via navigate(url, { loaders }) commits through
|
|
31
31
|
* production's commitInTransition (browser/partial-update.ts), which React
|
|
32
32
|
* holds: useNavigation() stays loading and the held reader's
|
|
33
33
|
* useLoader().isLoading reads true until the data lands. Without
|
|
34
34
|
* `transition` the commit is urgent (production's same-structure hold is
|
|
35
|
-
* not modeled).
|
|
35
|
+
* not modeled). navigate(url, { streamUntil }) gives the navigation a
|
|
36
|
+
* response stream that stays open past the commit (isStreaming).
|
|
37
|
+
* useAction().state stays idle: no action runs.
|
|
36
38
|
* - A spec's transition({ when }) decides every commit with production's
|
|
37
39
|
* browser decision: navigate() and useRouter().push model kind "push",
|
|
38
|
-
* useRouter().replace kind "replace", refresh() kind "revalidate"
|
|
39
|
-
*
|
|
40
|
-
* e2e territory.
|
|
40
|
+
* useRouter().replace kind "replace", refresh() kind "revalidate", and a
|
|
41
|
+
* back/forward that restores a page kind "pop". Action and optimistic
|
|
42
|
+
* clientUrls() swap decisions are e2e territory.
|
|
41
43
|
* - Location state reaches readers the way it does in production:
|
|
42
44
|
* NavigationProvider holds the committed entry's state next to the
|
|
43
45
|
* payload, so a navigation whose loader the test holds keeps the entry
|
|
44
46
|
* being left on screen with its own state. A `popstate` event is a
|
|
45
|
-
* back/forward onto the entry history.state holds
|
|
46
|
-
*
|
|
47
|
+
* back/forward onto the entry history.state holds. When it is the
|
|
48
|
+
* mounted entry or one a navigate() wrote, history still holds the state
|
|
49
|
+
* written there, and another page is on screen, its page is restored
|
|
50
|
+
* from the history cache, with the handle values it had, as production's
|
|
51
|
+
* restore is; otherwise the tree keeps its location. A stale entry's
|
|
52
|
+
* revalidation, a restore that fetches, a server action's state, a
|
|
47
53
|
* redirect's and an intercept's are e2e territory.
|
|
48
54
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
49
55
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
@@ -61,6 +67,7 @@
|
|
|
61
67
|
import { decideTransitionGatedOff } from "../browser/transition-when.js";
|
|
62
68
|
import {
|
|
63
69
|
StrictMode,
|
|
70
|
+
startTransition,
|
|
64
71
|
useEffect,
|
|
65
72
|
type ReactNode,
|
|
66
73
|
type ComponentType,
|
|
@@ -78,10 +85,12 @@ import {
|
|
|
78
85
|
shouldStartViewTransition,
|
|
79
86
|
} from "../browser/partial-update.js";
|
|
80
87
|
import type {
|
|
88
|
+
HandleData,
|
|
81
89
|
NavigateOptions,
|
|
82
90
|
NavigateOptionsInternal,
|
|
83
91
|
NavigationStore,
|
|
84
92
|
NavigationBridge,
|
|
93
|
+
NavigationUpdate,
|
|
85
94
|
UpdateSubscriber,
|
|
86
95
|
} from "../browser/types.js";
|
|
87
96
|
import type {
|
|
@@ -101,6 +110,7 @@ import type {
|
|
|
101
110
|
TransitionWhenKind,
|
|
102
111
|
} from "../types.js";
|
|
103
112
|
import {
|
|
113
|
+
setCurrentHistoryStateListener,
|
|
104
114
|
setLocationStateVersion,
|
|
105
115
|
type LocationStateDefinition,
|
|
106
116
|
} from "../browser/react/location-state-shared.js";
|
|
@@ -447,11 +457,40 @@ export interface TestRouterHandle {
|
|
|
447
457
|
* `handles`). They arrive on the response's handle stream, so `useHandle`
|
|
448
458
|
* readers take them with the page's commit: while React holds the page,
|
|
449
459
|
* readers keep the values of the page being left.
|
|
460
|
+
*
|
|
461
|
+
* `options.streamUntil` gives the navigation a response stream, as a real
|
|
462
|
+
* response has: it opens in a task after the call, before the commit (the
|
|
463
|
+
* response arrives, and `useNavigation().pendingUrl` clears), and stays open
|
|
464
|
+
* until the promise settles (a page that streams a tail past its commit:
|
|
465
|
+
* `isStreaming` reads true until then). Without it the navigation has no
|
|
466
|
+
* stream. A navigation that a newer one or a back/forward aborts before its
|
|
467
|
+
* response arrives opens none and commits nothing; one aborted after its
|
|
468
|
+
* response arrived stops counting as streaming at the abort, as in
|
|
469
|
+
* production, where its response streams on and is ignored.
|
|
470
|
+
*
|
|
471
|
+
* @example
|
|
472
|
+
* const tail = deferred();
|
|
473
|
+
* await router.navigate("/products/2", { streamUntil: tail.promise });
|
|
474
|
+
* // useNavigation(): idle, isStreaming true
|
|
475
|
+
* await act(async () => tail.resolve());
|
|
476
|
+
* // useNavigation(): idle, isStreaming false
|
|
477
|
+
*
|
|
478
|
+
* A navigation started while React holds a page goes inside
|
|
479
|
+
* `await act(async () => ...)`. Its first update makes React retry the held
|
|
480
|
+
* page, which suspends again; a synchronous act() scope drops that retry
|
|
481
|
+
* and React renders no later update of the same priority.
|
|
482
|
+
*
|
|
483
|
+
* @example
|
|
484
|
+
* await act(async () => {
|
|
485
|
+
* await router.navigate("/products/3", { transition: false });
|
|
486
|
+
* });
|
|
450
487
|
*/
|
|
451
488
|
navigate(
|
|
452
489
|
url: string,
|
|
453
490
|
options?: Pick<RenderRouteOptions, "loaders" | "handles"> &
|
|
454
|
-
Pick<NavigateOptions, "state" | "replace" | "transition"
|
|
491
|
+
Pick<NavigateOptions, "state" | "replace" | "transition"> & {
|
|
492
|
+
streamUntil?: Promise<unknown>;
|
|
493
|
+
},
|
|
455
494
|
): Promise<void>;
|
|
456
495
|
/**
|
|
457
496
|
* Re-render the current location, as router.refresh() does: a spec's
|
|
@@ -821,7 +860,49 @@ export async function renderRoute(
|
|
|
821
860
|
history?: Pick<NavigateOptionsInternal, "state" | "replace" | "transition">;
|
|
822
861
|
kind?: TransitionWhenKind;
|
|
823
862
|
transition?: boolean;
|
|
863
|
+
streamUntil?: Promise<unknown>;
|
|
824
864
|
};
|
|
865
|
+
|
|
866
|
+
// The page each history entry shows, by the entry's `idx` stamp (the
|
|
867
|
+
// seeded entry has none: 0), so a back/forward can restore it as
|
|
868
|
+
// production restores a history-cache entry. `state` is the entry's
|
|
869
|
+
// history state as the router last wrote it: an entry the test wrote over
|
|
870
|
+
// it (no `idx` reads as 0) is not that page. `handleData` is the page's
|
|
871
|
+
// pushes, taken when it is left.
|
|
872
|
+
interface EntryPage {
|
|
873
|
+
url: URL;
|
|
874
|
+
segments: ResolvedSegment[];
|
|
875
|
+
params: Record<string, string>;
|
|
876
|
+
state: unknown;
|
|
877
|
+
handleData?: HandleData;
|
|
878
|
+
}
|
|
879
|
+
const entries = new Map<number, EntryPage>();
|
|
880
|
+
const entryIndex = (): number => {
|
|
881
|
+
const idx = (window.history.state as { idx?: unknown } | null)?.idx;
|
|
882
|
+
return typeof idx === "number" ? idx : 0;
|
|
883
|
+
};
|
|
884
|
+
let entryOnScreen = entryIndex();
|
|
885
|
+
entries.set(entryOnScreen, {
|
|
886
|
+
url,
|
|
887
|
+
segments: initialSegments,
|
|
888
|
+
params: initialMatch.params,
|
|
889
|
+
state: window.history.state,
|
|
890
|
+
});
|
|
891
|
+
// A write the router makes to the entry history is on (a flash read,
|
|
892
|
+
// Def.write() or delete()) stays that entry's state, as production's
|
|
893
|
+
// per-entry memory follows it (navigation-store.ts). A test's own
|
|
894
|
+
// history.replaceState() does not.
|
|
895
|
+
setCurrentHistoryStateListener(() => {
|
|
896
|
+
store.rememberDisplayedEntry();
|
|
897
|
+
const written = entries.get(entryIndex());
|
|
898
|
+
if (written) written.state = window.history.state;
|
|
899
|
+
});
|
|
900
|
+
/** The page being left keeps its pushes for a back/forward onto it. */
|
|
901
|
+
const leaveEntry = (): void => {
|
|
902
|
+
const left = entries.get(entryOnScreen);
|
|
903
|
+
if (left) left.handleData = eventController.getHandleState().data;
|
|
904
|
+
};
|
|
905
|
+
let popGeneration = 0;
|
|
825
906
|
// The navigation's lifecycle, as navigation-transaction.ts drives it:
|
|
826
907
|
// useNavigation() reads loading from the call, delivered before the commit
|
|
827
908
|
// as a click's notification goes out while its response is in flight, and
|
|
@@ -837,6 +918,29 @@ export async function renderRoute(
|
|
|
837
918
|
// takes it, and no act() of the caller's can overlap it.
|
|
838
919
|
act(() => eventController.flushRouteState());
|
|
839
920
|
try {
|
|
921
|
+
const streamUntil = navOptions?.streamUntil;
|
|
922
|
+
if (streamUntil) {
|
|
923
|
+
// The response arrives a task after the request, ahead of the
|
|
924
|
+
// commit (navigation-transaction.ts: the stream opens with it).
|
|
925
|
+
await act(async () => {
|
|
926
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
927
|
+
});
|
|
928
|
+
if (handle.signal.aborted) return;
|
|
929
|
+
const stream = handle.startStreaming();
|
|
930
|
+
act(() => eventController.flushRouteState());
|
|
931
|
+
// A navigation that is left stops counting as streaming at the abort
|
|
932
|
+
// (partial-update.ts endStream); the abort's notification goes out
|
|
933
|
+
// with the newer navigation's.
|
|
934
|
+
handle.signal.addEventListener("abort", () => stream.end(), {
|
|
935
|
+
once: true,
|
|
936
|
+
});
|
|
937
|
+
const end = (): void =>
|
|
938
|
+
act(() => {
|
|
939
|
+
stream.end();
|
|
940
|
+
eventController.flushRouteState();
|
|
941
|
+
});
|
|
942
|
+
void streamUntil.then(end, end);
|
|
943
|
+
}
|
|
840
944
|
await commitNavigation(handle, target, navOptions);
|
|
841
945
|
} finally {
|
|
842
946
|
// Back to idle when the commit never came (a render that threw).
|
|
@@ -922,6 +1026,7 @@ export async function renderRoute(
|
|
|
922
1026
|
// A newer navigate() started meanwhile: this one commits nothing, as
|
|
923
1027
|
// production's (browser/partial-update.ts) returns on its aborted signal.
|
|
924
1028
|
if (handle.signal.aborted) return;
|
|
1029
|
+
leaveEntry();
|
|
925
1030
|
handle.complete(nextUrl);
|
|
926
1031
|
eventController.setParams(match.params);
|
|
927
1032
|
store.setCurrentUrl(nextUrl.href);
|
|
@@ -934,6 +1039,13 @@ export async function renderRoute(
|
|
|
934
1039
|
);
|
|
935
1040
|
eventController.commitLocationState(historyState);
|
|
936
1041
|
}
|
|
1042
|
+
entryOnScreen = entryIndex();
|
|
1043
|
+
entries.set(entryOnScreen, {
|
|
1044
|
+
url: nextUrl,
|
|
1045
|
+
segments,
|
|
1046
|
+
params: match.params,
|
|
1047
|
+
state: window.history.state,
|
|
1048
|
+
});
|
|
937
1049
|
const emit: UpdateSubscriber = (update) => store.emitUpdate(update);
|
|
938
1050
|
await act(async () => {
|
|
939
1051
|
// As production's emitters name their cause (suspense-audit.ts I6).
|
|
@@ -961,17 +1073,90 @@ export async function renderRoute(
|
|
|
961
1073
|
return navigate(current.pathname + current.search, { kind: "revalidate" });
|
|
962
1074
|
};
|
|
963
1075
|
|
|
1076
|
+
// A back/forward onto the mounted entry or one a navigation wrote, whose
|
|
1077
|
+
// page is not the one on screen: its page is restored from the entry, as production's
|
|
1078
|
+
// history-cache restore does (navigation-bridge.ts handlePopstate). Not
|
|
1079
|
+
// modeled: a stale entry's revalidation and a restore that has to fetch.
|
|
1080
|
+
const restoreEntry = async (index: number, page: EntryPage) => {
|
|
1081
|
+
const generation = ++popGeneration;
|
|
1082
|
+
eventController.abortNavigation();
|
|
1083
|
+
const historyState = window.history.state;
|
|
1084
|
+
// transition({ when }) decides the restore (kind "pop") against the
|
|
1085
|
+
// entry being left.
|
|
1086
|
+
const gatedOff = decideTransitionGatedOff(page.segments, () => ({
|
|
1087
|
+
kind: "pop",
|
|
1088
|
+
from: {
|
|
1089
|
+
url: eventController.getLocation().href,
|
|
1090
|
+
params: eventController.getParams(),
|
|
1091
|
+
routeName: leaf.name,
|
|
1092
|
+
state: entries.get(entryOnScreen)?.state ?? null,
|
|
1093
|
+
},
|
|
1094
|
+
to: {
|
|
1095
|
+
url: page.url,
|
|
1096
|
+
params: page.params,
|
|
1097
|
+
routeName: leaf.name,
|
|
1098
|
+
state: historyState,
|
|
1099
|
+
},
|
|
1100
|
+
}));
|
|
1101
|
+
const root = await renderSegments(page.segments, {
|
|
1102
|
+
forceAwait: true,
|
|
1103
|
+
outletPending: options.outletPending,
|
|
1104
|
+
transitionGatedOff: gatedOff,
|
|
1105
|
+
});
|
|
1106
|
+
// A later back/forward owns the screen.
|
|
1107
|
+
if (generation !== popGeneration) return;
|
|
1108
|
+
leaveEntry();
|
|
1109
|
+
entryOnScreen = index;
|
|
1110
|
+
store.setCurrentUrl(page.url.href);
|
|
1111
|
+
store.setSegmentIds(page.segments.map((s) => s.id));
|
|
1112
|
+
const update: NavigationUpdate = {
|
|
1113
|
+
root,
|
|
1114
|
+
metadata: {
|
|
1115
|
+
...makeMetadata(page.url.pathname, page.segments, page.params),
|
|
1116
|
+
isPartial: true,
|
|
1117
|
+
cachedHandleData: page.handleData,
|
|
1118
|
+
},
|
|
1119
|
+
};
|
|
1120
|
+
await act(async () => {
|
|
1121
|
+
// The restored entry's location and params, set where its tree is
|
|
1122
|
+
// handed over, so the hooks take them in that batch.
|
|
1123
|
+
eventController.setLocation(page.url);
|
|
1124
|
+
eventController.setParams(page.params);
|
|
1125
|
+
eventController.commitLocationState(historyState);
|
|
1126
|
+
auditTreeCause("popstate");
|
|
1127
|
+
if (!shouldStartViewTransition(page.segments, gatedOff)) {
|
|
1128
|
+
store.emitUpdate(update);
|
|
1129
|
+
} else {
|
|
1130
|
+
startTransition(() => {
|
|
1131
|
+
store.emitUpdate({ ...update, inTransition: true });
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
1135
|
+
};
|
|
1136
|
+
|
|
964
1137
|
let prefetchRoot: HTMLElement | undefined;
|
|
965
1138
|
const bridge: NavigationBridge = {
|
|
966
1139
|
navigate: (target, navigateOptions) =>
|
|
967
1140
|
navigate(target, { history: navigateOptions ?? {} }),
|
|
968
1141
|
refresh: () => refresh(),
|
|
969
|
-
// Back/forward onto the entry history.state holds.
|
|
970
|
-
//
|
|
971
|
-
//
|
|
972
|
-
//
|
|
973
|
-
//
|
|
1142
|
+
// Back/forward onto the entry history.state holds. Its page is restored
|
|
1143
|
+
// when the mount or a navigation wrote the entry, history still holds that
|
|
1144
|
+
// state and another page is on screen (restoreEntry). Otherwise the tree
|
|
1145
|
+
// keeps its location: a treeless commit of that entry, as production's
|
|
1146
|
+
// traversal that keeps every segment (navigation-transaction.ts commit).
|
|
1147
|
+
// Flushed, not left to the notify timer: the dispatching test's act()
|
|
1148
|
+
// has returned by then.
|
|
974
1149
|
handlePopstate: async () => {
|
|
1150
|
+
const index = entryIndex();
|
|
1151
|
+
const page = entries.get(index);
|
|
1152
|
+
if (
|
|
1153
|
+
page &&
|
|
1154
|
+
index !== entryOnScreen &&
|
|
1155
|
+
page.state === window.history.state
|
|
1156
|
+
) {
|
|
1157
|
+
await restoreEntry(index, page);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
975
1160
|
eventController.commitLocationState(window.history.state, true);
|
|
976
1161
|
eventController.flushRouteState();
|
|
977
1162
|
},
|
|
@@ -1091,6 +1276,7 @@ export async function renderRoute(
|
|
|
1091
1276
|
loaders: navOptions?.loaders,
|
|
1092
1277
|
handles: navOptions?.handles,
|
|
1093
1278
|
transition: navOptions?.transition,
|
|
1279
|
+
streamUntil: navOptions?.streamUntil,
|
|
1094
1280
|
history:
|
|
1095
1281
|
navOptions?.state !== undefined || navOptions?.replace
|
|
1096
1282
|
? navOptions
|
|
@@ -53,16 +53,16 @@ import type { ExecutionContext } from "../types/request-scope.js";
|
|
|
53
53
|
import type { HandlerCacheConfig, SSRModule } from "../rsc/types.js";
|
|
54
54
|
import type { createRSCHandler } from "../rsc/handler.js";
|
|
55
55
|
import { setDefaultSSRModuleLoaderForTests } from "../rsc/ssr-module-loader.js";
|
|
56
|
+
import {
|
|
57
|
+
createPrerenderStore,
|
|
58
|
+
prerenderManifestEntries,
|
|
59
|
+
} from "../prerender/store.js";
|
|
56
60
|
import {
|
|
57
61
|
POST_QUIESCE_TASK_HOPS,
|
|
58
62
|
SHELL_CAPTURE_MAX_WAIT_MS,
|
|
59
63
|
} from "../rsc/shell-capture-constants.js";
|
|
60
64
|
import { SEGMENT_FRAGMENT_CAPABILITY_HEADER } from "../segment-fragments.js";
|
|
61
|
-
import {
|
|
62
|
-
FILL_PARAM,
|
|
63
|
-
PREFETCH_HEADER,
|
|
64
|
-
STALE_PARAM,
|
|
65
|
-
} from "../prefetch-markers.js";
|
|
65
|
+
import { PREFETCH_HEADER } from "../prefetch-markers.js";
|
|
66
66
|
import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
|
|
67
67
|
import { withoutPushedBy } from "../handles/pushed-by.js";
|
|
68
68
|
import type { HandleData } from "../server/handle-store.js";
|
|
@@ -76,6 +76,7 @@ import {
|
|
|
76
76
|
type PprReplayStatus,
|
|
77
77
|
type ShellStatus,
|
|
78
78
|
} from "./shell-status.js";
|
|
79
|
+
import { writePartialParams } from "../browser/wire-protocol.js";
|
|
79
80
|
|
|
80
81
|
/** Options for {@link serveShellRequest}. */
|
|
81
82
|
export interface ServeShellRequestOptions<TEnv = any> {
|
|
@@ -114,7 +115,13 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
114
115
|
* and segments take an action's revalidation defaults; it renders live, as
|
|
115
116
|
* an action does, so neither the route cache nor the document cache answers
|
|
116
117
|
* or stores it, and a `ppr` route's `replayStatus` is
|
|
117
|
-
* `{ outcome: "BYPASS", reason: "stale" }`.
|
|
118
|
+
* `{ outcome: "BYPASS", reason: "stale" }`. `boundary` names the error
|
|
119
|
+
* segment of a thrown action's boundary the page at `from` shows
|
|
120
|
+
* (`_rsc_boundary`), as the follow-up an earlier action owes that page
|
|
121
|
+
* sends it, with `stale: true` and no `segments`: the response renders
|
|
122
|
+
* every segment above the boundary and nothing at or below it, claims the
|
|
123
|
+
* boundary in `matched` and sends no segment for it. The server reads it
|
|
124
|
+
* only beside `stale`.
|
|
118
125
|
*/
|
|
119
126
|
partial?:
|
|
120
127
|
| true
|
|
@@ -126,6 +133,7 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
126
133
|
fill?: boolean;
|
|
127
134
|
interceptSource?: string;
|
|
128
135
|
stale?: boolean;
|
|
136
|
+
boundary?: string;
|
|
129
137
|
};
|
|
130
138
|
/**
|
|
131
139
|
* POST this form as a submit before hydration: the no-JS request the
|
|
@@ -485,29 +493,14 @@ async function servePrerenderArtifacts(
|
|
|
485
493
|
const { hashParams } = await import("../prerender/param-hash.js");
|
|
486
494
|
baked.add(pathname);
|
|
487
495
|
if (result && !result.passthrough) {
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
key,
|
|
491
|
-
JSON.stringify({ segments: result.segments, handles: result.handles }),
|
|
492
|
-
);
|
|
493
|
-
// The intercept variant, keyed and shaped as the build writes it
|
|
494
|
-
// (vite/discovery/prerender-collection.ts).
|
|
495
|
-
if (result.interceptSegments?.length) {
|
|
496
|
-
payloads.set(
|
|
497
|
-
`${key}/i`,
|
|
498
|
-
JSON.stringify({
|
|
499
|
-
segments: [...result.segments, ...result.interceptSegments],
|
|
500
|
-
handles: result.interceptHandles ?? "",
|
|
501
|
-
}),
|
|
502
|
-
);
|
|
496
|
+
const paramHash = hashParams(result.params);
|
|
497
|
+
for (const [key, value] of prerenderManifestEntries(result, paramHash)) {
|
|
498
|
+
payloads.set(key, value);
|
|
503
499
|
}
|
|
504
500
|
}
|
|
505
501
|
}
|
|
506
|
-
const
|
|
507
|
-
await
|
|
508
|
-
import("../prerender/store.js"),
|
|
509
|
-
import("../router/match-middleware/cache-lookup.js"),
|
|
510
|
-
]);
|
|
502
|
+
const { setPrerenderStoreForTests } =
|
|
503
|
+
await import("../router/match-middleware/cache-lookup.js");
|
|
511
504
|
const previousLoader = globalThis.__loadPrerenderManifestModule;
|
|
512
505
|
globalThis.__loadPrerenderManifestModule = async () => ({
|
|
513
506
|
default: Object.fromEntries([...payloads.keys()].map((key) => [key, key])),
|
|
@@ -585,12 +578,12 @@ async function buildActionRequest(
|
|
|
585
578
|
const { encodeReply } =
|
|
586
579
|
await import("@vitejs/plugin-rsc/vendor/react-server-dom/client.edge");
|
|
587
580
|
registerTestAction(action.ref, action.id, "serveShellRequest");
|
|
588
|
-
const target = new URL(url)
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
581
|
+
const target = writePartialParams(new URL(url), {
|
|
582
|
+
action: action.id,
|
|
583
|
+
segmentIds: action.segments ?? [],
|
|
584
|
+
emptySegments: "send",
|
|
585
|
+
rewriteTarget: action.rewriteTarget,
|
|
586
|
+
});
|
|
594
587
|
headers.set("rsc-action", action.id);
|
|
595
588
|
if (!headers.has("X-RSC-Router-Client-Path")) {
|
|
596
589
|
headers.set("X-RSC-Router-Client-Path", url.href);
|
|
@@ -640,20 +633,23 @@ async function buildRequest(
|
|
|
640
633
|
fill = false,
|
|
641
634
|
interceptSource,
|
|
642
635
|
stale = false,
|
|
636
|
+
boundary,
|
|
643
637
|
} = partial === true ? {} : partial;
|
|
644
638
|
if (stale && prefetch) {
|
|
645
639
|
throw new Error(
|
|
646
640
|
"serveShellRequest: partial.stale cannot be combined with partial.prefetch",
|
|
647
641
|
);
|
|
648
642
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
target
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
643
|
+
// As the browser writes them: browser/navigation-client.ts, and
|
|
644
|
+
// browser/prefetch/fetch.ts for the prefetch header.
|
|
645
|
+
const target = writePartialParams(new URL(url), {
|
|
646
|
+
segmentIds: segments,
|
|
647
|
+
emptySegments: "send",
|
|
648
|
+
stale,
|
|
649
|
+
boundary,
|
|
650
|
+
rewriteTarget,
|
|
651
|
+
fill,
|
|
652
|
+
});
|
|
657
653
|
if (prefetch && !headers.has(PREFETCH_HEADER)) {
|
|
658
654
|
headers.set(PREFETCH_HEADER, "1");
|
|
659
655
|
}
|
package/src/types/cache-types.ts
CHANGED
|
@@ -137,9 +137,12 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
137
137
|
) => boolean;
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
|
-
* Custom cache key function
|
|
141
|
-
*
|
|
142
|
-
*
|
|
140
|
+
* Custom cache key function. Replaces the URL part of the default key
|
|
141
|
+
* (host, path, params, search) AND bypasses the store's keyGenerator. The
|
|
142
|
+
* request kind stays: a document request, a navigation and each intercept
|
|
143
|
+
* navigation keep their own record under one result, so the result decides
|
|
144
|
+
* which requests of one kind share a record. It is stored namespaced
|
|
145
|
+
* (`key:` plus its URI encoding, then the kind), so no value it returns
|
|
143
146
|
* names another route's record; a nested cache() keys its records within
|
|
144
147
|
* this partition.
|
|
145
148
|
*
|
package/src/types/error-types.ts
CHANGED
|
@@ -81,11 +81,38 @@ export interface OnErrorContext<TEnv = any> {
|
|
|
81
81
|
actionId?: string;
|
|
82
82
|
/** Environment/bindings (platform context) */
|
|
83
83
|
env?: TEnv;
|
|
84
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Duration from request start to error (milliseconds). For a `background`
|
|
86
|
+
* error it counts from the start of the request that started the work, so
|
|
87
|
+
* it includes that response and any wait before the work ran.
|
|
88
|
+
*/
|
|
85
89
|
duration?: number;
|
|
86
|
-
/**
|
|
90
|
+
/**
|
|
91
|
+
* Whether this is a partial/navigation request, on an error the segment
|
|
92
|
+
* walk catches (a handler that throws, a streamed `loading()` handler that
|
|
93
|
+
* rejects, a DSL loader that rejects): `true` for a navigation, unset for a
|
|
94
|
+
* document. A `cache()` record's refresh or proactive write reports the
|
|
95
|
+
* request that started it; a PPR shell capture renders a document. The
|
|
96
|
+
* `routing` phase reports `false`.
|
|
97
|
+
*/
|
|
87
98
|
isPartial?: boolean;
|
|
88
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* `true` when the error came from work whose output goes to a cache and
|
|
101
|
+
* never to the response of the request that started it: a stale `cache()`
|
|
102
|
+
* record's refresh, the write of a navigation that kept a layout mounted, a
|
|
103
|
+
* stale document cache entry's re-run, a PPR shell capture, or a stale
|
|
104
|
+
* `"use cache"` function's or cached loader's refresh, the work's own
|
|
105
|
+
* failure included. `request` and `url` are the starting request's (a
|
|
106
|
+
* shell capture's handler errors name the page it captures). A
|
|
107
|
+
* `router.prerender()` warm's own render is its request's: unset, while a
|
|
108
|
+
* shell capture it makes is `true`. Unset for every other error.
|
|
109
|
+
*/
|
|
110
|
+
background?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Whether an error boundary caught the error and its fallback reached the
|
|
113
|
+
* response. `false` on a `background` error: the failed render is never
|
|
114
|
+
* stored, so no visitor sees the fallback.
|
|
115
|
+
*/
|
|
89
116
|
handledByBoundary?: boolean;
|
|
90
117
|
/** Stack trace (if available) */
|
|
91
118
|
stack?: string;
|
|
@@ -462,7 +462,7 @@ export type InternalHandlerContext<
|
|
|
462
462
|
_reverseScope?: import("../server/context.js").ReverseScope;
|
|
463
463
|
/**
|
|
464
464
|
* @internal The on-demand overlay holds a "removed" marker for this request
|
|
465
|
-
* (
|
|
465
|
+
* (readPrerenderSource, cache-lookup.ts); gateOnDemandProducer reads it. On
|
|
466
466
|
* this context, not the request's: withCacheLookup documents that the
|
|
467
467
|
* request ALS can be lost mid-pipeline on workerd.
|
|
468
468
|
*/
|
|
@@ -510,6 +510,17 @@ export type InternalHandlerContext<
|
|
|
510
510
|
* request is a fill. See router/segment-resolution/prefetch-deferral.ts.
|
|
511
511
|
*/
|
|
512
512
|
_prefetchDeferral?: import("../router/segment-resolution/prefetch-deferral.js").PrefetchDeferral;
|
|
513
|
+
/**
|
|
514
|
+
* @internal DSL loaders registered and not started, by `$$id`: a prefetch
|
|
515
|
+
* deferred them, or a pass that skips loaders (`skipLoaders`) did not run
|
|
516
|
+
* them. A reader's ctx.use() starts one through its registration
|
|
517
|
+
* (registerDeferredLoader, startDeferredLoader in
|
|
518
|
+
* router/segment-resolution/prefetch-deferral.ts).
|
|
519
|
+
*/
|
|
520
|
+
_deferredLoaders?: Map<
|
|
521
|
+
string,
|
|
522
|
+
import("../router/segment-resolution/prefetch-deferral.js").DeferredLoader
|
|
523
|
+
>;
|
|
513
524
|
/**
|
|
514
525
|
* @internal Whether a loader was started in this request: a run of it (a
|
|
515
526
|
* handler's or another loader's ctx.use()) or its cache() binding. A
|
package/src/types/segments.ts
CHANGED
|
@@ -165,7 +165,7 @@ export interface ResolvedSegment {
|
|
|
165
165
|
loaderData?: any; // For loaders: the resolved data from loader execution
|
|
166
166
|
/**
|
|
167
167
|
* True when this loader was awaited before first flush
|
|
168
|
-
* (loader(Def, { ssr: false })). Stamped by
|
|
168
|
+
* (loader(Def, { ssr: false })). Stamped by resolveOwnerLoaders on
|
|
169
169
|
* document AND shell-capture renders — capture bakes flagged loaders and
|
|
170
170
|
* awaits the same lane. Feeds segment-system's settled-value delivery and
|
|
171
171
|
* the dev SSR suspension warning (ssr-suspension-warning.ts).
|