@rangojs/router 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -89
- package/dist/testing/vitest.js +252 -0
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -0
- package/dist/types/browser/navigation-transaction.d.ts +6 -0
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/ScrollRestoration.d.ts +6 -6
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/scroll-restoration.d.ts +7 -0
- package/dist/types/cache/cache-exec-scope.d.ts +18 -1
- package/dist/types/cache/cache-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +132 -19
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +54 -13
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +220 -44
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- package/dist/types/client.d.ts +4 -4
- package/dist/types/client.rsc.d.ts +4 -5
- package/dist/types/components/DefaultDocument.d.ts +5 -2
- package/dist/types/context-var.d.ts +6 -5
- package/dist/types/escape-script.d.ts +1 -1
- package/dist/types/handles/MetaTags.d.ts +5 -4
- package/dist/types/handles/Scripts.d.ts +8 -7
- package/dist/types/handles/meta.d.ts +2 -2
- package/dist/types/handles/script.d.ts +17 -16
- package/dist/types/href-client.d.ts +1 -1
- package/dist/types/html.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +3 -2
- package/dist/types/loader-store.d.ts +7 -4
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/reverse.d.ts +36 -4
- package/dist/types/route-definition/dsl-helpers.d.ts +3 -3
- package/dist/types/route-definition/helpers-types.d.ts +33 -9
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +1 -1
- package/dist/types/router/debug-manifest.d.ts +3 -2
- package/dist/types/router/error-handling.d.ts +20 -0
- package/dist/types/router/handler-context.d.ts +6 -0
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +9 -3
- package/dist/types/router/router-context.d.ts +3 -1
- package/dist/types/router/router-interfaces.d.ts +24 -2
- package/dist/types/router/router-options.d.ts +8 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +10 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +66 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +23 -12
- package/dist/types/router/segment-resolution/mask-nested.d.ts +67 -26
- package/dist/types/router/segment-resolution/revalidation.d.ts +6 -2
- package/dist/types/router/segment-wrappers.d.ts +1 -1
- package/dist/types/rsc/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +61 -6
- package/dist/types/rsc/shell-capture.d.ts +151 -19
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +17 -0
- package/dist/types/server/context.d.ts +139 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +182 -53
- package/dist/types/ssr/index.d.ts +24 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/static-handler.d.ts +4 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/generated-routes.d.ts +2 -2
- package/dist/types/testing/internal/context.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +32 -6
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +28 -5
- package/dist/types/testing/vitest.d.ts +35 -1
- package/dist/types/theme/ThemeScript.d.ts +3 -3
- package/dist/types/types/cache-types.d.ts +14 -10
- package/dist/types/types/handler-context.d.ts +28 -1
- package/dist/types/types/loader-types.d.ts +8 -6
- package/dist/types/urls/pattern-types.d.ts +22 -18
- package/dist/types/urls/response-types.d.ts +3 -12
- package/dist/types/use-loader.d.ts +12 -3
- package/dist/types/vite/plugins/use-cache-transform.d.ts +33 -2
- package/dist/types/vite/rango.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1420 -1348
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +75 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +306 -124
- package/skills/caching/SKILL.md +530 -98
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +65 -38
- package/skills/cloudflare/SKILL.md +87 -20
- package/skills/cloudflare/references/d1-and-local-dev.md +15 -2
- package/skills/cloudflare/references/streaming-and-deploy.md +10 -3
- package/skills/cloudflare/references/webhooks-and-crypto.md +9 -2
- package/skills/comparison/SKILL.md +5 -0
- package/skills/comparison/references/framework-comparison.md +75 -57
- package/skills/composability/SKILL.md +38 -29
- package/skills/css/SKILL.md +10 -13
- package/skills/debug-manifest/SKILL.md +74 -31
- package/skills/defer-hydration/SKILL.md +52 -31
- package/skills/deployment-caching/SKILL.md +11 -3
- package/skills/document-cache/SKILL.md +98 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +55 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +34 -17
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +39 -15
- package/skills/hooks/urls.md +7 -5
- package/skills/host-router/SKILL.md +64 -12
- package/skills/i18n/SKILL.md +60 -34
- package/skills/intercept/SKILL.md +161 -26
- package/skills/layout/SKILL.md +106 -50
- package/skills/links/SKILL.md +51 -19
- package/skills/loader/SKILL.md +386 -128
- package/skills/middleware/SKILL.md +129 -26
- package/skills/migrate-nextjs/SKILL.md +156 -70
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +51 -39
- package/skills/migrate-react-router/component-migration.md +28 -7
- package/skills/migrate-react-router/data-and-actions.md +21 -6
- package/skills/migrate-react-router/route-mapping.md +19 -10
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +92 -14
- package/skills/parallel/SKILL.md +88 -47
- package/skills/ppr/SKILL.md +866 -379
- package/skills/prerender/SKILL.md +210 -108
- package/skills/rango/SKILL.md +115 -65
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +170 -60
- package/skills/router-setup/SKILL.md +420 -240
- package/skills/scripts/SKILL.md +39 -21
- package/skills/server-actions/SKILL.md +231 -59
- package/skills/shell-manifest/SKILL.md +28 -14
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +73 -33
- package/skills/testing/cache-prerender.md +60 -33
- package/skills/testing/client-components.md +59 -6
- package/skills/testing/e2e-parity.md +12 -2
- package/skills/testing/flight.md +19 -11
- package/skills/testing/handles.md +8 -5
- package/skills/testing/loader.md +26 -6
- package/skills/testing/middleware.md +8 -3
- package/skills/testing/predicates.md +120 -0
- package/skills/testing/render-handler.md +70 -22
- package/skills/testing/response-routes.md +11 -4
- package/skills/testing/reverse-and-types.md +20 -14
- package/skills/testing/serve-shell-request.md +188 -0
- package/skills/testing/server-actions.md +12 -3
- package/skills/testing/server-tree.md +9 -0
- package/skills/testing/setup.md +9 -5
- package/skills/theme/SKILL.md +155 -30
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +137 -50
- package/skills/typesafety/generated-files-and-cli.md +26 -14
- package/skills/typesafety/params-and-search.md +44 -19
- package/skills/typesafety/route-types.md +50 -14
- package/skills/use-cache/SKILL.md +224 -83
- package/skills/vercel/SKILL.md +35 -13
- package/skills/view-transitions/SKILL.md +42 -21
- package/src/__augment-tests__/augmented.check.ts +26 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- package/src/browser/index.ts +2 -2
- package/src/browser/navigation-bridge.ts +3 -4
- package/src/browser/navigation-transaction.ts +22 -2
- package/src/browser/partial-update.ts +35 -25
- package/src/browser/react/ScrollRestoration.tsx +6 -6
- package/src/browser/rsc-router.tsx +6 -1
- package/src/browser/scroll-restoration.ts +53 -6
- package/src/cache/cache-exec-scope.ts +37 -4
- package/src/cache/cache-key-utils.ts +23 -8
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +264 -68
- package/src/cache/cache-scope.ts +419 -43
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +133 -28
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +108 -22
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +225 -44
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- package/src/client.rsc.tsx +10 -9
- package/src/client.tsx +5 -4
- package/src/components/DefaultDocument.tsx +7 -2
- package/src/context-var.ts +6 -5
- package/src/escape-script.ts +1 -1
- package/src/handles/MetaTags.tsx +13 -11
- package/src/handles/Scripts.tsx +11 -10
- package/src/handles/meta.ts +2 -2
- package/src/handles/script.ts +17 -16
- package/src/href-client.ts +1 -1
- package/src/html.ts +12 -0
- package/src/index.rsc.ts +5 -0
- package/src/index.ts +1 -0
- package/src/loader-store.ts +7 -4
- package/src/prerender/build-shell-capture.ts +73 -22
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +121 -33
- package/src/route-definition/helpers-types.ts +33 -9
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +0 -3
- package/src/router/debug-manifest.ts +10 -2
- package/src/router/error-handling.ts +51 -15
- package/src/router/handler-context.ts +71 -28
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +141 -43
- package/src/router/loader-resolution.ts +115 -39
- package/src/router/match-api.ts +40 -26
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +110 -10
- package/src/router/match-middleware/cache-store.ts +48 -101
- package/src/router/middleware-types.ts +9 -6
- package/src/router/middleware.ts +75 -13
- package/src/router/prerender-match.ts +59 -19
- package/src/router/route-snapshot.ts +11 -1
- package/src/router/router-context.ts +2 -0
- package/src/router/router-interfaces.ts +25 -2
- package/src/router/router-options.ts +8 -5
- package/src/router/segment-resolution/fresh.ts +62 -21
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +407 -76
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +208 -56
- package/src/router/segment-resolution/mask-nested.ts +138 -44
- package/src/router/segment-resolution/revalidation.ts +61 -16
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +608 -225
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +73 -6
- package/src/rsc/shell-capture.ts +1068 -296
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +18 -0
- package/src/server/context.ts +300 -32
- package/src/server/cookie-store.ts +87 -61
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +297 -67
- package/src/ssr/index.tsx +136 -44
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/generated-routes.ts +2 -2
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/internal/flight-client-globals.ts +7 -5
- package/src/testing/render-route.tsx +85 -24
- package/src/testing/run-loader.ts +68 -6
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +28 -1
- package/src/types/loader-types.ts +8 -6
- package/src/urls/pattern-types.ts +22 -18
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
- package/src/vite/router-discovery.ts +14 -4
package/src/ssr/index.tsx
CHANGED
|
@@ -2,7 +2,11 @@ import React from "react";
|
|
|
2
2
|
import { createSsrRootComponent, deserializeSsrPayload } from "./ssr-root.js";
|
|
3
3
|
import { injectRSCPayloadEager } from "./inject-rsc-eager.js";
|
|
4
4
|
import { runWithPreinitNonce } from "./preinit-client-references.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
POST_QUIESCE_TASK_HOPS,
|
|
7
|
+
SHELL_CAPTURE_MAX_WAIT_MS,
|
|
8
|
+
} from "../rsc/shell-capture-constants.js";
|
|
9
|
+
import { isThenable } from "../handles/is-thenable.js";
|
|
6
10
|
import type { ErrorPhase } from "../types.js";
|
|
7
11
|
import type { HeadScriptsOption } from "../vite/plugin-types.js";
|
|
8
12
|
|
|
@@ -27,6 +31,7 @@ interface RenderToReadableStreamOptions {
|
|
|
27
31
|
nonce?: string;
|
|
28
32
|
formState?: unknown;
|
|
29
33
|
progressiveChunkSize?: number;
|
|
34
|
+
onError?: (error: unknown) => void;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
/**
|
|
@@ -124,6 +129,13 @@ export interface SSRRenderOptions {
|
|
|
124
129
|
* prerender pass (host-agnostic bare captures).
|
|
125
130
|
*/
|
|
126
131
|
origin?: string;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Called for each error Fizz reports through its onError: a component that
|
|
135
|
+
* threw inside a Suspense boundary, which leaves the boundary errored in an
|
|
136
|
+
* otherwise completed document. React's default console.error is kept.
|
|
137
|
+
*/
|
|
138
|
+
onError?: (error: unknown) => void;
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
/**
|
|
@@ -229,28 +241,64 @@ export interface SSRDependencies<TEnv = unknown> {
|
|
|
229
241
|
}
|
|
230
242
|
|
|
231
243
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
244
|
+
* One set per running capture, collecting the client-reference module loads
|
|
245
|
+
* requested while it runs (captureClientLoads). The Flight client resolves a
|
|
246
|
+
* client component used as an element type lazily, so the payload settles
|
|
247
|
+
* while its module is still loading, and a shell that renders it outside any
|
|
248
|
+
* Suspense boundary cannot complete until the load does. On the
|
|
249
|
+
* cloudflare-basic build a cold load (the module, its imports, its CSS
|
|
250
|
+
* virtual) settled at +890.1ms, after the payload (+873.4ms) and 1ms before
|
|
251
|
+
* the abort, and uninstrumented builds lost that race (issue #949). The
|
|
252
|
+
* Flight client asks the loader again for every import row, even for a
|
|
253
|
+
* module another render is already loading, so a capture sees each load it
|
|
254
|
+
* needs; one a concurrent render requests meanwhile is collected too and is
|
|
255
|
+
* the same bounded I/O. The sets are per capture, their contents isolate-wide:
|
|
256
|
+
* a load that never settles can only hold the captures running when it was
|
|
257
|
+
* requested, each until its deadline.
|
|
258
|
+
*/
|
|
259
|
+
const activeCaptureClientLoads = new Set<Set<PromiseLike<unknown>>>();
|
|
260
|
+
|
|
261
|
+
/** The loader wrapper {@link captureClientLoads} last installed. */
|
|
262
|
+
let recordingClientReferenceLoader: ((id: string) => unknown) | undefined;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Start collecting the client-reference loads requested during a capture;
|
|
266
|
+
* call the returned `stop` when the capture ends. Wraps plugin-rsc's SSR
|
|
267
|
+
* loader (`globalThis.__vite_rsc_client_require__`, which
|
|
268
|
+
* `__vite_rsc_require__` reads per call) on the first capture, so an isolate
|
|
269
|
+
* that never captures keeps the bare loader. Wraps again when another wrapper
|
|
270
|
+
* replaced this one (the build shell phase bridges hashed ids the same way,
|
|
271
|
+
* vite/discovery/shell-prerender-phase.ts). Collects nothing without the
|
|
272
|
+
* loader.
|
|
252
273
|
*/
|
|
253
|
-
|
|
274
|
+
function captureClientLoads(): {
|
|
275
|
+
loads: Set<PromiseLike<unknown>>;
|
|
276
|
+
stop: () => void;
|
|
277
|
+
} {
|
|
278
|
+
const g = globalThis as {
|
|
279
|
+
__vite_rsc_client_require__?: (id: string) => unknown;
|
|
280
|
+
};
|
|
281
|
+
const load = g.__vite_rsc_client_require__;
|
|
282
|
+
if (typeof load === "function" && load !== recordingClientReferenceLoader) {
|
|
283
|
+
recordingClientReferenceLoader = (id) => {
|
|
284
|
+
const loading = load(id);
|
|
285
|
+
// plugin-rsc memoizes one promise per id and the Flight client stamps
|
|
286
|
+
// status "fulfilled" on it once loaded: a warm module is not collected.
|
|
287
|
+
if (
|
|
288
|
+
activeCaptureClientLoads.size > 0 &&
|
|
289
|
+
isThenable(loading) &&
|
|
290
|
+
(loading as { status?: string }).status !== "fulfilled"
|
|
291
|
+
) {
|
|
292
|
+
for (const loads of activeCaptureClientLoads) loads.add(loading);
|
|
293
|
+
}
|
|
294
|
+
return loading;
|
|
295
|
+
};
|
|
296
|
+
g.__vite_rsc_client_require__ = recordingClientReferenceLoader;
|
|
297
|
+
}
|
|
298
|
+
const loads = new Set<PromiseLike<unknown>>();
|
|
299
|
+
activeCaptureClientLoads.add(loads);
|
|
300
|
+
return { loads, stop: () => activeCaptureClientLoads.delete(loads) };
|
|
301
|
+
}
|
|
254
302
|
|
|
255
303
|
/**
|
|
256
304
|
* Route an SSR error through the deps.onError notification callback with the
|
|
@@ -407,6 +455,19 @@ interface ShellCaptureOptions {
|
|
|
407
455
|
* capture, resume, and browser hydration.
|
|
408
456
|
*/
|
|
409
457
|
origin?: string;
|
|
458
|
+
/**
|
|
459
|
+
* Called for each component error the prerender reports (an errored
|
|
460
|
+
* boundary in the prelude), never for the capture's own abort. The caller
|
|
461
|
+
* refuses to store the shell (issue #915).
|
|
462
|
+
*/
|
|
463
|
+
onError?: (error: unknown) => void;
|
|
464
|
+
/**
|
|
465
|
+
* Called with React's errorInfo for each task still pending when the
|
|
466
|
+
* capture's own abort froze the prelude; its componentStack is computed
|
|
467
|
+
* when read. The dev no-shell warning prints the stacks: one of them is what
|
|
468
|
+
* suspended above <body> (issue #941).
|
|
469
|
+
*/
|
|
470
|
+
onAbortedTask?: (errorInfo: { componentStack?: string } | undefined) => void;
|
|
410
471
|
}
|
|
411
472
|
|
|
412
473
|
/**
|
|
@@ -437,6 +498,8 @@ interface ShellResumeOptions {
|
|
|
437
498
|
search?: string;
|
|
438
499
|
/** The HIT request's origin — same host as the capture's (key-scoped). */
|
|
439
500
|
origin?: string;
|
|
501
|
+
/** Called for each component error the resumed holes report. */
|
|
502
|
+
onError?: (error: unknown) => void;
|
|
440
503
|
}
|
|
441
504
|
|
|
442
505
|
/**
|
|
@@ -601,7 +664,14 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
601
664
|
rscStream: ReadableStream<Uint8Array>,
|
|
602
665
|
options?: SSRRenderOptions,
|
|
603
666
|
): Promise<ReadableStream<Uint8Array>> {
|
|
604
|
-
const {
|
|
667
|
+
const {
|
|
668
|
+
nonce,
|
|
669
|
+
formState,
|
|
670
|
+
streamMode,
|
|
671
|
+
search,
|
|
672
|
+
origin,
|
|
673
|
+
onError: onRenderError,
|
|
674
|
+
} = options ?? {};
|
|
605
675
|
|
|
606
676
|
try {
|
|
607
677
|
// Tee the stream:
|
|
@@ -659,6 +729,13 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
659
729
|
formState,
|
|
660
730
|
nonce,
|
|
661
731
|
...(progressiveChunkSize !== undefined && { progressiveChunkSize }),
|
|
732
|
+
...(onRenderError && {
|
|
733
|
+
onError: (error: unknown) => {
|
|
734
|
+
onRenderError(error);
|
|
735
|
+
// What React's default onError does (it only logs).
|
|
736
|
+
console.error(error);
|
|
737
|
+
},
|
|
738
|
+
}),
|
|
662
739
|
}),
|
|
663
740
|
);
|
|
664
741
|
|
|
@@ -731,12 +808,13 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
731
808
|
const deadline = (await isDebuggerAttached())
|
|
732
809
|
? { promise: new Promise<void>(() => {}), cancel: () => {} }
|
|
733
810
|
: createCancelableTimeout(maxWaitMs);
|
|
811
|
+
const clientLoads = captureClientLoads();
|
|
734
812
|
try {
|
|
735
813
|
// No nonce (nonce'd requests never reach capture); no formState.
|
|
736
|
-
// payloadSettled: fires when the Flight payload root settles
|
|
737
|
-
//
|
|
738
|
-
//
|
|
739
|
-
//
|
|
814
|
+
// payloadSettled: fires when the Flight payload root settles and fizz
|
|
815
|
+
// can emit the tree. The abort below gates on it and on the
|
|
816
|
+
// client-reference loads in flight in the isolate (bounded by the same
|
|
817
|
+
// deadline): Flight byte-quiet alone is NOT render-readiness.
|
|
740
818
|
let settlePayload!: () => void;
|
|
741
819
|
const payloadSettled = new Promise<void>((resolve) => {
|
|
742
820
|
settlePayload = resolve;
|
|
@@ -793,10 +871,12 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
793
871
|
// render errors are NOT our sentinel and still surface through
|
|
794
872
|
// deps.onError, the same channel renderHTML uses. See
|
|
795
873
|
// docs/design/ppr-shell-resume.md.
|
|
796
|
-
onError: (error: unknown) => {
|
|
874
|
+
onError: (error: unknown, errorInfo?: { componentStack?: string }) => {
|
|
797
875
|
if (error === abortReason) {
|
|
876
|
+
opts.onAbortedTask?.(errorInfo);
|
|
798
877
|
return;
|
|
799
878
|
}
|
|
879
|
+
opts.onError?.(error);
|
|
800
880
|
reportRenderError(onError, error);
|
|
801
881
|
},
|
|
802
882
|
});
|
|
@@ -815,25 +895,34 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
815
895
|
// shell that never goes quiet (a root postpone / hung handle).
|
|
816
896
|
await Promise.race([opts.quiesce, deadline.promise]);
|
|
817
897
|
// Then wait for fizz RENDER-READINESS, bounded by the same deadline:
|
|
818
|
-
// the payload root settles
|
|
819
|
-
//
|
|
820
|
-
//
|
|
898
|
+
// the payload root settles, then the client-reference module loads the
|
|
899
|
+
// capture started settle — a client component used as an element type
|
|
900
|
+
// resolves lazily, after the payload (real module-runner I/O in dev;
|
|
901
|
+
// 100ms+ on a cold graph). Flight byte-quiet does NOT imply this — a fully
|
|
821
902
|
// REPLAYED (prerendered) route's Flight stream finishes in ~1-3ms and
|
|
822
903
|
// an abort taken on quiet-plus-task-hops alone landed BEFORE fizz could
|
|
823
904
|
// emit <html>, freezing a zero-byte prelude: the eternal-MISS shape
|
|
824
905
|
// this route class showed on every cold graph (dev cold boots, GH
|
|
825
906
|
// runners) while ordinary routes — whose live handler execution keeps
|
|
826
|
-
// Flight noisy long enough — never hit it.
|
|
827
|
-
// block
|
|
828
|
-
//
|
|
829
|
-
//
|
|
830
|
-
//
|
|
831
|
-
//
|
|
907
|
+
// Flight noisy long enough — never hit it. Holes the payload carries
|
|
908
|
+
// block neither wait (masked live loaders and loader-nested promises postpone
|
|
909
|
+
// below the root and load no module; the Flight input stays frozen),
|
|
910
|
+
// so this await costs a genuinely hole-y shell nothing; a payload that
|
|
911
|
+
// NEVER settles (hung handles) degrades at the deadline exactly as
|
|
912
|
+
// before. A prerender that SETTLES first (early success or a hard
|
|
913
|
+
// rejection) ends the wait immediately — fizz is already done either
|
|
914
|
+
// way.
|
|
832
915
|
const prerenderSettled = prerenderPromise.then(
|
|
833
916
|
() => {},
|
|
834
917
|
() => {},
|
|
835
918
|
);
|
|
836
|
-
|
|
919
|
+
// The capture's loads all started when their import rows were parsed,
|
|
920
|
+
// before the Flight input froze.
|
|
921
|
+
await Promise.race([
|
|
922
|
+
payloadSettled.then(() => Promise.allSettled(clientLoads.loads)),
|
|
923
|
+
prerenderSettled,
|
|
924
|
+
deadline.promise,
|
|
925
|
+
]);
|
|
837
926
|
// Fixed task hops before the abort: give React's fizz worker turns to flush
|
|
838
927
|
// the now-complete shell and mark the still-pending boundaries as POSTPONED
|
|
839
928
|
// rather than errored. Deterministic (the byte set is already frozen and
|
|
@@ -877,11 +966,10 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
877
966
|
|
|
878
967
|
// Sanity gate: a prelude with no `<body` is the no-shell failure mode.
|
|
879
968
|
// Return null and store nothing; the request falls back to axis 1 and a
|
|
880
|
-
// later request re-captures. The dominant real-world cause is a
|
|
881
|
-
//
|
|
882
|
-
//
|
|
883
|
-
//
|
|
884
|
-
// hung handles degrade the same way. shell-capture.ts logs a once-per-key
|
|
969
|
+
// later request re-captures. The dominant real-world cause is a masked
|
|
970
|
+
// live-loader read with no boundary above it (lane rule: see
|
|
971
|
+
// resolveLoaderData, loader-cache.ts), which root-postpones above
|
|
972
|
+
// <body>. Root-postponing layouts and hung handles degrade the same way. shell-capture.ts logs a once-per-key
|
|
885
973
|
// warning so the eternal-MISS shape is diagnosable.
|
|
886
974
|
if (!new TextDecoder().decode(prelude).includes("<body")) {
|
|
887
975
|
return null;
|
|
@@ -893,6 +981,7 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
893
981
|
};
|
|
894
982
|
} finally {
|
|
895
983
|
deadline.cancel();
|
|
984
|
+
clientLoads.stop();
|
|
896
985
|
}
|
|
897
986
|
};
|
|
898
987
|
}
|
|
@@ -982,7 +1071,10 @@ export function createShellResumeHandler<TEnv = unknown>(
|
|
|
982
1071
|
// resumed stream and need the per-request nonce.
|
|
983
1072
|
const resumed = await runWithPreinitNonce(nonce, () =>
|
|
984
1073
|
resume(<SsrRoot />, JSON.parse(postponed), {
|
|
985
|
-
onError: (error) =>
|
|
1074
|
+
onError: (error) => {
|
|
1075
|
+
opts.onError?.(error);
|
|
1076
|
+
reportRenderError(onError, error);
|
|
1077
|
+
},
|
|
986
1078
|
nonce,
|
|
987
1079
|
}),
|
|
988
1080
|
);
|
package/src/ssr/ssr-root.tsx
CHANGED
|
@@ -189,14 +189,15 @@ export interface SsrRootOptions {
|
|
|
189
189
|
nonce?: string;
|
|
190
190
|
/**
|
|
191
191
|
* Fires once when the Flight payload root settles (resolve OR reject) — the
|
|
192
|
-
* signal that
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
* a
|
|
199
|
-
*
|
|
192
|
+
* signal that fizz can start emitting the tree. A client component used as
|
|
193
|
+
* an element type resolves lazily, so its module load can still be in
|
|
194
|
+
* flight (captureShellHTML waits for those separately, issue #949). The
|
|
195
|
+
* capture pass gates its abort on this: a fully REPLAYED (prerendered)
|
|
196
|
+
* route's Flight stream goes byte-quiet in ~1-3ms, but fizz cannot render
|
|
197
|
+
* even <html> until the module loads finish (real module-runner I/O in dev;
|
|
198
|
+
* 100ms+ on a cold graph), so an abort gated on Flight quiet alone fires
|
|
199
|
+
* first and freezes a zero-byte prelude. Masked-loader holes do NOT block
|
|
200
|
+
* this signal — they postpone below the root.
|
|
200
201
|
*/
|
|
201
202
|
onPayloadSettled?: () => void;
|
|
202
203
|
/**
|
package/src/static-handler.ts
CHANGED
|
@@ -21,11 +21,14 @@
|
|
|
21
21
|
* ```ts
|
|
22
22
|
* export const DocsNav = Static((ctx) => <Nav docs={readDocsSync()} />);
|
|
23
23
|
* export const DocShell = Static((ctx) => <Shell />);
|
|
24
|
+
* // Prerender/Static must be exported consts; an inline call inside path()
|
|
25
|
+
* // gets no build-injected $$id and throws.
|
|
26
|
+
* export const DocPage = Prerender(getParams, DocPageHandler);
|
|
24
27
|
*
|
|
25
28
|
* urls(({ path, layout }) => [
|
|
26
29
|
* layout(DocsNav, () => [
|
|
27
30
|
* path("/getting-started", DocShell, { name: "doc.gs" }),
|
|
28
|
-
* path("/:slug",
|
|
31
|
+
* path("/:slug", DocPage),
|
|
29
32
|
* ]),
|
|
30
33
|
* ]);
|
|
31
34
|
* ```
|
|
@@ -19,6 +19,9 @@ declare module "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge" {
|
|
|
19
19
|
options?: { onError?: (error: unknown) => string | void },
|
|
20
20
|
): ReadableStream<Uint8Array>;
|
|
21
21
|
|
|
22
|
+
/** Server-side temporary-reference set (a WeakMap). */
|
|
23
|
+
export function createTemporaryReferenceSet(): unknown;
|
|
24
|
+
|
|
22
25
|
/**
|
|
23
26
|
* Tag a value as a client reference. Mutates `impl` in place (defining
|
|
24
27
|
* `$$typeof`/`$$id`/`$$async`) and returns it, so a server tree that imports
|
|
@@ -55,3 +58,21 @@ declare module "@vitejs/plugin-rsc/core/browser" {
|
|
|
55
58
|
load: (id: string) => unknown;
|
|
56
59
|
}): void;
|
|
57
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The vendored react-server-dom CLIENT build the RSC environment decodes with
|
|
64
|
+
* (plugin-rsc `react/rsc/client`). The plugin-rsc stub runs the "use cache"
|
|
65
|
+
* codec on it under the react-server condition.
|
|
66
|
+
*/
|
|
67
|
+
declare module "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge" {
|
|
68
|
+
export function createFromReadableStream<T = unknown>(
|
|
69
|
+
stream: ReadableStream<Uint8Array>,
|
|
70
|
+
options?: object,
|
|
71
|
+
): Promise<T>;
|
|
72
|
+
export function encodeReply(
|
|
73
|
+
value: unknown,
|
|
74
|
+
options?: object,
|
|
75
|
+
): Promise<string | FormData>;
|
|
76
|
+
/** Client-side temporary-reference set (a Map). */
|
|
77
|
+
export function createTemporaryReferenceSet(): unknown;
|
|
78
|
+
}
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
* element tree, so you can assert typed prop fidelity across the client boundary
|
|
19
19
|
* (a `Date` comes back a `Date`) and detect inlined-vs-island. Serialize +
|
|
20
20
|
* deserialize only — no hydration/interaction (that is the e2e tier).
|
|
21
|
+
*
|
|
22
|
+
* `serveShellRequest` serves one request through the router's production
|
|
23
|
+
* handler, so a `ppr` route's MISS runs the real shell capture and a later
|
|
24
|
+
* request is a real HIT; only the HTML step is stubbed. `resetShellTestState`
|
|
25
|
+
* clears the per-isolate PPR state between tests.
|
|
21
26
|
*/
|
|
22
27
|
|
|
23
28
|
export {
|
|
@@ -50,3 +55,12 @@ export type {
|
|
|
50
55
|
RenderHandlerResult,
|
|
51
56
|
StateCookieSeed,
|
|
52
57
|
} from "./render-handler.js";
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
serveShellRequest,
|
|
61
|
+
resetShellTestState,
|
|
62
|
+
} from "./serve-shell-request.js";
|
|
63
|
+
export type {
|
|
64
|
+
ServeShellRequestOptions,
|
|
65
|
+
ServeShellRequestResult,
|
|
66
|
+
} from "./serve-shell-request.js";
|
|
@@ -158,10 +158,10 @@ export async function diffGeneratedRoutes(
|
|
|
158
158
|
*
|
|
159
159
|
* @example
|
|
160
160
|
* ```ts
|
|
161
|
-
* import
|
|
161
|
+
* import { NamedRoutes } from "./router.named-routes.gen";
|
|
162
162
|
* import { router } from "./router";
|
|
163
163
|
*
|
|
164
|
-
* await assertGeneratedRoutesMatch(router,
|
|
164
|
+
* await assertGeneratedRoutesMatch(router, NamedRoutes);
|
|
165
165
|
* ```
|
|
166
166
|
*/
|
|
167
167
|
export async function assertGeneratedRoutesMatch(
|
|
@@ -347,7 +347,7 @@ export function buildRunSnapshot<TEnv>(
|
|
|
347
347
|
*
|
|
348
348
|
* @example
|
|
349
349
|
* ```ts
|
|
350
|
-
* const {
|
|
350
|
+
* const { cookies, headers, response, thrown } = await runInRequestContext(
|
|
351
351
|
* () => loginAction(input), // sets a session cookie, then `throw redirect("/app")`
|
|
352
352
|
* {
|
|
353
353
|
* env,
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* (@vitejs/plugin-rsc `core/plugin.js`). That transform does NOT run in a bare
|
|
8
8
|
* Vitest process, so the vendored client's free `__webpack_require__` /
|
|
9
9
|
* `__webpack_chunk_load__` references would be undefined. We provide minimal
|
|
10
|
-
* shims: `__webpack_require__`
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* shims: `__webpack_require__` forwards to `__vite_rsc_require__` (the build's
|
|
11
|
+
* rename), which plugin-rsc's `setRequireModule` installs (core/browser from
|
|
12
|
+
* renderServerTree, core/rsc from the plugin-rsc stub), and
|
|
13
|
+
* `__webpack_chunk_load__` is a no-op (the test manifests use empty `chunks`,
|
|
14
|
+
* so no chunk fetch ever happens).
|
|
13
15
|
*
|
|
14
16
|
* MUST be imported (for side effect) BEFORE `@vitejs/plugin-rsc/react/browser`,
|
|
15
17
|
* which is why flight-tree.ts lists it first.
|
|
@@ -17,11 +19,11 @@
|
|
|
17
19
|
const g = globalThis as unknown as {
|
|
18
20
|
__webpack_require__?: ((id: string) => unknown) & { u?: unknown };
|
|
19
21
|
__webpack_chunk_load__?: (chunkId: string) => Promise<unknown>;
|
|
20
|
-
|
|
22
|
+
__vite_rsc_require__?: (id: string) => unknown;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
if (!g.__webpack_require__) {
|
|
24
|
-
g.__webpack_require__ = (id: string) => g.
|
|
26
|
+
g.__webpack_require__ = (id: string) => g.__vite_rsc_require__!(id);
|
|
25
27
|
}
|
|
26
28
|
if (!g.__webpack_chunk_load__) {
|
|
27
29
|
g.__webpack_chunk_load__ = async () => {};
|
|
@@ -23,10 +23,13 @@
|
|
|
23
23
|
* SEEDED directly into client context (see the `loaders` / `locationState` /
|
|
24
24
|
* `handles` / `outletPending` options) — nothing is executed on the server.
|
|
25
25
|
* This exercises the context read path, not the run path.
|
|
26
|
-
* - navigate()
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* - navigate() does NOT drive the navigation lifecycle: useNavigation().state,
|
|
27
|
+
* useLinkStatus().pending, and useAction().state stay "idle". Assert
|
|
28
|
+
* pending/loading/submitting transition states with renderServerTree / e2e
|
|
29
|
+
* instead (navigate() warns once if used). The one pending state it does
|
|
30
|
+
* model is the held-navigation useLoader().isLoading: a `transition` spec
|
|
31
|
+
* plus a pending Promise seeded via navigate(url, { loaders }) commits
|
|
32
|
+
* through production's commitInTransition (browser/partial-update.ts).
|
|
30
33
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
31
34
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
32
35
|
* useRouter, usePathname, useSearchParams, Outlet/useOutlet nesting and seeded
|
|
@@ -48,7 +51,15 @@ import {
|
|
|
48
51
|
} from "../browser/navigation-store.js";
|
|
49
52
|
import { createEventController } from "../browser/event-controller.js";
|
|
50
53
|
import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
|
|
51
|
-
import
|
|
54
|
+
import {
|
|
55
|
+
commitInTransition,
|
|
56
|
+
shouldStartViewTransition,
|
|
57
|
+
} from "../browser/partial-update.js";
|
|
58
|
+
import type {
|
|
59
|
+
NavigationStore,
|
|
60
|
+
NavigationBridge,
|
|
61
|
+
UpdateSubscriber,
|
|
62
|
+
} from "../browser/types.js";
|
|
52
63
|
import type { EventController } from "../browser/event-controller.js";
|
|
53
64
|
import type { ResolvedSegment, RscMetadata } from "../browser/types.js";
|
|
54
65
|
import { NavigationProvider } from "../browser/react/NavigationProvider.js";
|
|
@@ -57,7 +68,7 @@ import {
|
|
|
57
68
|
buildParamsFromMatch,
|
|
58
69
|
} from "../router/pattern-matching.js";
|
|
59
70
|
import { normalizeBasename } from "../router/basename.js";
|
|
60
|
-
import type { LoaderDefinition } from "../types.js";
|
|
71
|
+
import type { LoaderDefinition, TransitionConfig } from "../types.js";
|
|
61
72
|
import type { LocationStateDefinition } from "../browser/react/location-state-shared.js";
|
|
62
73
|
import type { Handle } from "../handle.js";
|
|
63
74
|
import type { ThemeConfig } from "../theme/types.js";
|
|
@@ -130,6 +141,14 @@ export interface RenderRouteSpec {
|
|
|
130
141
|
loaderIds?: string[];
|
|
131
142
|
/** Optional route name (informational; not used for matching). */
|
|
132
143
|
name?: string;
|
|
144
|
+
/**
|
|
145
|
+
* The `transition()` config this node declares (`{}` for a bare
|
|
146
|
+
* `transition()`), attached to its segment as the DSL does. Puts the chain in
|
|
147
|
+
* a transition scope: param-agnostic keys (segment-system.tsx) and a held
|
|
148
|
+
* navigate() commit, so a pending loader seed passed to
|
|
149
|
+
* `router.navigate(url, { loaders })` pins `useLoader().isLoading`.
|
|
150
|
+
*/
|
|
151
|
+
transition?: TransitionConfig;
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
/**
|
|
@@ -303,8 +322,26 @@ export interface TestRouterHandle {
|
|
|
303
322
|
* updates params + location, and re-renders the segment tree. This is a
|
|
304
323
|
* client-only navigation: no server fetch occurs, so only the components in
|
|
305
324
|
* `routes` can be reached.
|
|
325
|
+
*
|
|
326
|
+
* `options.loaders` seeds loader data for THIS navigation, merged over the
|
|
327
|
+
* render-time seeds (the data a real navigation's response would carry). A
|
|
328
|
+
* pending Promise suspends the read like a streaming Flight chunk. When a
|
|
329
|
+
* spec in the chain has `transition`, the commit goes through production's
|
|
330
|
+
* commitInTransition: the reader stays on screen with
|
|
331
|
+
* `useLoader().isLoading === true` until the promise settles. Settle it
|
|
332
|
+
* inside RTL's `act()` to flush the commit. Without `transition` the commit
|
|
333
|
+
* is urgent and the read suspends to the nearest Suspense boundary.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* await router.navigate("/products/2", { loaders: [[ProductLoader, next]] });
|
|
337
|
+
* // reader: isLoading true, old data
|
|
338
|
+
* await act(async () => resolve({ name: "Product 2" }));
|
|
339
|
+
* // reader: isLoading false, new data
|
|
306
340
|
*/
|
|
307
|
-
navigate(
|
|
341
|
+
navigate(
|
|
342
|
+
url: string,
|
|
343
|
+
options?: Pick<RenderRouteOptions, "loaders">,
|
|
344
|
+
): Promise<void>;
|
|
308
345
|
/** The current committed pathname. */
|
|
309
346
|
pathname(): string;
|
|
310
347
|
/** The current committed params. */
|
|
@@ -413,6 +450,7 @@ function buildSegments(
|
|
|
413
450
|
belongsToRoute: true,
|
|
414
451
|
};
|
|
415
452
|
if (mount) node.mountPath = mount;
|
|
453
|
+
if (spec.transition) node.transition = spec.transition;
|
|
416
454
|
if (isLeaf && spec.layout) {
|
|
417
455
|
const Layout = spec.layout;
|
|
418
456
|
node.layout = <Layout />;
|
|
@@ -464,10 +502,17 @@ export async function renderRoute(
|
|
|
464
502
|
const initialUrl = requestUrl ?? staticPrefix(leaf.path) ?? "/";
|
|
465
503
|
const url = new URL(initialUrl, TEST_ORIGIN);
|
|
466
504
|
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
505
|
+
const seedLoaders = (
|
|
506
|
+
base: Record<string, unknown>,
|
|
507
|
+
loaders: RenderRouteOptions["loaders"],
|
|
508
|
+
): Record<string, unknown> => {
|
|
509
|
+
const out = { ...base };
|
|
510
|
+
for (const [loader, data] of loaders ?? []) {
|
|
511
|
+
out[ensureSyntheticId(loader as object, "$$id")] = data;
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
};
|
|
515
|
+
const loaderData = seedLoaders(options.loaderData ?? {}, options.loaders);
|
|
471
516
|
|
|
472
517
|
if (typeof window !== "undefined") {
|
|
473
518
|
const stateObj: Record<string, unknown> = {};
|
|
@@ -550,25 +595,33 @@ export async function renderRoute(
|
|
|
550
595
|
);
|
|
551
596
|
|
|
552
597
|
let warnedNavLifecycle = false;
|
|
553
|
-
const navigate = async (
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
//
|
|
558
|
-
//
|
|
598
|
+
const navigate = async (
|
|
599
|
+
target: string,
|
|
600
|
+
navOptions?: Pick<RenderRouteOptions, "loaders">,
|
|
601
|
+
): Promise<void> => {
|
|
602
|
+
// No server fetch, so the navigation lifecycle never starts: the state
|
|
603
|
+
// useNavigation()/useLinkStatus()/useAction() read stays "idle" — asserting
|
|
604
|
+
// a pending/loading/submitting state here proves nothing. Warn once (per
|
|
605
|
+
// render) under the test runner so that false-confidence trap is loud.
|
|
559
606
|
if (isUnderTestRunner() && !warnedNavLifecycle) {
|
|
560
607
|
warnedNavLifecycle = true;
|
|
561
608
|
console.warn(
|
|
562
|
-
"renderRoute: navigate()/useRouter().push
|
|
563
|
-
"
|
|
564
|
-
'
|
|
565
|
-
"
|
|
566
|
-
"
|
|
609
|
+
"renderRoute: navigate()/useRouter().push do NOT drive the navigation " +
|
|
610
|
+
"lifecycle. useNavigation().state, useLinkStatus().pending, and " +
|
|
611
|
+
'useAction().state stay "idle" here. Assert params/pathname/content ' +
|
|
612
|
+
"after navigate(); use renderServerTree or e2e to assert " +
|
|
613
|
+
"pending/loading/submitting transition states. A held " +
|
|
614
|
+
"useLoader().isLoading is modeled: see navigate(url, { loaders }).",
|
|
567
615
|
);
|
|
568
616
|
}
|
|
569
617
|
const nextUrl = new URL(target, TEST_ORIGIN);
|
|
570
618
|
const match = resolve(nextUrl.pathname);
|
|
571
|
-
const segments = buildSegments(
|
|
619
|
+
const segments = buildSegments(
|
|
620
|
+
routes,
|
|
621
|
+
match.params,
|
|
622
|
+
seedLoaders(loaderData, navOptions?.loaders),
|
|
623
|
+
mount,
|
|
624
|
+
);
|
|
572
625
|
const metadata = makeMetadata(nextUrl.pathname, segments, match.params);
|
|
573
626
|
const root = await renderSegments(segments, {
|
|
574
627
|
outletPending: options.outletPending,
|
|
@@ -577,8 +630,16 @@ export async function renderRoute(
|
|
|
577
630
|
eventController.setParams(match.params);
|
|
578
631
|
store.setCurrentUrl(nextUrl.href);
|
|
579
632
|
store.setSegmentIds(segments.map((s) => s.id));
|
|
633
|
+
const emit: UpdateSubscriber = (update) => store.emitUpdate(update);
|
|
580
634
|
await act(async () => {
|
|
581
|
-
|
|
635
|
+
// Production's transition() lane (browser/partial-update.ts). Its
|
|
636
|
+
// same-structure / fully-prefetched / optimistic hold lanes are not
|
|
637
|
+
// modeled: without transition() the commit stays urgent.
|
|
638
|
+
if (shouldStartViewTransition(segments)) {
|
|
639
|
+
commitInTransition(emit, segments, { root, metadata }, ["navigation"]);
|
|
640
|
+
} else {
|
|
641
|
+
emit({ root, metadata });
|
|
642
|
+
}
|
|
582
643
|
});
|
|
583
644
|
};
|
|
584
645
|
|