@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650
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 +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +71 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +316 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -10
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -41
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +5 -112
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +62 -67
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -48
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -34
- package/src/vite/utils/shared-utils.ts +95 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
package/src/vite/plugin-types.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface BuildEnvFactoryContext {
|
|
|
12
12
|
/** Vite command ("serve" for dev, "build" for production). */
|
|
13
13
|
command: "serve" | "build";
|
|
14
14
|
/** Router deployment preset. */
|
|
15
|
-
preset: "node" | "cloudflare";
|
|
15
|
+
preset: "node" | "cloudflare" | "vercel";
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -47,6 +47,64 @@ export type BuildEnvOption =
|
|
|
47
47
|
| Record<string, unknown>
|
|
48
48
|
| BuildEnvFactory;
|
|
49
49
|
|
|
50
|
+
// -- Client chunking --------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Metadata for one client ("use client") module, passed to a {@link ClientChunks}
|
|
54
|
+
* function. Mirrors the shape `@vitejs/plugin-rsc` passes to its own
|
|
55
|
+
* `clientChunks` option.
|
|
56
|
+
*/
|
|
57
|
+
export interface ClientChunkMeta {
|
|
58
|
+
/** Absolute module id of the "use client" file. */
|
|
59
|
+
id: string;
|
|
60
|
+
/** Normalized (posix) module id — convenient for path-based matching. */
|
|
61
|
+
normalizedId: string;
|
|
62
|
+
/**
|
|
63
|
+
* The RSC/server chunk that statically imports this client reference. This is
|
|
64
|
+
* the key used for the default grouping when no override is supplied: a single
|
|
65
|
+
* router that statically imports every route yields ONE `serverChunk`, hence
|
|
66
|
+
* one client chunk for all routes.
|
|
67
|
+
*/
|
|
68
|
+
serverChunk: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Controls how client ("use client") components are grouped into browser
|
|
73
|
+
* chunks, i.e. per-route / per-feature code splitting of the client bundle.
|
|
74
|
+
*
|
|
75
|
+
* Without splitting, a single router ships ONE client chunk containing every
|
|
76
|
+
* route's client components (and their CSS) — navigating to one route downloads
|
|
77
|
+
* every other route's client code. (Host sub-apps loaded via a dynamic `import()`
|
|
78
|
+
* are the exception: each forms its own chunk.) This option controls how that
|
|
79
|
+
* monolith is split.
|
|
80
|
+
*
|
|
81
|
+
* Behavior branches:
|
|
82
|
+
* - `true` / omitted (**default**, pre-1.0): Rango's built-in **directory
|
|
83
|
+
* strategy**. It splits app `"use client"` modules by **route id** — the segment
|
|
84
|
+
* after a route-root directory (`routes`, `app`, `pages`, `features`, `handlers`,
|
|
85
|
+
* …) — so `routes/dashboard/**` becomes `app-dashboard` at any nesting depth.
|
|
86
|
+
* Where it finds NO route structure (a flat `src/components/`, or host sub-apps
|
|
87
|
+
* already split by a dynamic `import()`), it inherits the default grouping
|
|
88
|
+
* unchanged — so the shared `src/components` chunk stays shared and host apps do
|
|
89
|
+
* not leak across each other. Shared runtime (React, the router, `node_modules`)
|
|
90
|
+
* is never split.
|
|
91
|
+
* - `false`: opt out — inherit `@vitejs/plugin-rsc`'s default grouping everywhere
|
|
92
|
+
* (one chunk per router / per host sub-app).
|
|
93
|
+
* - function: full override. Return a chunk group name, or `undefined` to fall
|
|
94
|
+
* back to the default grouping for that one module. Forwarded directly to
|
|
95
|
+
* `@vitejs/plugin-rsc`'s `clientChunks`.
|
|
96
|
+
*
|
|
97
|
+
* Every module maps to exactly one group, so there is no byte duplication: a
|
|
98
|
+
* component used by two routes lives in one group and is fetched whenever it
|
|
99
|
+
* renders. Put genuinely shared client components OUTSIDE route directories so
|
|
100
|
+
* they land in the shared group rather than one route's chunk.
|
|
101
|
+
*
|
|
102
|
+
* @default true
|
|
103
|
+
*/
|
|
104
|
+
export type ClientChunks =
|
|
105
|
+
| boolean
|
|
106
|
+
| ((meta: ClientChunkMeta) => string | undefined);
|
|
107
|
+
|
|
50
108
|
// -- Plugin options ---------------------------------------------------------
|
|
51
109
|
|
|
52
110
|
/**
|
|
@@ -60,16 +118,27 @@ interface RangoBaseOptions {
|
|
|
60
118
|
banner?: boolean;
|
|
61
119
|
|
|
62
120
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
121
|
+
* Group client ("use client") components into browser chunks for per-route /
|
|
122
|
+
* per-feature code splitting. On by default (pre-1.0); pass `false` to opt out.
|
|
123
|
+
* See {@link ClientChunks}.
|
|
66
124
|
*
|
|
67
|
-
*
|
|
68
|
-
|
|
125
|
+
* @default true
|
|
126
|
+
*/
|
|
127
|
+
clientChunks?: ClientChunks;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Filter which files route discovery scans, by glob. Paths are matched
|
|
131
|
+
* root-relative (e.g. `src/routes/**`). `include` restricts discovery to
|
|
132
|
+
* matching files; `exclude` removes matches (the defaults cover tests, dist,
|
|
133
|
+
* coverage, etc.). Mirrors the CLI's `--include`/`--exclude`.
|
|
69
134
|
*
|
|
70
|
-
* @
|
|
135
|
+
* @example
|
|
136
|
+
* rango({ discovery: { include: ["src/routes/**"] } })
|
|
71
137
|
*/
|
|
72
|
-
|
|
138
|
+
discovery?: {
|
|
139
|
+
include?: string[];
|
|
140
|
+
exclude?: string[];
|
|
141
|
+
};
|
|
73
142
|
}
|
|
74
143
|
|
|
75
144
|
/**
|
|
@@ -80,6 +149,31 @@ export interface RangoNodeOptions extends RangoBaseOptions {
|
|
|
80
149
|
* Deployment preset. Defaults to 'node' when not specified.
|
|
81
150
|
*/
|
|
82
151
|
preset?: "node";
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Path to a host-router entry (a module that calls `createHostRouter()` and
|
|
155
|
+
* exports the instance) to serve instead of a single `createRouter()` app.
|
|
156
|
+
* Root-relative (e.g. `"./src/worker.rsc.tsx"`).
|
|
157
|
+
*
|
|
158
|
+
* Set this when the app is a multi-app host router: auto-discovery otherwise
|
|
159
|
+
* finds the sub-apps' multiple `createRouter()` files and cannot pick an entry.
|
|
160
|
+
* When omitted, rango auto-detects a single `createHostRouter()` file if the
|
|
161
|
+
* app has several `createRouter()` files. The host module must export the
|
|
162
|
+
* `HostRouter` instance (default export or a named `hostRouter`/`router`
|
|
163
|
+
* export), not a Cloudflare-style `{ fetch }` object.
|
|
164
|
+
*/
|
|
165
|
+
host?: string;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Environment bindings available to Prerender and Static handlers at build
|
|
169
|
+
* time via `ctx.env`. Shared across all prerender invocations for the build.
|
|
170
|
+
*
|
|
171
|
+
* `"auto"` is Cloudflare-only (it resolves the wrangler platform proxy), so it
|
|
172
|
+
* is not accepted on the Node preset — pass an object or a factory instead.
|
|
173
|
+
*
|
|
174
|
+
* @default false
|
|
175
|
+
*/
|
|
176
|
+
buildEnv?: Exclude<BuildEnvOption, "auto">;
|
|
83
177
|
}
|
|
84
178
|
|
|
85
179
|
/**
|
|
@@ -89,15 +183,96 @@ export interface RangoCloudflareOptions extends RangoBaseOptions {
|
|
|
89
183
|
/**
|
|
90
184
|
* Deployment preset for Cloudflare Workers.
|
|
91
185
|
* When using cloudflare preset:
|
|
92
|
-
* - @vitejs/plugin-rsc
|
|
186
|
+
* - @vitejs/plugin-rsc IS still added by rango(), but with `serverHandler: false`
|
|
187
|
+
* (the cloudflare plugin owns the RSC worker/server entry); only `client` and
|
|
188
|
+
* `ssr` virtual entries are configured, no rsc entry
|
|
93
189
|
* - Your worker entry (e.g., worker.rsc.tsx) imports the router directly
|
|
94
190
|
* - Browser and SSR use virtual entries
|
|
95
191
|
* - Build-time manifest generation is auto-detected from the resolved RSC environment config
|
|
96
192
|
*/
|
|
97
193
|
preset: "cloudflare";
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Environment bindings available to Prerender and Static handlers at build
|
|
197
|
+
* time via `ctx.env`. Shared across all prerender invocations for the build.
|
|
198
|
+
*
|
|
199
|
+
* `"auto"` resolves the Cloudflare platform proxy via wrangler
|
|
200
|
+
* `getPlatformProxy()`.
|
|
201
|
+
*
|
|
202
|
+
* @default false
|
|
203
|
+
*/
|
|
204
|
+
buildEnv?: BuildEnvOption;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Per-function knobs for the Vercel deployment, written into the generated
|
|
209
|
+
* `.vc-config.json` (and `config.json` for `functionName`).
|
|
210
|
+
*/
|
|
211
|
+
export interface VercelPresetOptions {
|
|
212
|
+
/** Node runtime for the function. @default "nodejs22.x" */
|
|
213
|
+
runtime?: string;
|
|
214
|
+
/** Max execution time in seconds. @default 30 */
|
|
215
|
+
maxDuration?: number;
|
|
216
|
+
/** Function memory in MB (platform default when omitted). */
|
|
217
|
+
memory?: number;
|
|
218
|
+
/** Regions to pin the function to (platform default when omitted). */
|
|
219
|
+
regions?: string[];
|
|
220
|
+
/**
|
|
221
|
+
* Function name — the `<name>.func` directory and the `config.json` route
|
|
222
|
+
* destination. @default "index"
|
|
223
|
+
*/
|
|
224
|
+
functionName?: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Options for Vercel Functions deployment.
|
|
229
|
+
*
|
|
230
|
+
* Builds like the node preset (Vercel runs Node Functions, not Workers): rango
|
|
231
|
+
* owns the RSC entry, `process.env.NODE_ENV` is folded for the build, and after
|
|
232
|
+
* the build a `.vercel/output` directory (Build Output API v3) is assembled from
|
|
233
|
+
* `dist/` — a single streaming Node Function plus the static client assets. The
|
|
234
|
+
* app must install `@vercel/functions` (used by `VercelCacheStore` and the
|
|
235
|
+
* generated function launcher).
|
|
236
|
+
*/
|
|
237
|
+
export interface RangoVercelOptions extends RangoBaseOptions {
|
|
238
|
+
/**
|
|
239
|
+
* Deployment preset for Vercel Functions.
|
|
240
|
+
*/
|
|
241
|
+
preset: "vercel";
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Path to a host-router entry (a module that calls `createHostRouter()` and
|
|
245
|
+
* exports the instance) to serve instead of a single `createRouter()` app.
|
|
246
|
+
* Root-relative (e.g. `"./src/worker.rsc.tsx"`).
|
|
247
|
+
*
|
|
248
|
+
* Set this when the app is a multi-app host router: auto-discovery otherwise
|
|
249
|
+
* finds the sub-apps' multiple `createRouter()` files and cannot pick an entry.
|
|
250
|
+
* When omitted, rango auto-detects a single `createHostRouter()` file if the
|
|
251
|
+
* app has several `createRouter()` files. The host module must export the
|
|
252
|
+
* `HostRouter` instance (default export or a named `hostRouter`/`router`
|
|
253
|
+
* export), not a Cloudflare-style `{ fetch }` object. The Vercel function then
|
|
254
|
+
* runs `hostRouter.match()` for every request (single-function deploy).
|
|
255
|
+
*/
|
|
256
|
+
host?: string;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Environment bindings available to Prerender and Static handlers at build
|
|
260
|
+
* time via `ctx.env`. `"auto"` is Cloudflare-only; pass an object or a factory.
|
|
261
|
+
*
|
|
262
|
+
* @default false
|
|
263
|
+
*/
|
|
264
|
+
buildEnv?: Exclude<BuildEnvOption, "auto">;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Vercel function configuration written into the Build Output.
|
|
268
|
+
*/
|
|
269
|
+
vercel?: VercelPresetOptions;
|
|
98
270
|
}
|
|
99
271
|
|
|
100
272
|
/**
|
|
101
273
|
* Options for rango() Vite plugin
|
|
102
274
|
*/
|
|
103
|
-
export type RangoOptions =
|
|
275
|
+
export type RangoOptions =
|
|
276
|
+
| RangoNodeOptions
|
|
277
|
+
| RangoCloudflareOptions
|
|
278
|
+
| RangoVercelOptions;
|
|
@@ -12,13 +12,9 @@ export function createCjsToEsmPlugin(): Plugin {
|
|
|
12
12
|
name: "@rangojs/router:cjs-to-esm",
|
|
13
13
|
enforce: "pre",
|
|
14
14
|
transform(code, id) {
|
|
15
|
-
const cleanId = id.split("?")[0];
|
|
15
|
+
const cleanId = id.split("?")[0].replaceAll("\\", "/");
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
cleanId.includes("vendor/react-server-dom/client.browser.js") ||
|
|
20
|
-
cleanId.includes("vendor\\react-server-dom\\client.browser.js")
|
|
21
|
-
) {
|
|
17
|
+
if (cleanId.includes("vendor/react-server-dom/client.browser.js")) {
|
|
22
18
|
const isProd = process.env.NODE_ENV === "production";
|
|
23
19
|
const cjsFile = isProd
|
|
24
20
|
? "./cjs/react-server-dom-webpack-client.browser.production.js"
|
|
@@ -31,58 +27,47 @@ export function createCjsToEsmPlugin(): Plugin {
|
|
|
31
27
|
};
|
|
32
28
|
}
|
|
33
29
|
|
|
34
|
-
// Transform the actual CJS files to ESM
|
|
35
30
|
if (
|
|
36
|
-
|
|
37
|
-
cleanId.includes("vendor\\react-server-dom\\cjs\\")) &&
|
|
31
|
+
cleanId.includes("vendor/react-server-dom/cjs/") &&
|
|
38
32
|
cleanId.includes("client.browser")
|
|
39
33
|
) {
|
|
40
34
|
let transformed = code;
|
|
41
35
|
|
|
42
|
-
// Extract the license comment to preserve it
|
|
43
36
|
const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
|
|
44
37
|
const license = licenseMatch ? licenseMatch[0] : "";
|
|
45
38
|
if (license) {
|
|
46
39
|
transformed = transformed.slice(license.length);
|
|
47
40
|
}
|
|
48
41
|
|
|
49
|
-
// Remove "use strict" (both dev and prod have this)
|
|
50
42
|
transformed = transformed.replace(/^\s*["']use strict["'];\s*/, "");
|
|
51
43
|
|
|
52
|
-
// Remove the conditional IIFE wrapper (development only)
|
|
53
44
|
transformed = transformed.replace(
|
|
54
45
|
/^\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
|
|
55
46
|
"",
|
|
56
47
|
);
|
|
57
48
|
|
|
58
|
-
// Remove the closing of the conditional IIFE at the end (development only)
|
|
59
49
|
transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
|
|
60
50
|
|
|
61
|
-
// Replace require('react') and require('react-dom') with imports (development)
|
|
62
51
|
transformed = transformed.replace(
|
|
63
52
|
/var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
64
53
|
'import React from "react";\nimport ReactDOM from "react-dom";\nvar ',
|
|
65
54
|
);
|
|
66
55
|
|
|
67
|
-
// Replace require('react-dom') only (production - doesn't import React)
|
|
68
56
|
transformed = transformed.replace(
|
|
69
57
|
/var\s+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
|
|
70
58
|
'import ReactDOM from "react-dom";\nvar ',
|
|
71
59
|
);
|
|
72
60
|
|
|
73
|
-
// Transform exports.xyz = function() to export function xyz()
|
|
74
61
|
transformed = transformed.replace(
|
|
75
62
|
/exports\.(\w+)\s*=\s*function\s*\(/g,
|
|
76
63
|
"export function $1(",
|
|
77
64
|
);
|
|
78
65
|
|
|
79
|
-
// Transform exports.xyz = value to export const xyz = value
|
|
80
66
|
transformed = transformed.replace(
|
|
81
67
|
/exports\.(\w+)\s*=/g,
|
|
82
68
|
"export const $1 =",
|
|
83
69
|
);
|
|
84
70
|
|
|
85
|
-
// Reconstruct with license at the top
|
|
86
71
|
transformed = license + "\n" + transformed;
|
|
87
72
|
|
|
88
73
|
debug?.("cjs-to-esm body rewrite %s", id);
|
|
@@ -73,8 +73,6 @@ export function clientRefDedup(): Plugin {
|
|
|
73
73
|
apply: "serve",
|
|
74
74
|
|
|
75
75
|
configResolved(config: ResolvedConfig) {
|
|
76
|
-
// Respect user's optimizeDeps.exclude — if a package is explicitly
|
|
77
|
-
// excluded from pre-bundling, we shouldn't redirect it there.
|
|
78
76
|
const clientEnv = config.environments?.["client"];
|
|
79
77
|
clientExclude =
|
|
80
78
|
clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
|
|
@@ -91,27 +89,19 @@ export function clientRefDedup(): Plugin {
|
|
|
91
89
|
},
|
|
92
90
|
|
|
93
91
|
resolveId(source, importer, options) {
|
|
94
|
-
// Only intercept in the client environment
|
|
95
92
|
if (this.environment?.name !== "client") return;
|
|
96
93
|
|
|
97
|
-
// Only handle imports from client-in-server-package-proxy virtual modules
|
|
98
94
|
if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
|
|
99
95
|
|
|
100
|
-
// Only handle absolute node_modules paths
|
|
101
96
|
if (!source.includes("/node_modules/")) return;
|
|
102
97
|
|
|
103
|
-
// Must have an importer
|
|
104
|
-
if (!importer) return;
|
|
105
|
-
|
|
106
98
|
const packageName = extractPackageName(source);
|
|
107
99
|
if (!packageName) return;
|
|
108
100
|
|
|
109
|
-
// Don't redirect packages that are excluded from optimization
|
|
110
101
|
if (clientExclude.includes(packageName)) return;
|
|
111
102
|
|
|
112
103
|
if (debug) dedupedPackages.add(packageName);
|
|
113
104
|
|
|
114
|
-
// Return a virtual module that re-exports via bare specifier
|
|
115
105
|
return `\0rango:dedup/${packageName}`;
|
|
116
106
|
},
|
|
117
107
|
|
|
@@ -120,7 +110,6 @@ export function clientRefDedup(): Plugin {
|
|
|
120
110
|
|
|
121
111
|
const packageName = id.slice("\0rango:dedup/".length);
|
|
122
112
|
|
|
123
|
-
// Re-export via bare specifier so Vite routes through pre-bundling
|
|
124
113
|
return [
|
|
125
114
|
`export * from ${JSON.stringify(packageName)};`,
|
|
126
115
|
`import * as __all__ from ${JSON.stringify(packageName)};`,
|
|
@@ -5,7 +5,6 @@ import { createRangoDebugger, createCounter, NS } from "../debug.js";
|
|
|
5
5
|
|
|
6
6
|
const debug = createRangoDebugger(NS.transform);
|
|
7
7
|
|
|
8
|
-
// Dev-mode client-reference key prefixes emitted by @vitejs/plugin-rsc
|
|
9
8
|
const CLIENT_PKG_PROXY_PREFIX =
|
|
10
9
|
"/@id/__x00__virtual:vite-rsc/client-package-proxy/";
|
|
11
10
|
const CLIENT_IN_SERVER_PKG_PROXY_PREFIX =
|
|
@@ -22,6 +21,17 @@ const FS_PREFIX = "/@fs/";
|
|
|
22
21
|
* Returns the input unchanged if it doesn't match a known dev-mode pattern
|
|
23
22
|
* (e.g., already a production hash).
|
|
24
23
|
*/
|
|
24
|
+
/**
|
|
25
|
+
* The production client-reference key hash: `sha256(relativeId).slice(0,12)`,
|
|
26
|
+
* matching @vitejs/plugin-rsc's `hashString`. Exported so the client-chunks
|
|
27
|
+
* strategy can hash a `clientChunks` callback's `meta.normalizedId` (already the
|
|
28
|
+
* project-root-relative id) and compare it against fallback hashes collected
|
|
29
|
+
* during discovery.
|
|
30
|
+
*/
|
|
31
|
+
export function hashRefKey(relativeId: string): string {
|
|
32
|
+
return createHash("sha256").update(relativeId).digest("hex").slice(0, 12);
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
export function computeProductionHash(
|
|
26
36
|
projectRoot: string,
|
|
27
37
|
refKey: string,
|
|
@@ -29,32 +39,24 @@ export function computeProductionHash(
|
|
|
29
39
|
let toHash: string;
|
|
30
40
|
|
|
31
41
|
if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
|
|
32
|
-
// /@id/__x00__virtual:vite-rsc/client-package-proxy/<pkg> -> hash("<pkg>")
|
|
33
42
|
toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
|
|
34
43
|
} else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
|
|
35
|
-
// /@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/<encodedAbsPath>
|
|
36
44
|
const absPath = decodeURIComponent(
|
|
37
45
|
refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length),
|
|
38
46
|
);
|
|
39
47
|
toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
|
|
40
48
|
} else if (refKey.startsWith(FS_PREFIX)) {
|
|
41
|
-
// /@fs/abs/path.tsx -> hash(relative(root, "/abs/path.tsx"))
|
|
42
49
|
const absPath = refKey.slice(FS_PREFIX.length - 1); // keep leading /
|
|
43
50
|
toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
|
|
44
51
|
} else if (refKey.startsWith("/")) {
|
|
45
|
-
// /src/Button.tsx -> hash("src/Button.tsx")
|
|
46
52
|
toHash = refKey.slice(1);
|
|
47
53
|
} else {
|
|
48
|
-
// Already hashed or unknown format — return unchanged
|
|
49
54
|
return refKey;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
return
|
|
57
|
+
return hashRefKey(toHash);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
|
-
// Regex to match registerClientReference() calls as emitted by @vitejs/plugin-rsc.
|
|
56
|
-
// Captures the reference key (second argument) from the call.
|
|
57
|
-
// Handles two proxy forms: parenthesized expression `(expr)` and arrow-throw `() => { ... }`.
|
|
58
60
|
const REGISTER_CLIENT_REF_RE =
|
|
59
61
|
/registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
|
|
60
62
|
|
|
@@ -95,7 +97,6 @@ export function hashClientRefs(projectRoot: string): Plugin {
|
|
|
95
97
|
const counter = createCounter(debug, "hash-client-refs");
|
|
96
98
|
return {
|
|
97
99
|
name: "@rangojs/router:hash-client-refs",
|
|
98
|
-
// Run after the RSC plugin's transform (default enforce is normal)
|
|
99
100
|
enforce: "post",
|
|
100
101
|
applyToEnvironment(env) {
|
|
101
102
|
return env.name === "rsc";
|
|
@@ -32,14 +32,6 @@ const IMPORT_NODE_TYPES = new Set([
|
|
|
32
32
|
"ExportAllDeclaration",
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
|
-
// Keep in sync with `STUBS` in cloudflare-protocol-loader-hook.mjs —
|
|
36
|
-
// both paths (Vite transform and Node loader) need to hand out the same
|
|
37
|
-
// classes. Unknown `cloudflare:*` modules fall back to an empty default
|
|
38
|
-
// export so third-party packages (e.g. the Cloudflare Agents SDK) can
|
|
39
|
-
// pull them into the graph without crashing discovery. Discovery only
|
|
40
|
-
// evaluates module top-level code — no handlers run — so missing named
|
|
41
|
-
// exports only fail if something does `class X extends Missing {}` at
|
|
42
|
-
// module scope, which is rare outside the already-stubbed classes.
|
|
43
35
|
const STUBS: Record<string, string> = {
|
|
44
36
|
"cloudflare:workers": `
|
|
45
37
|
export class DurableObject { constructor(_ctx, _env) {} }
|
|
@@ -65,15 +57,6 @@ export default {};
|
|
|
65
57
|
`,
|
|
66
58
|
};
|
|
67
59
|
|
|
68
|
-
// Policy: unknown `cloudflare:*` specifiers resolve permissively (empty
|
|
69
|
-
// default export) rather than throwing. We prioritize dependency-graph
|
|
70
|
-
// resilience over strict validation of user imports because third-party
|
|
71
|
-
// packages can pull `cloudflare:*` modules we haven't curated, and
|
|
72
|
-
// discovery should not fail just because those modules appear in the graph.
|
|
73
|
-
// Tradeoff: unsupported user-authored `cloudflare:*` imports may fail later
|
|
74
|
-
// with a generic JS/module error instead of a tailored rango-branded hint.
|
|
75
|
-
// The test below pins this behavior so dependency compatibility is not
|
|
76
|
-
// regressed accidentally.
|
|
77
60
|
const FALLBACK_STUB = `export default {};\n`;
|
|
78
61
|
|
|
79
62
|
interface AstNode {
|
|
@@ -139,7 +122,7 @@ export function createCloudflareProtocolStubPlugin(): Plugin {
|
|
|
139
122
|
|
|
140
123
|
let ast: AstNode;
|
|
141
124
|
try {
|
|
142
|
-
ast = this.parse(code) as unknown as AstNode;
|
|
125
|
+
ast = this.parse(code, { lang: "tsx" }) as unknown as AstNode;
|
|
143
126
|
} catch {
|
|
144
127
|
// Malformed source — let a downstream plugin surface the parse error.
|
|
145
128
|
return null;
|
|
@@ -163,9 +146,6 @@ export function createCloudflareProtocolStubPlugin(): Plugin {
|
|
|
163
146
|
|
|
164
147
|
if (hits.length === 0) return null;
|
|
165
148
|
|
|
166
|
-
// Rewrite from last to first so earlier offsets stay valid. `start`/
|
|
167
|
-
// `end` span the full literal including quotes, so we re-emit the
|
|
168
|
-
// same quote character around the new specifier.
|
|
169
149
|
hits.sort((a, b) => b.start - a.start);
|
|
170
150
|
let out = code;
|
|
171
151
|
for (const hit of hits) {
|
|
@@ -7,9 +7,6 @@ import { createRangoDebugger, createCounter, NS } from "../debug.js";
|
|
|
7
7
|
|
|
8
8
|
const debug = createRangoDebugger(NS.transform);
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Type for the RSC plugin's manager API
|
|
12
|
-
*/
|
|
13
10
|
interface RscPluginManager {
|
|
14
11
|
serverReferenceMetaMap: Record<
|
|
15
12
|
string,
|
|
@@ -26,14 +23,9 @@ interface RscPluginApi {
|
|
|
26
23
|
manager: RscPluginManager;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
/**
|
|
30
|
-
* Get the RSC plugin's API from Vite config
|
|
31
|
-
*/
|
|
32
26
|
function getRscPluginApi(config: ResolvedConfig): RscPluginApi | undefined {
|
|
33
|
-
// Try by name first
|
|
34
27
|
let plugin = config.plugins.find((p) => p.name === "rsc:minimal");
|
|
35
28
|
|
|
36
|
-
// Fallback: find by API structure if name lookup fails
|
|
37
29
|
if (!plugin) {
|
|
38
30
|
plugin = config.plugins.find(
|
|
39
31
|
(p) =>
|
|
@@ -42,7 +34,7 @@ function getRscPluginApi(config: ResolvedConfig): RscPluginApi | undefined {
|
|
|
42
34
|
);
|
|
43
35
|
if (plugin) {
|
|
44
36
|
console.warn(
|
|
45
|
-
`[
|
|
37
|
+
`[rango:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). ` +
|
|
46
38
|
`Consider updating the name lookup if the plugin was renamed.`,
|
|
47
39
|
);
|
|
48
40
|
}
|
|
@@ -62,13 +54,11 @@ function getRscPluginApi(config: ResolvedConfig): RscPluginApi | undefined {
|
|
|
62
54
|
function isUseServerModule(filePath: string): boolean {
|
|
63
55
|
try {
|
|
64
56
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
65
|
-
// Remove leading comments and whitespace to find the first meaningful content
|
|
66
57
|
const trimmed = content
|
|
67
|
-
.replace(/^\s*\/\/[^\n]*\n/gm, "")
|
|
68
|
-
.replace(/^\s*\/\*[\s\S]*?\*\/\s*/gm, "")
|
|
58
|
+
.replace(/^\s*\/\/[^\n]*\n/gm, "")
|
|
59
|
+
.replace(/^\s*\/\*[\s\S]*?\*\/\s*/gm, "")
|
|
69
60
|
.trimStart();
|
|
70
61
|
|
|
71
|
-
// Check if the file starts with "use server" directive
|
|
72
62
|
return (
|
|
73
63
|
trimmed.startsWith('"use server"') || trimmed.startsWith("'use server'")
|
|
74
64
|
);
|
|
@@ -77,18 +67,6 @@ function isUseServerModule(filePath: string): boolean {
|
|
|
77
67
|
}
|
|
78
68
|
}
|
|
79
69
|
|
|
80
|
-
/**
|
|
81
|
-
* Transform code to expose action IDs on createServerReference calls.
|
|
82
|
-
* Wraps each call with an IIFE that attaches $id to the returned function.
|
|
83
|
-
*
|
|
84
|
-
* @param code - The source code to transform
|
|
85
|
-
* @param sourceId - The source file identifier (for sourcemap)
|
|
86
|
-
* @param hashToFileMap - Optional mapping from hash to file path (for server bundles)
|
|
87
|
-
*/
|
|
88
|
-
/**
|
|
89
|
-
* Apply createServerReference wrapping to a MagicString instance.
|
|
90
|
-
* Returns true if any changes were made.
|
|
91
|
-
*/
|
|
92
70
|
function applyServerReferenceWrapping(
|
|
93
71
|
code: string,
|
|
94
72
|
s: MagicString,
|
|
@@ -98,11 +76,6 @@ function applyServerReferenceWrapping(
|
|
|
98
76
|
return false;
|
|
99
77
|
}
|
|
100
78
|
|
|
101
|
-
// Match: createServerReference("hash#actionName", ...) or $$ReactClient.createServerReference(...)
|
|
102
|
-
// The RSC plugin uses $$ReactClient namespace in transformed code.
|
|
103
|
-
// Note: [^)]* cannot handle nested parens in trailing args. This is safe in practice
|
|
104
|
-
// because the RSC plugin always generates simple variable references (e.g., callServer)
|
|
105
|
-
// as the second argument, never nested function calls.
|
|
106
79
|
const pattern =
|
|
107
80
|
/((?:\$\$\w+\.)?createServerReference)\(("[^"]+#[^"]+")([^)]*)\)/g;
|
|
108
81
|
|
|
@@ -115,23 +88,19 @@ function applyServerReferenceWrapping(
|
|
|
115
88
|
const start = match.index;
|
|
116
89
|
const end = start + fullMatch.length;
|
|
117
90
|
|
|
118
|
-
// Parse the ID to potentially replace hash with file path
|
|
119
91
|
let finalIdArg = idArg;
|
|
120
92
|
if (hashToFileMap) {
|
|
121
|
-
// idArg is like '"hash#actionName"', extract the parts
|
|
122
93
|
const idValue = idArg.slice(1, -1); // Remove quotes
|
|
123
94
|
const hashMatch = idValue.match(/^([^#]+)#(.+)$/);
|
|
124
95
|
if (hashMatch) {
|
|
125
96
|
const [, hash, actionName] = hashMatch;
|
|
126
97
|
const filePath = hashToFileMap.get(hash);
|
|
127
98
|
if (filePath) {
|
|
128
|
-
// Replace hash with file path for server-side
|
|
129
99
|
finalIdArg = `"${filePath}#${actionName}"`;
|
|
130
100
|
}
|
|
131
101
|
}
|
|
132
102
|
}
|
|
133
103
|
|
|
134
|
-
// Wrap the createServerReference call to attach $$id to the returned function
|
|
135
104
|
const replacement = `(function(fn) { fn.$$id = ${finalIdArg}; return fn; })(${fnCall}(${idArg}${rest}))`;
|
|
136
105
|
s.overwrite(start, end, replacement);
|
|
137
106
|
}
|
|
@@ -155,29 +124,6 @@ function transformServerReferences(
|
|
|
155
124
|
};
|
|
156
125
|
}
|
|
157
126
|
|
|
158
|
-
/**
|
|
159
|
-
* Transform registerServerReference calls in server bundles to use file paths instead of hashes.
|
|
160
|
-
* Pattern: registerServerReference(fn, "hash", "exportName")
|
|
161
|
-
* React's registerServerReference sets $$id = hash + "#" + exportName
|
|
162
|
-
* By replacing the hash with file path, $$id will contain the file path for revalidation matching.
|
|
163
|
-
*
|
|
164
|
-
* Only actions from module-level "use server" files are transformed.
|
|
165
|
-
* Inline actions (defined in RSC components with "use server" inside a function) are NOT in
|
|
166
|
-
* hashToFileMap and keep their hashed IDs. This is intentional for client security:
|
|
167
|
-
* - Module-level "use server" files: shared action modules, file path helps revalidation
|
|
168
|
-
* - Inline actions: one-off actions in RSC, hash ID prevents file path exposure to client
|
|
169
|
-
*
|
|
170
|
-
* @param code - The source code to transform
|
|
171
|
-
* @param sourceId - The source file identifier (for sourcemap)
|
|
172
|
-
* @param hashToFileMap - Mapping from hash to file path (only module-level "use server" files)
|
|
173
|
-
*/
|
|
174
|
-
/**
|
|
175
|
-
* Apply registerServerReference wrapping to a MagicString instance.
|
|
176
|
-
* Returns true if any changes were made.
|
|
177
|
-
*
|
|
178
|
-
* Only actions from module-level "use server" files are transformed.
|
|
179
|
-
* Inline actions keep their hashed IDs for client security.
|
|
180
|
-
*/
|
|
181
127
|
function applyRegisterReferenceWrapping(
|
|
182
128
|
code: string,
|
|
183
129
|
s: MagicString,
|
|
@@ -187,8 +133,6 @@ function applyRegisterReferenceWrapping(
|
|
|
187
133
|
return false;
|
|
188
134
|
}
|
|
189
135
|
|
|
190
|
-
// Match: registerServerReference(fn, "hash", "exportName")
|
|
191
|
-
// The hash is the second argument, exportName is the third
|
|
192
136
|
const pattern =
|
|
193
137
|
/registerServerReference\(([^,]+),\s*"([^"]+)",\s*"([^"]+)"\)/g;
|
|
194
138
|
|
|
@@ -200,15 +144,9 @@ function applyRegisterReferenceWrapping(
|
|
|
200
144
|
const start = match.index;
|
|
201
145
|
const end = start + fullMatch.length;
|
|
202
146
|
|
|
203
|
-
// Look up the file path for this hash
|
|
204
147
|
const filePath = hashToFileMap.get(hash);
|
|
205
148
|
if (filePath) {
|
|
206
149
|
hasChanges = true;
|
|
207
|
-
// WRAP the call to add $id property with file path
|
|
208
|
-
// Keep the original hash for React's action registry (so loadServerAction works)
|
|
209
|
-
// Add $id (single dollar) with file path for revalidation matching
|
|
210
|
-
// Note: We use $id instead of $$id because React's registerServerReference
|
|
211
|
-
// sets $$id as a non-writable property
|
|
212
150
|
const filePathId = `${filePath}#${exportName}`;
|
|
213
151
|
const replacement = `(function(fn) { fn.$id = "${filePathId}"; return fn; })(registerServerReference(${fnArg}, "${hash}", "${exportName}"))`;
|
|
214
152
|
s.overwrite(start, end, replacement);
|
|
@@ -287,7 +225,7 @@ export function exposeActionId(): Plugin {
|
|
|
287
225
|
|
|
288
226
|
if (!rscPluginApi) {
|
|
289
227
|
throw new Error(
|
|
290
|
-
"[
|
|
228
|
+
"[rango] Could not find @vitejs/plugin-rsc. " +
|
|
291
229
|
"@rangojs/router requires the Vite RSC plugin, which is included automatically by rango().",
|
|
292
230
|
);
|
|
293
231
|
}
|
|
@@ -336,27 +274,19 @@ export function exposeActionId(): Plugin {
|
|
|
336
274
|
|
|
337
275
|
const start = counterTransform ? performance.now() : 0;
|
|
338
276
|
try {
|
|
339
|
-
// Dev mode: no hash-to-file mapping needed (IDs are already file paths)
|
|
340
277
|
return transformServerReferences(code, id);
|
|
341
278
|
} finally {
|
|
342
279
|
counterTransform?.record(id, performance.now() - start);
|
|
343
280
|
}
|
|
344
281
|
},
|
|
345
282
|
|
|
346
|
-
// Build mode: renderChunk runs after all transforms and bundling complete
|
|
347
283
|
renderChunk(code, chunk) {
|
|
348
284
|
const start = counterRender ? performance.now() : 0;
|
|
349
285
|
try {
|
|
350
|
-
// Only RSC bundle should get file paths for revalidation matching
|
|
351
|
-
// SSR bundle must NOT use file paths because client components run there
|
|
352
|
-
// and need to match the client bundle during hydration (otherwise: error #418)
|
|
353
286
|
const isRscEnv = this.environment?.name === "rsc";
|
|
354
287
|
|
|
355
|
-
// Only use file path mapping for RSC environment
|
|
356
288
|
const effectiveMap = isRscEnv ? hashToFileMap : undefined;
|
|
357
289
|
|
|
358
|
-
// For RSC bundles, both createServerReference and registerServerReference
|
|
359
|
-
// may need transforming. Use a single MagicString for correct sourcemaps.
|
|
360
290
|
if (isRscEnv && hashToFileMap) {
|
|
361
291
|
const s = new MagicString(code);
|
|
362
292
|
const changed1 = applyServerReferenceWrapping(code, s, effectiveMap);
|
|
@@ -377,7 +307,6 @@ export function exposeActionId(): Plugin {
|
|
|
377
307
|
return null;
|
|
378
308
|
}
|
|
379
309
|
|
|
380
|
-
// Non-RSC environments: only transform createServerReference calls
|
|
381
310
|
return transformServerReferences(code, chunk.fileName, effectiveMap);
|
|
382
311
|
} finally {
|
|
383
312
|
counterRender?.record(chunk.fileName, performance.now() - start);
|