@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1fa245e2

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 (160) hide show
  1. package/{CLAUDE.md → AGENTS.md} +4 -0
  2. package/README.md +122 -30
  3. package/dist/bin/rango.js +245 -63
  4. package/dist/vite/index.js +859 -418
  5. package/package.json +3 -3
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +32 -0
  8. package/skills/caching/SKILL.md +49 -8
  9. package/skills/document-cache/SKILL.md +2 -2
  10. package/skills/hooks/SKILL.md +33 -31
  11. package/skills/host-router/SKILL.md +218 -0
  12. package/skills/links/SKILL.md +3 -1
  13. package/skills/loader/SKILL.md +72 -22
  14. package/skills/middleware/SKILL.md +2 -0
  15. package/skills/parallel/SKILL.md +126 -0
  16. package/skills/prerender/SKILL.md +112 -70
  17. package/skills/rango/SKILL.md +0 -1
  18. package/skills/route/SKILL.md +34 -4
  19. package/skills/router-setup/SKILL.md +95 -5
  20. package/skills/typesafety/SKILL.md +35 -23
  21. package/src/__internal.ts +92 -0
  22. package/src/bin/rango.ts +18 -0
  23. package/src/browser/app-version.ts +14 -0
  24. package/src/browser/event-controller.ts +5 -0
  25. package/src/browser/link-interceptor.ts +4 -0
  26. package/src/browser/navigation-bridge.ts +114 -18
  27. package/src/browser/navigation-client.ts +126 -44
  28. package/src/browser/navigation-store.ts +43 -8
  29. package/src/browser/navigation-transaction.ts +11 -9
  30. package/src/browser/partial-update.ts +80 -15
  31. package/src/browser/prefetch/cache.ts +166 -27
  32. package/src/browser/prefetch/fetch.ts +52 -39
  33. package/src/browser/prefetch/policy.ts +6 -0
  34. package/src/browser/prefetch/queue.ts +92 -20
  35. package/src/browser/prefetch/resource-ready.ts +77 -0
  36. package/src/browser/react/Link.tsx +70 -14
  37. package/src/browser/react/NavigationProvider.tsx +40 -4
  38. package/src/browser/react/context.ts +7 -2
  39. package/src/browser/react/use-handle.ts +9 -58
  40. package/src/browser/react/use-router.ts +21 -8
  41. package/src/browser/rsc-router.tsx +143 -59
  42. package/src/browser/scroll-restoration.ts +41 -42
  43. package/src/browser/segment-reconciler.ts +6 -1
  44. package/src/browser/server-action-bridge.ts +454 -436
  45. package/src/browser/types.ts +60 -5
  46. package/src/build/generate-manifest.ts +6 -6
  47. package/src/build/generate-route-types.ts +5 -0
  48. package/src/build/route-trie.ts +19 -3
  49. package/src/build/route-types/include-resolution.ts +8 -1
  50. package/src/build/route-types/router-processing.ts +346 -87
  51. package/src/build/route-types/scan-filter.ts +8 -1
  52. package/src/cache/cache-runtime.ts +15 -11
  53. package/src/cache/cache-scope.ts +48 -7
  54. package/src/cache/cf/cf-cache-store.ts +453 -11
  55. package/src/cache/cf/index.ts +5 -1
  56. package/src/cache/document-cache.ts +17 -7
  57. package/src/cache/index.ts +1 -0
  58. package/src/cache/taint.ts +55 -0
  59. package/src/client.rsc.tsx +2 -1
  60. package/src/client.tsx +3 -102
  61. package/src/context-var.ts +72 -2
  62. package/src/debug.ts +2 -2
  63. package/src/handle.ts +40 -0
  64. package/src/handles/breadcrumbs.ts +66 -0
  65. package/src/handles/index.ts +1 -0
  66. package/src/host/index.ts +0 -3
  67. package/src/index.rsc.ts +8 -37
  68. package/src/index.ts +40 -66
  69. package/src/prerender/store.ts +57 -15
  70. package/src/prerender.ts +138 -77
  71. package/src/reverse.ts +22 -1
  72. package/src/route-definition/dsl-helpers.ts +73 -25
  73. package/src/route-definition/helpers-types.ts +10 -6
  74. package/src/route-definition/index.ts +3 -3
  75. package/src/route-definition/redirect.ts +11 -3
  76. package/src/route-definition/resolve-handler-use.ts +149 -0
  77. package/src/route-map-builder.ts +7 -1
  78. package/src/route-types.ts +11 -0
  79. package/src/router/content-negotiation.ts +100 -1
  80. package/src/router/find-match.ts +4 -2
  81. package/src/router/handler-context.ts +108 -25
  82. package/src/router/intercept-resolution.ts +11 -4
  83. package/src/router/lazy-includes.ts +4 -1
  84. package/src/router/loader-resolution.ts +123 -11
  85. package/src/router/logging.ts +5 -2
  86. package/src/router/manifest.ts +9 -3
  87. package/src/router/match-api.ts +125 -190
  88. package/src/router/match-middleware/background-revalidation.ts +30 -2
  89. package/src/router/match-middleware/cache-lookup.ts +88 -16
  90. package/src/router/match-middleware/cache-store.ts +53 -10
  91. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  92. package/src/router/match-middleware/segment-resolution.ts +61 -5
  93. package/src/router/match-result.ts +22 -15
  94. package/src/router/metrics.ts +238 -13
  95. package/src/router/middleware-types.ts +53 -12
  96. package/src/router/middleware.ts +172 -85
  97. package/src/router/navigation-snapshot.ts +182 -0
  98. package/src/router/pattern-matching.ts +20 -5
  99. package/src/router/prerender-match.ts +114 -10
  100. package/src/router/preview-match.ts +30 -102
  101. package/src/router/request-classification.ts +310 -0
  102. package/src/router/revalidation.ts +27 -7
  103. package/src/router/route-snapshot.ts +245 -0
  104. package/src/router/router-context.ts +6 -1
  105. package/src/router/router-interfaces.ts +50 -5
  106. package/src/router/router-options.ts +50 -19
  107. package/src/router/segment-resolution/fresh.ts +200 -19
  108. package/src/router/segment-resolution/helpers.ts +30 -25
  109. package/src/router/segment-resolution/loader-cache.ts +1 -0
  110. package/src/router/segment-resolution/revalidation.ts +429 -301
  111. package/src/router/segment-wrappers.ts +2 -0
  112. package/src/router/trie-matching.ts +20 -2
  113. package/src/router/types.ts +1 -0
  114. package/src/router.ts +88 -15
  115. package/src/rsc/handler.ts +546 -359
  116. package/src/rsc/index.ts +0 -20
  117. package/src/rsc/manifest-init.ts +5 -1
  118. package/src/rsc/progressive-enhancement.ts +25 -8
  119. package/src/rsc/rsc-rendering.ts +35 -43
  120. package/src/rsc/server-action.ts +16 -10
  121. package/src/rsc/ssr-setup.ts +128 -0
  122. package/src/rsc/types.ts +10 -1
  123. package/src/search-params.ts +16 -13
  124. package/src/segment-system.tsx +140 -4
  125. package/src/server/context.ts +148 -16
  126. package/src/server/loader-registry.ts +9 -8
  127. package/src/server/request-context.ts +182 -34
  128. package/src/server.ts +6 -0
  129. package/src/ssr/index.tsx +4 -0
  130. package/src/static-handler.ts +18 -6
  131. package/src/theme/index.ts +4 -13
  132. package/src/types/cache-types.ts +4 -4
  133. package/src/types/handler-context.ts +149 -49
  134. package/src/types/loader-types.ts +36 -9
  135. package/src/types/route-config.ts +17 -8
  136. package/src/types/route-entry.ts +8 -1
  137. package/src/types/segments.ts +2 -5
  138. package/src/urls/path-helper-types.ts +9 -2
  139. package/src/urls/path-helper.ts +48 -13
  140. package/src/urls/pattern-types.ts +12 -0
  141. package/src/urls/response-types.ts +16 -6
  142. package/src/use-loader.tsx +73 -4
  143. package/src/vite/discovery/bundle-postprocess.ts +61 -89
  144. package/src/vite/discovery/discover-routers.ts +23 -5
  145. package/src/vite/discovery/prerender-collection.ts +48 -15
  146. package/src/vite/discovery/state.ts +17 -13
  147. package/src/vite/index.ts +8 -3
  148. package/src/vite/plugin-types.ts +51 -79
  149. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  150. package/src/vite/plugins/expose-action-id.ts +1 -3
  151. package/src/vite/plugins/performance-tracks.ts +88 -0
  152. package/src/vite/plugins/refresh-cmd.ts +127 -0
  153. package/src/vite/plugins/version-plugin.ts +13 -1
  154. package/src/vite/rango.ts +174 -211
  155. package/src/vite/router-discovery.ts +169 -42
  156. package/src/vite/utils/banner.ts +3 -3
  157. package/src/vite/utils/prerender-utils.ts +78 -0
  158. package/src/vite/utils/shared-utils.ts +3 -2
  159. package/skills/testing/SKILL.md +0 -226
  160. package/src/route-definition/route-function.ts +0 -119
package/src/vite/rango.ts CHANGED
@@ -7,15 +7,13 @@ import {
7
7
  exposeRouterId,
8
8
  } from "./plugins/expose-internal-ids.js";
9
9
  import { useCacheTransform } from "./plugins/use-cache-transform.js";
10
+ import { clientRefDedup } from "./plugins/client-ref-dedup.js";
10
11
  import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
11
12
  import {
12
13
  getExcludeDeps,
13
14
  getPackageAliases,
14
15
  } from "./utils/package-resolution.js";
15
- import {
16
- createScanFilter,
17
- findRouterFiles,
18
- } from "../build/generate-route-types.js";
16
+ import { findRouterFiles } from "../build/generate-route-types.js";
19
17
  import { createVersionPlugin } from "./plugins/version-plugin.js";
20
18
  import {
21
19
  sharedEsbuildOptions,
@@ -23,15 +21,12 @@ import {
23
21
  onwarn,
24
22
  getManualChunks,
25
23
  } from "./utils/shared-utils.js";
26
- import type {
27
- RangoOptions,
28
- RangoNodeOptions,
29
- RscPluginOptions,
30
- } from "./plugin-types.js";
24
+ import type { RangoOptions } from "./plugin-types.js";
31
25
  import { printBanner, rangoVersion } from "./utils/banner.js";
32
26
  import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
33
27
  import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
34
28
  import { createRouterDiscoveryPlugin } from "./router-discovery.js";
29
+ import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
35
30
 
36
31
  /**
37
32
  * Vite plugin for @rangojs/router.
@@ -42,7 +37,7 @@ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
42
37
  * @example Node.js (default)
43
38
  * ```ts
44
39
  * export default defineConfig({
45
- * plugins: [react(), rango({ router: './src/router.tsx' })],
40
+ * plugins: [react(), rango()],
46
41
  * });
47
42
  * ```
48
43
  *
@@ -66,10 +61,16 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
66
61
 
67
62
  // Get package resolution info (workspace vs npm install)
68
63
  const rangoAliases = getPackageAliases();
69
- const excludeDeps = getExcludeDeps();
70
-
71
- // Track RSC entry path for version injection
72
- let rscEntryPath: string | null = null;
64
+ const excludeDeps = [
65
+ ...getExcludeDeps(),
66
+ // The public browser entry re-exports the RSDW browser client.
67
+ // Excluding both keeps Vite from freezing the unpatched bundle into
68
+ // .vite/deps before our source transforms run.
69
+ "@vitejs/plugin-rsc/browser",
70
+ // Keep the browser RSDW client out of Vite's dep optimizer so our
71
+ // cjs-to-esm transform can patch the real file.
72
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
73
+ ];
73
74
 
74
75
  // Mutable ref for router path (node preset only).
75
76
  // Set immediately when user-specified, or populated by the auto-discover
@@ -191,6 +192,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
191
192
 
192
193
  plugins.push(createVirtualEntriesPlugin(finalEntries));
193
194
 
195
+ // Dev-only: RSDW client patch for React Performance Tracks
196
+ plugins.push(performanceTracksPlugin());
197
+
194
198
  // Add RSC plugin with cloudflare-specific options
195
199
  // Note: loadModuleDevProxy should NOT be used with childEnvironments
196
200
  // since SSR runs in workerd alongside RSC
@@ -200,199 +204,162 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
200
204
  serverHandler: false,
201
205
  }) as PluginOption,
202
206
  );
207
+
208
+ // Deduplicate client references from third-party packages in dev mode.
209
+ // Prevents module duplication when server components import "use client"
210
+ // packages that are also imported directly by client components.
211
+ plugins.push(clientRefDedup());
203
212
  } else {
204
- // Node preset: full RSC plugin integration
205
- const nodeOptions = resolvedOptions as RangoNodeOptions;
213
+ // Auto-discover router using Vite's resolved root (not process.cwd())
214
+ plugins.push({
215
+ name: "@rangojs/router:auto-discover",
216
+ config(userConfig) {
217
+ if (routerRef.path) return;
218
+ const root = userConfig.root
219
+ ? resolve(process.cwd(), userConfig.root)
220
+ : process.cwd();
221
+ const candidates = findRouterFiles(root);
222
+ if (candidates.length === 1) {
223
+ const abs = candidates[0];
224
+ routerRef.path = (
225
+ abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
226
+ ).replaceAll("\\", "/");
227
+ } else if (candidates.length > 1) {
228
+ const list = candidates
229
+ .map(
230
+ (f) =>
231
+ " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
232
+ )
233
+ .join("\n");
234
+ throw new Error(`[rsc-router] Multiple routers found:\n${list}`);
235
+ }
236
+ // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
237
+ },
238
+ });
206
239
 
207
- routerRef.path = nodeOptions.router;
240
+ // Always use virtual entries for client, ssr, and rsc
241
+ const finalEntries = {
242
+ client: VIRTUAL_IDS.browser,
243
+ ssr: VIRTUAL_IDS.ssr,
244
+ rsc: VIRTUAL_IDS.rsc,
245
+ };
208
246
 
209
- // Auto-discover router using Vite's resolved root (not process.cwd())
210
- if (!routerRef.path) {
211
- plugins.push({
212
- name: "@rangojs/router:auto-discover",
213
- config(userConfig) {
214
- if (routerRef.path) return;
215
- const root = userConfig.root
216
- ? resolve(process.cwd(), userConfig.root)
217
- : process.cwd();
218
- const filter = createScanFilter(root, {
219
- include: resolvedOptions.include,
220
- exclude: resolvedOptions.exclude,
221
- });
222
- const candidates = findRouterFiles(root, filter);
223
- if (candidates.length === 1) {
224
- const abs = candidates[0];
225
- routerRef.path = (
226
- abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
227
- ).replaceAll("\\", "/");
228
- } else if (candidates.length > 1) {
229
- const list = candidates
230
- .map(
231
- (f) =>
232
- " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
233
- )
234
- .join("\n");
235
- throw new Error(
236
- `[rsc-router] Multiple routers found. Specify \`router\` to choose one:\n${list}`,
237
- );
238
- }
239
- // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
240
- },
241
- });
242
- }
243
-
244
- const rscOption = nodeOptions.rsc ?? true;
245
-
246
- // Add RSC plugin by default (can be disabled with rsc: false)
247
- if (rscOption !== false) {
248
- // Dynamically import @vitejs/plugin-rsc
249
- const { default: rsc } = await import("@vitejs/plugin-rsc");
250
-
251
- // Resolve entry paths: use explicit config or virtual modules
252
- const userEntries =
253
- typeof rscOption === "boolean" ? {} : rscOption.entries || {};
254
- const finalEntries = {
255
- client: userEntries.client ?? VIRTUAL_IDS.browser,
256
- ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
257
- rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc,
258
- };
259
-
260
- // Track RSC entry for version injection (only if custom entry provided)
261
- rscEntryPath = userEntries.rsc ?? null;
262
-
263
- // Create wrapper plugin that checks for duplicates
264
- let hasWarnedDuplicate = false;
265
-
266
- plugins.push({
267
- name: "@rangojs/router:rsc-integration",
268
- enforce: "pre",
269
-
270
- config() {
271
- // Configure environments for RSC
272
- // When using virtual entries, we need to explicitly configure optimizeDeps
273
- // so Vite pre-bundles React before processing the virtual modules.
274
- // Without this, the dep optimizer may run multiple times with different hashes,
275
- // causing React instance mismatches.
276
- const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
277
- const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
278
- const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
279
-
280
- return {
281
- // Exclude rsc-router modules from optimization to prevent module duplication
282
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
283
- optimizeDeps: {
284
- exclude: excludeDeps,
285
- esbuildOptions: sharedEsbuildOptions,
286
- },
287
- build: {
288
- rollupOptions: { onwarn },
289
- },
290
- resolve: {
291
- alias: rangoAliases,
292
- },
293
- environments: {
294
- client: {
295
- build: {
296
- rollupOptions: {
297
- output: {
298
- manualChunks: getManualChunks,
299
- },
247
+ // Dynamically import @vitejs/plugin-rsc
248
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
249
+
250
+ let hasWarnedDuplicate = false;
251
+
252
+ plugins.push({
253
+ name: "@rangojs/router:rsc-integration",
254
+ enforce: "pre",
255
+
256
+ config() {
257
+ return {
258
+ optimizeDeps: {
259
+ exclude: excludeDeps,
260
+ esbuildOptions: sharedEsbuildOptions,
261
+ },
262
+ build: {
263
+ rollupOptions: { onwarn },
264
+ },
265
+ resolve: {
266
+ alias: rangoAliases,
267
+ },
268
+ environments: {
269
+ client: {
270
+ build: {
271
+ rollupOptions: {
272
+ output: {
273
+ manualChunks: getManualChunks,
300
274
  },
301
275
  },
302
- // Always exclude rsc-router modules, conditionally add virtual entry
303
- optimizeDeps: {
304
- // Pre-bundle React and rsc-html-stream to prevent late discovery
305
- // triggering ERR_OUTDATED_OPTIMIZED_DEP on cold starts
306
- include: [
307
- "react",
308
- "react-dom",
309
- "react/jsx-runtime",
310
- "react/jsx-dev-runtime",
311
- "rsc-html-stream/client",
312
- ],
313
- exclude: excludeDeps,
314
- esbuildOptions: sharedEsbuildOptions,
315
- ...(useVirtualClient && {
316
- // Tell Vite to scan the virtual entry for dependencies
317
- entries: [VIRTUAL_IDS.browser],
318
- }),
319
- },
320
276
  },
321
- ...(useVirtualSSR && {
322
- ssr: {
323
- optimizeDeps: {
324
- entries: [VIRTUAL_IDS.ssr],
325
- // Pre-bundle all SSR deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
326
- include: [
327
- "react",
328
- "react-dom",
329
- "react-dom/server.edge",
330
- "react-dom/static.edge",
331
- "react/jsx-runtime",
332
- "react/jsx-dev-runtime",
333
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
334
- ],
335
- exclude: excludeDeps,
336
- esbuildOptions: sharedEsbuildOptions,
337
- },
338
- },
339
- }),
340
- ...(useVirtualRSC && {
341
- rsc: {
342
- optimizeDeps: {
343
- entries: [VIRTUAL_IDS.rsc],
344
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
345
- include: [
346
- "react",
347
- "react/jsx-runtime",
348
- "react/jsx-dev-runtime",
349
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
350
- ],
351
- esbuildOptions: sharedEsbuildOptions,
352
- },
353
- },
354
- }),
277
+ optimizeDeps: {
278
+ include: [
279
+ "react",
280
+ "react-dom",
281
+ "react/jsx-runtime",
282
+ "react/jsx-dev-runtime",
283
+ "rsc-html-stream/client",
284
+ ],
285
+ exclude: excludeDeps,
286
+ esbuildOptions: sharedEsbuildOptions,
287
+ entries: [VIRTUAL_IDS.browser],
288
+ },
289
+ },
290
+ ssr: {
291
+ optimizeDeps: {
292
+ entries: [VIRTUAL_IDS.ssr],
293
+ include: [
294
+ "react",
295
+ "react-dom",
296
+ "react-dom/server.edge",
297
+ "react-dom/static.edge",
298
+ "react/jsx-runtime",
299
+ "react/jsx-dev-runtime",
300
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
301
+ ],
302
+ exclude: excludeDeps,
303
+ esbuildOptions: sharedEsbuildOptions,
304
+ },
305
+ },
306
+ rsc: {
307
+ optimizeDeps: {
308
+ entries: [VIRTUAL_IDS.rsc],
309
+ include: [
310
+ "react",
311
+ "react/jsx-runtime",
312
+ "react/jsx-dev-runtime",
313
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
314
+ ],
315
+ esbuildOptions: sharedEsbuildOptions,
316
+ },
355
317
  },
356
- };
357
- },
358
-
359
- configResolved(config) {
360
- if (showBanner) {
361
- const mode =
362
- config.command === "serve"
363
- ? process.argv.includes("preview")
364
- ? "preview"
365
- : "dev"
366
- : "build";
367
- printBanner(mode, "node", rangoVersion);
368
- }
369
-
370
- // Count how many RSC base plugins there are (rsc:minimal is the main one)
371
- const rscMinimalCount = config.plugins.filter(
372
- (p) => p.name === "rsc:minimal",
373
- ).length;
374
-
375
- if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
376
- hasWarnedDuplicate = true;
377
- console.warn(
378
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
379
- "Remove rsc() from your config or use rango({ rsc: false }) for manual configuration.",
380
- );
381
- }
382
- },
383
- });
384
-
385
- // Add virtual entries plugin (RSC entry generated lazily from routerRef)
386
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
387
-
388
- // Add the RSC plugin directly
389
- // Cast to PluginOption to handle type differences between bundled vite types
390
- plugins.push(
391
- rsc({
392
- entries: finalEntries,
393
- }) as PluginOption,
394
- );
395
- }
318
+ },
319
+ };
320
+ },
321
+
322
+ configResolved(config) {
323
+ if (showBanner) {
324
+ const mode =
325
+ config.command === "serve"
326
+ ? process.argv.includes("preview")
327
+ ? "preview"
328
+ : "dev"
329
+ : "build";
330
+ printBanner(mode, "node", rangoVersion);
331
+ }
332
+
333
+ const rscMinimalCount = config.plugins.filter(
334
+ (p) => p.name === "rsc:minimal",
335
+ ).length;
336
+
337
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
338
+ hasWarnedDuplicate = true;
339
+ console.warn(
340
+ "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
341
+ "Remove rsc() from your vite config rango() includes it automatically.",
342
+ );
343
+ }
344
+ },
345
+ });
346
+
347
+ // Add virtual entries plugin (RSC entry generated lazily from routerRef)
348
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
349
+
350
+ // Dev-only: RSDW client patch for React Performance Tracks
351
+ plugins.push(performanceTracksPlugin());
352
+
353
+ plugins.push(
354
+ rsc({
355
+ entries: finalEntries,
356
+ }) as PluginOption,
357
+ );
358
+
359
+ // Deduplicate client references from third-party packages in dev mode.
360
+ // Prevents module duplication when server components import "use client"
361
+ // packages that are also imported directly by client components.
362
+ plugins.push(clientRefDedup());
396
363
  }
397
364
 
398
365
  // Fix HMR for "use client" components.
@@ -468,14 +435,11 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
468
435
  // Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
469
436
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
470
437
 
471
- // Version injector: auto-injects VERSION and routes-manifest into custom entry.rsc files.
472
- // Only applies when there's an explicit rscEntryPath or for cloudflare preset (resolved
473
- // lazily in configResolved). For node preset without a custom entry, the router file
474
- // must NOT be transformed — injecting routes-manifest there creates a circular dependency.
475
- const injectorEntryPath =
476
- rscEntryPath ?? (preset === "cloudflare" ? undefined : null);
477
- if (injectorEntryPath !== null) {
478
- plugins.push(createVersionInjectorPlugin(injectorEntryPath));
438
+ // Version injector: auto-injects VERSION and routes-manifest into the RSC entry.
439
+ // For cloudflare preset, the entry is resolved lazily in configResolved.
440
+ // For node preset, the virtual entry already includes these imports.
441
+ if (preset === "cloudflare") {
442
+ plugins.push(createVersionInjectorPlugin(undefined));
479
443
  }
480
444
 
481
445
  // Transform CJS vendor files to ESM for browser compatibility
@@ -489,9 +453,8 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
489
453
  createRouterDiscoveryPlugin(discoveryEntryPath, {
490
454
  routerPathRef: discoveryRouterRef,
491
455
  enableBuildPrerender: prerenderEnabled,
492
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
493
- include: resolvedOptions.include,
494
- exclude: resolvedOptions.exclude,
456
+ buildEnv: options?.buildEnv,
457
+ preset,
495
458
  }),
496
459
  );
497
460