@rangojs/router 0.0.0-experimental.d20dd405 → 0.0.0-experimental.dacec167

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 (212) hide show
  1. package/README.md +8 -8
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +914 -485
  5. package/package.json +55 -11
  6. package/skills/bundle-analysis/SKILL.md +159 -0
  7. package/skills/cache-guide/SKILL.md +220 -30
  8. package/skills/caching/SKILL.md +116 -8
  9. package/skills/composability/SKILL.md +27 -2
  10. package/skills/document-cache/SKILL.md +78 -55
  11. package/skills/handler-use/SKILL.md +1 -1
  12. package/skills/hooks/SKILL.md +196 -21
  13. package/skills/host-router/SKILL.md +45 -20
  14. package/skills/intercept/SKILL.md +1 -4
  15. package/skills/layout/SKILL.md +4 -7
  16. package/skills/links/SKILL.md +22 -10
  17. package/skills/loader/SKILL.md +149 -6
  18. package/skills/middleware/SKILL.md +13 -9
  19. package/skills/migrate-nextjs/SKILL.md +1 -1
  20. package/skills/mime-routes/SKILL.md +27 -0
  21. package/skills/observability/SKILL.md +137 -0
  22. package/skills/parallel/SKILL.md +3 -6
  23. package/skills/prerender/SKILL.md +14 -33
  24. package/skills/rango/SKILL.md +242 -26
  25. package/skills/react-compiler/SKILL.md +168 -0
  26. package/skills/response-routes/SKILL.md +58 -9
  27. package/skills/route/SKILL.md +9 -4
  28. package/skills/router-setup/SKILL.md +3 -3
  29. package/skills/server-actions/SKILL.md +53 -41
  30. package/skills/testing/SKILL.md +778 -0
  31. package/skills/typesafety/SKILL.md +310 -26
  32. package/skills/use-cache/SKILL.md +34 -5
  33. package/skills/view-transitions/SKILL.md +85 -3
  34. package/src/__augment-tests__/augment.ts +81 -0
  35. package/src/__augment-tests__/augmented.check.ts +117 -0
  36. package/src/browser/action-coordinator.ts +53 -36
  37. package/src/browser/event-controller.ts +42 -66
  38. package/src/browser/history-state.ts +21 -0
  39. package/src/browser/index.ts +3 -3
  40. package/src/browser/navigation-bridge.ts +9 -67
  41. package/src/browser/navigation-client.ts +12 -15
  42. package/src/browser/navigation-store.ts +7 -8
  43. package/src/browser/navigation-transaction.ts +10 -28
  44. package/src/browser/partial-update.ts +8 -16
  45. package/src/browser/react/NavigationProvider.tsx +55 -65
  46. package/src/browser/react/location-state-shared.ts +175 -4
  47. package/src/browser/react/location-state.ts +39 -13
  48. package/src/browser/react/use-handle.ts +17 -9
  49. package/src/browser/react/use-params.ts +3 -4
  50. package/src/browser/react/use-reverse.ts +19 -12
  51. package/src/browser/react/use-router.ts +14 -1
  52. package/src/browser/response-adapter.ts +25 -0
  53. package/src/browser/rsc-router.tsx +30 -16
  54. package/src/browser/scroll-restoration.ts +30 -25
  55. package/src/browser/segment-structure-assert.ts +2 -2
  56. package/src/browser/server-action-bridge.ts +23 -30
  57. package/src/browser/types.ts +2 -0
  58. package/src/build/collect-fallback-refs.ts +107 -0
  59. package/src/build/generate-manifest.ts +60 -35
  60. package/src/build/generate-route-types.ts +2 -0
  61. package/src/build/index.ts +2 -0
  62. package/src/build/route-types/codegen.ts +4 -4
  63. package/src/build/route-types/include-resolution.ts +1 -1
  64. package/src/build/route-types/per-module-writer.ts +7 -4
  65. package/src/build/route-types/router-processing.ts +55 -14
  66. package/src/build/route-types/scan-filter.ts +1 -1
  67. package/src/build/route-types/source-scan.ts +118 -0
  68. package/src/build/runtime-discovery.ts +9 -20
  69. package/src/cache/cache-scope.ts +28 -42
  70. package/src/cache/cf/cf-cache-store.ts +49 -6
  71. package/src/client.tsx +5 -7
  72. package/src/context-var.ts +5 -5
  73. package/src/decode-loader-results.ts +36 -0
  74. package/src/errors.ts +30 -1
  75. package/src/handle.ts +26 -13
  76. package/src/host/index.ts +2 -2
  77. package/src/host/router.ts +129 -57
  78. package/src/host/types.ts +31 -2
  79. package/src/host/utils.ts +1 -1
  80. package/src/href-client.ts +136 -19
  81. package/src/index.rsc.ts +6 -4
  82. package/src/index.ts +13 -6
  83. package/src/loader-store.ts +500 -0
  84. package/src/loader.rsc.ts +21 -6
  85. package/src/loader.ts +3 -10
  86. package/src/missing-id-error.ts +68 -0
  87. package/src/prerender.ts +4 -4
  88. package/src/response-utils.ts +9 -0
  89. package/src/reverse.ts +16 -13
  90. package/src/route-content-wrapper.tsx +6 -28
  91. package/src/route-definition/dsl-helpers.ts +238 -263
  92. package/src/route-definition/helper-factories.ts +29 -139
  93. package/src/route-definition/helpers-types.ts +37 -14
  94. package/src/route-definition/use-item-types.ts +32 -0
  95. package/src/route-types.ts +19 -41
  96. package/src/router/basename.ts +14 -0
  97. package/src/router/content-negotiation.ts +15 -2
  98. package/src/router/error-handling.ts +1 -1
  99. package/src/router/intercept-resolution.ts +4 -18
  100. package/src/router/lazy-includes.ts +2 -2
  101. package/src/router/loader-resolution.ts +16 -2
  102. package/src/router/match-handlers.ts +62 -20
  103. package/src/router/match-middleware/cache-lookup.ts +44 -91
  104. package/src/router/match-middleware/cache-store.ts +3 -2
  105. package/src/router/match-result.ts +32 -30
  106. package/src/router/metrics.ts +1 -1
  107. package/src/router/middleware-types.ts +1 -1
  108. package/src/router/middleware.ts +46 -78
  109. package/src/router/prerender-match.ts +1 -1
  110. package/src/router/preview-match.ts +3 -1
  111. package/src/router/request-classification.ts +4 -28
  112. package/src/router/revalidation.ts +43 -1
  113. package/src/router/router-interfaces.ts +45 -28
  114. package/src/router/router-options.ts +40 -1
  115. package/src/router/router-registry.ts +2 -5
  116. package/src/router/segment-resolution/fresh.ts +19 -6
  117. package/src/router/segment-resolution/revalidation.ts +19 -6
  118. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  119. package/src/router/telemetry.ts +99 -0
  120. package/src/router/types.ts +8 -0
  121. package/src/router.ts +37 -21
  122. package/src/rsc/handler-context.ts +2 -2
  123. package/src/rsc/handler.ts +20 -65
  124. package/src/rsc/helpers.ts +22 -2
  125. package/src/rsc/index.ts +1 -1
  126. package/src/rsc/origin-guard.ts +28 -10
  127. package/src/rsc/response-route-handler.ts +32 -52
  128. package/src/rsc/rsc-rendering.ts +27 -53
  129. package/src/rsc/runtime-warnings.ts +9 -10
  130. package/src/rsc/server-action.ts +13 -37
  131. package/src/rsc/ssr-setup.ts +16 -0
  132. package/src/rsc/types.ts +2 -2
  133. package/src/search-params.ts +4 -4
  134. package/src/segment-system.tsx +64 -49
  135. package/src/serialize.ts +243 -0
  136. package/src/server/context.ts +118 -51
  137. package/src/server/cookie-store.ts +28 -4
  138. package/src/server/request-context.ts +10 -0
  139. package/src/static-handler.ts +1 -1
  140. package/src/testing/cache-status.ts +166 -0
  141. package/src/testing/collect-handle.ts +63 -0
  142. package/src/testing/dispatch.ts +440 -0
  143. package/src/testing/dom.entry.ts +22 -0
  144. package/src/testing/e2e/fixture.ts +154 -0
  145. package/src/testing/e2e/index.ts +149 -0
  146. package/src/testing/e2e/matchers.ts +51 -0
  147. package/src/testing/e2e/page-helpers.ts +272 -0
  148. package/src/testing/e2e/parity.ts +306 -0
  149. package/src/testing/e2e/server.ts +183 -0
  150. package/src/testing/flight-matchers.ts +104 -0
  151. package/src/testing/flight-runtime.d.ts +57 -0
  152. package/src/testing/flight-tree.ts +320 -0
  153. package/src/testing/flight.entry.ts +39 -0
  154. package/src/testing/flight.ts +197 -0
  155. package/src/testing/generated-routes.ts +223 -0
  156. package/src/testing/index.ts +106 -0
  157. package/src/testing/internal/context.ts +331 -0
  158. package/src/testing/internal/flight-client-globals.ts +30 -0
  159. package/src/testing/render-route.tsx +565 -0
  160. package/src/testing/run-loader.ts +341 -0
  161. package/src/testing/run-middleware.ts +188 -0
  162. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  163. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  164. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  165. package/src/testing/vitest-stubs/version.ts +5 -0
  166. package/src/testing/vitest.ts +270 -0
  167. package/src/types/global-namespace.ts +39 -26
  168. package/src/types/handler-context.ts +56 -11
  169. package/src/types/index.ts +1 -0
  170. package/src/types/segments.ts +18 -1
  171. package/src/urls/include-helper.ts +10 -53
  172. package/src/urls/index.ts +0 -3
  173. package/src/urls/path-helper-types.ts +11 -3
  174. package/src/urls/path-helper.ts +17 -52
  175. package/src/urls/pattern-types.ts +36 -19
  176. package/src/urls/response-types.ts +20 -19
  177. package/src/urls/type-extraction.ts +26 -116
  178. package/src/urls/urls-function.ts +1 -5
  179. package/src/use-loader.tsx +413 -42
  180. package/src/vite/debug.ts +1 -0
  181. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  182. package/src/vite/discovery/discover-routers.ts +70 -48
  183. package/src/vite/discovery/discovery-errors.ts +194 -0
  184. package/src/vite/discovery/prerender-collection.ts +19 -25
  185. package/src/vite/discovery/route-types-writer.ts +40 -84
  186. package/src/vite/discovery/state.ts +33 -0
  187. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  188. package/src/vite/index.ts +2 -0
  189. package/src/vite/plugin-types.ts +67 -0
  190. package/src/vite/plugins/cjs-to-esm.ts +3 -7
  191. package/src/vite/plugins/client-ref-hashing.ts +12 -1
  192. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
  193. package/src/vite/plugins/expose-action-id.ts +2 -2
  194. package/src/vite/plugins/expose-id-utils.ts +12 -8
  195. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  196. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  197. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  198. package/src/vite/plugins/expose-internal-ids.ts +47 -67
  199. package/src/vite/plugins/performance-tracks.ts +12 -16
  200. package/src/vite/plugins/use-cache-transform.ts +13 -11
  201. package/src/vite/plugins/version-injector.ts +2 -12
  202. package/src/vite/plugins/version-plugin.ts +59 -2
  203. package/src/vite/plugins/virtual-entries.ts +2 -2
  204. package/src/vite/rango.ts +67 -15
  205. package/src/vite/router-discovery.ts +208 -63
  206. package/src/vite/utils/ast-handler-extract.ts +15 -15
  207. package/src/vite/utils/bundle-analysis.ts +4 -2
  208. package/src/vite/utils/client-chunks.ts +190 -0
  209. package/src/vite/utils/forward-user-plugins.ts +193 -0
  210. package/src/vite/utils/manifest-utils.ts +21 -5
  211. package/src/vite/utils/shared-utils.ts +107 -26
  212. package/src/browser/action-response-classifier.ts +0 -99
@@ -18,7 +18,7 @@ function getClientModuleSignature(
18
18
  ): ClientModuleSignature | undefined {
19
19
  let program: any;
20
20
  try {
21
- program = parseAst(source, { jsx: true });
21
+ program = parseAst(source, { lang: "tsx" });
22
22
  } catch {
23
23
  return undefined;
24
24
  }
@@ -133,6 +133,7 @@ export function createVersionPlugin(): Plugin {
133
133
  let currentVersion = buildVersion;
134
134
  let isDev = false;
135
135
  let server: any = null;
136
+ let resolvedCacheDir: string | undefined;
136
137
  const clientModuleSignatures = new Map<string, ClientModuleSignature>();
137
138
 
138
139
  let versionCounter = 0;
@@ -140,7 +141,7 @@ export function createVersionPlugin(): Plugin {
140
141
  // Use timestamp + counter to guarantee uniqueness even when multiple
141
142
  // bumps happen within the same millisecond (e.g. cascading HMR events).
142
143
  currentVersion = Date.now().toString(16) + String(++versionCounter);
143
- console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
144
+ console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
144
145
 
145
146
  const rscEnv = server?.environments?.rsc;
146
147
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
@@ -157,6 +158,12 @@ export function createVersionPlugin(): Plugin {
157
158
 
158
159
  configResolved(config) {
159
160
  isDev = config.command === "serve";
161
+ // Capture the resolved cacheDir so we can ignore optimizer-output
162
+ // writes inside it. Vite resolves cacheDir against the project root,
163
+ // so this is a stable absolute path for the lifetime of the server.
164
+ resolvedCacheDir = config.cacheDir
165
+ ? String(config.cacheDir).replace(/\\/g, "/")
166
+ : undefined;
160
167
  },
161
168
 
162
169
  configureServer(devServer) {
@@ -214,6 +221,14 @@ export function createVersionPlugin(): Plugin {
214
221
 
215
222
  if (!isRscModule) return;
216
223
 
224
+ // Skip Vite's own pre-bundled dep cache writes. The optimizer rewrites
225
+ // files inside the configured `cacheDir` on every discovery cycle
226
+ // (and when other dev servers under the same cwd populate their own
227
+ // isolated cache dirs). These are not user-source changes, so bumping
228
+ // the app version on them produces spurious version mismatches that
229
+ // surface as forced reloads on in-flight actions.
230
+ if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
231
+
217
232
  // Skip re-bumping when the version virtual module itself is invalidated
218
233
  // (our own bumpVersion() invalidates it, which re-triggers hotUpdate).
219
234
  if (
@@ -264,3 +279,45 @@ export function createVersionPlugin(): Plugin {
264
279
  },
265
280
  };
266
281
  }
282
+
283
+ /**
284
+ * Match Vite's pre-bundled dep cache directories. These paths are rewritten
285
+ * by the dep optimizer (and by isolated test fixtures sharing the same cwd),
286
+ * not by user source changes, so they should not bump the app version (which
287
+ * would force a client reload mid-request).
288
+ *
289
+ * Two checks:
290
+ * 1. Anything inside the resolved `cacheDir` (precise — covers custom paths
291
+ * like the `RANGO_E2E_VITE_CACHE_DIR` overrides in the test fixtures).
292
+ * 2. Heuristic match for any `node_modules/.vite*` directory or a
293
+ * `.vite-isolated/` segment anywhere in the path. This catches the
294
+ * *other* dev servers in the same cwd whose cacheDir we cannot read
295
+ * (we only see config of the server we're attached to).
296
+ */
297
+ export function isViteDepCachePath(
298
+ filePath: string | undefined,
299
+ cacheDir?: string,
300
+ ): boolean {
301
+ if (!filePath) return false;
302
+ const normalized = filePath.replace(/\\/g, "/");
303
+
304
+ if (cacheDir) {
305
+ const normalizedCacheDir = cacheDir.replace(/\\/g, "/").replace(/\/+$/, "");
306
+ if (
307
+ normalized === normalizedCacheDir ||
308
+ normalized.startsWith(normalizedCacheDir + "/")
309
+ ) {
310
+ return true;
311
+ }
312
+ }
313
+
314
+ // Vite/optimizer convention: cache dirs always sit directly under
315
+ // `node_modules/` and start with `.vite` (e.g. `.vite`, `.vite-temp`,
316
+ // `.vite_rango_generate`, `.vite-e2e-test-app`). The `/.vite-isolated/`
317
+ // segment covers the test-fixture pattern that places the cache outside
318
+ // node_modules.
319
+ return (
320
+ /\/node_modules\/\.vite[^/]*\//.test(normalized) ||
321
+ normalized.includes("/.vite-isolated/")
322
+ );
323
+ }
@@ -14,7 +14,7 @@ import {
14
14
  import { createElement, StrictMode } from "react";
15
15
  import { hydrateRoot } from "react-dom/client";
16
16
  import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
17
- import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
17
+ import { initBrowserApp, Rango } from "@rangojs/router/browser";
18
18
 
19
19
  async function initializeApp() {
20
20
  const deps = {
@@ -29,7 +29,7 @@ async function initializeApp() {
29
29
 
30
30
  hydrateRoot(
31
31
  document,
32
- createElement(StrictMode, null, createElement(RSCRouter))
32
+ createElement(StrictMode, null, createElement(Rango))
33
33
  );
34
34
  }
35
35
 
package/src/vite/rango.ts CHANGED
@@ -18,11 +18,15 @@ import {
18
18
  import { findRouterFiles } from "../build/generate-route-types.js";
19
19
  import { createVersionPlugin } from "./plugins/version-plugin.js";
20
20
  import {
21
- sharedEsbuildOptions,
21
+ sharedRolldownOptions,
22
22
  createVirtualEntriesPlugin,
23
23
  onwarn,
24
24
  getManualChunks,
25
25
  } from "./utils/shared-utils.js";
26
+ import {
27
+ resolveClientChunks,
28
+ type ClientChunkContext,
29
+ } from "./utils/client-chunks.js";
26
30
  import type { RangoOptions } from "./plugin-types.js";
27
31
  import { printBanner, rangoVersion } from "./utils/banner.js";
28
32
  import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
@@ -62,6 +66,23 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
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);
65
86
  debugConfig?.("rango(%s) setup start", preset);
66
87
 
67
88
  const plugins: PluginOption[] = [];
@@ -127,10 +148,18 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
127
148
  // This ensures the same Context instance is used by both browser entry and RSC proxy modules
128
149
  optimizeDeps: {
129
150
  exclude: excludeDeps,
130
- esbuildOptions: sharedEsbuildOptions,
151
+ rolldownOptions: sharedRolldownOptions,
131
152
  },
132
153
  resolve: {
133
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"],
134
163
  },
135
164
  build: {
136
165
  rollupOptions: { onwarn },
@@ -139,6 +168,14 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
139
168
  client: {
140
169
  build: {
141
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,
142
179
  output: {
143
180
  manualChunks: getManualChunks,
144
181
  },
@@ -149,7 +186,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
149
186
  optimizeDeps: {
150
187
  include: [nested("rsc-html-stream/client")],
151
188
  exclude: excludeDeps,
152
- esbuildOptions: sharedEsbuildOptions,
189
+ rolldownOptions: sharedRolldownOptions,
153
190
  },
154
191
  },
155
192
  ssr: {
@@ -157,10 +194,6 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
157
194
  build: {
158
195
  outDir: "./dist/rsc/ssr",
159
196
  },
160
- resolve: {
161
- // Ensure single React instance in SSR child environment
162
- dedupe: ["react", "react-dom"],
163
- },
164
197
  // Pre-bundle SSR entry and React for proper module linking with childEnvironments
165
198
  // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
166
199
  optimizeDeps: {
@@ -178,7 +211,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
178
211
  ),
179
212
  ],
180
213
  exclude: excludeDeps,
181
- esbuildOptions: sharedEsbuildOptions,
214
+ rolldownOptions: sharedRolldownOptions,
182
215
  },
183
216
  },
184
217
  rsc: {
@@ -195,7 +228,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
195
228
  ),
196
229
  ],
197
230
  exclude: excludeDeps,
198
- esbuildOptions: sharedEsbuildOptions,
231
+ rolldownOptions: sharedRolldownOptions,
199
232
  },
200
233
  },
201
234
  },
@@ -227,6 +260,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
227
260
  rsc({
228
261
  entries: finalEntries,
229
262
  serverHandler: false,
263
+ clientChunks,
230
264
  }) as PluginOption,
231
265
  );
232
266
 
@@ -256,7 +290,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
256
290
  " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
257
291
  )
258
292
  .join("\n");
259
- throw new Error(`[rsc-router] Multiple routers found:\n${list}`);
293
+ throw new Error(`[rango] Multiple routers found:\n${list}`);
260
294
  }
261
295
  // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
262
296
  },
@@ -282,18 +316,34 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
282
316
  return {
283
317
  optimizeDeps: {
284
318
  exclude: excludeDeps,
285
- esbuildOptions: sharedEsbuildOptions,
319
+ rolldownOptions: sharedRolldownOptions,
286
320
  },
287
321
  build: {
288
322
  rollupOptions: { onwarn },
289
323
  },
290
324
  resolve: {
291
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"],
292
334
  },
293
335
  environments: {
294
336
  client: {
295
337
  build: {
296
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,
297
347
  output: {
298
348
  manualChunks: getManualChunks,
299
349
  },
@@ -308,7 +358,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
308
358
  nested("rsc-html-stream/client"),
309
359
  ],
310
360
  exclude: excludeDeps,
311
- esbuildOptions: sharedEsbuildOptions,
361
+ rolldownOptions: sharedRolldownOptions,
312
362
  entries: [VIRTUAL_IDS.browser],
313
363
  },
314
364
  },
@@ -327,7 +377,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
327
377
  ),
328
378
  ],
329
379
  exclude: excludeDeps,
330
- esbuildOptions: sharedEsbuildOptions,
380
+ rolldownOptions: sharedRolldownOptions,
331
381
  },
332
382
  },
333
383
  rsc: {
@@ -341,7 +391,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
341
391
  "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
342
392
  ),
343
393
  ],
344
- esbuildOptions: sharedEsbuildOptions,
394
+ rolldownOptions: sharedRolldownOptions,
345
395
  },
346
396
  },
347
397
  },
@@ -366,7 +416,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
366
416
  if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
367
417
  hasWarnedDuplicate = true;
368
418
  console.warn(
369
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
419
+ "[rango] Duplicate @vitejs/plugin-rsc detected. " +
370
420
  "Remove rsc() from your vite config — rango() includes it automatically.",
371
421
  );
372
422
  }
@@ -382,6 +432,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
382
432
  plugins.push(
383
433
  rsc({
384
434
  entries: finalEntries,
435
+ clientChunks,
385
436
  }) as PluginOption,
386
437
  );
387
438
 
@@ -484,6 +535,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
484
535
  enableBuildPrerender: prerenderEnabled,
485
536
  buildEnv: options?.buildEnv,
486
537
  preset,
538
+ clientChunkCtx,
487
539
  }),
488
540
  );
489
541