@rangojs/router 0.0.0-experimental.5 → 0.0.0-experimental.50

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.
Files changed (301) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1606 -0
  4. package/dist/vite/index.js +4567 -769
  5. package/package.json +77 -58
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +85 -23
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +334 -72
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +89 -30
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +204 -1
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +85 -16
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +9 -8
  29. package/skills/typesafety/SKILL.md +318 -89
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +102 -4
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +92 -64
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/intercept-utils.ts +52 -0
  38. package/src/browser/link-interceptor.ts +24 -4
  39. package/src/browser/logging.ts +55 -0
  40. package/src/browser/merge-segment-loaders.ts +20 -12
  41. package/src/browser/navigation-bridge.ts +282 -557
  42. package/src/browser/navigation-client.ts +157 -71
  43. package/src/browser/navigation-store.ts +33 -50
  44. package/src/browser/navigation-transaction.ts +297 -0
  45. package/src/browser/network-error-handler.ts +61 -0
  46. package/src/browser/partial-update.ts +303 -310
  47. package/src/browser/prefetch/cache.ts +206 -0
  48. package/src/browser/prefetch/fetch.ts +144 -0
  49. package/src/browser/prefetch/observer.ts +65 -0
  50. package/src/browser/prefetch/policy.ts +48 -0
  51. package/src/browser/prefetch/queue.ts +128 -0
  52. package/src/browser/rango-state.ts +112 -0
  53. package/src/browser/react/Link.tsx +193 -73
  54. package/src/browser/react/NavigationProvider.tsx +160 -13
  55. package/src/browser/react/context.ts +6 -0
  56. package/src/browser/react/filter-segment-order.ts +11 -0
  57. package/src/browser/react/index.ts +12 -12
  58. package/src/browser/react/location-state-shared.ts +95 -53
  59. package/src/browser/react/location-state.ts +60 -15
  60. package/src/browser/react/mount-context.ts +24 -1
  61. package/src/browser/react/nonce-context.ts +23 -0
  62. package/src/browser/react/shallow-equal.ts +27 -0
  63. package/src/browser/react/use-action.ts +29 -51
  64. package/src/browser/react/use-client-cache.ts +5 -3
  65. package/src/browser/react/use-handle.ts +32 -79
  66. package/src/browser/react/use-href.tsx +2 -2
  67. package/src/browser/react/use-link-status.ts +6 -5
  68. package/src/browser/react/use-navigation.ts +22 -63
  69. package/src/browser/react/use-params.ts +65 -0
  70. package/src/browser/react/use-pathname.ts +47 -0
  71. package/src/browser/react/use-router.ts +63 -0
  72. package/src/browser/react/use-search-params.ts +56 -0
  73. package/src/browser/react/use-segments.ts +80 -97
  74. package/src/browser/response-adapter.ts +73 -0
  75. package/src/browser/rsc-router.tsx +188 -55
  76. package/src/browser/scroll-restoration.ts +117 -44
  77. package/src/browser/segment-reconciler.ts +221 -0
  78. package/src/browser/segment-structure-assert.ts +16 -0
  79. package/src/browser/server-action-bridge.ts +504 -599
  80. package/src/browser/shallow.ts +6 -1
  81. package/src/browser/types.ts +118 -47
  82. package/src/browser/validate-redirect-origin.ts +29 -0
  83. package/src/build/generate-manifest.ts +235 -24
  84. package/src/build/generate-route-types.ts +36 -0
  85. package/src/build/index.ts +13 -0
  86. package/src/build/route-trie.ts +265 -0
  87. package/src/build/route-types/ast-helpers.ts +25 -0
  88. package/src/build/route-types/ast-route-extraction.ts +98 -0
  89. package/src/build/route-types/codegen.ts +102 -0
  90. package/src/build/route-types/include-resolution.ts +411 -0
  91. package/src/build/route-types/param-extraction.ts +48 -0
  92. package/src/build/route-types/per-module-writer.ts +128 -0
  93. package/src/build/route-types/router-processing.ts +479 -0
  94. package/src/build/route-types/scan-filter.ts +78 -0
  95. package/src/build/runtime-discovery.ts +231 -0
  96. package/src/cache/background-task.ts +34 -0
  97. package/src/cache/cache-key-utils.ts +44 -0
  98. package/src/cache/cache-policy.ts +125 -0
  99. package/src/cache/cache-runtime.ts +342 -0
  100. package/src/cache/cache-scope.ts +167 -309
  101. package/src/cache/cf/cf-cache-store.ts +571 -17
  102. package/src/cache/cf/index.ts +13 -3
  103. package/src/cache/document-cache.ts +116 -77
  104. package/src/cache/handle-capture.ts +81 -0
  105. package/src/cache/handle-snapshot.ts +41 -0
  106. package/src/cache/index.ts +1 -15
  107. package/src/cache/memory-segment-store.ts +191 -13
  108. package/src/cache/profile-registry.ts +73 -0
  109. package/src/cache/read-through-swr.ts +134 -0
  110. package/src/cache/segment-codec.ts +256 -0
  111. package/src/cache/taint.ts +98 -0
  112. package/src/cache/types.ts +72 -122
  113. package/src/client.rsc.tsx +3 -1
  114. package/src/client.tsx +106 -126
  115. package/src/component-utils.ts +4 -4
  116. package/src/components/DefaultDocument.tsx +5 -1
  117. package/src/context-var.ts +86 -0
  118. package/src/debug.ts +19 -9
  119. package/src/errors.ts +108 -2
  120. package/src/handle.ts +15 -29
  121. package/src/handles/MetaTags.tsx +73 -20
  122. package/src/handles/breadcrumbs.ts +66 -0
  123. package/src/handles/index.ts +1 -0
  124. package/src/handles/meta.ts +30 -13
  125. package/src/host/cookie-handler.ts +165 -0
  126. package/src/host/errors.ts +97 -0
  127. package/src/host/index.ts +53 -0
  128. package/src/host/pattern-matcher.ts +214 -0
  129. package/src/host/router.ts +352 -0
  130. package/src/host/testing.ts +79 -0
  131. package/src/host/types.ts +146 -0
  132. package/src/host/utils.ts +25 -0
  133. package/src/href-client.ts +119 -29
  134. package/src/index.rsc.ts +153 -19
  135. package/src/index.ts +211 -30
  136. package/src/internal-debug.ts +11 -0
  137. package/src/loader.rsc.ts +26 -147
  138. package/src/loader.ts +27 -10
  139. package/src/network-error-thrower.tsx +3 -1
  140. package/src/outlet-provider.tsx +45 -0
  141. package/src/prerender/param-hash.ts +37 -0
  142. package/src/prerender/store.ts +185 -0
  143. package/src/prerender.ts +463 -0
  144. package/src/reverse.ts +330 -0
  145. package/src/root-error-boundary.tsx +41 -29
  146. package/src/route-content-wrapper.tsx +7 -4
  147. package/src/route-definition/dsl-helpers.ts +959 -0
  148. package/src/route-definition/helper-factories.ts +200 -0
  149. package/src/route-definition/helpers-types.ts +430 -0
  150. package/src/route-definition/index.ts +52 -0
  151. package/src/route-definition/redirect.ts +93 -0
  152. package/src/route-definition.ts +1 -1428
  153. package/src/route-map-builder.ts +217 -123
  154. package/src/route-name.ts +53 -0
  155. package/src/route-types.ts +59 -8
  156. package/src/router/content-negotiation.ts +116 -0
  157. package/src/router/debug-manifest.ts +72 -0
  158. package/src/router/error-handling.ts +9 -9
  159. package/src/router/find-match.ts +160 -0
  160. package/src/router/handler-context.ts +374 -81
  161. package/src/router/intercept-resolution.ts +397 -0
  162. package/src/router/lazy-includes.ts +237 -0
  163. package/src/router/loader-resolution.ts +215 -122
  164. package/src/router/logging.ts +251 -0
  165. package/src/router/manifest.ts +154 -35
  166. package/src/router/match-api.ts +620 -0
  167. package/src/router/match-context.ts +5 -3
  168. package/src/router/match-handlers.ts +440 -0
  169. package/src/router/match-middleware/background-revalidation.ts +108 -93
  170. package/src/router/match-middleware/cache-lookup.ts +440 -10
  171. package/src/router/match-middleware/cache-store.ts +98 -26
  172. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  173. package/src/router/match-middleware/segment-resolution.ts +27 -6
  174. package/src/router/match-pipelines.ts +10 -45
  175. package/src/router/match-result.ts +55 -33
  176. package/src/router/metrics.ts +240 -15
  177. package/src/router/middleware-cookies.ts +55 -0
  178. package/src/router/middleware-types.ts +222 -0
  179. package/src/router/middleware.ts +327 -369
  180. package/src/router/pattern-matching.ts +211 -43
  181. package/src/router/prerender-match.ts +402 -0
  182. package/src/router/preview-match.ts +170 -0
  183. package/src/router/revalidation.ts +137 -38
  184. package/src/router/router-context.ts +41 -21
  185. package/src/router/router-interfaces.ts +452 -0
  186. package/src/router/router-options.ts +592 -0
  187. package/src/router/router-registry.ts +24 -0
  188. package/src/router/segment-resolution/fresh.ts +677 -0
  189. package/src/router/segment-resolution/helpers.ts +263 -0
  190. package/src/router/segment-resolution/loader-cache.ts +199 -0
  191. package/src/router/segment-resolution/revalidation.ts +1296 -0
  192. package/src/router/segment-resolution/static-store.ts +67 -0
  193. package/src/router/segment-resolution.ts +21 -0
  194. package/src/router/segment-wrappers.ts +291 -0
  195. package/src/router/telemetry-otel.ts +299 -0
  196. package/src/router/telemetry.ts +300 -0
  197. package/src/router/timeout.ts +148 -0
  198. package/src/router/trie-matching.ts +239 -0
  199. package/src/router/types.ts +77 -3
  200. package/src/router.ts +665 -4182
  201. package/src/rsc/handler-context.ts +45 -0
  202. package/src/rsc/handler.ts +764 -754
  203. package/src/rsc/helpers.ts +140 -6
  204. package/src/rsc/index.ts +0 -20
  205. package/src/rsc/loader-fetch.ts +209 -0
  206. package/src/rsc/manifest-init.ts +86 -0
  207. package/src/rsc/nonce.ts +14 -0
  208. package/src/rsc/origin-guard.ts +141 -0
  209. package/src/rsc/progressive-enhancement.ts +379 -0
  210. package/src/rsc/response-error.ts +37 -0
  211. package/src/rsc/response-route-handler.ts +347 -0
  212. package/src/rsc/rsc-rendering.ts +237 -0
  213. package/src/rsc/runtime-warnings.ts +42 -0
  214. package/src/rsc/server-action.ts +348 -0
  215. package/src/rsc/ssr-setup.ts +128 -0
  216. package/src/rsc/types.ts +38 -11
  217. package/src/search-params.ts +230 -0
  218. package/src/segment-system.tsx +172 -21
  219. package/src/server/context.ts +266 -58
  220. package/src/server/cookie-store.ts +190 -0
  221. package/src/server/fetchable-loader-store.ts +37 -0
  222. package/src/server/handle-store.ts +94 -15
  223. package/src/server/loader-registry.ts +15 -56
  224. package/src/server/request-context.ts +439 -73
  225. package/src/server.ts +35 -128
  226. package/src/ssr/index.tsx +101 -31
  227. package/src/static-handler.ts +114 -0
  228. package/src/theme/ThemeProvider.tsx +21 -15
  229. package/src/theme/ThemeScript.tsx +5 -5
  230. package/src/theme/constants.ts +5 -2
  231. package/src/theme/index.ts +4 -14
  232. package/src/theme/theme-context.ts +4 -30
  233. package/src/theme/theme-script.ts +21 -18
  234. package/src/types/boundaries.ts +158 -0
  235. package/src/types/cache-types.ts +198 -0
  236. package/src/types/error-types.ts +192 -0
  237. package/src/types/global-namespace.ts +100 -0
  238. package/src/types/handler-context.ts +773 -0
  239. package/src/types/index.ts +88 -0
  240. package/src/types/loader-types.ts +183 -0
  241. package/src/types/route-config.ts +170 -0
  242. package/src/types/route-entry.ts +109 -0
  243. package/src/types/segments.ts +150 -0
  244. package/src/types.ts +1 -1623
  245. package/src/urls/include-helper.ts +197 -0
  246. package/src/urls/index.ts +53 -0
  247. package/src/urls/path-helper-types.ts +339 -0
  248. package/src/urls/path-helper.ts +329 -0
  249. package/src/urls/pattern-types.ts +95 -0
  250. package/src/urls/response-types.ts +106 -0
  251. package/src/urls/type-extraction.ts +372 -0
  252. package/src/urls/urls-function.ts +98 -0
  253. package/src/urls.ts +1 -802
  254. package/src/use-loader.tsx +85 -77
  255. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  256. package/src/vite/discovery/discover-routers.ts +344 -0
  257. package/src/vite/discovery/prerender-collection.ts +385 -0
  258. package/src/vite/discovery/route-types-writer.ts +258 -0
  259. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  260. package/src/vite/discovery/state.ts +108 -0
  261. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  262. package/src/vite/index.ts +11 -782
  263. package/src/vite/plugin-types.ts +48 -0
  264. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  265. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  266. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  267. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  268. package/src/vite/plugins/expose-id-utils.ts +287 -0
  269. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  270. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  271. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  272. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  273. package/src/vite/plugins/expose-ids/types.ts +45 -0
  274. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  275. package/src/vite/plugins/refresh-cmd.ts +65 -0
  276. package/src/vite/plugins/use-cache-transform.ts +323 -0
  277. package/src/vite/plugins/version-injector.ts +83 -0
  278. package/src/vite/plugins/version-plugin.ts +266 -0
  279. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +27 -16
  280. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  281. package/src/vite/rango.ts +445 -0
  282. package/src/vite/router-discovery.ts +777 -0
  283. package/src/vite/utils/ast-handler-extract.ts +517 -0
  284. package/src/vite/utils/banner.ts +36 -0
  285. package/src/vite/utils/bundle-analysis.ts +137 -0
  286. package/src/vite/utils/manifest-utils.ts +70 -0
  287. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  288. package/src/vite/utils/prerender-utils.ts +189 -0
  289. package/src/vite/utils/shared-utils.ts +169 -0
  290. package/CLAUDE.md +0 -43
  291. package/src/browser/lru-cache.ts +0 -69
  292. package/src/browser/request-controller.ts +0 -164
  293. package/src/cache/memory-store.ts +0 -253
  294. package/src/href-context.ts +0 -33
  295. package/src/href.ts +0 -255
  296. package/src/server/route-manifest-cache.ts +0 -173
  297. package/src/vite/expose-handle-id.ts +0 -209
  298. package/src/vite/expose-loader-id.ts +0 -426
  299. package/src/vite/expose-location-state-id.ts +0 -177
  300. package/src/warmup/connection-warmup.tsx +0 -94
  301. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -73,23 +73,34 @@ import { VERSION } from "@rangojs/router:version";
73
73
  // might not be imported before a GET request arrives
74
74
  import "virtual:rsc-router/loader-manifest";
75
75
 
76
- // Route manifest is now loaded at runtime on first request via getRouteManifestData()
77
- // This eliminates the need for build-time manifest generation
76
+ // Import pre-generated route manifest so href() works immediately on cold start.
77
+ // In build mode, this contains the full route map generated at build time.
78
+ // In dev mode, this is a no-op (manifest is populated in-memory by the discovery plugin).
79
+ import "virtual:rsc-router/routes-manifest";
78
80
 
79
- export default createRSCHandler({
80
- router,
81
- version: VERSION,
82
- deps: {
83
- renderToReadableStream,
84
- decodeReply,
85
- createTemporaryReferenceSet,
86
- loadServerAction,
87
- decodeAction,
88
- decodeFormState,
89
- },
90
- loadSSRModule: () =>
91
- import.meta.viteRsc.loadModule("ssr", "index"),
92
- });
81
+ // Lazily create the handler on first request so that ESM live bindings
82
+ // have resolved by the time we read \`router\`. During HMR the module may
83
+ // re-evaluate before router.tsx finishes, leaving the import undefined.
84
+ let _handler;
85
+ export default function handler(request, env) {
86
+ if (!_handler) {
87
+ _handler = createRSCHandler({
88
+ router,
89
+ version: VERSION,
90
+ deps: {
91
+ renderToReadableStream,
92
+ decodeReply,
93
+ createTemporaryReferenceSet,
94
+ loadServerAction,
95
+ decodeAction,
96
+ decodeFormState,
97
+ },
98
+ loadSSRModule: () =>
99
+ import.meta.viteRsc.loadModule("ssr", "index"),
100
+ });
101
+ }
102
+ return _handler(request, env);
103
+ }
93
104
  `.trim();
94
105
  }
95
106
 
@@ -0,0 +1,29 @@
1
+ import type { Plugin } from "vite";
2
+
3
+ /**
4
+ * Stub plugin that resolves and provides empty exports for virtual modules
5
+ * that the RSC entry may import but aren't needed for route discovery.
6
+ * @internal
7
+ */
8
+ export function createVirtualStubPlugin(): Plugin {
9
+ const STUB_PREFIXES = [
10
+ "virtual:rsc-router/",
11
+ "virtual:entry-",
12
+ "virtual:vite-rsc/",
13
+ ];
14
+ return {
15
+ name: "@rangojs/router:virtual-stubs",
16
+ resolveId(id) {
17
+ if (STUB_PREFIXES.some((p) => id.startsWith(p))) {
18
+ return "\0stub:" + id;
19
+ }
20
+ return null;
21
+ },
22
+ load(id) {
23
+ if (id.startsWith("\0stub:")) {
24
+ return "export default {}";
25
+ }
26
+ return null;
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,445 @@
1
+ import type { PluginOption } from "vite";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { exposeActionId } from "./plugins/expose-action-id.js";
5
+ import {
6
+ exposeInternalIds,
7
+ exposeRouterId,
8
+ } from "./plugins/expose-internal-ids.js";
9
+ import { useCacheTransform } from "./plugins/use-cache-transform.js";
10
+ import { clientRefDedup } from "./plugins/client-ref-dedup.js";
11
+ import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
12
+ import {
13
+ getExcludeDeps,
14
+ getPackageAliases,
15
+ } from "./utils/package-resolution.js";
16
+ import { findRouterFiles } from "../build/generate-route-types.js";
17
+ import { createVersionPlugin } from "./plugins/version-plugin.js";
18
+ import {
19
+ sharedEsbuildOptions,
20
+ createVirtualEntriesPlugin,
21
+ onwarn,
22
+ getManualChunks,
23
+ } from "./utils/shared-utils.js";
24
+ import type { RangoOptions } from "./plugin-types.js";
25
+ import { printBanner, rangoVersion } from "./utils/banner.js";
26
+ import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
27
+ import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
28
+ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
29
+
30
+ /**
31
+ * Vite plugin for @rangojs/router.
32
+ *
33
+ * Includes @vitejs/plugin-rsc and all necessary transforms for the router
34
+ * to function correctly with React Server Components.
35
+ *
36
+ * @example Node.js (default)
37
+ * ```ts
38
+ * export default defineConfig({
39
+ * plugins: [react(), rango()],
40
+ * });
41
+ * ```
42
+ *
43
+ * @example Cloudflare Workers
44
+ * ```ts
45
+ * export default defineConfig({
46
+ * plugins: [
47
+ * react(),
48
+ * rango({ preset: 'cloudflare' }),
49
+ * cloudflare({ viteEnvironment: { name: 'rsc' } }),
50
+ * ],
51
+ * });
52
+ * ```
53
+ */
54
+ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
55
+ const resolvedOptions: RangoOptions = options ?? { preset: "node" };
56
+ const preset = resolvedOptions.preset ?? "node";
57
+ const showBanner = resolvedOptions.banner ?? true;
58
+
59
+ const plugins: PluginOption[] = [];
60
+
61
+ // Get package resolution info (workspace vs npm install)
62
+ const rangoAliases = getPackageAliases();
63
+ const excludeDeps = getExcludeDeps();
64
+
65
+ // Mutable ref for router path (node preset only).
66
+ // Set immediately when user-specified, or populated by the auto-discover
67
+ // config() hook using Vite's resolved root.
68
+ const routerRef: { path: string | undefined } = { path: undefined };
69
+
70
+ // Build-time prerendering is enabled for both presets.
71
+ // Collection runs in-process via the RSC dev environment runner during discoverRouters().
72
+ const prerenderEnabled = true;
73
+
74
+ if (preset === "cloudflare") {
75
+ // Cloudflare preset: configure entries for cloudflare worker setup
76
+ // Router is not needed here - worker.rsc.tsx imports it directly
77
+
78
+ // Dynamically import @vitejs/plugin-rsc
79
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
80
+
81
+ // Only client and ssr entries - rsc entry is handled by cloudflare plugin
82
+ // Always use virtual modules for cloudflare preset
83
+ const finalEntries: { client: string; ssr: string } = {
84
+ client: VIRTUAL_IDS.browser,
85
+ ssr: VIRTUAL_IDS.ssr,
86
+ };
87
+
88
+ plugins.push({
89
+ name: "@rangojs/router:cloudflare-integration",
90
+ enforce: "pre",
91
+
92
+ config() {
93
+ // Configure environments for cloudflare deployment
94
+ return {
95
+ // Exclude rsc-router modules from optimization to prevent module duplication
96
+ // This ensures the same Context instance is used by both browser entry and RSC proxy modules
97
+ optimizeDeps: {
98
+ exclude: excludeDeps,
99
+ esbuildOptions: sharedEsbuildOptions,
100
+ },
101
+ resolve: {
102
+ alias: rangoAliases,
103
+ },
104
+ build: {
105
+ rollupOptions: { onwarn },
106
+ },
107
+ environments: {
108
+ client: {
109
+ build: {
110
+ rollupOptions: {
111
+ output: {
112
+ manualChunks: getManualChunks,
113
+ },
114
+ },
115
+ },
116
+ // Pre-bundle rsc-html-stream to prevent discovery during first request
117
+ // Exclude rsc-router modules to ensure same Context instance
118
+ optimizeDeps: {
119
+ include: ["rsc-html-stream/client"],
120
+ exclude: excludeDeps,
121
+ esbuildOptions: sharedEsbuildOptions,
122
+ },
123
+ },
124
+ ssr: {
125
+ // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
126
+ build: {
127
+ outDir: "./dist/rsc/ssr",
128
+ },
129
+ resolve: {
130
+ // Ensure single React instance in SSR child environment
131
+ dedupe: ["react", "react-dom"],
132
+ },
133
+ // Pre-bundle SSR entry and React for proper module linking with childEnvironments
134
+ // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
135
+ optimizeDeps: {
136
+ entries: [finalEntries.ssr],
137
+ include: [
138
+ "react",
139
+ "react-dom",
140
+ "react-dom/server.edge",
141
+ "react-dom/static.edge",
142
+ "react/jsx-runtime",
143
+ "react/jsx-dev-runtime",
144
+ "rsc-html-stream/server",
145
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
146
+ ],
147
+ exclude: excludeDeps,
148
+ esbuildOptions: sharedEsbuildOptions,
149
+ },
150
+ },
151
+ rsc: {
152
+ // RSC environment needs exclude list and esbuild options
153
+ // Exclude rsc-router modules to prevent createContext in RSC environment
154
+ optimizeDeps: {
155
+ // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
156
+ include: [
157
+ "react",
158
+ "react/jsx-runtime",
159
+ "react/jsx-dev-runtime",
160
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
161
+ ],
162
+ exclude: excludeDeps,
163
+ esbuildOptions: sharedEsbuildOptions,
164
+ },
165
+ },
166
+ },
167
+ };
168
+ },
169
+
170
+ configResolved(config) {
171
+ if (showBanner) {
172
+ const mode =
173
+ config.command === "serve"
174
+ ? process.argv.includes("preview")
175
+ ? "preview"
176
+ : "dev"
177
+ : "build";
178
+ printBanner(mode, "cloudflare", rangoVersion);
179
+ }
180
+ },
181
+ });
182
+
183
+ plugins.push(createVirtualEntriesPlugin(finalEntries));
184
+
185
+ // Add RSC plugin with cloudflare-specific options
186
+ // Note: loadModuleDevProxy should NOT be used with childEnvironments
187
+ // since SSR runs in workerd alongside RSC
188
+ plugins.push(
189
+ rsc({
190
+ entries: finalEntries,
191
+ serverHandler: false,
192
+ }) as PluginOption,
193
+ );
194
+
195
+ // Deduplicate client references from third-party packages in dev mode.
196
+ // Prevents module duplication when server components import "use client"
197
+ // packages that are also imported directly by client components.
198
+ plugins.push(clientRefDedup());
199
+ } else {
200
+ // Auto-discover router using Vite's resolved root (not process.cwd())
201
+ plugins.push({
202
+ name: "@rangojs/router:auto-discover",
203
+ config(userConfig) {
204
+ if (routerRef.path) return;
205
+ const root = userConfig.root
206
+ ? resolve(process.cwd(), userConfig.root)
207
+ : process.cwd();
208
+ const candidates = findRouterFiles(root);
209
+ if (candidates.length === 1) {
210
+ const abs = candidates[0];
211
+ routerRef.path = (
212
+ abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
213
+ ).replaceAll("\\", "/");
214
+ } else if (candidates.length > 1) {
215
+ const list = candidates
216
+ .map(
217
+ (f) =>
218
+ " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
219
+ )
220
+ .join("\n");
221
+ throw new Error(`[rsc-router] Multiple routers found:\n${list}`);
222
+ }
223
+ // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
224
+ },
225
+ });
226
+
227
+ // Always use virtual entries for client, ssr, and rsc
228
+ const finalEntries = {
229
+ client: VIRTUAL_IDS.browser,
230
+ ssr: VIRTUAL_IDS.ssr,
231
+ rsc: VIRTUAL_IDS.rsc,
232
+ };
233
+
234
+ // Dynamically import @vitejs/plugin-rsc
235
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
236
+
237
+ let hasWarnedDuplicate = false;
238
+
239
+ plugins.push({
240
+ name: "@rangojs/router:rsc-integration",
241
+ enforce: "pre",
242
+
243
+ config() {
244
+ return {
245
+ optimizeDeps: {
246
+ exclude: excludeDeps,
247
+ esbuildOptions: sharedEsbuildOptions,
248
+ },
249
+ build: {
250
+ rollupOptions: { onwarn },
251
+ },
252
+ resolve: {
253
+ alias: rangoAliases,
254
+ },
255
+ environments: {
256
+ client: {
257
+ build: {
258
+ rollupOptions: {
259
+ output: {
260
+ manualChunks: getManualChunks,
261
+ },
262
+ },
263
+ },
264
+ optimizeDeps: {
265
+ include: [
266
+ "react",
267
+ "react-dom",
268
+ "react/jsx-runtime",
269
+ "react/jsx-dev-runtime",
270
+ "rsc-html-stream/client",
271
+ ],
272
+ exclude: excludeDeps,
273
+ esbuildOptions: sharedEsbuildOptions,
274
+ entries: [VIRTUAL_IDS.browser],
275
+ },
276
+ },
277
+ ssr: {
278
+ optimizeDeps: {
279
+ entries: [VIRTUAL_IDS.ssr],
280
+ include: [
281
+ "react",
282
+ "react-dom",
283
+ "react-dom/server.edge",
284
+ "react-dom/static.edge",
285
+ "react/jsx-runtime",
286
+ "react/jsx-dev-runtime",
287
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
288
+ ],
289
+ exclude: excludeDeps,
290
+ esbuildOptions: sharedEsbuildOptions,
291
+ },
292
+ },
293
+ rsc: {
294
+ optimizeDeps: {
295
+ entries: [VIRTUAL_IDS.rsc],
296
+ include: [
297
+ "react",
298
+ "react/jsx-runtime",
299
+ "react/jsx-dev-runtime",
300
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
301
+ ],
302
+ esbuildOptions: sharedEsbuildOptions,
303
+ },
304
+ },
305
+ },
306
+ };
307
+ },
308
+
309
+ configResolved(config) {
310
+ if (showBanner) {
311
+ const mode =
312
+ config.command === "serve"
313
+ ? process.argv.includes("preview")
314
+ ? "preview"
315
+ : "dev"
316
+ : "build";
317
+ printBanner(mode, "node", rangoVersion);
318
+ }
319
+
320
+ const rscMinimalCount = config.plugins.filter(
321
+ (p) => p.name === "rsc:minimal",
322
+ ).length;
323
+
324
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
325
+ hasWarnedDuplicate = true;
326
+ console.warn(
327
+ "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
328
+ "Remove rsc() from your vite config — rango() includes it automatically.",
329
+ );
330
+ }
331
+ },
332
+ });
333
+
334
+ // Add virtual entries plugin (RSC entry generated lazily from routerRef)
335
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
336
+
337
+ plugins.push(
338
+ rsc({
339
+ entries: finalEntries,
340
+ }) as PluginOption,
341
+ );
342
+
343
+ // Deduplicate client references from third-party packages in dev mode.
344
+ // Prevents module duplication when server components import "use client"
345
+ // packages that are also imported directly by client components.
346
+ plugins.push(clientRefDedup());
347
+ }
348
+
349
+ // Fix HMR for "use client" components.
350
+ //
351
+ // @vitejs/plugin-rsc's hotUpdate returns undefined for "use client" files
352
+ // in the RSC environment. Vite then tries to propagate through the RSC
353
+ // module graph, but the proxy module has no import.meta.hot.accept()
354
+ // boundary, causing a full page reload. The client env would handle it
355
+ // fine via React Refresh, but the RSC env's full-reload arrives first.
356
+ //
357
+ // Fix: in the RSC env, return [] for "use client" files to signal
358
+ // "handled, nothing to propagate". The client env is left alone so
359
+ // React Refresh processes the update normally.
360
+ plugins.push({
361
+ name: "@rangojs/router:client-component-hmr",
362
+ hotUpdate(ctx) {
363
+ const envName = this.environment?.name;
364
+ if (envName !== "rsc" && envName !== "ssr") return;
365
+
366
+ // Check if the changed file is a "use client" module
367
+ const file = ctx.file;
368
+ if (
369
+ !file.endsWith(".tsx") &&
370
+ !file.endsWith(".ts") &&
371
+ !file.endsWith(".jsx") &&
372
+ !file.endsWith(".js")
373
+ )
374
+ return;
375
+
376
+ try {
377
+ const source = readFileSync(file, "utf-8");
378
+ const trimmed = source.trimStart();
379
+ if (
380
+ trimmed.startsWith('"use client"') ||
381
+ trimmed.startsWith("'use client'")
382
+ ) {
383
+ // Consume the update in RSC/SSR envs. The proxy module was already
384
+ // re-transformed by the RSC plugin's hotUpdate. Without this, Vite
385
+ // tries to propagate through the RSC/SSR module graph where the proxy
386
+ // has no import.meta.hot.accept() boundary, triggering a full reload.
387
+ // The actual component update is handled by React Refresh in the
388
+ // client environment.
389
+ return [];
390
+ }
391
+ } catch {
392
+ // File deleted/moved during HMR, let default handling proceed
393
+ }
394
+ },
395
+ });
396
+
397
+ plugins.push(exposeActionId());
398
+
399
+ // "use cache" directive transform (enforce: "post"):
400
+ // Wraps exports with registerCachedFunction() for function-level caching.
401
+ plugins.push(useCacheTransform());
402
+
403
+ // Consolidated plugin for create* ID injection (enforce: "post"):
404
+ // loaders, handles, location state, and prerender handlers.
405
+ plugins.push(exposeInternalIds());
406
+
407
+ // Router ID injection runs at normal priority (no enforce) to avoid
408
+ // changing Vite's dep optimization timing.
409
+ plugins.push(exposeRouterId());
410
+
411
+ // Add version virtual module plugin for cache invalidation
412
+ plugins.push(createVersionPlugin());
413
+
414
+ // Entry path for discovery: user-specified value (if any) or undefined.
415
+ // Auto-discovered path is passed separately via routerRef.
416
+ // Cloudflare preset: deferred to configResolved (read from resolved Vite env config).
417
+ const discoveryEntryPath =
418
+ preset !== "cloudflare" ? routerRef.path : undefined;
419
+ // Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
420
+ const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
421
+
422
+ // Version injector: auto-injects VERSION and routes-manifest into the RSC entry.
423
+ // For cloudflare preset, the entry is resolved lazily in configResolved.
424
+ // For node preset, the virtual entry already includes these imports.
425
+ if (preset === "cloudflare") {
426
+ plugins.push(createVersionInjectorPlugin(undefined));
427
+ }
428
+
429
+ // Transform CJS vendor files to ESM for browser compatibility
430
+ // optimizeDeps.include doesn't work because the file is loaded after initial optimization
431
+ plugins.push(createCjsToEsmPlugin());
432
+
433
+ // Router discovery plugin for build-time manifest generation.
434
+ // For cloudflare, the entry is resolved lazily in configResolved from the RSC environment.
435
+ // For node, discoveryRouterRef provides the auto-discovered path when not user-specified.
436
+ plugins.push(
437
+ createRouterDiscoveryPlugin(discoveryEntryPath, {
438
+ routerPathRef: discoveryRouterRef,
439
+ enableBuildPrerender: prerenderEnabled,
440
+ staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
441
+ }),
442
+ );
443
+
444
+ return plugins;
445
+ }