@rangojs/router 0.16.0 → 0.17.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 +254 -89
- package/dist/testing/vitest.js +252 -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-scope.d.ts +34 -5
- package/dist/types/cache/handle-capture.d.ts +41 -3
- package/dist/types/cache/handle-snapshot.d.ts +53 -13
- package/dist/types/cache/segment-codec.d.ts +10 -3
- package/dist/types/cache/taint.d.ts +1 -1
- package/dist/types/cache/types.d.ts +15 -3
- 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/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/match-middleware/background-revalidation.d.ts +46 -5
- package/dist/types/router/middleware-types.d.ts +3 -2
- 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 +37 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -19
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +3 -3
- package/dist/types/router/segment-resolution/mask-nested.d.ts +7 -7
- 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/response-route-handler.d.ts +1 -1
- package/dist/types/rsc/rsc-rendering.d.ts +1 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +8 -0
- package/dist/types/rsc/shell-capture.d.ts +5 -4
- package/dist/types/rsc/ssr-setup.d.ts +6 -6
- package/dist/types/rsc/types.d.ts +10 -0
- package/dist/types/server/context.d.ts +33 -3
- package/dist/types/server/handle-store.d.ts +26 -2
- package/dist/types/server/request-context.d.ts +24 -21
- package/dist/types/ssr/index.d.ts +15 -0
- package/dist/types/static-handler.d.ts +4 -1
- 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/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 +14 -0
- package/dist/types/types/loader-types.d.ts +8 -6
- 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/vite/index.js +1381 -1346
- package/package.json +2 -2
- package/skills/api-client/SKILL.md +22 -10
- package/skills/breadcrumbs/SKILL.md +65 -22
- package/skills/bundle-analysis/SKILL.md +22 -22
- package/skills/cache-guide/SKILL.md +207 -97
- package/skills/caching/SKILL.md +295 -93
- package/skills/catalog.json +16 -16
- package/skills/client-urls/SKILL.md +53 -31
- package/skills/cloudflare/SKILL.md +72 -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 +39 -33
- 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 +2 -1
- package/skills/deployment-caching/SKILL.md +6 -1
- package/skills/document-cache/SKILL.md +92 -28
- package/skills/fonts/SKILL.md +16 -8
- package/skills/handler-use/SKILL.md +53 -17
- package/skills/hooks/SKILL.md +28 -20
- package/skills/hooks/data.md +23 -3
- package/skills/hooks/handle-and-actions.md +31 -16
- package/skills/hooks/navigation.md +39 -10
- package/skills/hooks/outlets.md +13 -6
- package/skills/hooks/state.md +37 -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 +242 -106
- package/skills/middleware/SKILL.md +119 -23
- package/skills/migrate-nextjs/SKILL.md +100 -45
- package/skills/migrate-nextjs/backend-host-swap.md +5 -2
- package/skills/migrate-react-router/SKILL.md +49 -38
- package/skills/migrate-react-router/component-migration.md +25 -6
- package/skills/migrate-react-router/data-and-actions.md +18 -5
- package/skills/migrate-react-router/route-mapping.md +12 -6
- package/skills/mime-routes/SKILL.md +12 -7
- package/skills/observability/SKILL.md +28 -14
- package/skills/parallel/SKILL.md +75 -39
- package/skills/ppr/SKILL.md +345 -150
- package/skills/prerender/SKILL.md +189 -100
- package/skills/rango/SKILL.md +65 -34
- package/skills/react-compiler/SKILL.md +6 -5
- package/skills/response-routes/SKILL.md +133 -25
- package/skills/route/SKILL.md +165 -60
- package/skills/router-setup/SKILL.md +418 -240
- package/skills/scripts/SKILL.md +31 -20
- package/skills/server-actions/SKILL.md +220 -59
- package/skills/shell-manifest/SKILL.md +12 -7
- package/skills/streams-and-websockets/SKILL.md +32 -14
- package/skills/tailwind/SKILL.md +37 -33
- package/skills/testing/SKILL.md +68 -32
- package/skills/testing/cache-prerender.md +55 -29
- 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 +8 -5
- 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/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 +106 -29
- package/skills/typesafety/SKILL.md +20 -9
- package/skills/typesafety/env-and-bindings.md +126 -47
- 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 +200 -82
- package/skills/vercel/SKILL.md +21 -11
- package/skills/view-transitions/SKILL.md +41 -20
- package/src/__augment-tests__/augmented.check.ts +26 -1
- 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 +1 -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 +1 -6
- package/src/cache/cache-runtime.ts +230 -61
- package/src/cache/cache-scope.ts +76 -12
- package/src/cache/document-cache.ts +15 -0
- package/src/cache/handle-capture.ts +90 -10
- package/src/cache/handle-snapshot.ts +132 -28
- package/src/cache/segment-codec.ts +26 -7
- package/src/cache/tag-invalidation.ts +4 -3
- package/src/cache/taint.ts +1 -1
- package/src/cache/types.ts +16 -3
- 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 +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +117 -30
- 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 +34 -25
- package/src/router/intercept-resolution.ts +125 -39
- package/src/router/loader-resolution.ts +56 -11
- package/src/router/match-api.ts +12 -17
- package/src/router/match-middleware/background-revalidation.ts +161 -96
- package/src/router/match-middleware/cache-lookup.ts +93 -10
- package/src/router/match-middleware/cache-store.ts +17 -90
- package/src/router/middleware-types.ts +3 -5
- package/src/router/middleware.ts +52 -9
- package/src/router/prerender-match.ts +53 -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 +44 -12
- package/src/router/segment-resolution/loader-cache.ts +205 -45
- package/src/router/segment-resolution/loader-mask.ts +12 -22
- package/src/router/segment-resolution/loader-snapshot.ts +6 -6
- package/src/router/segment-resolution/mask-nested.ts +7 -7
- package/src/router/segment-resolution/revalidation.ts +34 -6
- package/src/router/segment-wrappers.ts +4 -0
- package/src/rsc/render-pipeline.ts +3 -0
- package/src/rsc/response-route-handler.ts +11 -3
- package/src/rsc/rsc-rendering.ts +8 -45
- package/src/rsc/shell-capture-constants.ts +13 -0
- package/src/rsc/shell-capture.ts +111 -90
- package/src/rsc/ssr-setup.ts +14 -8
- package/src/rsc/types.ts +11 -0
- package/src/server/context.ts +88 -11
- package/src/server/cookie-store.ts +5 -4
- package/src/server/handle-store.ts +232 -7
- package/src/server/request-context.ts +35 -30
- package/src/ssr/index.tsx +40 -7
- package/src/static-handler.ts +4 -1
- package/src/testing/flight-runtime.d.ts +21 -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 +5 -1
- package/src/testing/vitest-stubs/plugin-rsc.ts +123 -20
- package/src/testing/vitest.ts +62 -1
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/types/cache-types.ts +14 -10
- package/src/types/handler-context.ts +14 -0
- package/src/types/loader-types.ts +8 -6
- package/src/urls/response-types.ts +3 -23
- package/src/use-loader.tsx +12 -3
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
package/src/ssr/index.tsx
CHANGED
|
@@ -27,6 +27,7 @@ interface RenderToReadableStreamOptions {
|
|
|
27
27
|
nonce?: string;
|
|
28
28
|
formState?: unknown;
|
|
29
29
|
progressiveChunkSize?: number;
|
|
30
|
+
onError?: (error: unknown) => void;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -124,6 +125,13 @@ export interface SSRRenderOptions {
|
|
|
124
125
|
* prerender pass (host-agnostic bare captures).
|
|
125
126
|
*/
|
|
126
127
|
origin?: string;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Called for each error Fizz reports through its onError: a component that
|
|
131
|
+
* threw inside a Suspense boundary, which leaves the boundary errored in an
|
|
132
|
+
* otherwise completed document. React's default console.error is kept.
|
|
133
|
+
*/
|
|
134
|
+
onError?: (error: unknown) => void;
|
|
127
135
|
}
|
|
128
136
|
|
|
129
137
|
/**
|
|
@@ -407,6 +415,12 @@ interface ShellCaptureOptions {
|
|
|
407
415
|
* capture, resume, and browser hydration.
|
|
408
416
|
*/
|
|
409
417
|
origin?: string;
|
|
418
|
+
/**
|
|
419
|
+
* Called for each component error the prerender reports (an errored
|
|
420
|
+
* boundary in the prelude), never for the capture's own abort. The caller
|
|
421
|
+
* refuses to store the shell (issue #915).
|
|
422
|
+
*/
|
|
423
|
+
onError?: (error: unknown) => void;
|
|
410
424
|
}
|
|
411
425
|
|
|
412
426
|
/**
|
|
@@ -437,6 +451,8 @@ interface ShellResumeOptions {
|
|
|
437
451
|
search?: string;
|
|
438
452
|
/** The HIT request's origin — same host as the capture's (key-scoped). */
|
|
439
453
|
origin?: string;
|
|
454
|
+
/** Called for each component error the resumed holes report. */
|
|
455
|
+
onError?: (error: unknown) => void;
|
|
440
456
|
}
|
|
441
457
|
|
|
442
458
|
/**
|
|
@@ -601,7 +617,14 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
601
617
|
rscStream: ReadableStream<Uint8Array>,
|
|
602
618
|
options?: SSRRenderOptions,
|
|
603
619
|
): Promise<ReadableStream<Uint8Array>> {
|
|
604
|
-
const {
|
|
620
|
+
const {
|
|
621
|
+
nonce,
|
|
622
|
+
formState,
|
|
623
|
+
streamMode,
|
|
624
|
+
search,
|
|
625
|
+
origin,
|
|
626
|
+
onError: onRenderError,
|
|
627
|
+
} = options ?? {};
|
|
605
628
|
|
|
606
629
|
try {
|
|
607
630
|
// Tee the stream:
|
|
@@ -659,6 +682,13 @@ export function createSSRHandler<TEnv = unknown>(deps: SSRDependencies<TEnv>) {
|
|
|
659
682
|
formState,
|
|
660
683
|
nonce,
|
|
661
684
|
...(progressiveChunkSize !== undefined && { progressiveChunkSize }),
|
|
685
|
+
...(onRenderError && {
|
|
686
|
+
onError: (error: unknown) => {
|
|
687
|
+
onRenderError(error);
|
|
688
|
+
// What React's default onError does (it only logs).
|
|
689
|
+
console.error(error);
|
|
690
|
+
},
|
|
691
|
+
}),
|
|
662
692
|
}),
|
|
663
693
|
);
|
|
664
694
|
|
|
@@ -797,6 +827,7 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
797
827
|
if (error === abortReason) {
|
|
798
828
|
return;
|
|
799
829
|
}
|
|
830
|
+
opts.onError?.(error);
|
|
800
831
|
reportRenderError(onError, error);
|
|
801
832
|
},
|
|
802
833
|
});
|
|
@@ -877,11 +908,10 @@ export function createShellCaptureHandler<TEnv = unknown>(
|
|
|
877
908
|
|
|
878
909
|
// Sanity gate: a prelude with no `<body` is the no-shell failure mode.
|
|
879
910
|
// 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
|
|
911
|
+
// later request re-captures. The dominant real-world cause is a masked
|
|
912
|
+
// live-loader read with no boundary above it (lane rule: see
|
|
913
|
+
// resolveLoaderData, loader-cache.ts), which root-postpones above
|
|
914
|
+
// <body>. Root-postponing layouts and hung handles degrade the same way. shell-capture.ts logs a once-per-key
|
|
885
915
|
// warning so the eternal-MISS shape is diagnosable.
|
|
886
916
|
if (!new TextDecoder().decode(prelude).includes("<body")) {
|
|
887
917
|
return null;
|
|
@@ -982,7 +1012,10 @@ export function createShellResumeHandler<TEnv = unknown>(
|
|
|
982
1012
|
// resumed stream and need the per-request nonce.
|
|
983
1013
|
const resumed = await runWithPreinitNonce(nonce, () =>
|
|
984
1014
|
resume(<SsrRoot />, JSON.parse(postponed), {
|
|
985
|
-
onError: (error) =>
|
|
1015
|
+
onError: (error) => {
|
|
1016
|
+
opts.onError?.(error);
|
|
1017
|
+
reportRenderError(onError, error);
|
|
1018
|
+
},
|
|
986
1019
|
nonce,
|
|
987
1020
|
}),
|
|
988
1021
|
);
|
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
|
+
}
|
|
@@ -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
|
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
type RequestContext,
|
|
39
39
|
} from "../server/request-context.js";
|
|
40
40
|
import { createReverseFunction } from "../router/handler-context.js";
|
|
41
|
+
import { NOCACHE_SYMBOL } from "../cache/taint.js";
|
|
41
42
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
42
43
|
import type { LoaderContext, LoaderDefinition } from "../types.js";
|
|
43
44
|
import type { ContextVar } from "../context-var.js";
|
|
@@ -268,7 +269,7 @@ function runWithLoaderContext<R>(
|
|
|
268
269
|
);
|
|
269
270
|
}) as TestLoaderContext["reverse"]);
|
|
270
271
|
|
|
271
|
-
const loaderCtx: TestLoaderContext = {
|
|
272
|
+
const loaderCtx: TestLoaderContext & { _routeName?: string } = {
|
|
272
273
|
params: opts.params ?? {},
|
|
273
274
|
routeParams: (opts.params ?? {}) as Record<string, string>,
|
|
274
275
|
request: reqCtx.request,
|
|
@@ -324,6 +325,7 @@ function runWithLoaderContext<R>(
|
|
|
324
325
|
body: opts.body,
|
|
325
326
|
formData: opts.formData,
|
|
326
327
|
reverse: reverse as TestLoaderContext["reverse"],
|
|
328
|
+
_routeName: reqCtx._routeName,
|
|
327
329
|
rendered:
|
|
328
330
|
opts.rendered !== undefined && opts.rendered !== false
|
|
329
331
|
? async () => {
|
|
@@ -344,6 +346,8 @@ function runWithLoaderContext<R>(
|
|
|
344
346
|
},
|
|
345
347
|
};
|
|
346
348
|
|
|
349
|
+
// Request-scoped for "use cache" keys, as production brands it (#940).
|
|
350
|
+
(loaderCtx as Record<symbol, unknown>)[NOCACHE_SYMBOL] = true;
|
|
347
351
|
return fn(loaderCtx);
|
|
348
352
|
});
|
|
349
353
|
}
|
|
@@ -1,24 +1,127 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
/// <reference path="../flight-runtime.d.ts" />
|
|
2
|
+
/**
|
|
3
|
+
* Stub for `@vitejs/plugin-rsc/rsc` and the split `/rsc/server`, `/rsc/client`
|
|
4
|
+
* entries, shipped so consumers do not have to write a per-file `vi.mock(...)`.
|
|
5
|
+
* The real entries top-level import Vite virtuals that do not resolve in plain
|
|
6
|
+
* node.
|
|
7
|
+
*
|
|
8
|
+
* Under the `react-server` condition (the Flight project) the "use cache"
|
|
9
|
+
* codec runs on real Flight: the vendored react-server-dom `server.edge` /
|
|
10
|
+
* `client.edge` builds that plugin-rsc's `react/rsc/server` and
|
|
11
|
+
* `react/rsc/client` wrap, with the same manifests and the same server-side
|
|
12
|
+
* reference loader (`@vitejs/plugin-rsc/core/rsc` `setRequireModule`), so
|
|
13
|
+
* keys, writes, hits, value round trips and error rows match a build. The
|
|
14
|
+
* wrappers are not reused: their manifests read `import.meta.env.DEV`, which
|
|
15
|
+
* is undefined in a module node loads directly (vitest externalizes
|
|
16
|
+
* node_modules).
|
|
17
|
+
*
|
|
18
|
+
* Without the condition React's server build refuses to load, so the codec
|
|
19
|
+
* functions throw and a "use cache" call runs uncached. Any other load failure
|
|
20
|
+
* (a moved vendored path, a missing peer) throws. The action entries stay
|
|
21
|
+
* inert either way: no primitive decodes an action request.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** React's server build throws this on load without the react-server condition. */
|
|
25
|
+
const MISSING_REACT_SERVER_CONDITION =
|
|
26
|
+
/"react-server" condition must be enabled/;
|
|
27
|
+
|
|
28
|
+
async function loadFlightRuntime() {
|
|
29
|
+
// The server build first, alone. `@vitejs/plugin-rsc/core/rsc` statically
|
|
30
|
+
// imports it, and importing that ESM module after the CJS build failed to
|
|
31
|
+
// load leaks an unhandled rejection from Node's module loader even when every
|
|
32
|
+
// import() is caught (Node 22 and 24). So core/rsc loads only once the server
|
|
33
|
+
// build has.
|
|
34
|
+
const server =
|
|
35
|
+
await import("@vitejs/plugin-rsc/vendor/react-server-dom/server.edge").catch(
|
|
36
|
+
(error: unknown) => {
|
|
37
|
+
if (
|
|
38
|
+
error instanceof Error &&
|
|
39
|
+
MISSING_REACT_SERVER_CONDITION.test(error.message)
|
|
40
|
+
) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
if (!server) return undefined;
|
|
47
|
+
const [client, core] = await Promise.all([
|
|
48
|
+
import("@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"),
|
|
49
|
+
import("@vitejs/plugin-rsc/core/rsc"),
|
|
50
|
+
]);
|
|
51
|
+
await import("../internal/flight-client-globals.js");
|
|
52
|
+
// Resolves the `$$decode-client:` / `$$preserve:` ids a cached value's client
|
|
53
|
+
// and server references decode to; any other id would load a server module.
|
|
54
|
+
core.setRequireModule({
|
|
55
|
+
load: (id) => {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`plugin-rsc stub: server reference module "${id}" is not loadable in a test`,
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
return { server, client, core };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const runtime = await loadFlightRuntime();
|
|
65
|
+
|
|
66
|
+
/** `<module id>#<export name>` -> manifest entry, as plugin-rsc builds it. */
|
|
67
|
+
function referenceManifest(prefix: string): Record<string, unknown> {
|
|
68
|
+
return new Proxy(
|
|
69
|
+
{},
|
|
70
|
+
{
|
|
71
|
+
get(_target, $$id) {
|
|
72
|
+
if (typeof $$id !== "string") return undefined;
|
|
73
|
+
const [id, name] = $$id.split("#");
|
|
74
|
+
return { id: prefix + id, name, chunks: [], async: true };
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const CLIENT_MANIFEST = referenceManifest("");
|
|
81
|
+
|
|
82
|
+
function unavailable(name: string): never {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`plugin-rsc stub: ${name} needs the react-server condition (the Flight test project)`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const renderToReadableStream = (
|
|
89
|
+
value: unknown,
|
|
90
|
+
options?: object,
|
|
91
|
+
): ReadableStream<Uint8Array> =>
|
|
92
|
+
runtime
|
|
93
|
+
? runtime.server.renderToReadableStream(value, CLIENT_MANIFEST, options)
|
|
94
|
+
: unavailable("renderToReadableStream");
|
|
95
|
+
export const createTemporaryReferenceSet = (): unknown =>
|
|
96
|
+
runtime ? runtime.server.createTemporaryReferenceSet() : {};
|
|
97
|
+
export const createFromReadableStream = (
|
|
98
|
+
stream: ReadableStream<Uint8Array>,
|
|
99
|
+
options?: object,
|
|
100
|
+
extraOptions?: { preserveServerReferences?: boolean },
|
|
101
|
+
): Promise<unknown> =>
|
|
102
|
+
runtime
|
|
103
|
+
? runtime.client.createFromReadableStream(stream, {
|
|
104
|
+
serverConsumerManifest: {
|
|
105
|
+
serverModuleMap: referenceManifest(
|
|
106
|
+
extraOptions?.preserveServerReferences
|
|
107
|
+
? "$$server:$$preserve:"
|
|
108
|
+
: "$$server:",
|
|
109
|
+
),
|
|
110
|
+
moduleMap: runtime.core.createServerDecodeClientManifest(),
|
|
111
|
+
},
|
|
112
|
+
...options,
|
|
113
|
+
})
|
|
114
|
+
: unavailable("createFromReadableStream");
|
|
115
|
+
export const encodeReply = (
|
|
116
|
+
value: unknown,
|
|
117
|
+
options?: object,
|
|
118
|
+
): Promise<string | FormData> =>
|
|
119
|
+
runtime
|
|
120
|
+
? runtime.client.encodeReply(value, options)
|
|
121
|
+
: unavailable("encodeReply");
|
|
122
|
+
export const createClientTemporaryReferenceSet = (): unknown =>
|
|
123
|
+
runtime ? runtime.client.createTemporaryReferenceSet() : {};
|
|
13
124
|
export const loadServerAction = (): undefined => undefined;
|
|
14
125
|
export const decodeReply = (): undefined => undefined;
|
|
15
126
|
export const decodeAction = (): undefined => undefined;
|
|
16
127
|
export const decodeFormState = (): undefined => undefined;
|
|
17
|
-
export const createTemporaryReferenceSet = (): Record<string, never> => ({});
|
|
18
|
-
export const encodeReply = (): never => {
|
|
19
|
-
throw new Error("plugin-rsc stub: encodeReply not available");
|
|
20
|
-
};
|
|
21
|
-
export const createClientTemporaryReferenceSet = (): Record<
|
|
22
|
-
string,
|
|
23
|
-
never
|
|
24
|
-
> => ({});
|
package/src/testing/vitest.ts
CHANGED
|
@@ -78,6 +78,10 @@
|
|
|
78
78
|
|
|
79
79
|
import { createRequire } from "node:module";
|
|
80
80
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
81
|
+
import {
|
|
82
|
+
isUseCacheCandidate,
|
|
83
|
+
transformUseCache,
|
|
84
|
+
} from "../vite/plugins/use-cache-transform.js";
|
|
81
85
|
|
|
82
86
|
/** A single Vite/Vitest resolve alias entry. Structurally a Vite `Alias`. */
|
|
83
87
|
export interface TestAlias {
|
|
@@ -262,6 +266,7 @@ interface FlightTransformPlugin {
|
|
|
262
266
|
* import { defineConfig } from "vitest/config";
|
|
263
267
|
* import {
|
|
264
268
|
* rangoUseClientTransform,
|
|
269
|
+
* rangoUseCacheTransform,
|
|
265
270
|
* rangoTestAliases,
|
|
266
271
|
* rangoInlineDeps,
|
|
267
272
|
* } from "@rangojs/router/testing/vitest";
|
|
@@ -271,7 +276,7 @@ interface FlightTransformPlugin {
|
|
|
271
276
|
* process.env.NODE_ENV = "production";
|
|
272
277
|
*
|
|
273
278
|
* export default defineConfig({
|
|
274
|
-
* plugins: [rangoUseClientTransform()],
|
|
279
|
+
* plugins: [rangoUseClientTransform(), rangoUseCacheTransform()],
|
|
275
280
|
* resolve: {
|
|
276
281
|
* conditions: ["react-server"],
|
|
277
282
|
* // Bare `@rangojs/router` -> its react-server build, so a handler/component
|
|
@@ -340,3 +345,59 @@ export function rangoUseClientTransform(): FlightTransformPlugin {
|
|
|
340
345
|
},
|
|
341
346
|
};
|
|
342
347
|
}
|
|
348
|
+
|
|
349
|
+
/** The Vite plugin shape {@link rangoUseCacheTransform} returns. */
|
|
350
|
+
interface UseCacheTransformPlugin {
|
|
351
|
+
name: string;
|
|
352
|
+
enforce: "post";
|
|
353
|
+
configResolved(config: { root: string }): void;
|
|
354
|
+
transform(
|
|
355
|
+
this: { warn(message: string): void },
|
|
356
|
+
code: string,
|
|
357
|
+
id: string,
|
|
358
|
+
): Promise<{ code: string; map: unknown } | undefined>;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* A Vite plugin that applies the rango plugin's `"use cache"` transform in a
|
|
363
|
+
* Vitest project, so a function written with the directive is wrapped with
|
|
364
|
+
* `registerCachedFunction` as in dev and a build. `rango()` runs that transform
|
|
365
|
+
* only in its `rsc` environment and Vitest transforms in `ssr`, so without this
|
|
366
|
+
* plugin the directive is an inert string and the function runs on every call.
|
|
367
|
+
*
|
|
368
|
+
* Add it to the react-server project next to {@link rangoUseClientTransform}
|
|
369
|
+
* (see that function's config). There, with a seeded `cacheStore`,
|
|
370
|
+
* `renderHandler` / `runLoader` observe real hits. In the node project the
|
|
371
|
+
* wrap applies too, but the stubbed Flight serializer cannot write an entry.
|
|
372
|
+
*
|
|
373
|
+
* Ids are the ones `vite dev` emits: `<path relative to the Vitest root>#<name>`
|
|
374
|
+
* (a function-level directive gives `src/data.ts#$$hoist_0_getData`), not the
|
|
375
|
+
* build's hashed path, so store keys stay readable. `configResolved` sets that
|
|
376
|
+
* root. If `@vitejs/plugin-rsc/transforms` fails to load, the transform throws
|
|
377
|
+
* instead of leaving the module unwrapped (which the rango plugin does).
|
|
378
|
+
*/
|
|
379
|
+
export function rangoUseCacheTransform(): UseCacheTransformPlugin {
|
|
380
|
+
let root = process.cwd();
|
|
381
|
+
return {
|
|
382
|
+
name: "rango:testing-use-cache",
|
|
383
|
+
enforce: "post",
|
|
384
|
+
configResolved(config) {
|
|
385
|
+
root = config.root;
|
|
386
|
+
},
|
|
387
|
+
async transform(code, id) {
|
|
388
|
+
if (!isUseCacheCandidate(code, id)) return undefined;
|
|
389
|
+
return transformUseCache(code, id, {
|
|
390
|
+
root,
|
|
391
|
+
isBuild: false,
|
|
392
|
+
warn: (message) => this.warn(message),
|
|
393
|
+
onTransformsError: (error) => {
|
|
394
|
+
throw new Error(
|
|
395
|
+
`rangoUseCacheTransform: @vitejs/plugin-rsc/transforms failed to ` +
|
|
396
|
+
`load, so "use cache" functions in ${id} would run unwrapped.`,
|
|
397
|
+
{ cause: error },
|
|
398
|
+
);
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|