@rangojs/router 0.19.1 → 0.21.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 +26 -14
- package/dist/types/browser/event-controller.d.ts +32 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/context.d.ts +11 -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/react/subscribe-to-nothing.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +4 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +27 -6
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/handle-snapshot.d.ts +36 -50
- package/dist/types/cache/index.d.ts +2 -0
- package/dist/types/cache/shell-snapshot.d.ts +19 -3
- package/dist/types/cache/types.d.ts +21 -10
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +28 -16
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/handles/deferred-resolution.d.ts +11 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/router/match-middleware/cache-lookup.d.ts +1 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/router-interfaces.d.ts +2 -1
- package/dist/types/router/router-options.d.ts +17 -7
- package/dist/types/router/segment-resolution/loader-cache.d.ts +21 -9
- 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 +3 -2
- package/dist/types/rsc/types.d.ts +4 -6
- package/dist/types/server/build-version-table.d.ts +44 -0
- package/dist/types/server/handle-store.d.ts +87 -46
- package/dist/types/server/request-context.d.ts +22 -8
- package/dist/types/ssr/entry-preload-priority.d.ts +69 -0
- package/dist/types/ssr/index.d.ts +1 -0
- package/dist/types/ssr/preinit-client-references.d.ts +17 -5
- 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 +1 -1
- package/dist/types/testing/index.d.ts +2 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- 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 +21 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/types/handler-context.d.ts +8 -5
- 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 +14 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/plugin-types.d.ts +47 -6
- 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 +21 -2
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/client-chunks.d.ts +24 -2
- 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 +1544 -420
- package/package.json +1 -2
- package/skills/bundle-analysis/SKILL.md +1 -1
- package/skills/cache-guide/SKILL.md +84 -20
- package/skills/caching/SKILL.md +16 -5
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +2 -1
- package/skills/comparison/references/framework-comparison.md +16 -12
- package/skills/hooks/SKILL.md +3 -0
- package/skills/hooks/handle-and-actions.md +6 -0
- package/skills/hooks/navigation.md +28 -0
- package/skills/hooks/state.md +209 -8
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +13 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +19 -1
- package/skills/middleware/SKILL.md +2 -0
- package/skills/ppr/SKILL.md +119 -40
- package/skills/prerender/SKILL.md +7 -3
- package/skills/rango/SKILL.md +8 -7
- package/skills/router-setup/SKILL.md +23 -3
- package/skills/testing/SKILL.md +30 -26
- 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 +63 -14
- 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 +16 -5
- package/skills/vercel/SKILL.md +3 -3
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +75 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/navigation-bridge.ts +41 -18
- package/src/browser/navigation-transaction.ts +15 -10
- package/src/browser/partial-update.ts +10 -2
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +2 -3
- package/src/browser/react/NavigationProvider.tsx +25 -1
- package/src/browser/react/context.ts +13 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +40 -75
- package/src/browser/react/subscribe-to-nothing.ts +7 -0
- package/src/browser/react/use-handle.ts +20 -1
- package/src/browser/rsc-router.tsx +33 -11
- package/src/browser/server-action-bridge.ts +17 -1
- package/src/cache/cache-scope.ts +18 -8
- package/src/cache/cf/cf-cache-store.ts +75 -55
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/handle-snapshot.ts +69 -87
- package/src/cache/index.ts +4 -0
- package/src/cache/shell-snapshot.ts +35 -5
- package/src/cache/types.ts +21 -10
- package/src/cache/vercel/vercel-cache-store.ts +48 -20
- 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/handles/deferred-resolution.ts +31 -0
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +11 -18
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/request-classification.ts +15 -12
- package/src/router/router-interfaces.ts +2 -1
- package/src/router/router-options.ts +17 -7
- package/src/router/segment-resolution/loader-cache.ts +95 -60
- package/src/router-versions.ts +60 -0
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +14 -4
- package/src/rsc/rsc-rendering.ts +12 -1
- package/src/rsc/shell-build-manifest.ts +10 -7
- package/src/rsc/shell-capture.ts +36 -26
- package/src/rsc/types.ts +4 -6
- package/src/server/build-version-table.ts +115 -0
- package/src/server/handle-store.ts +344 -127
- package/src/server/request-context.ts +62 -11
- package/src/ssr/entry-preload-priority.ts +308 -0
- package/src/ssr/index.tsx +46 -9
- package/src/ssr/preinit-client-references.ts +17 -5
- package/src/ssr/ssr-root.tsx +16 -4
- 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.entry.ts +1 -0
- package/src/testing/index.ts +3 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +277 -43
- package/src/testing/serve-shell-request.ts +80 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/types/handler-context.ts +8 -5
- package/src/vite/discovery/build-versions.ts +1281 -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 +28 -38
- package/src/vite/discovery/state.ts +19 -0
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/plugin-types.ts +48 -6
- 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 +40 -7
- package/src/vite/rango.ts +30 -3
- package/src/vite/router-discovery.ts +82 -13
- package/src/vite/utils/client-chunks.ts +84 -14
- 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
|
@@ -17,6 +17,25 @@ export type HandleData = Record<string, Record<string, unknown[]>>;
|
|
|
17
17
|
* Reads data directly from the store for each segment in order.
|
|
18
18
|
*/
|
|
19
19
|
export declare function buildHandleSnapshot(handleStore: HandleStore, segmentOrder: string[]): HandleData;
|
|
20
|
+
/**
|
|
21
|
+
* What a restored record is to one loader's handle pushes on this request
|
|
22
|
+
* (loader-cache.ts loaderPins; docs/design/handle-push-ownership.md). Two
|
|
23
|
+
* answers let the record's copies stand, two make them placeholders, and in
|
|
24
|
+
* each pair one also speaks for the loader's other pushes:
|
|
25
|
+
* - "pin": the request serves the loader's value from the pin stored with
|
|
26
|
+
* the record, and the record lists every settled push of that run. Its
|
|
27
|
+
* copies stand, and any other settled push of the loader is dropped.
|
|
28
|
+
* - "copies": its copies stand and nothing else is known: a dependency of
|
|
29
|
+
* pinned loaders, or a pin written before captures recorded every push.
|
|
30
|
+
* - "hole": the route runs the loader on this request (it registers it and
|
|
31
|
+
* the record does not pin it). Its copies are placeholders and a push made
|
|
32
|
+
* in its body is its own, with or without copies.
|
|
33
|
+
* - "placeholders": its copies are placeholders and nothing else is known: a
|
|
34
|
+
* dependency the route does not register while a pin is missing (the
|
|
35
|
+
* record does not name the loader that ran it). Without copies its push
|
|
36
|
+
* counts for the loader that awaits it.
|
|
37
|
+
*/
|
|
38
|
+
export type RecordAuthority = "pin" | "copies" | "hole" | "placeholders";
|
|
20
39
|
/**
|
|
21
40
|
* HandleStore tracks pending handler promises and stores handle data.
|
|
22
41
|
*
|
|
@@ -81,69 +100,86 @@ export interface HandleStore {
|
|
|
81
100
|
*/
|
|
82
101
|
push(handleName: string, segmentId: string, data: unknown, loaderPush?: boolean, owner?: string): void;
|
|
83
102
|
/**
|
|
84
|
-
* push() a
|
|
85
|
-
*
|
|
86
|
-
* in this request, its first push removes every
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
103
|
+
* push() a cached unit's copy of a value `loaderId`'s body pushed
|
|
104
|
+
* (handle-snapshot.ts appendHandles: a loader-cache or "use cache" HIT). If
|
|
105
|
+
* that loader then runs live in this request, its first push removes every
|
|
106
|
+
* copy pushed for it (replays and placeholders) and takes the first one's
|
|
107
|
+
* position, and the pushes of that run follow in push order, a dependency's
|
|
108
|
+
* included, so the handle output stays live without a duplicate. A push
|
|
109
|
+
* counts as that loader's when it is made in its body, or in a body it
|
|
110
|
+
* entered that has no copies of its own (the capture credits a dependency's
|
|
111
|
+
* push under a live-lane loader to that loader). A run that ends without a
|
|
112
|
+
* push leaves a replay in place, unless the loader is a hole
|
|
113
|
+
* (settleLoaderRun).
|
|
93
114
|
*/
|
|
94
115
|
pushReplayed(handleName: string, segmentId: string, data: unknown, loaderId: string): void;
|
|
95
116
|
/**
|
|
96
|
-
* push() a
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*
|
|
117
|
+
* push() a record's copy of `loaderId`'s push as a placeholder
|
|
118
|
+
* (handle-snapshot.ts restoreHandles): the record does not supply that
|
|
119
|
+
* loader's value on this request, so its own source decides its pushes. A
|
|
120
|
+
* live run replaces it as it does a replay; a run that ends without a push
|
|
121
|
+
* drops it (settleLoaderRun); a cached unit that claims the loader delivers
|
|
122
|
+
* in its place (replacePlaceholders).
|
|
123
|
+
*/
|
|
124
|
+
pushPlaceholder(handleName: string, segmentId: string, data: unknown, loaderId: string): void;
|
|
125
|
+
/**
|
|
126
|
+
* push() a record's copy of `loaderId`'s settled push that stands
|
|
127
|
+
* (restoreHandles): the record supplies the loader's value too. From then
|
|
128
|
+
* on this request drops every settled, thenable-free push the loader makes
|
|
129
|
+
* (live, or replayed from a cache entry, anywhere inside its body), and
|
|
130
|
+
* keeps the ones holding a thenable (holdsThenable: what the record could
|
|
131
|
+
* not keep), placed among the restored copies in the loader's push order.
|
|
106
132
|
*/
|
|
107
133
|
pushRestored(handleName: string, segmentId: string, data: unknown, loaderId: string): void;
|
|
108
134
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* never takes a push made inside a hole it awaited, while a body inside the
|
|
116
|
-
* hole that restored copies of its own (a dependency the capture ran under
|
|
117
|
-
* a bake-lane loader) still takes its pushes.
|
|
135
|
+
* What the record this request restored is to each loader (RecordAuthority;
|
|
136
|
+
* CacheScope.lookupRouteDetailed sets it on a hit). A "pin" loader counts
|
|
137
|
+
* as restored with or without copies. A hole is a "hole" loader or one with
|
|
138
|
+
* placeholders: the walk out through enclosing loader bodies that finds the
|
|
139
|
+
* loader a push counts for stops at its body, so a pinned loader never
|
|
140
|
+
* takes a push made inside a hole it awaited.
|
|
118
141
|
*/
|
|
119
|
-
|
|
142
|
+
setRecordAuthority(authority: (loaderId: string) => RecordAuthority): void;
|
|
120
143
|
/**
|
|
121
144
|
* `loaderId`'s run in this request settled (resolved or rejected), its
|
|
122
|
-
* pushes made (loader-resolution.ts createLoaderExecutor).
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* its settled pushes, as its pin is for its data. Restored copies are not
|
|
128
|
-
* replays and stand.
|
|
145
|
+
* pushes made (loader-resolution.ts createLoaderExecutor). Its placeholders
|
|
146
|
+
* that no push of the run replaced are dropped: the run's output decides,
|
|
147
|
+
* so a loader that throws or skips its push shows none rather than the
|
|
148
|
+
* record's. A hole's replays go with them. Any other loader's replays stay
|
|
149
|
+
* (a dependency another entry's HIT replayed), and so do restored copies.
|
|
129
150
|
*/
|
|
130
151
|
settleLoaderRun(loaderId: string): void;
|
|
131
152
|
/**
|
|
132
|
-
* A
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* place in each array. Any other loader, or a hole with nothing replayed,
|
|
139
|
-
* delivers as a plain replay.
|
|
153
|
+
* A cached unit's HIT delivering the pushes it recorded for `loaderIds`,
|
|
154
|
+
* the loaders it claimed (appendHandles). Their placeholders give way to
|
|
155
|
+
* what `deliver` pushes for them, nothing when it pushes nothing, so the
|
|
156
|
+
* page shows the pushes of the run that produced the unit's value. The
|
|
157
|
+
* pushReplayed calls `deliver` makes take the removed values' place in each
|
|
158
|
+
* array, in order. Without placeholders it is a plain replay.
|
|
140
159
|
*/
|
|
141
|
-
|
|
160
|
+
replacePlaceholders(loaderIds: readonly string[], deliver: () => void): void;
|
|
142
161
|
/**
|
|
143
162
|
* Get all collected handle data after all handlers have settled.
|
|
144
163
|
* Waits for `settled` (handler lane), then returns the data at that point.
|
|
145
164
|
*/
|
|
146
165
|
getData(): Promise<HandleData>;
|
|
166
|
+
/**
|
|
167
|
+
* Fix the document lane at the store's current state. From then on
|
|
168
|
+
* `stream("settled")` yields this state and nothing newer, and
|
|
169
|
+
* `streamLate()` delivers every change made after it, whether or not it
|
|
170
|
+
* beat the handler barrier. The first call wins. It must run before the
|
|
171
|
+
* document stream is first read: a stream that started on the live state
|
|
172
|
+
* stays on it (reported through `onError` in development).
|
|
173
|
+
*
|
|
174
|
+
* A PPR shell HIT calls it once its record is replayed and before a loader
|
|
175
|
+
* runs (rsc-rendering.ts serveShellHit). The prelude was rendered from the
|
|
176
|
+
* record's handle data (resolvedHandleStream `recordedOnly`), so that is
|
|
177
|
+
* what the client must hydrate with. What the request's loaders then push,
|
|
178
|
+
* replace or drop is theirs, not the shell's: in the document snapshot it
|
|
179
|
+
* rendered elements the prelude never had, or left out ones it has (issue
|
|
180
|
+
* #1035), and it got there or not by a race with the lane's first read.
|
|
181
|
+
*/
|
|
182
|
+
freezeDocumentSnapshot(): void;
|
|
147
183
|
/**
|
|
148
184
|
* Get an async iterator that yields handle data on each push.
|
|
149
185
|
* Completes at the chosen settlement barrier: "fullySettled" (default —
|
|
@@ -152,6 +188,9 @@ export interface HandleStore {
|
|
|
152
188
|
* blocking positions and must not wait on loader bodies).
|
|
153
189
|
* Each yield contains the full accumulated state (not just the delta).
|
|
154
190
|
* Safe for concurrent consumers (per-consumer version cursor).
|
|
191
|
+
* The "settled" lane of a frozen store (freezeDocumentSnapshot) yields the
|
|
192
|
+
* frozen state once, at once, and ends. That yield is the store's own
|
|
193
|
+
* frozen object: a consumer must not write to it.
|
|
155
194
|
*/
|
|
156
195
|
stream(until?: "settled" | "fullySettled"): AsyncGenerator<HandleData, void, unknown>;
|
|
157
196
|
/**
|
|
@@ -160,7 +199,9 @@ export interface HandleStore {
|
|
|
160
199
|
* fullySettled. Returns without yielding when the auxiliary lane is empty
|
|
161
200
|
* by the time `settled` resolves. The client consumes this post-hydration
|
|
162
201
|
* (rsc-router.tsx) and merges via the same application path as nav-lane
|
|
163
|
-
* progressive handle updates.
|
|
202
|
+
* progressive handle updates. On a frozen store (freezeDocumentSnapshot)
|
|
203
|
+
* it starts from the frozen state instead: a change made before the
|
|
204
|
+
* handler barrier is late too.
|
|
164
205
|
*/
|
|
165
206
|
streamLate(): AsyncGenerator<HandleData, void, unknown>;
|
|
166
207
|
/**
|
|
@@ -20,6 +20,7 @@ import { type ContextVar } from "../context-var.js";
|
|
|
20
20
|
import { type HandleStore, type HandleData } from "./handle-store.js";
|
|
21
21
|
import { type MetricsStore } from "./context.js";
|
|
22
22
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
23
|
+
import type { RouterVersions } from "../router-versions.js";
|
|
23
24
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
24
25
|
import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
|
|
25
26
|
import type { TransitionWhenRecord } from "../router/transition-when.js";
|
|
@@ -283,7 +284,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
283
284
|
_routeRecordWindow?: RouteRecordWindow;
|
|
284
285
|
/**
|
|
285
286
|
* @internal Loader-family snapshot seed for a shell HIT's tail render:
|
|
286
|
-
*
|
|
287
|
+
* loader id -> the capture's elided container (already Flight-deserialized
|
|
287
288
|
* by serveShellHit) plus its capture-computed hole bit. resolveLoaderData
|
|
288
289
|
* overlays it onto the fresh run's container (recorded paths pinned,
|
|
289
290
|
* hole-marker paths keep the fresh nested promises) so the payload's baked
|
|
@@ -291,7 +292,9 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
291
292
|
* and does not run the loader. Two owners: serveShellHit sets it on the
|
|
292
293
|
* HIT tail's derived context, and matchPartialWithPprReplay sets it on the
|
|
293
294
|
* request context of a PPR partial replay once its doc record hits, and
|
|
294
|
-
* restores the previous value after the match.
|
|
295
|
+
* restores the previous value after the match. Read through servedPins
|
|
296
|
+
* (loader-cache.ts) only: it also decides which of the record's
|
|
297
|
+
* loader-owned handle pushes stand (loaderPins).
|
|
295
298
|
*/
|
|
296
299
|
_shellLoaderSeed?: Map<string, import("../cache/shell-snapshot.js").ShellLoaderSeedEntry>;
|
|
297
300
|
/**
|
|
@@ -348,9 +351,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
348
351
|
* scope replaces any route-derived cache() scope for the tail's match
|
|
349
352
|
* (resolveShellImplicitCacheScope), and a lookup that does not hit throws
|
|
350
353
|
* ShellRecordUnavailableError instead of resolving segments
|
|
351
|
-
* (withCacheLookup): a HIT never runs a handler.
|
|
352
|
-
* loader-owned handle pushes restore as authoritative
|
|
353
|
-
* (HandleStore.pushRestored): the prelude rendered them.
|
|
354
|
+
* (withCacheLookup): a HIT never runs a handler.
|
|
354
355
|
*/
|
|
355
356
|
docTail?: true;
|
|
356
357
|
/**
|
|
@@ -708,6 +709,14 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
|
|
|
708
709
|
/** @internal Owning router id; scopes the search-schema/root-scope registry
|
|
709
710
|
* lookups per router (route-map-builder.ts) */
|
|
710
711
|
_routerId?: string;
|
|
712
|
+
/**
|
|
713
|
+
* @internal Cache versions of the router serving this request. The cache
|
|
714
|
+
* factory builds a store per request without knowing the router, so a store
|
|
715
|
+
* reads its key versions from here, lazily, the same way CFCacheStore
|
|
716
|
+
* resolves its base URL (getCacheVersions, server/build-version-table.ts).
|
|
717
|
+
* Unset on a context no router created; a store then uses the build version.
|
|
718
|
+
*/
|
|
719
|
+
_versions?: RouterVersions;
|
|
711
720
|
/**
|
|
712
721
|
* @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
|
|
713
722
|
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
@@ -748,7 +757,7 @@ export interface RouteRecordWindow {
|
|
|
748
757
|
* This is the type exported to library consumers. Internal code should
|
|
749
758
|
* use the full RequestContext interface directly.
|
|
750
759
|
*/
|
|
751
|
-
export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_searchParamsFilter" | "_shellCaptureRun" | "_shellImplicitCache" | "_shellLoaderSeed" | "_shellCaptureLoaderRecords" | "_shellCaptureUnrecordedLoaderPush" | "_shellCaptureLoaderLanes" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_routeRecordWindow" | "_shellFragmentPayload" | "_shellCaptureGuardTripped" | "_shellCaptureGuardTrippedLoaderId" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_locationState" | "_routeName" | "_prevRouteKey" | "_inActionRevalidation" | "_reportedErrors" | "_renderErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_tracing" | "_basename" | "_routerId" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
|
|
760
|
+
export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "_readCookie" | "_readCookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhenRefs" | "_pprReplayPostMatchReason" | "_cacheStore" | "_searchParamsFilter" | "_shellCaptureRun" | "_shellImplicitCache" | "_shellLoaderSeed" | "_shellCaptureLoaderRecords" | "_shellCaptureUnrecordedLoaderPush" | "_shellCaptureLoaderLanes" | "_resolvedCacheKeys" | "_shellKey" | "_shellForcedMiss" | "_routeRecordWindow" | "_shellFragmentPayload" | "_shellCaptureGuardTripped" | "_shellCaptureGuardTrippedLoaderId" | "_explicitTaggedStores" | "_requestTags" | "_recordTagOwners" | "_recordLoaderTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_readTheme" | "_documentCacheRender" | "_payloadVisitorTheme" | "_locationState" | "_routeName" | "_prevRouteKey" | "_inActionRevalidation" | "_reportedErrors" | "_renderErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_awaitBeforeFlushLoaderIds" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_activeRoutine" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_tracing" | "_basename" | "_routerId" | "_versions" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_freshReads" | "_setFreshReadsCookie" | "_requestRoot" | "_requestStart" | "_responseSent" | "_variables" | "_classifiedRoute" | "_requestMode" | "_cacheSignal" | "_dynamic" | "res">;
|
|
752
761
|
/**
|
|
753
762
|
* Marker for a waitUntil-scheduled fn whose task promise must NOT enter
|
|
754
763
|
* _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
|
|
@@ -824,8 +833,13 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
824
833
|
themeConfig?: ResolvedThemeConfig | null;
|
|
825
834
|
/** Resolved rango state cookie name, for the server seat of invalidateClientCache(). */
|
|
826
835
|
stateCookieName?: string;
|
|
827
|
-
/**
|
|
828
|
-
|
|
836
|
+
/**
|
|
837
|
+
* The versions of the router serving this request (resolveRouterVersions).
|
|
838
|
+
* Stored as `_versions` for the cache stores; `document` is also the prefix
|
|
839
|
+
* of a server-rotated rango state value, which the browser compares with the
|
|
840
|
+
* payload's `metadata.version`.
|
|
841
|
+
*/
|
|
842
|
+
versions?: RouterVersions;
|
|
829
843
|
}
|
|
830
844
|
/**
|
|
831
845
|
* Create a full request context with all methods implemented
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client entry's head hint at default fetch priority, after the head chunk
|
|
3
|
+
* scripts (issue #1025).
|
|
4
|
+
*
|
|
5
|
+
* For `bootstrapModules`, Fizz writes `<link rel="modulepreload"
|
|
6
|
+
* fetchPriority="low">` into the head and claims the URL
|
|
7
|
+
* (`moduleScriptResources[url] = null`, react-dom 19.3.0
|
|
8
|
+
* `react-dom-server.edge.production.js:546-565`) before the render starts, so
|
|
9
|
+
* no `preloadModule`/`preinitModule` call can replace it, and the
|
|
10
|
+
* `bootstrapModules` entry shape has no priority field. Chromium fetches that
|
|
11
|
+
* hint at Low, and once it boosts in-viewport images to High after first
|
|
12
|
+
* layout, the entry queues behind them; the executing end-of-shell module
|
|
13
|
+
* script reuses the in-flight Low fetch through the module map. Hydration
|
|
14
|
+
* then waits on a few-hundred-byte file while the head chunk scripts it
|
|
15
|
+
* imports (High) have long arrived: measured +3.8 s on a priority-honouring
|
|
16
|
+
* HTTP/2 link at Slow 4G.
|
|
17
|
+
*
|
|
18
|
+
* The fix serves that one tag without the attribute, so it fetches at
|
|
19
|
+
* Chromium's modulepreload default (High), and moves it after the head
|
|
20
|
+
* chunk scripts. Fizz flushes the bootstrap hint before the head chunk scripts; at
|
|
21
|
+
* High in that position it took one of HTTP/1.1's six connections ahead of
|
|
22
|
+
* the react chunk and delayed the last head chunk (hydration +91 ms, LCP
|
|
23
|
+
* +84 ms measured). After the scripts it is the last High request in the
|
|
24
|
+
* queue, so it never displaces a chunk hydration also needs. The accepted
|
|
25
|
+
* cost: an HTTP/2 server that sends equal-priority streams first in, first
|
|
26
|
+
* out delivers it with the last head chunk instead of before it (text-only
|
|
27
|
+
* pages +12 to +56 ms measured, against -0.15 to -3.3 s hydration on image
|
|
28
|
+
* pages; CHANGELOG has the matrix). Everything else about the bootstrap pair
|
|
29
|
+
* stays Fizz's: the nonce, the executing `id="_R_"` tag, the URL claim that
|
|
30
|
+
* keeps plugin-rsc's and the preinit hook's calls for the entry inert, and the
|
|
31
|
+
* PPR prelude/resume behaviour.
|
|
32
|
+
*
|
|
33
|
+
* The match is the exact tag Fizz writes for a string `bootstrapModules`
|
|
34
|
+
* entry (`rel`, `fetchPriority`, `nonce`, `href` in that order), not a
|
|
35
|
+
* pattern, and only before the first `<body`. A Fizz version that writes it
|
|
36
|
+
* differently leaves the bytes untouched: the output keeps the Low hint.
|
|
37
|
+
*
|
|
38
|
+
* Where the tag goes: right after the run of Fizz resource tags that directly
|
|
39
|
+
* follows the hint, before the first token that is anything else (`</head>`,
|
|
40
|
+
* the app's own head children, or body content in a head-less document).
|
|
41
|
+
* Fizz flushes the head chunk scripts and the bulk preloads straight after
|
|
42
|
+
* the bootstrap hint (react-dom 19.3.0 `flushCompletedQueues`, `:7321-7325`),
|
|
43
|
+
* as `<script …></script>` with no body and `<link …/>`; the scan accepts
|
|
44
|
+
* only those two shapes and stops at any other token without reading into it.
|
|
45
|
+
* It never searches the app's head content for `</head>` or `<body`: React
|
|
46
|
+
* does not escape either inside `<script>` or `<style>` text, so a head script
|
|
47
|
+
* holding the string `"<body>"` once received the tag inside its source and
|
|
48
|
+
* threw. Fizz escapes `>` in attribute values, so the first `>` ends a tag.
|
|
49
|
+
* Every insertion point is the boundary between two complete tags; a stream
|
|
50
|
+
* that ends mid-run, or a run tag longer than {@link MAX_HELD_TAG}, gets the
|
|
51
|
+
* tag before the unfinished one. Only that one tag is held back while the run
|
|
52
|
+
* streams; each complete run tag is forwarded as soon as it is scanned.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* Rewrite a complete document prefix (the PPR shell capture's prelude).
|
|
56
|
+
* Returns `bytes` unchanged when no hint precedes the first `<body`.
|
|
57
|
+
*/
|
|
58
|
+
export declare function rewriteEntryPreloadPriority(bytes: Uint8Array, entryUrl: string, nonce?: string): Uint8Array;
|
|
59
|
+
/**
|
|
60
|
+
* Streaming form for live SSR: the same rewrite as
|
|
61
|
+
* {@link rewriteEntryPreloadPriority}, then pass-through. Before the hint is
|
|
62
|
+
* found, only a chunk tail that may be the start of a split `<body` or hint
|
|
63
|
+
* tag is held back. After it, complete run tags are forwarded as they are
|
|
64
|
+
* scanned and only an unfinished one (at most {@link MAX_HELD_TAG} bytes) is
|
|
65
|
+
* held, so every byte is scanned a bounded number of times. Once the tag is
|
|
66
|
+
* placed, or once `<body` passes without a hint, chunks are forwarded
|
|
67
|
+
* untouched and unscanned.
|
|
68
|
+
*/
|
|
69
|
+
export declare function entryPreloadPriorityTransform(entryUrl: string, nonce?: string): TransformStream<Uint8Array, Uint8Array>;
|
|
@@ -145,6 +145,7 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
145
145
|
* undefined keeps the inline bootstrap verbatim, so a custom SSR entry that
|
|
146
146
|
* never installed the preinit hook cannot drift into the half-converted
|
|
147
147
|
* state on upgrade (the generated entry always passes an explicit value).
|
|
148
|
+
* Any other value throws when the handler is created.
|
|
148
149
|
*/
|
|
149
150
|
headScripts?: HeadScriptsOption;
|
|
150
151
|
/**
|
|
@@ -55,11 +55,23 @@ export declare function runWithPreinitNonce<T>(nonce: string | undefined, fn: ()
|
|
|
55
55
|
*
|
|
56
56
|
* Known trades (deliberate, measured neutral-to-positive on the e2e apps —
|
|
57
57
|
* PR #694 has the Lighthouse/hydration numbers):
|
|
58
|
-
* - Fetch priority:
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
58
|
+
* - Fetch priority: Chromium gives a parser-inserted `async` module script
|
|
59
|
+
* initial priority High: below the render-blocking stylesheet (Highest),
|
|
60
|
+
* and alongside the LCP image once Chromium boosts the in-viewport image to
|
|
61
|
+
* High (#1021). Every head chunk gets the same priority: the shared
|
|
62
|
+
* dependency chunks (react, router, entry.rsc and the bundler runtime) and
|
|
63
|
+
* every client-component chunk. The entry itself is a Fizz bootstrapModules
|
|
64
|
+
* hint, not a preinit (Fizz claims its URL, so this hook's call for it is
|
|
65
|
+
* inert); the handlers serve that hint at default priority, High as well,
|
|
66
|
+
* after these scripts (entry-preload-priority.ts, #1025).
|
|
67
|
+
* Lowering the chunks to `fetchpriority="low"` was measured and rejected
|
|
68
|
+
* (#1021; Chromium 153, 20 interleaved runs per variant and cell). On pages
|
|
69
|
+
* with images above the fold the Low chunks queue behind the boosted
|
|
70
|
+
* images: hydration came 206 to 6407 ms later on HTTP/1.1 and 65 to 642 ms
|
|
71
|
+
* later on HTTP/2. The one gain, FCP -136 ms, needed an image-free page
|
|
72
|
+
* with 35 KB of render-blocking CSS on HTTP/1.1 Slow 4G, and hydration was
|
|
73
|
+
* still 98 ms later there. `headScripts: "preload"` is the way to get Low
|
|
74
|
+
* chunk fetches.
|
|
63
75
|
* - Build only: plugin-rsc's dev load path reports `js: []` per reference, so
|
|
64
76
|
* dev documents have no head chunk scripts — a client module whose module
|
|
65
77
|
* scope assumes body-parsed DOM can break in production only. The
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type RouterVersions } from "../router-versions.js";
|
|
2
|
+
export type { RouterVersions };
|
|
3
|
+
/**
|
|
4
|
+
* The versions a simulated build ships.
|
|
5
|
+
*
|
|
6
|
+
* - `data` and `document`: the versions of every router the test does not list
|
|
7
|
+
* under `routers` (the build's whole-build pair).
|
|
8
|
+
* - `routers`: versions per router id (`router.id`), as a host-router build
|
|
9
|
+
* computes them.
|
|
10
|
+
*/
|
|
11
|
+
export interface BuildVersions extends RouterVersions {
|
|
12
|
+
routers?: Record<string, RouterVersions>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Install the cache versions of a simulated build, or remove them with no
|
|
16
|
+
* argument (routers serve unversioned again, the default in tests).
|
|
17
|
+
*
|
|
18
|
+
* A router that sets `createRouter({ version })` keeps that value for both
|
|
19
|
+
* versions, exactly as in production.
|
|
20
|
+
*
|
|
21
|
+
* @example A deploy that changes the client only keeps cached data
|
|
22
|
+
* ```ts
|
|
23
|
+
* setBuildVersions({ data: "d1", document: "h1" });
|
|
24
|
+
* await serveShellRequest(router, "/product/1", { cacheStore });
|
|
25
|
+
* setBuildVersions({ data: "d1", document: "h2" });
|
|
26
|
+
* const { shellStatus } = await serveShellRequest(router, "/product/1", {
|
|
27
|
+
* cacheStore,
|
|
28
|
+
* });
|
|
29
|
+
* // shellStatus is "MISS": the stored HTML was keyed by document version h1.
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function setBuildVersions(versions?: BuildVersions): void;
|
|
@@ -12,4 +12,4 @@
|
|
|
12
12
|
* (`happy-dom` or `jsdom`).
|
|
13
13
|
*/
|
|
14
14
|
export { renderRoute } from "./render-route.js";
|
|
15
|
-
export type { RenderRouteSpec, RenderRouteOptions, TestRouterHandle, RenderRouteResult, HandleDataSeed, } from "./render-route.js";
|
|
15
|
+
export type { RenderRouteSpec, RenderRouteOptions, TestRouterHandle, RenderRouteResult, RenderRouteHydrateResult, HandleDataSeed, } from "./render-route.js";
|
|
@@ -31,4 +31,4 @@ export type { RenderServerTreeOptions, RenderServerTreeResult, ClientBoundary, B
|
|
|
31
31
|
export { renderHandler } from "./render-handler.js";
|
|
32
32
|
export type { TestableHandler, RenderHandlerOptions, RenderHandlerResult, StateCookieSeed, } from "./render-handler.js";
|
|
33
33
|
export { serveShellRequest, resetShellTestState, } from "./serve-shell-request.js";
|
|
34
|
-
export type { ServeShellRequestOptions, ServeShellRequestResult, } from "./serve-shell-request.js";
|
|
34
|
+
export type { ServeShellRequestOptions, ServeShellRequestResult, ShellRequestHandles, } from "./serve-shell-request.js";
|
|
@@ -48,6 +48,8 @@ export type { ExpectedCacheStatus, CacheStatusTarget, CacheSink, } from "./cache
|
|
|
48
48
|
export type { TelemetryEvent, TelemetrySink, CacheDecisionEvent, CacheSegmentSignal, CacheSegmentStatus, } from "../router/telemetry.js";
|
|
49
49
|
export { assertPprReplayStatus, assertShellStatus, parsePprReplayStatus, parseShellStatus, PPR_REPLAY_STATUS_HEADER, shellCacheKey, SHELL_STATUS_HEADER, } from "./shell-status.js";
|
|
50
50
|
export type { PprReplayBypassReason, PprReplayStatus, ShellCachePartition, ShellStatus, ShellStatusTarget, } from "./shell-status.js";
|
|
51
|
+
export { setBuildVersions } from "./build-versions.js";
|
|
52
|
+
export type { BuildVersions, RouterVersions } from "./build-versions.js";
|
|
51
53
|
export { collectHandle } from "./collect-handle.js";
|
|
52
54
|
export { withLocationStateKey } from "./location-state-key.js";
|
|
53
55
|
export { diffGeneratedRoutes, assertGeneratedRoutesMatch, } from "./generated-routes.js";
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* client/browser modules).
|
|
8
8
|
*/
|
|
9
9
|
import { type ContextVar } from "../../context-var.js";
|
|
10
|
+
import { type RouterVersions } from "../../router-versions.js";
|
|
10
11
|
export interface StateCookieSeed {
|
|
11
12
|
/**
|
|
12
13
|
* Cookie-name prefix, sanitized then composed with `routerId` exactly like
|
|
@@ -20,11 +21,15 @@ export interface StateCookieSeed {
|
|
|
20
21
|
*/
|
|
21
22
|
routerId?: string;
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
24
|
+
* The app's version, as `createRouter({ version })` would set it: the
|
|
25
|
+
* rotated value's prefix (`{version}:{timestamp}`) and the version a cache
|
|
26
|
+
* store keys with. Defaults to `"0"` for the prefix (resolved inside
|
|
27
|
+
* createRequestContext) and to the build versions for the store.
|
|
25
28
|
*/
|
|
26
29
|
version?: string;
|
|
27
30
|
}
|
|
28
31
|
export declare function resolveSeededStateCookieName(seed?: StateCookieSeed): string;
|
|
32
|
+
/** The request context's `versions` for a seeded `stateCookie.version`. */
|
|
33
|
+
export declare function seededVersions(seed?: StateCookieSeed): RouterVersions | undefined;
|
|
29
34
|
export type VarsInit = Record<string, unknown> | ReadonlyArray<readonly [ContextVar<unknown> | string, unknown]>;
|
|
30
35
|
export declare function seedVariables(variables: Record<string, unknown>, vars?: VarsInit): Record<string, unknown>;
|
|
@@ -6,11 +6,15 @@ import { type LocationStateDefinition } from "../browser/react/location-state-sh
|
|
|
6
6
|
* use (`MyState(value)`, `useLocationState(MyState)`, `.read()`).
|
|
7
7
|
*
|
|
8
8
|
* - `withLocationStateKey(GridState, "GridState")` sets `__rsc_ls_GridState`
|
|
9
|
-
* (a name that already starts with `__rsc_ls_` is used as-is).
|
|
9
|
+
* (a name that already starts with `__rsc_ls_` is used as-is). A definition
|
|
10
|
+
* with `clearOnReload` appends its suffix, so read the stored key back from
|
|
11
|
+
* `GridState.__rsc_ls_key` (`__rsc_ls_GridState~r`).
|
|
10
12
|
* - Without a name, a key already set (by the plugin or an earlier call) is
|
|
11
13
|
* kept; otherwise the definition gets a synthetic `__rsc_ls_test_<n>` key,
|
|
12
14
|
* which then stays on it.
|
|
13
15
|
*
|
|
16
|
+
* A name may not contain "~": that separator is reserved for the suffix.
|
|
17
|
+
*
|
|
14
18
|
* Returns the definition.
|
|
15
19
|
*
|
|
16
20
|
* @example
|
|
@@ -35,11 +35,19 @@
|
|
|
35
35
|
* useRouter().replace kind "replace", refresh() kind "revalidate".
|
|
36
36
|
* Back/forward, action and optimistic clientUrls() swap decisions are
|
|
37
37
|
* e2e territory.
|
|
38
|
+
* - Location state reaches readers the way it does in production:
|
|
39
|
+
* NavigationProvider holds the committed entry's state next to the
|
|
40
|
+
* payload, so a navigation whose loader the test holds keeps the entry
|
|
41
|
+
* being left on screen with its own state. A `popstate` event is a
|
|
42
|
+
* back/forward onto the entry history.state holds; the tree has one
|
|
43
|
+
* location, so no page is restored with it. A server action's state, a
|
|
44
|
+
* redirect's and an intercept's are e2e territory.
|
|
38
45
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
39
46
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
40
47
|
* useRouter, usePathname, useSearchParams, Outlet/useOutlet nesting and seeded
|
|
41
48
|
* descendant pending state, useLoader/useFetchLoader (seeded data),
|
|
42
|
-
* useLocationState (seeded), and useHandle
|
|
49
|
+
* useLocationState (seeded, or written by a navigation), and useHandle
|
|
50
|
+
* (seeded).
|
|
43
51
|
* Basename-mounted apps: pass the `basename` option so useRouter().basename,
|
|
44
52
|
* <Link> prefixing, and useMount/useHref resolve against the mount prefix
|
|
45
53
|
* (without it they resolve at the root "/"). For an include("/shop", ...)
|
|
@@ -48,10 +56,10 @@
|
|
|
48
56
|
*/
|
|
49
57
|
import { type ComponentType } from "react";
|
|
50
58
|
import type { RenderResult } from "@testing-library/react";
|
|
51
|
-
import type { NavigationStore } from "../browser/types.js";
|
|
59
|
+
import type { NavigateOptions, NavigationStore } from "../browser/types.js";
|
|
52
60
|
import type { EventController } from "../browser/event-controller.js";
|
|
53
61
|
import type { LoaderDefinition, TransitionConfig } from "../types.js";
|
|
54
|
-
import type
|
|
62
|
+
import { type LocationStateDefinition } from "../browser/react/location-state-shared.js";
|
|
55
63
|
import type { Handle } from "../handle.js";
|
|
56
64
|
import type { ThemeConfig } from "../theme/types.js";
|
|
57
65
|
import type { PrefetchStrategy } from "../router/prefetch-default.js";
|
|
@@ -159,6 +167,8 @@ export interface RenderRouteOptions {
|
|
|
159
167
|
* `useLocationState(StateDef)`. Like loaders, a real `createLocationState()`
|
|
160
168
|
* handle has an empty injected key in a bare test, so pass `[def, value]`
|
|
161
169
|
* pairs; renderRoute assigns a synthetic key and writes it to `history.state`.
|
|
170
|
+
* With `hydrate: true` a `clearOnReload` definition's seed is removed before
|
|
171
|
+
* hydration, as on a document load.
|
|
162
172
|
*
|
|
163
173
|
* @example
|
|
164
174
|
* renderRoute([{ path: "/", Component: FlashBanner }], {
|
|
@@ -192,6 +202,24 @@ export interface RenderRouteOptions {
|
|
|
192
202
|
* computes the segment id for you). Merged with `handles`.
|
|
193
203
|
*/
|
|
194
204
|
handle?: HandleDataSeed;
|
|
205
|
+
/**
|
|
206
|
+
* Handle values a document delivers after hydration, as a loader's push
|
|
207
|
+
* that is not in the document's handle snapshot is (a push made after the
|
|
208
|
+
* handler output settled, or any loader push on a PPR shell HIT). Each
|
|
209
|
+
* entry is `[handle, values]`: the handle's values after the update, which
|
|
210
|
+
* replace its `handles` seed once the root has hydrated (or mounted). With
|
|
211
|
+
* `hydrate: true` the server HTML is rendered from `handles`, and a reader
|
|
212
|
+
* inside a `<Suspense>` boundary hydrates after the update was applied, as
|
|
213
|
+
* a boundary that hydrates after the root does in production.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* const { serverHtml, recoverableErrors } = await renderRoute(routes, {
|
|
217
|
+
* hydrate: true,
|
|
218
|
+
* handles: [[Notes, ["from-handler"]]],
|
|
219
|
+
* lateHandles: [[Notes, ["from-handler", "from-loader"]]],
|
|
220
|
+
* });
|
|
221
|
+
*/
|
|
222
|
+
lateHandles?: ReadonlyArray<readonly [Handle<any, any>, unknown[]]>;
|
|
195
223
|
/**
|
|
196
224
|
* Route name -> pattern map. Informational for parity with the server test
|
|
197
225
|
* context; client useReverse takes its map directly as an argument, so this
|
|
@@ -253,6 +281,25 @@ export interface RenderRouteOptions {
|
|
|
253
281
|
* not override a `"none"` default.
|
|
254
282
|
*/
|
|
255
283
|
defaultPrefetch?: PrefetchStrategy;
|
|
284
|
+
/**
|
|
285
|
+
* Hydrate instead of mounting fresh: the same element is rendered to HTML
|
|
286
|
+
* (react-dom/server `renderToString`), placed in the container and hydrated.
|
|
287
|
+
* The result gains `serverHtml` and `recoverableErrors`
|
|
288
|
+
* (RenderRouteHydrateResult). `data-hydrated` is set on `<html>` after
|
|
289
|
+
* hydration, as production's root does, and removed on unmount.
|
|
290
|
+
*
|
|
291
|
+
* Limits:
|
|
292
|
+
* - The server pass runs without `window` and `document`, and only those. A
|
|
293
|
+
* hook that reads a global that stays defined there (`history`,
|
|
294
|
+
* `localStorage`, `navigator`) outside a server snapshot renders the same
|
|
295
|
+
* in both passes here and can still mismatch in real SSR.
|
|
296
|
+
* - Content that suspends in the server pass (`lazy`, a pending or untracked
|
|
297
|
+
* `use()` promise) is emitted as its fallback and client-rendered, not
|
|
298
|
+
* hydrated; React reports the boundary in `recoverableErrors`. Outside any
|
|
299
|
+
* `<Suspense>` it rejects.
|
|
300
|
+
* - Needs @testing-library/react >= 16.2.0; throws on older versions.
|
|
301
|
+
*/
|
|
302
|
+
hydrate?: boolean;
|
|
256
303
|
}
|
|
257
304
|
/**
|
|
258
305
|
* Imperative handle returned alongside the RTL result.
|
|
@@ -282,10 +329,17 @@ export interface TestRouterHandle {
|
|
|
282
329
|
* `options.transition: false` is the per-navigation opt-out
|
|
283
330
|
* (`router.push(url, { transition: false })`, `<Link transition={false}>`):
|
|
284
331
|
* the commit is urgent and no transition({ when }) predicate is called.
|
|
332
|
+
*
|
|
333
|
+
* `options.state` and `options.replace: true` write the history entry the
|
|
334
|
+
* way `useRouter().push(url, { state })` and `.replace(url, { state })` do,
|
|
335
|
+
* so a test can carry location state on a navigation whose loader it
|
|
336
|
+
* holds: `state` pushes an entry with it, `replace: true` replaces the
|
|
337
|
+
* current entry (with `state`, or with none, which drops the entry's
|
|
338
|
+
* location state as a production replace does). With neither, history is
|
|
339
|
+
* left alone and readers keep the state they have: `navigate(url)` and
|
|
340
|
+
* `navigate(url, { replace: false })` are the same call.
|
|
285
341
|
*/
|
|
286
|
-
navigate(url: string, options?: Pick<RenderRouteOptions, "loaders"> &
|
|
287
|
-
transition?: boolean;
|
|
288
|
-
}): Promise<void>;
|
|
342
|
+
navigate(url: string, options?: Pick<RenderRouteOptions, "loaders"> & Pick<NavigateOptions, "state" | "replace" | "transition">): Promise<void>;
|
|
289
343
|
/**
|
|
290
344
|
* Re-render the current location, as router.refresh() does: a spec's
|
|
291
345
|
* transition({ when }) decides it with kind "revalidate" (`to` is `from`).
|
|
@@ -304,4 +358,21 @@ export interface TestRouterHandle {
|
|
|
304
358
|
export type RenderRouteResult = RenderResult & {
|
|
305
359
|
router: TestRouterHandle;
|
|
306
360
|
};
|
|
361
|
+
/** Result of renderRoute with `hydrate: true`. */
|
|
362
|
+
export type RenderRouteHydrateResult = RenderRouteResult & {
|
|
363
|
+
/** The HTML the server pass produced, as placed in the container. */
|
|
364
|
+
serverHtml: string;
|
|
365
|
+
/**
|
|
366
|
+
* Messages of the errors React recovered from (`onRecoverableError`): a
|
|
367
|
+
* hydration mismatch that made it re-render on the client, or a boundary the
|
|
368
|
+
* server pass could not finish. Empty means the tree hydrated as rendered.
|
|
369
|
+
* Live: React appends for as long as the root is mounted. An attribute-only
|
|
370
|
+
* mismatch is NOT here — React keeps the server attribute and only logs it
|
|
371
|
+
* with `console.error` in development.
|
|
372
|
+
*/
|
|
373
|
+
recoverableErrors: string[];
|
|
374
|
+
};
|
|
375
|
+
export declare function renderRoute(routes: RenderRouteSpec[], options: RenderRouteOptions & {
|
|
376
|
+
hydrate: true;
|
|
377
|
+
}): Promise<RenderRouteHydrateResult>;
|
|
307
378
|
export declare function renderRoute(routes: RenderRouteSpec[], options?: RenderRouteOptions): Promise<RenderRouteResult>;
|