@rangojs/router 0.15.1 → 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 +52 -0
- 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 +36 -10
- package/dist/types/route-definition/resolve-handler-use.d.ts +23 -1
- package/dist/types/route-types.d.ts +3 -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/thenable-status.d.ts +9 -0
- 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 +884 -768
- package/package.json +4 -4
- 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 +43 -1
- 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 +75 -80
- 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 +106 -0
- package/src/prerender/build-shell-capture.ts +8 -11
- package/src/reverse.ts +44 -4
- package/src/route-definition/dsl-helpers.ts +134 -42
- package/src/route-definition/helpers-types.ts +36 -10
- package/src/route-definition/resolve-handler-use.ts +121 -6
- package/src/route-types.ts +2 -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/ssr-suspension-warning.ts +2 -13
- 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/ThemeProvider.tsx +14 -12
- package/src/theme/ThemeScript.tsx +3 -3
- package/src/thenable-status.ts +16 -0
- 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 +24 -4
- package/src/vite/plugins/use-cache-transform.ts +118 -74
- package/src/vite/rango.ts +7 -3
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
|
+
}
|
|
@@ -114,8 +114,6 @@ export function ThemeProvider({
|
|
|
114
114
|
initialTheme,
|
|
115
115
|
children,
|
|
116
116
|
}: ThemeProviderProps): React.ReactNode {
|
|
117
|
-
const [mounted, setMounted] = useState(false);
|
|
118
|
-
|
|
119
117
|
// HYDRATION PARITY: this initializer is the server (SSR/resume) render AND
|
|
120
118
|
// the client's hydration render — both must produce the same value. It must
|
|
121
119
|
// NEVER read cookie/localStorage: whenever the payload's initialTheme
|
|
@@ -132,7 +130,6 @@ export function ThemeProvider({
|
|
|
132
130
|
const [systemTheme, setSystemTheme] = useState<ResolvedTheme>("light");
|
|
133
131
|
|
|
134
132
|
useEffect(() => {
|
|
135
|
-
setMounted(true);
|
|
136
133
|
setSystemTheme(getSystemTheme());
|
|
137
134
|
// Re-sync state from an EXPLICITLY stored theme after mount. initialTheme
|
|
138
135
|
// comes from the payload and can legitimately differ from the visitor's
|
|
@@ -229,18 +226,16 @@ export function ThemeProvider({
|
|
|
229
226
|
};
|
|
230
227
|
}, [config]);
|
|
231
228
|
|
|
229
|
+
// No pre-mount special case: `theme` and `systemTheme` ("light" until the
|
|
230
|
+
// mount effect) are identical on the server and in the hydration render, so
|
|
231
|
+
// this is hydration-safe, and a concrete defaultTheme without an initialTheme
|
|
232
|
+
// no longer flips at mount.
|
|
232
233
|
const resolvedTheme: ResolvedTheme = useMemo(() => {
|
|
233
|
-
if (!mounted) {
|
|
234
|
-
if (initialTheme && initialTheme !== "system") {
|
|
235
|
-
return initialTheme as ResolvedTheme;
|
|
236
|
-
}
|
|
237
|
-
return "light";
|
|
238
|
-
}
|
|
239
234
|
if (theme === "system" && config.enableSystem) {
|
|
240
235
|
return systemTheme;
|
|
241
236
|
}
|
|
242
237
|
return theme as ResolvedTheme;
|
|
243
|
-
}, [theme, systemTheme, config.enableSystem
|
|
238
|
+
}, [theme, systemTheme, config.enableSystem]);
|
|
244
239
|
|
|
245
240
|
const themes = useMemo(() => {
|
|
246
241
|
if (config.enableSystem) {
|
|
@@ -249,16 +244,23 @@ export function ThemeProvider({
|
|
|
249
244
|
return config.themes;
|
|
250
245
|
}, [config.themes, config.enableSystem]);
|
|
251
246
|
|
|
247
|
+
// Keyed on the exposed field values only: a NEW context object with
|
|
248
|
+
// unchanged fields propagates into every dehydrated Suspense boundary still
|
|
249
|
+
// streaming in (React cannot see their consumers) and makes React
|
|
250
|
+
// client-render those boundaries instead of adopting the server HTML their
|
|
251
|
+
// $RC script delivers. A field that genuinely changes at mount (dark system
|
|
252
|
+
// theme, stored override) must still publish; that case stays open
|
|
253
|
+
// (CHANGELOG).
|
|
252
254
|
const contextValue: ThemeContextValue = useMemo(
|
|
253
255
|
() => ({
|
|
254
256
|
theme,
|
|
255
257
|
setTheme,
|
|
256
258
|
resolvedTheme,
|
|
257
|
-
systemTheme
|
|
259
|
+
systemTheme,
|
|
258
260
|
themes,
|
|
259
261
|
config,
|
|
260
262
|
}),
|
|
261
|
-
[theme, setTheme, resolvedTheme, systemTheme, themes, config
|
|
263
|
+
[theme, setTheme, resolvedTheme, systemTheme, themes, config],
|
|
262
264
|
);
|
|
263
265
|
|
|
264
266
|
return (
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Must be placed in the <head> element of your document, before any stylesheets.
|
|
10
10
|
*
|
|
11
|
-
* Note: when theme is enabled in the router config, `<
|
|
11
|
+
* Note: when theme is enabled in the router config, `<Html.Meta />` ALREADY
|
|
12
12
|
* renders this FOUC script. Use `<ThemeScript />` only if you do NOT render
|
|
13
|
-
* `<
|
|
13
|
+
* `<Html.Meta />`. Rendering both is safe — the inline script guards the
|
|
14
14
|
* matchMedia listener registration against double-running — but it is redundant.
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
17
17
|
* ```tsx
|
|
18
|
-
* // In your document component. Use ThemeScript only when you do not render
|
|
18
|
+
* // In your document component. Use ThemeScript only when you do not render Html.Meta.
|
|
19
19
|
* import { ThemeScript } from "@rangojs/router/theme";
|
|
20
20
|
*
|
|
21
21
|
* export function Document({ children }) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thenable statuses React's `use()` unwraps without suspending: React's
|
|
3
|
+
* re-check after `.then()` handles "fulfilled", and Flight chunks in the
|
|
4
|
+
* resolved_* states initialize synchronously inside that same call. Anything
|
|
5
|
+
* else (pending, blocked, an unstamped native promise) suspends at least once.
|
|
6
|
+
* Shared by the dev SSR-suspension diagnostic and the held-navigation
|
|
7
|
+
* isLoading registration (loader-store.ts), which must agree on "settled".
|
|
8
|
+
*/
|
|
9
|
+
export function unwrapsSynchronously(stream: Promise<unknown>): boolean {
|
|
10
|
+
const status = (stream as { status?: string }).status;
|
|
11
|
+
return (
|
|
12
|
+
status === "fulfilled" ||
|
|
13
|
+
status === "resolved_model" ||
|
|
14
|
+
status === "resolved_module"
|
|
15
|
+
);
|
|
16
|
+
}
|
package/src/types/cache-types.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Context passed to cache condition/key/tags functions
|
|
2
|
+
* Context passed to cache condition/key/tags functions (the full
|
|
3
|
+
* RequestContext).
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Ordering: global middleware wraps the whole request, and on RSC routes route
|
|
6
|
+
* middleware wraps the match/render pipeline (rsc/handler.ts executeRender), so
|
|
7
|
+
* variables they set before `next()` are readable via `ctx.get()`. On response
|
|
8
|
+
* routes the cache lookup runs BEFORE route middleware, which wraps only the
|
|
9
|
+
* handler run on a miss (rsc/response-route-handler.ts); only global-middleware
|
|
10
|
+
* variables are populated there.
|
|
10
11
|
*/
|
|
11
12
|
export type { RequestContext as CacheContext } from "../server/request-context.js";
|
|
12
13
|
|
|
@@ -109,7 +110,8 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
109
110
|
* Return false to skip cache for this request (always fetch fresh).
|
|
110
111
|
*
|
|
111
112
|
* Has access to full RequestContext including env, request, params, cookies, etc.
|
|
112
|
-
*
|
|
113
|
+
* Runs after global and route middleware on RSC routes; on response routes,
|
|
114
|
+
* before route middleware (see CacheContext).
|
|
113
115
|
*
|
|
114
116
|
* @example
|
|
115
117
|
* ```typescript
|
|
@@ -131,7 +133,8 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
131
133
|
* Bypasses default key generation AND store's keyGenerator.
|
|
132
134
|
*
|
|
133
135
|
* Has access to full RequestContext including env, request, params, cookies, etc.
|
|
134
|
-
*
|
|
136
|
+
* Runs after global and route middleware on RSC routes; on response routes,
|
|
137
|
+
* before route middleware (see CacheContext).
|
|
135
138
|
*
|
|
136
139
|
* @example
|
|
137
140
|
* ```typescript
|
|
@@ -153,7 +156,8 @@ export interface CacheOptions<TEnv = unknown> {
|
|
|
153
156
|
* Tags for cache invalidation.
|
|
154
157
|
* Can be a static array or a function that returns tags.
|
|
155
158
|
*
|
|
156
|
-
* The built-in `MemorySegmentCacheStore
|
|
159
|
+
* The built-in `MemorySegmentCacheStore`, `CFCacheStore`, and
|
|
160
|
+
* `VercelCacheStore` index by tag.
|
|
157
161
|
* Invalidate on demand with `updateTag(...tags)` (awaitable, read-your-own-writes;
|
|
158
162
|
* for server actions) or `revalidateTag(...tags)` (background hard-purge, not
|
|
159
163
|
* awaited; for route handlers / webhooks). For `CFCacheStore`, distributed
|
|
@@ -455,6 +455,20 @@ export type InternalHandlerContext<
|
|
|
455
455
|
* table (so a loader cannot await its own in-flight memoized promise).
|
|
456
456
|
*/
|
|
457
457
|
_loaderCacheOriginalUse?: (item: any) => any;
|
|
458
|
+
/**
|
|
459
|
+
* @internal A loader-cache HIT asks before replaying a loader's recorded
|
|
460
|
+
* pushes: true when that loader has not run in this request and no other
|
|
461
|
+
* replay delivered it; its later run then replaces the replayed values
|
|
462
|
+
* (HandleStore.pushReplayed). One copy per loader per request. See
|
|
463
|
+
* setupLoaderAccess.
|
|
464
|
+
*/
|
|
465
|
+
_claimLoaderPushes?: (loaderId: string) => boolean;
|
|
466
|
+
/**
|
|
467
|
+
* @internal Run a loader on a fresh executor (own memo map): a loader-cache
|
|
468
|
+
* stale refresh, whose diverted pushes must not take a dependency's run
|
|
469
|
+
* from the page. See setupLoaderAccess.
|
|
470
|
+
*/
|
|
471
|
+
_runLoaderIsolated?: (loader: any) => Promise<any>;
|
|
458
472
|
};
|
|
459
473
|
|
|
460
474
|
/**
|
|
@@ -28,9 +28,10 @@ import type { RequestScope } from "./request-scope.js";
|
|
|
28
28
|
* return await db.cart.get(user.id);
|
|
29
29
|
* });
|
|
30
30
|
*
|
|
31
|
-
* //
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* // createLoader's one type parameter is the data type; params are
|
|
32
|
+
* // Record<string, string | undefined>:
|
|
33
|
+
* const ProductLoader = createLoader<Product>(async (ctx) => {
|
|
34
|
+
* const { slug } = ctx.params; // string | undefined
|
|
34
35
|
* return await db.products.findBySlug(slug);
|
|
35
36
|
* });
|
|
36
37
|
* ```
|
|
@@ -243,9 +244,10 @@ export type LoadOptions =
|
|
|
243
244
|
* return await db.cart.get(ctx.get("user").id);
|
|
244
245
|
* });
|
|
245
246
|
*
|
|
246
|
-
* //
|
|
247
|
-
*
|
|
248
|
-
*
|
|
247
|
+
* // createLoader's one type parameter is the data type; params are
|
|
248
|
+
* // Record<string, string | undefined>:
|
|
249
|
+
* export const ProductLoader = createLoader<Product>(async (ctx) => {
|
|
250
|
+
* const { slug } = ctx.params; // string | undefined
|
|
249
251
|
* return await db.products.findBySlug(slug);
|
|
250
252
|
* });
|
|
251
253
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ContextVar } from "../context-var.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { GlobalReverseFunction } from "../reverse.js";
|
|
3
3
|
import type {
|
|
4
4
|
DefaultReverseRouteMap,
|
|
5
5
|
DefaultVars,
|
|
@@ -7,27 +7,6 @@ import type {
|
|
|
7
7
|
import type { UseItems, ResponseRouteUseItem } from "../route-types.js";
|
|
8
8
|
import type { RequestScope } from "../types/request-scope.js";
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Reverse function for response handler contexts.
|
|
12
|
-
* Global names get autocomplete and param validation from the generated route map.
|
|
13
|
-
* Local `.name` calls are accepted but not validated (scope unknown at type level).
|
|
14
|
-
*/
|
|
15
|
-
type ResponseReverseFunction = [DefaultReverseRouteMap] extends [
|
|
16
|
-
Record<string, string>,
|
|
17
|
-
]
|
|
18
|
-
? (
|
|
19
|
-
name: string,
|
|
20
|
-
params?: Record<string, string>,
|
|
21
|
-
search?: Record<string, unknown>,
|
|
22
|
-
) => string
|
|
23
|
-
: ReverseFunction<DefaultReverseRouteMap> & {
|
|
24
|
-
(
|
|
25
|
-
name: `.${string}`,
|
|
26
|
-
params?: Record<string, string>,
|
|
27
|
-
search?: Record<string, unknown>,
|
|
28
|
-
): string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
10
|
/**
|
|
32
11
|
* Symbol marking a route as a response route (non-RSC).
|
|
33
12
|
* Stored on PathOptions and UrlPatterns to signal the trie to short-circuit.
|
|
@@ -99,7 +78,8 @@ export interface ResponseHandlerContext<
|
|
|
99
78
|
params: TParams;
|
|
100
79
|
/** @internal Phantom property for params type invariance. Prevents mounting handlers on wrong routes. */
|
|
101
80
|
readonly _paramCheck?: (params: TParams) => TParams;
|
|
102
|
-
|
|
81
|
+
/** Global route names only; see {@link GlobalReverseFunction}. */
|
|
82
|
+
reverse: GlobalReverseFunction<DefaultReverseRouteMap>;
|
|
103
83
|
/** Read a variable set by middleware via ctx.set(key, value) or ctx.set(ContextVar, value). */
|
|
104
84
|
get: {
|
|
105
85
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
package/src/use-loader.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useContext,
|
|
9
9
|
useEffect,
|
|
10
10
|
useMemo,
|
|
11
|
+
useOptimistic,
|
|
11
12
|
useRef,
|
|
12
13
|
useState,
|
|
13
14
|
type ReactNode,
|
|
@@ -116,6 +117,14 @@ export type LoadFunction<T> = (options?: LoadOptions) => Promise<T>;
|
|
|
116
117
|
|
|
117
118
|
export interface UseLoaderResult<T> {
|
|
118
119
|
data: T;
|
|
120
|
+
/**
|
|
121
|
+
* True while this loader family (`loader.$$id`) is in flight: a `load()` /
|
|
122
|
+
* group refresh, or a held navigation still streaming a re-run of this
|
|
123
|
+
* loader. Family-keyed, not segment-keyed: a layout `useLoader` of the same
|
|
124
|
+
* `createLoader` the child route is re-running also reports true, even if
|
|
125
|
+
* the layout copy is not replaced. Not used for the initial streamed read
|
|
126
|
+
* (that suspends).
|
|
127
|
+
*/
|
|
119
128
|
isLoading: boolean;
|
|
120
129
|
error: Error | null;
|
|
121
130
|
load: LoadFunction<T>;
|
|
@@ -124,6 +133,7 @@ export interface UseLoaderResult<T> {
|
|
|
124
133
|
|
|
125
134
|
export interface UseFetchLoaderResult<T> {
|
|
126
135
|
data: T | undefined;
|
|
136
|
+
/** Same family-keyed contract as {@link UseLoaderResult.isLoading}. */
|
|
127
137
|
isLoading: boolean;
|
|
128
138
|
error: Error | null;
|
|
129
139
|
load: LoadFunction<T>;
|
|
@@ -261,6 +271,13 @@ function useLoaderInternal<T>(
|
|
|
261
271
|
}
|
|
262
272
|
}, [loaderId, bucketKey]);
|
|
263
273
|
|
|
274
|
+
// Held-navigation pin: a transition commit whose stream for this loader is
|
|
275
|
+
// still pending announces it (loader-store.ts announcePendingStreams, which
|
|
276
|
+
// documents the lane discipline); this optimistic update renders urgently
|
|
277
|
+
// on the held content and React reverts it when that transition commits,
|
|
278
|
+
// i.e. together with the new data.
|
|
279
|
+
const [streamPending, setStreamPending] = useOptimistic(false);
|
|
280
|
+
|
|
264
281
|
const [sharedState, setSharedState] = useState<{
|
|
265
282
|
bucketKey: string;
|
|
266
283
|
snapshot: LoaderEntry;
|
|
@@ -295,6 +312,9 @@ function useLoaderInternal<T>(
|
|
|
295
312
|
ephemeral: !hasContextData,
|
|
296
313
|
group: hasGroups ? groupList : undefined,
|
|
297
314
|
refetch: hasGroups ? groupRefetch : undefined,
|
|
315
|
+
onStreamPending: hasContextData
|
|
316
|
+
? () => startTransition(() => setStreamPending(true))
|
|
317
|
+
: undefined,
|
|
298
318
|
},
|
|
299
319
|
);
|
|
300
320
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentional:
|
|
@@ -362,7 +382,7 @@ function useLoaderInternal<T>(
|
|
|
362
382
|
: sharedSnapshot.hasValue
|
|
363
383
|
? (sharedSnapshot.value as T | undefined)
|
|
364
384
|
: contextData;
|
|
365
|
-
const isLoading = localIsLoading || sharedSnapshot.isLoading;
|
|
385
|
+
const isLoading = localIsLoading || sharedSnapshot.isLoading || streamPending;
|
|
366
386
|
const error = localError ?? sharedSnapshot.error;
|
|
367
387
|
|
|
368
388
|
const throwOnError = options?.throwOnError ?? true;
|
|
@@ -549,7 +569,7 @@ function useLoaderInternal<T>(
|
|
|
549
569
|
* @example Basic usage - accessing route loader data
|
|
550
570
|
* ```tsx
|
|
551
571
|
* "use client";
|
|
552
|
-
* import { useLoader } from "
|
|
572
|
+
* import { useLoader } from "@rangojs/router/client";
|
|
553
573
|
* import { CartLoader } from "../loaders/cart";
|
|
554
574
|
*
|
|
555
575
|
* // In route definition: loader(CartLoader)
|
|
@@ -597,7 +617,7 @@ export function useLoader<T>(
|
|
|
597
617
|
* @example On-demand fetching
|
|
598
618
|
* ```tsx
|
|
599
619
|
* "use client";
|
|
600
|
-
* import { useFetchLoader } from "
|
|
620
|
+
* import { useFetchLoader } from "@rangojs/router/client";
|
|
601
621
|
* import { SearchLoader } from "../loaders/search";
|
|
602
622
|
*
|
|
603
623
|
* export function SearchResults() {
|
|
@@ -659,7 +679,7 @@ export function useFetchLoader<T>(
|
|
|
659
679
|
* @example
|
|
660
680
|
* ```tsx
|
|
661
681
|
* "use client";
|
|
662
|
-
* import { useLoader, useRefreshLoaders } from "
|
|
682
|
+
* import { useLoader, useRefreshLoaders } from "@rangojs/router/client";
|
|
663
683
|
*
|
|
664
684
|
* function Profile() {
|
|
665
685
|
* const { data } = useLoader(ProfileLoader, { key: userId, refreshGroup: "account" });
|
|
@@ -16,14 +16,18 @@
|
|
|
16
16
|
* - transformHoistInlineDirective() for function-level hoisting
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import type { Plugin } from "vite";
|
|
19
|
+
import type { Plugin, Rollup } from "vite";
|
|
20
20
|
import type { ModuleExportMeta } from "@vitejs/plugin-rsc/transforms";
|
|
21
21
|
import path from "node:path";
|
|
22
|
-
import MagicString from "magic-string";
|
|
23
22
|
import { normalizePath, hashId } from "./expose-id-utils.js";
|
|
24
|
-
import {
|
|
23
|
+
import type { Counter } from "../debug.js";
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
// No static import of `magic-string` or `../debug.js` (the `debug` package):
|
|
26
|
+
// `rangoUseCacheTransform()` bundles this module into `dist/testing/vitest.js`,
|
|
27
|
+
// whose static imports are node builtins only, and every package it needs
|
|
28
|
+
// loads lazily, as `rangoUseClientTransform()`'s `vite` and plugin-rsc
|
|
29
|
+
// imports do. src/__tests__/installed-vitest-config.test.ts loads that file
|
|
30
|
+
// from a fixture holding only `package.json` and `dist/`.
|
|
27
31
|
|
|
28
32
|
const CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
|
|
29
33
|
|
|
@@ -33,12 +37,13 @@ const LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
|
|
|
33
37
|
|
|
34
38
|
export const USE_CACHE_DIRECTIVE_RE: RegExp = /^use cache(:\s*[\w-]+)?$/;
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
/**
|
|
41
|
+
* @param counter `rango:transform` timing counter, created by `rango()`
|
|
42
|
+
* (`createCounter` in ../debug.js); omitted when debugging is off.
|
|
43
|
+
*/
|
|
44
|
+
export function useCacheTransform(counter?: Counter): Plugin {
|
|
37
45
|
let projectRoot = "";
|
|
38
46
|
let isBuild = false;
|
|
39
|
-
let rscTransforms: typeof import("@vitejs/plugin-rsc/transforms") | null =
|
|
40
|
-
null;
|
|
41
|
-
const counter = createCounter(debug, "use-cache");
|
|
42
47
|
|
|
43
48
|
return {
|
|
44
49
|
name: "@rangojs/router:use-cache",
|
|
@@ -56,75 +61,15 @@ export function useCacheTransform(): Plugin {
|
|
|
56
61
|
async transform(code, id) {
|
|
57
62
|
// Only process in RSC environment
|
|
58
63
|
if (this.environment?.name !== "rsc") return;
|
|
59
|
-
|
|
60
|
-
// Quick bail: no "use cache" in source
|
|
61
|
-
if (!code.includes("use cache")) return;
|
|
62
|
-
|
|
63
|
-
// Skip node_modules and virtual modules
|
|
64
|
-
if (id.includes("/node_modules/") || id.startsWith("\0")) return;
|
|
65
|
-
|
|
66
|
-
// Only JS/TS files
|
|
67
|
-
if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
|
|
64
|
+
if (!isUseCacheCandidate(code, id)) return;
|
|
68
65
|
|
|
69
66
|
const start = counter ? performance.now() : 0;
|
|
70
67
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
rscTransforms = await import("@vitejs/plugin-rsc/transforms");
|
|
74
|
-
} catch {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const {
|
|
80
|
-
hasDirective,
|
|
81
|
-
transformWrapExport,
|
|
82
|
-
transformHoistInlineDirective,
|
|
83
|
-
} = rscTransforms;
|
|
84
|
-
|
|
85
|
-
let ast: any;
|
|
86
|
-
try {
|
|
87
|
-
const { parseAst } = await import("vite");
|
|
88
|
-
ast = parseAst(code, { lang: "tsx" });
|
|
89
|
-
} catch {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// plugin-rsc 0.5.34 `matchDirective` does `stmt.directive.match(...)`
|
|
94
|
-
// after `"directive" in node`. Vite/oxc parseAst now emits
|
|
95
|
-
// `directive: null` on ordinary ExpressionStatements, so a file that
|
|
96
|
-
// mixes a `"use cache"` function with a sibling handler whose first
|
|
97
|
-
// statement is an expression throws and the wrap is dropped.
|
|
98
|
-
stripNullDirectiveFields(ast);
|
|
99
|
-
|
|
100
|
-
const filePath = normalizePath(path.relative(projectRoot, id));
|
|
101
|
-
const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
|
|
102
|
-
|
|
103
|
-
if (hasDirective(ast.body, "use cache")) {
|
|
104
|
-
return transformFileLevelUseCache(
|
|
105
|
-
code,
|
|
106
|
-
ast,
|
|
107
|
-
filePath,
|
|
108
|
-
id,
|
|
109
|
-
isBuild,
|
|
110
|
-
isLayoutOrTemplate,
|
|
111
|
-
transformWrapExport,
|
|
112
|
-
hasDirective,
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const functionResult = transformFunctionLevelUseCache(
|
|
117
|
-
code,
|
|
118
|
-
ast,
|
|
119
|
-
filePath,
|
|
120
|
-
id,
|
|
68
|
+
return await transformUseCache(code, id, {
|
|
69
|
+
root: projectRoot,
|
|
121
70
|
isBuild,
|
|
122
|
-
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
warnOnNearMissDirectives(ast, id, this.warn.bind(this));
|
|
126
|
-
|
|
127
|
-
if (functionResult) return functionResult;
|
|
71
|
+
warn: (message) => this.warn(message),
|
|
72
|
+
});
|
|
128
73
|
} finally {
|
|
129
74
|
counter?.record(id, performance.now() - start);
|
|
130
75
|
}
|
|
@@ -132,7 +77,105 @@ export function useCacheTransform(): Plugin {
|
|
|
132
77
|
};
|
|
133
78
|
}
|
|
134
79
|
|
|
135
|
-
|
|
80
|
+
/** Options for {@link transformUseCache}. */
|
|
81
|
+
export interface UseCacheTransformOptions {
|
|
82
|
+
/** Root the ids are relative to (Vite's `config.root`). */
|
|
83
|
+
root: string;
|
|
84
|
+
/** Build ids hash the path (`hashId`); dev ids are `<path>#<export>`. */
|
|
85
|
+
isBuild: boolean;
|
|
86
|
+
/** Sink for the near-miss directive warning. */
|
|
87
|
+
warn: (message: string) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Called when `@vitejs/plugin-rsc/transforms` fails to load; the module is
|
|
90
|
+
* then left unwrapped. The rango plugin omits it (skip silently).
|
|
91
|
+
*/
|
|
92
|
+
onTransformsError?: (error: unknown) => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Whether a module may carry a `"use cache"` directive the transform wraps. */
|
|
96
|
+
export function isUseCacheCandidate(code: string, id: string): boolean {
|
|
97
|
+
return (
|
|
98
|
+
// Quick bail: no "use cache" in source
|
|
99
|
+
code.includes("use cache") &&
|
|
100
|
+
// Skip node_modules and virtual modules
|
|
101
|
+
!id.includes("/node_modules/") &&
|
|
102
|
+
!id.startsWith("\0") &&
|
|
103
|
+
// Only JS/TS files
|
|
104
|
+
/\.(tsx?|jsx?|mjs)$/.test(id)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let rscTransforms: typeof import("@vitejs/plugin-rsc/transforms") | null = null;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Wrap a module's `"use cache"` functions with `registerCachedFunction`. The
|
|
112
|
+
* rango plugin runs it in the rsc environment; `rangoUseCacheTransform()`
|
|
113
|
+
* (testing/vitest.ts) runs it in a Vitest project, whose environment is `ssr`.
|
|
114
|
+
* Call it only for an {@link isUseCacheCandidate} module.
|
|
115
|
+
*/
|
|
116
|
+
export async function transformUseCache(
|
|
117
|
+
code: string,
|
|
118
|
+
id: string,
|
|
119
|
+
options: UseCacheTransformOptions,
|
|
120
|
+
): Promise<{ code: string; map: Rollup.ExistingRawSourceMap } | undefined> {
|
|
121
|
+
if (!rscTransforms) {
|
|
122
|
+
try {
|
|
123
|
+
rscTransforms = await import("@vitejs/plugin-rsc/transforms");
|
|
124
|
+
} catch (error) {
|
|
125
|
+
options.onTransformsError?.(error);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { hasDirective, transformWrapExport, transformHoistInlineDirective } =
|
|
131
|
+
rscTransforms;
|
|
132
|
+
|
|
133
|
+
let ast: any;
|
|
134
|
+
try {
|
|
135
|
+
const { parseAst } = await import("vite");
|
|
136
|
+
ast = parseAst(code, { lang: "tsx" });
|
|
137
|
+
} catch {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// plugin-rsc 0.5.34 `matchDirective` does `stmt.directive.match(...)`
|
|
142
|
+
// after `"directive" in node`. Vite/oxc parseAst now emits
|
|
143
|
+
// `directive: null` on ordinary ExpressionStatements, so a file that
|
|
144
|
+
// mixes a `"use cache"` function with a sibling handler whose first
|
|
145
|
+
// statement is an expression throws and the wrap is dropped.
|
|
146
|
+
stripNullDirectiveFields(ast);
|
|
147
|
+
|
|
148
|
+
const filePath = normalizePath(path.relative(options.root, id));
|
|
149
|
+
const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
|
|
150
|
+
|
|
151
|
+
if (hasDirective(ast.body, "use cache")) {
|
|
152
|
+
return transformFileLevelUseCache(
|
|
153
|
+
code,
|
|
154
|
+
ast,
|
|
155
|
+
filePath,
|
|
156
|
+
id,
|
|
157
|
+
options.isBuild,
|
|
158
|
+
isLayoutOrTemplate,
|
|
159
|
+
transformWrapExport,
|
|
160
|
+
hasDirective,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const functionResult = transformFunctionLevelUseCache(
|
|
165
|
+
code,
|
|
166
|
+
ast,
|
|
167
|
+
filePath,
|
|
168
|
+
id,
|
|
169
|
+
options.isBuild,
|
|
170
|
+
transformHoistInlineDirective,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
warnOnNearMissDirectives(ast, id, options.warn);
|
|
174
|
+
|
|
175
|
+
if (functionResult) return functionResult;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function transformFileLevelUseCache(
|
|
136
179
|
code: string,
|
|
137
180
|
ast: any,
|
|
138
181
|
filePath: string,
|
|
@@ -193,6 +236,7 @@ function transformFileLevelUseCache(
|
|
|
193
236
|
}
|
|
194
237
|
|
|
195
238
|
if (exportNames.length === 0) {
|
|
239
|
+
const { default: MagicString } = await import("magic-string");
|
|
196
240
|
const s = new MagicString(code);
|
|
197
241
|
const directive = findFileLevelDirective(ast);
|
|
198
242
|
if (directive) {
|