@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc

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 (285) hide show
  1. package/README.md +196 -43
  2. package/dist/bin/rango.js +277 -99
  3. package/dist/testing/vitest.js +48 -0
  4. package/dist/vite/index.js +2779 -1064
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +57 -11
  8. package/skills/breadcrumbs/SKILL.md +3 -1
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +243 -21
  11. package/skills/caching/SKILL.md +155 -6
  12. package/skills/composability/SKILL.md +27 -2
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +229 -20
  16. package/skills/host-router/SKILL.md +45 -20
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +46 -4
  19. package/skills/layout/SKILL.md +28 -7
  20. package/skills/links/SKILL.md +249 -17
  21. package/skills/loader/SKILL.md +273 -53
  22. package/skills/middleware/SKILL.md +49 -12
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +27 -0
  26. package/skills/observability/SKILL.md +137 -0
  27. package/skills/parallel/SKILL.md +197 -6
  28. package/skills/prerender/SKILL.md +123 -100
  29. package/skills/rango/SKILL.md +242 -22
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +66 -9
  32. package/skills/route/SKILL.md +88 -4
  33. package/skills/router-setup/SKILL.md +90 -5
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/testing/SKILL.md +716 -0
  37. package/skills/typesafety/SKILL.md +329 -27
  38. package/skills/use-cache/SKILL.md +34 -5
  39. package/skills/view-transitions/SKILL.md +294 -0
  40. package/src/__augment-tests__/augment.ts +81 -0
  41. package/src/__augment-tests__/augmented.check.ts +117 -0
  42. package/src/__internal.ts +1 -1
  43. package/src/browser/action-coordinator.ts +53 -36
  44. package/src/browser/app-shell.ts +52 -0
  45. package/src/browser/app-version.ts +14 -0
  46. package/src/browser/event-controller.ts +91 -70
  47. package/src/browser/history-state.ts +21 -0
  48. package/src/browser/index.ts +3 -3
  49. package/src/browser/navigation-bridge.ts +102 -16
  50. package/src/browser/navigation-client.ts +164 -59
  51. package/src/browser/navigation-store.ts +75 -17
  52. package/src/browser/navigation-transaction.ts +21 -37
  53. package/src/browser/partial-update.ts +139 -38
  54. package/src/browser/prefetch/cache.ts +175 -15
  55. package/src/browser/prefetch/fetch.ts +180 -33
  56. package/src/browser/prefetch/queue.ts +123 -20
  57. package/src/browser/prefetch/resource-ready.ts +77 -0
  58. package/src/browser/rango-state.ts +53 -13
  59. package/src/browser/react/Link.tsx +81 -9
  60. package/src/browser/react/NavigationProvider.tsx +110 -33
  61. package/src/browser/react/context.ts +7 -2
  62. package/src/browser/react/filter-segment-order.ts +51 -7
  63. package/src/browser/react/index.ts +3 -0
  64. package/src/browser/react/location-state-shared.ts +175 -4
  65. package/src/browser/react/location-state.ts +39 -13
  66. package/src/browser/react/use-handle.ts +23 -64
  67. package/src/browser/react/use-navigation.ts +22 -2
  68. package/src/browser/react/use-params.ts +20 -8
  69. package/src/browser/react/use-reverse.ts +106 -0
  70. package/src/browser/react/use-router.ts +43 -10
  71. package/src/browser/react/use-segments.ts +11 -8
  72. package/src/browser/response-adapter.ts +25 -0
  73. package/src/browser/rsc-router.tsx +191 -74
  74. package/src/browser/scroll-restoration.ts +41 -14
  75. package/src/browser/segment-reconciler.ts +36 -9
  76. package/src/browser/segment-structure-assert.ts +2 -2
  77. package/src/browser/server-action-bridge.ts +31 -36
  78. package/src/browser/types.ts +57 -5
  79. package/src/build/collect-fallback-refs.ts +107 -0
  80. package/src/build/generate-manifest.ts +65 -40
  81. package/src/build/generate-route-types.ts +5 -0
  82. package/src/build/index.ts +2 -0
  83. package/src/build/route-trie.ts +52 -25
  84. package/src/build/route-types/codegen.ts +4 -4
  85. package/src/build/route-types/include-resolution.ts +9 -2
  86. package/src/build/route-types/per-module-writer.ts +7 -4
  87. package/src/build/route-types/router-processing.ts +278 -88
  88. package/src/build/route-types/scan-filter.ts +9 -2
  89. package/src/build/route-types/source-scan.ts +118 -0
  90. package/src/build/runtime-discovery.ts +9 -20
  91. package/src/cache/cache-runtime.ts +15 -11
  92. package/src/cache/cache-scope.ts +76 -49
  93. package/src/cache/cf/cf-cache-store.ts +501 -18
  94. package/src/cache/cf/index.ts +5 -1
  95. package/src/cache/document-cache.ts +17 -7
  96. package/src/cache/index.ts +1 -0
  97. package/src/cache/taint.ts +55 -0
  98. package/src/client.rsc.tsx +3 -0
  99. package/src/client.tsx +94 -238
  100. package/src/context-var.ts +72 -2
  101. package/src/debug.ts +2 -2
  102. package/src/decode-loader-results.ts +36 -0
  103. package/src/errors.ts +30 -1
  104. package/src/handle.ts +65 -12
  105. package/src/host/index.ts +2 -2
  106. package/src/host/router.ts +129 -57
  107. package/src/host/types.ts +31 -2
  108. package/src/host/utils.ts +1 -1
  109. package/src/href-client.ts +140 -20
  110. package/src/index.rsc.ts +12 -5
  111. package/src/index.ts +61 -11
  112. package/src/loader-store.ts +500 -0
  113. package/src/loader.rsc.ts +2 -5
  114. package/src/loader.ts +3 -10
  115. package/src/missing-id-error.ts +68 -0
  116. package/src/outlet-context.ts +1 -1
  117. package/src/prerender/store.ts +5 -4
  118. package/src/prerender.ts +141 -80
  119. package/src/response-utils.ts +37 -0
  120. package/src/reverse.ts +65 -15
  121. package/src/route-content-wrapper.tsx +6 -28
  122. package/src/route-definition/dsl-helpers.ts +435 -260
  123. package/src/route-definition/helper-factories.ts +29 -139
  124. package/src/route-definition/helpers-types.ts +110 -34
  125. package/src/route-definition/index.ts +3 -0
  126. package/src/route-definition/redirect.ts +11 -3
  127. package/src/route-definition/resolve-handler-use.ts +155 -0
  128. package/src/route-definition/use-item-types.ts +32 -0
  129. package/src/route-map-builder.ts +7 -1
  130. package/src/route-types.ts +37 -41
  131. package/src/router/basename.ts +14 -0
  132. package/src/router/content-negotiation.ts +113 -1
  133. package/src/router/error-handling.ts +1 -1
  134. package/src/router/find-match.ts +4 -2
  135. package/src/router/handler-context.ts +77 -38
  136. package/src/router/intercept-resolution.ts +15 -22
  137. package/src/router/lazy-includes.ts +12 -9
  138. package/src/router/loader-resolution.ts +174 -22
  139. package/src/router/logging.ts +5 -2
  140. package/src/router/manifest.ts +31 -16
  141. package/src/router/match-api.ts +128 -192
  142. package/src/router/match-handlers.ts +63 -20
  143. package/src/router/match-middleware/background-revalidation.ts +30 -2
  144. package/src/router/match-middleware/cache-lookup.ts +136 -106
  145. package/src/router/match-middleware/cache-store.ts +54 -10
  146. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  147. package/src/router/match-middleware/segment-resolution.ts +61 -5
  148. package/src/router/match-result.ts +125 -10
  149. package/src/router/metrics.ts +7 -2
  150. package/src/router/middleware-types.ts +21 -34
  151. package/src/router/middleware.ts +103 -90
  152. package/src/router/navigation-snapshot.ts +182 -0
  153. package/src/router/pattern-matching.ts +101 -17
  154. package/src/router/prerender-match.ts +110 -10
  155. package/src/router/preview-match.ts +32 -102
  156. package/src/router/request-classification.ts +286 -0
  157. package/src/router/revalidation.ts +58 -2
  158. package/src/router/route-snapshot.ts +245 -0
  159. package/src/router/router-context.ts +6 -1
  160. package/src/router/router-interfaces.ts +77 -28
  161. package/src/router/router-options.ts +76 -11
  162. package/src/router/router-registry.ts +2 -5
  163. package/src/router/segment-resolution/fresh.ts +223 -24
  164. package/src/router/segment-resolution/helpers.ts +29 -24
  165. package/src/router/segment-resolution/loader-cache.ts +1 -0
  166. package/src/router/segment-resolution/revalidation.ts +466 -285
  167. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  168. package/src/router/segment-wrappers.ts +2 -0
  169. package/src/router/substitute-pattern-params.ts +56 -0
  170. package/src/router/telemetry.ts +99 -0
  171. package/src/router/trie-matching.ts +18 -13
  172. package/src/router/types.ts +9 -0
  173. package/src/router/url-params.ts +49 -0
  174. package/src/router.ts +91 -23
  175. package/src/rsc/handler-context.ts +2 -2
  176. package/src/rsc/handler.ts +440 -381
  177. package/src/rsc/helpers.ts +91 -43
  178. package/src/rsc/index.ts +1 -1
  179. package/src/rsc/loader-fetch.ts +23 -3
  180. package/src/rsc/manifest-init.ts +5 -1
  181. package/src/rsc/origin-guard.ts +28 -10
  182. package/src/rsc/progressive-enhancement.ts +18 -2
  183. package/src/rsc/response-route-handler.ts +46 -53
  184. package/src/rsc/rsc-rendering.ts +41 -48
  185. package/src/rsc/runtime-warnings.ts +9 -10
  186. package/src/rsc/server-action.ts +25 -37
  187. package/src/rsc/ssr-setup.ts +18 -2
  188. package/src/rsc/types.ts +17 -3
  189. package/src/search-params.ts +4 -4
  190. package/src/segment-content-promise.ts +67 -0
  191. package/src/segment-loader-promise.ts +122 -0
  192. package/src/segment-system.tsx +219 -67
  193. package/src/serialize.ts +243 -0
  194. package/src/server/context.ts +277 -61
  195. package/src/server/cookie-store.ts +28 -4
  196. package/src/server/handle-store.ts +19 -0
  197. package/src/server/loader-registry.ts +9 -8
  198. package/src/server/request-context.ts +204 -60
  199. package/src/ssr/index.tsx +9 -1
  200. package/src/static-handler.ts +19 -7
  201. package/src/testing/cache-status.ts +166 -0
  202. package/src/testing/collect-handle.ts +63 -0
  203. package/src/testing/dispatch.ts +440 -0
  204. package/src/testing/dom.entry.ts +22 -0
  205. package/src/testing/e2e/fixture.ts +154 -0
  206. package/src/testing/e2e/index.ts +149 -0
  207. package/src/testing/e2e/matchers.ts +51 -0
  208. package/src/testing/e2e/page-helpers.ts +272 -0
  209. package/src/testing/e2e/parity.ts +306 -0
  210. package/src/testing/e2e/server.ts +183 -0
  211. package/src/testing/flight-matchers.ts +104 -0
  212. package/src/testing/flight-runtime.d.ts +21 -0
  213. package/src/testing/flight.entry.ts +22 -0
  214. package/src/testing/flight.ts +182 -0
  215. package/src/testing/generated-routes.ts +223 -0
  216. package/src/testing/index.ts +106 -0
  217. package/src/testing/internal/context.ts +255 -0
  218. package/src/testing/render-route.tsx +565 -0
  219. package/src/testing/run-loader.ts +296 -0
  220. package/src/testing/run-middleware.ts +179 -0
  221. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  222. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  223. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  224. package/src/testing/vitest-stubs/version.ts +5 -0
  225. package/src/testing/vitest.ts +183 -0
  226. package/src/types/cache-types.ts +4 -4
  227. package/src/types/global-namespace.ts +39 -26
  228. package/src/types/handler-context.ts +194 -72
  229. package/src/types/index.ts +1 -0
  230. package/src/types/loader-types.ts +41 -15
  231. package/src/types/request-scope.ts +126 -0
  232. package/src/types/route-entry.ts +19 -1
  233. package/src/types/segments.ts +37 -1
  234. package/src/urls/include-helper.ts +34 -67
  235. package/src/urls/index.ts +0 -3
  236. package/src/urls/path-helper-types.ts +50 -9
  237. package/src/urls/path-helper.ts +63 -63
  238. package/src/urls/pattern-types.ts +48 -19
  239. package/src/urls/response-types.ts +25 -22
  240. package/src/urls/type-extraction.ts +26 -116
  241. package/src/urls/urls-function.ts +1 -5
  242. package/src/use-loader.tsx +487 -44
  243. package/src/vite/debug.ts +185 -0
  244. package/src/vite/discovery/bundle-postprocess.ts +34 -37
  245. package/src/vite/discovery/discover-routers.ts +105 -51
  246. package/src/vite/discovery/discovery-errors.ts +194 -0
  247. package/src/vite/discovery/gate-state.ts +171 -0
  248. package/src/vite/discovery/prerender-collection.ts +188 -93
  249. package/src/vite/discovery/route-types-writer.ts +40 -84
  250. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  251. package/src/vite/discovery/state.ts +46 -6
  252. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  253. package/src/vite/index.ts +6 -0
  254. package/src/vite/plugin-types.ts +111 -72
  255. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  256. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  257. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  258. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  259. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  260. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  261. package/src/vite/plugins/expose-action-id.ts +55 -33
  262. package/src/vite/plugins/expose-id-utils.ts +24 -8
  263. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  264. package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
  265. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  266. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  267. package/src/vite/plugins/expose-internal-ids.ts +544 -317
  268. package/src/vite/plugins/performance-tracks.ts +92 -0
  269. package/src/vite/plugins/refresh-cmd.ts +88 -26
  270. package/src/vite/plugins/use-cache-transform.ts +65 -50
  271. package/src/vite/plugins/version-injector.ts +39 -23
  272. package/src/vite/plugins/version-plugin.ts +72 -3
  273. package/src/vite/plugins/virtual-entries.ts +2 -2
  274. package/src/vite/rango.ts +265 -226
  275. package/src/vite/router-discovery.ts +920 -137
  276. package/src/vite/utils/ast-handler-extract.ts +15 -15
  277. package/src/vite/utils/banner.ts +4 -4
  278. package/src/vite/utils/bundle-analysis.ts +4 -2
  279. package/src/vite/utils/client-chunks.ts +190 -0
  280. package/src/vite/utils/forward-user-plugins.ts +193 -0
  281. package/src/vite/utils/manifest-utils.ts +21 -5
  282. package/src/vite/utils/package-resolution.ts +41 -1
  283. package/src/vite/utils/prerender-utils.ts +38 -5
  284. package/src/vite/utils/shared-utils.ts +109 -27
  285. package/src/browser/action-response-classifier.ts +0 -99
package/src/vite/rango.ts CHANGED
@@ -12,27 +12,30 @@ import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
12
12
  import {
13
13
  getExcludeDeps,
14
14
  getPackageAliases,
15
+ getPublishedPackageName,
16
+ getVendorAliases,
15
17
  } from "./utils/package-resolution.js";
16
- import {
17
- createScanFilter,
18
- findRouterFiles,
19
- } from "../build/generate-route-types.js";
18
+ import { findRouterFiles } from "../build/generate-route-types.js";
20
19
  import { createVersionPlugin } from "./plugins/version-plugin.js";
21
20
  import {
22
- sharedEsbuildOptions,
21
+ sharedRolldownOptions,
23
22
  createVirtualEntriesPlugin,
24
23
  onwarn,
25
24
  getManualChunks,
26
25
  } from "./utils/shared-utils.js";
27
- import type {
28
- RangoOptions,
29
- RangoNodeOptions,
30
- RscPluginOptions,
31
- } from "./plugin-types.js";
26
+ import {
27
+ resolveClientChunks,
28
+ type ClientChunkContext,
29
+ } from "./utils/client-chunks.js";
30
+ import type { RangoOptions } from "./plugin-types.js";
32
31
  import { printBanner, rangoVersion } from "./utils/banner.js";
33
32
  import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
34
33
  import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
35
34
  import { createRouterDiscoveryPlugin } from "./router-discovery.js";
35
+ import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
36
+ import { createRangoDebugger, NS } from "./debug.js";
37
+
38
+ const debugConfig = createRangoDebugger(NS.config);
36
39
 
37
40
  /**
38
41
  * Vite plugin for @rangojs/router.
@@ -43,7 +46,7 @@ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
43
46
  * @example Node.js (default)
44
47
  * ```ts
45
48
  * export default defineConfig({
46
- * plugins: [react(), rango({ router: './src/router.tsx' })],
49
+ * plugins: [react(), rango()],
47
50
  * });
48
51
  * ```
49
52
  *
@@ -59,18 +62,57 @@ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
59
62
  * ```
60
63
  */
61
64
  export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
65
+ const rangoStart = performance.now();
62
66
  const resolvedOptions: RangoOptions = options ?? { preset: "node" };
63
67
  const preset = resolvedOptions.preset ?? "node";
64
68
  const showBanner = resolvedOptions.banner ?? true;
69
+ // Client-chunking strategy (per-route/per-feature splitting of the browser
70
+ // bundle). Defaults to the built-in directory strategy (`true`) pre-1.0; pass
71
+ // `clientChunks: false` to opt out. Resolved once and forwarded to
72
+ // @vitejs/plugin-rsc in both presets. The built-in strategy only splits where it
73
+ // recognizes a route structure, so this default is a no-op for flat / host-split
74
+ // apps and never duplicates the shared runtime.
75
+ const clientChunksOption = resolvedOptions.clientChunks ?? true;
76
+ // Shared context the built-in strategy reads at build time: the production
77
+ // hashes of registered error/notFound fallback modules (-> app-fallback).
78
+ // Populated by the discovery plugin in buildStart, before the client build
79
+ // invokes the strategy. Only wired when the built-in strategy is active; a
80
+ // custom function owns its own grouping.
81
+ const useBuiltInClientChunks = clientChunksOption === true;
82
+ const clientChunkCtx: ClientChunkContext | undefined = useBuiltInClientChunks
83
+ ? { fallbackRefs: new Set<string>() }
84
+ : undefined;
85
+ const clientChunks = resolveClientChunks(clientChunksOption, clientChunkCtx);
86
+ debugConfig?.("rango(%s) setup start", preset);
65
87
 
66
88
  const plugins: PluginOption[] = [];
67
89
 
68
- // Get package resolution info (workspace vs npm install)
69
- const rangoAliases = getPackageAliases();
70
- const excludeDeps = getExcludeDeps();
71
-
72
- // Track RSC entry path for version injection
73
- let rscEntryPath: string | null = null;
90
+ // Get package resolution info (workspace vs npm install).
91
+ // Vendor aliases redirect the bare plugin-rsc vendor specs (which plugin-rsc
92
+ // itself injects into optimizeDeps.include) to absolute paths resolved from
93
+ // this package — so strict-pnpm consumers don't hit "Failed to resolve
94
+ // dependency" warnings when those deps aren't hoisted to their app root.
95
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
96
+ const excludeDeps = [
97
+ ...getExcludeDeps(),
98
+ // plugin-rsc itself injects these into the client env's
99
+ // optimizeDeps.include, which overrides exclude for the dep's own
100
+ // pre-bundle entry. What exclude still controls is how *other*
101
+ // pre-bundled deps treat imports of these specs (external vs inlined)
102
+ // via esbuildCjsExternalPlugin. The cjs-to-esm transform in
103
+ // plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
104
+ // where client.browser's bare include fails to resolve and Vite ends up
105
+ // serving the raw CJS file at dev-serve time.
106
+ "@vitejs/plugin-rsc/browser",
107
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
108
+ ];
109
+
110
+ // Vite supports a nested `A > B` syntax in optimizeDeps.include that resolves
111
+ // B from A's location. We anchor transitive deps (rsc-html-stream,
112
+ // @vitejs/plugin-rsc/vendor/*) to @rangojs/router so pnpm consumers — where
113
+ // these aren't visible at the app root — can still pre-bundle them.
114
+ const pkg = getPublishedPackageName();
115
+ const nested = (spec: string) => `${pkg} > ${spec}`;
74
116
 
75
117
  // Mutable ref for router path (node preset only).
76
118
  // Set immediately when user-specified, or populated by the auto-discover
@@ -106,10 +148,18 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
106
148
  // This ensures the same Context instance is used by both browser entry and RSC proxy modules
107
149
  optimizeDeps: {
108
150
  exclude: excludeDeps,
109
- esbuildOptions: sharedEsbuildOptions,
151
+ rolldownOptions: sharedRolldownOptions,
110
152
  },
111
153
  resolve: {
112
154
  alias: rangoAliases,
155
+ // Force a single React/React-DOM copy across all three RSC
156
+ // environments. RSC requires exactly one react/react-dom instance
157
+ // per environment runtime; consumer install topologies (pnpm
158
+ // strict layout, experimental React pins, third-party "use client"
159
+ // packages) can otherwise resolve duplicate copies, causing
160
+ // "Invalid hook call" / lost context. Child environments inherit
161
+ // this root dedupe, and Vite merges it with any consumer dedupe.
162
+ dedupe: ["react", "react-dom"],
113
163
  },
114
164
  build: {
115
165
  rollupOptions: { onwarn },
@@ -118,6 +168,14 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
118
168
  client: {
119
169
  build: {
120
170
  rollupOptions: {
171
+ // FILE_NAME_CONFLICT (and any other client-build warning) is
172
+ // emitted by the CLIENT environment build, which consults THIS
173
+ // env's onwarn -- Vite 8's environment builds do NOT propagate
174
+ // the top-level build.rollupOptions.onwarn into the client env.
175
+ // Wire it here so the suppression runs where the conflicts
176
+ // originate (the top-level handler is invoked 0x for these; the
177
+ // client-env handler is invoked for all of them).
178
+ onwarn,
121
179
  output: {
122
180
  manualChunks: getManualChunks,
123
181
  },
@@ -126,9 +184,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
126
184
  // Pre-bundle rsc-html-stream to prevent discovery during first request
127
185
  // Exclude rsc-router modules to ensure same Context instance
128
186
  optimizeDeps: {
129
- include: ["rsc-html-stream/client"],
187
+ include: [nested("rsc-html-stream/client")],
130
188
  exclude: excludeDeps,
131
- esbuildOptions: sharedEsbuildOptions,
189
+ rolldownOptions: sharedRolldownOptions,
132
190
  },
133
191
  },
134
192
  ssr: {
@@ -136,10 +194,6 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
136
194
  build: {
137
195
  outDir: "./dist/rsc/ssr",
138
196
  },
139
- resolve: {
140
- // Ensure single React instance in SSR child environment
141
- dedupe: ["react", "react-dom"],
142
- },
143
197
  // Pre-bundle SSR entry and React for proper module linking with childEnvironments
144
198
  // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
145
199
  optimizeDeps: {
@@ -151,11 +205,13 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
151
205
  "react-dom/static.edge",
152
206
  "react/jsx-runtime",
153
207
  "react/jsx-dev-runtime",
154
- "rsc-html-stream/server",
155
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
208
+ nested("rsc-html-stream/server"),
209
+ nested(
210
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
211
+ ),
156
212
  ],
157
213
  exclude: excludeDeps,
158
- esbuildOptions: sharedEsbuildOptions,
214
+ rolldownOptions: sharedRolldownOptions,
159
215
  },
160
216
  },
161
217
  rsc: {
@@ -167,10 +223,12 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
167
223
  "react",
168
224
  "react/jsx-runtime",
169
225
  "react/jsx-dev-runtime",
170
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
226
+ nested(
227
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
228
+ ),
171
229
  ],
172
230
  exclude: excludeDeps,
173
- esbuildOptions: sharedEsbuildOptions,
231
+ rolldownOptions: sharedRolldownOptions,
174
232
  },
175
233
  },
176
234
  },
@@ -192,6 +250,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
192
250
 
193
251
  plugins.push(createVirtualEntriesPlugin(finalEntries));
194
252
 
253
+ // Dev-only: RSDW client patch for React Performance Tracks
254
+ plugins.push(performanceTracksPlugin());
255
+
195
256
  // Add RSC plugin with cloudflare-specific options
196
257
  // Note: loadModuleDevProxy should NOT be used with childEnvironments
197
258
  // since SSR runs in workerd alongside RSC
@@ -199,6 +260,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
199
260
  rsc({
200
261
  entries: finalEntries,
201
262
  serverHandler: false,
263
+ clientChunks,
202
264
  }) as PluginOption,
203
265
  );
204
266
 
@@ -207,198 +269,172 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
207
269
  // packages that are also imported directly by client components.
208
270
  plugins.push(clientRefDedup());
209
271
  } else {
210
- // Node preset: full RSC plugin integration
211
- const nodeOptions = resolvedOptions as RangoNodeOptions;
272
+ // Auto-discover router using Vite's resolved root (not process.cwd())
273
+ plugins.push({
274
+ name: "@rangojs/router:auto-discover",
275
+ config(userConfig) {
276
+ if (routerRef.path) return;
277
+ const root = userConfig.root
278
+ ? resolve(process.cwd(), userConfig.root)
279
+ : process.cwd();
280
+ const candidates = findRouterFiles(root);
281
+ if (candidates.length === 1) {
282
+ const abs = candidates[0];
283
+ routerRef.path = (
284
+ abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
285
+ ).replaceAll("\\", "/");
286
+ } else if (candidates.length > 1) {
287
+ const list = candidates
288
+ .map(
289
+ (f) =>
290
+ " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
291
+ )
292
+ .join("\n");
293
+ throw new Error(`[rango] Multiple routers found:\n${list}`);
294
+ }
295
+ // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
296
+ },
297
+ });
212
298
 
213
- routerRef.path = nodeOptions.router;
299
+ // Always use virtual entries for client, ssr, and rsc
300
+ const finalEntries = {
301
+ client: VIRTUAL_IDS.browser,
302
+ ssr: VIRTUAL_IDS.ssr,
303
+ rsc: VIRTUAL_IDS.rsc,
304
+ };
214
305
 
215
- // Auto-discover router using Vite's resolved root (not process.cwd())
216
- if (!routerRef.path) {
217
- plugins.push({
218
- name: "@rangojs/router:auto-discover",
219
- config(userConfig) {
220
- if (routerRef.path) return;
221
- const root = userConfig.root
222
- ? resolve(process.cwd(), userConfig.root)
223
- : process.cwd();
224
- const filter = createScanFilter(root, {
225
- include: resolvedOptions.include,
226
- exclude: resolvedOptions.exclude,
227
- });
228
- const candidates = findRouterFiles(root, filter);
229
- if (candidates.length === 1) {
230
- const abs = candidates[0];
231
- routerRef.path = (
232
- abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
233
- ).replaceAll("\\", "/");
234
- } else if (candidates.length > 1) {
235
- const list = candidates
236
- .map(
237
- (f) =>
238
- " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
239
- )
240
- .join("\n");
241
- throw new Error(
242
- `[rsc-router] Multiple routers found. Specify \`router\` to choose one:\n${list}`,
243
- );
244
- }
245
- // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
246
- },
247
- });
248
- }
249
-
250
- const rscOption = nodeOptions.rsc ?? true;
251
-
252
- // Add RSC plugin by default (can be disabled with rsc: false)
253
- if (rscOption !== false) {
254
- // Dynamically import @vitejs/plugin-rsc
255
- const { default: rsc } = await import("@vitejs/plugin-rsc");
256
-
257
- // Resolve entry paths: use explicit config or virtual modules
258
- const userEntries =
259
- typeof rscOption === "boolean" ? {} : rscOption.entries || {};
260
- const finalEntries = {
261
- client: userEntries.client ?? VIRTUAL_IDS.browser,
262
- ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
263
- rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc,
264
- };
265
-
266
- // Track RSC entry for version injection (only if custom entry provided)
267
- rscEntryPath = userEntries.rsc ?? null;
268
-
269
- // Create wrapper plugin that checks for duplicates
270
- let hasWarnedDuplicate = false;
271
-
272
- plugins.push({
273
- name: "@rangojs/router:rsc-integration",
274
- enforce: "pre",
275
-
276
- config() {
277
- // Configure environments for RSC
278
- // When using virtual entries, we need to explicitly configure optimizeDeps
279
- // so Vite pre-bundles React before processing the virtual modules.
280
- // Without this, the dep optimizer may run multiple times with different hashes,
281
- // causing React instance mismatches.
282
- const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
283
- const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
284
- const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
285
-
286
- return {
287
- // Exclude rsc-router modules from optimization to prevent module duplication
288
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
289
- optimizeDeps: {
290
- exclude: excludeDeps,
291
- esbuildOptions: sharedEsbuildOptions,
292
- },
293
- build: {
294
- rollupOptions: { onwarn },
295
- },
296
- resolve: {
297
- alias: rangoAliases,
298
- },
299
- environments: {
300
- client: {
301
- build: {
302
- rollupOptions: {
303
- output: {
304
- manualChunks: getManualChunks,
305
- },
306
+ // Dynamically import @vitejs/plugin-rsc
307
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
308
+
309
+ let hasWarnedDuplicate = false;
310
+
311
+ plugins.push({
312
+ name: "@rangojs/router:rsc-integration",
313
+ enforce: "pre",
314
+
315
+ config() {
316
+ return {
317
+ optimizeDeps: {
318
+ exclude: excludeDeps,
319
+ rolldownOptions: sharedRolldownOptions,
320
+ },
321
+ build: {
322
+ rollupOptions: { onwarn },
323
+ },
324
+ resolve: {
325
+ alias: rangoAliases,
326
+ // Force a single React/React-DOM copy across all three RSC
327
+ // environments. RSC requires exactly one react/react-dom instance
328
+ // per environment runtime; consumer install topologies (pnpm
329
+ // strict layout, experimental React pins, third-party "use client"
330
+ // packages) can otherwise resolve duplicate copies, causing
331
+ // "Invalid hook call" / lost context. Child environments inherit
332
+ // this root dedupe, and Vite merges it with any consumer dedupe.
333
+ dedupe: ["react", "react-dom"],
334
+ },
335
+ environments: {
336
+ client: {
337
+ build: {
338
+ rollupOptions: {
339
+ // FILE_NAME_CONFLICT (and any other client-build warning) is
340
+ // emitted by the CLIENT environment build, which consults THIS
341
+ // env's onwarn -- Vite 8's environment builds do NOT propagate
342
+ // the top-level build.rollupOptions.onwarn into the client env.
343
+ // Wire it here so the suppression runs where the conflicts
344
+ // originate (the top-level handler is invoked 0x for these; the
345
+ // client-env handler is invoked for all of them).
346
+ onwarn,
347
+ output: {
348
+ manualChunks: getManualChunks,
306
349
  },
307
350
  },
308
- // Always exclude rsc-router modules, conditionally add virtual entry
309
- optimizeDeps: {
310
- // Pre-bundle React and rsc-html-stream to prevent late discovery
311
- // triggering ERR_OUTDATED_OPTIMIZED_DEP on cold starts
312
- include: [
313
- "react",
314
- "react-dom",
315
- "react/jsx-runtime",
316
- "react/jsx-dev-runtime",
317
- "rsc-html-stream/client",
318
- ],
319
- exclude: excludeDeps,
320
- esbuildOptions: sharedEsbuildOptions,
321
- ...(useVirtualClient && {
322
- // Tell Vite to scan the virtual entry for dependencies
323
- entries: [VIRTUAL_IDS.browser],
324
- }),
325
- },
326
351
  },
327
- ...(useVirtualSSR && {
328
- ssr: {
329
- optimizeDeps: {
330
- entries: [VIRTUAL_IDS.ssr],
331
- // Pre-bundle all SSR deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
332
- include: [
333
- "react",
334
- "react-dom",
335
- "react-dom/server.edge",
336
- "react-dom/static.edge",
337
- "react/jsx-runtime",
338
- "react/jsx-dev-runtime",
339
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
340
- ],
341
- exclude: excludeDeps,
342
- esbuildOptions: sharedEsbuildOptions,
343
- },
344
- },
345
- }),
346
- ...(useVirtualRSC && {
347
- rsc: {
348
- optimizeDeps: {
349
- entries: [VIRTUAL_IDS.rsc],
350
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
351
- include: [
352
- "react",
353
- "react/jsx-runtime",
354
- "react/jsx-dev-runtime",
355
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
356
- ],
357
- esbuildOptions: sharedEsbuildOptions,
358
- },
359
- },
360
- }),
352
+ optimizeDeps: {
353
+ include: [
354
+ "react",
355
+ "react-dom",
356
+ "react/jsx-runtime",
357
+ "react/jsx-dev-runtime",
358
+ nested("rsc-html-stream/client"),
359
+ ],
360
+ exclude: excludeDeps,
361
+ rolldownOptions: sharedRolldownOptions,
362
+ entries: [VIRTUAL_IDS.browser],
363
+ },
361
364
  },
362
- };
363
- },
364
-
365
- configResolved(config) {
366
- if (showBanner) {
367
- const mode =
368
- config.command === "serve"
369
- ? process.argv.includes("preview")
370
- ? "preview"
371
- : "dev"
372
- : "build";
373
- printBanner(mode, "node", rangoVersion);
374
- }
375
-
376
- // Count how many RSC base plugins there are (rsc:minimal is the main one)
377
- const rscMinimalCount = config.plugins.filter(
378
- (p) => p.name === "rsc:minimal",
379
- ).length;
380
-
381
- if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
382
- hasWarnedDuplicate = true;
383
- console.warn(
384
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
385
- "Remove rsc() from your config or use rango({ rsc: false }) for manual configuration.",
386
- );
387
- }
388
- },
389
- });
390
-
391
- // Add virtual entries plugin (RSC entry generated lazily from routerRef)
392
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
393
-
394
- // Add the RSC plugin directly
395
- // Cast to PluginOption to handle type differences between bundled vite types
396
- plugins.push(
397
- rsc({
398
- entries: finalEntries,
399
- }) as PluginOption,
400
- );
401
- }
365
+ ssr: {
366
+ optimizeDeps: {
367
+ entries: [VIRTUAL_IDS.ssr],
368
+ include: [
369
+ "react",
370
+ "react-dom",
371
+ "react-dom/server.edge",
372
+ "react-dom/static.edge",
373
+ "react/jsx-runtime",
374
+ "react/jsx-dev-runtime",
375
+ nested(
376
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
377
+ ),
378
+ ],
379
+ exclude: excludeDeps,
380
+ rolldownOptions: sharedRolldownOptions,
381
+ },
382
+ },
383
+ rsc: {
384
+ optimizeDeps: {
385
+ entries: [VIRTUAL_IDS.rsc],
386
+ include: [
387
+ "react",
388
+ "react/jsx-runtime",
389
+ "react/jsx-dev-runtime",
390
+ nested(
391
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
392
+ ),
393
+ ],
394
+ rolldownOptions: sharedRolldownOptions,
395
+ },
396
+ },
397
+ },
398
+ };
399
+ },
400
+
401
+ configResolved(config) {
402
+ if (showBanner) {
403
+ const mode =
404
+ config.command === "serve"
405
+ ? process.argv.includes("preview")
406
+ ? "preview"
407
+ : "dev"
408
+ : "build";
409
+ printBanner(mode, "node", rangoVersion);
410
+ }
411
+
412
+ const rscMinimalCount = config.plugins.filter(
413
+ (p) => p.name === "rsc:minimal",
414
+ ).length;
415
+
416
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
417
+ hasWarnedDuplicate = true;
418
+ console.warn(
419
+ "[rango] Duplicate @vitejs/plugin-rsc detected. " +
420
+ "Remove rsc() from your vite config — rango() includes it automatically.",
421
+ );
422
+ }
423
+ },
424
+ });
425
+
426
+ // Add virtual entries plugin (RSC entry generated lazily from routerRef)
427
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
428
+
429
+ // Dev-only: RSDW client patch for React Performance Tracks
430
+ plugins.push(performanceTracksPlugin());
431
+
432
+ plugins.push(
433
+ rsc({
434
+ entries: finalEntries,
435
+ clientChunks,
436
+ }) as PluginOption,
437
+ );
402
438
 
403
439
  // Deduplicate client references from third-party packages in dev mode.
404
440
  // Prevents module duplication when server components import "use client"
@@ -479,14 +515,11 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
479
515
  // Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
480
516
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
481
517
 
482
- // Version injector: auto-injects VERSION and routes-manifest into custom entry.rsc files.
483
- // Only applies when there's an explicit rscEntryPath or for cloudflare preset (resolved
484
- // lazily in configResolved). For node preset without a custom entry, the router file
485
- // must NOT be transformed — injecting routes-manifest there creates a circular dependency.
486
- const injectorEntryPath =
487
- rscEntryPath ?? (preset === "cloudflare" ? undefined : null);
488
- if (injectorEntryPath !== null) {
489
- plugins.push(createVersionInjectorPlugin(injectorEntryPath));
518
+ // Version injector: auto-injects VERSION and routes-manifest into the RSC entry.
519
+ // For cloudflare preset, the entry is resolved lazily in configResolved.
520
+ // For node preset, the virtual entry already includes these imports.
521
+ if (preset === "cloudflare") {
522
+ plugins.push(createVersionInjectorPlugin(undefined));
490
523
  }
491
524
 
492
525
  // Transform CJS vendor files to ESM for browser compatibility
@@ -500,11 +533,17 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
500
533
  createRouterDiscoveryPlugin(discoveryEntryPath, {
501
534
  routerPathRef: discoveryRouterRef,
502
535
  enableBuildPrerender: prerenderEnabled,
503
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
504
- include: resolvedOptions.include,
505
- exclude: resolvedOptions.exclude,
536
+ buildEnv: options?.buildEnv,
537
+ preset,
538
+ clientChunkCtx,
506
539
  }),
507
540
  );
508
541
 
542
+ debugConfig?.(
543
+ "rango(%s) setup done: %d plugin(s) (%sms)",
544
+ preset,
545
+ plugins.length,
546
+ (performance.now() - rangoStart).toFixed(1),
547
+ );
509
548
  return plugins;
510
549
  }