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

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 (242) 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 +917 -500
  5. package/package.json +55 -11
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/bundle-analysis/SKILL.md +159 -0
  8. package/skills/cache-guide/SKILL.md +220 -30
  9. package/skills/caching/SKILL.md +116 -8
  10. package/skills/composability/SKILL.md +27 -2
  11. package/skills/document-cache/SKILL.md +78 -55
  12. package/skills/handler-use/SKILL.md +1 -1
  13. package/skills/hooks/SKILL.md +196 -21
  14. package/skills/host-router/SKILL.md +45 -20
  15. package/skills/intercept/SKILL.md +1 -4
  16. package/skills/layout/SKILL.md +4 -7
  17. package/skills/links/SKILL.md +22 -10
  18. package/skills/loader/SKILL.md +166 -23
  19. package/skills/middleware/SKILL.md +13 -9
  20. package/skills/migrate-nextjs/SKILL.md +1 -1
  21. package/skills/mime-routes/SKILL.md +27 -0
  22. package/skills/observability/SKILL.md +137 -0
  23. package/skills/parallel/SKILL.md +3 -6
  24. package/skills/prerender/SKILL.md +14 -33
  25. package/skills/rango/SKILL.md +243 -26
  26. package/skills/react-compiler/SKILL.md +168 -0
  27. package/skills/response-routes/SKILL.md +114 -47
  28. package/skills/route/SKILL.md +9 -4
  29. package/skills/router-setup/SKILL.md +3 -3
  30. package/skills/server-actions/SKILL.md +53 -41
  31. package/skills/testing/SKILL.md +128 -0
  32. package/skills/testing/bindings.md +89 -0
  33. package/skills/testing/cache-prerender.md +98 -0
  34. package/skills/testing/client-components.md +121 -0
  35. package/skills/testing/e2e-parity.md +124 -0
  36. package/skills/testing/flight.md +89 -0
  37. package/skills/testing/handles.md +127 -0
  38. package/skills/testing/loader.md +108 -0
  39. package/skills/testing/middleware.md +97 -0
  40. package/skills/testing/render-handler.md +102 -0
  41. package/skills/testing/response-routes.md +94 -0
  42. package/skills/testing/reverse-and-types.md +83 -0
  43. package/skills/testing/server-actions.md +89 -0
  44. package/skills/testing/server-tree.md +128 -0
  45. package/skills/testing/setup.md +120 -0
  46. package/skills/typesafety/SKILL.md +310 -26
  47. package/skills/use-cache/SKILL.md +34 -5
  48. package/skills/view-transitions/SKILL.md +85 -3
  49. package/src/__augment-tests__/augment.ts +81 -0
  50. package/src/__augment-tests__/augmented.check.ts +116 -0
  51. package/src/browser/action-coordinator.ts +53 -36
  52. package/src/browser/event-controller.ts +42 -66
  53. package/src/browser/history-state.ts +21 -0
  54. package/src/browser/index.ts +3 -3
  55. package/src/browser/navigation-bridge.ts +9 -67
  56. package/src/browser/navigation-client.ts +68 -83
  57. package/src/browser/navigation-store.ts +7 -8
  58. package/src/browser/navigation-transaction.ts +10 -28
  59. package/src/browser/partial-update.ts +8 -16
  60. package/src/browser/prefetch/cache.ts +58 -27
  61. package/src/browser/prefetch/fetch.ts +92 -33
  62. package/src/browser/react/NavigationProvider.tsx +55 -65
  63. package/src/browser/react/location-state-shared.ts +175 -4
  64. package/src/browser/react/location-state.ts +39 -13
  65. package/src/browser/react/use-handle.ts +17 -9
  66. package/src/browser/react/use-params.ts +3 -4
  67. package/src/browser/react/use-reverse.ts +19 -12
  68. package/src/browser/react/use-router.ts +14 -1
  69. package/src/browser/response-adapter.ts +32 -1
  70. package/src/browser/rsc-router.tsx +35 -16
  71. package/src/browser/scroll-restoration.ts +30 -25
  72. package/src/browser/segment-structure-assert.ts +2 -2
  73. package/src/browser/server-action-bridge.ts +23 -30
  74. package/src/browser/types.ts +2 -0
  75. package/src/build/collect-fallback-refs.ts +107 -0
  76. package/src/build/generate-manifest.ts +60 -35
  77. package/src/build/generate-route-types.ts +2 -0
  78. package/src/build/index.ts +8 -1
  79. package/src/build/prefix-tree-utils.ts +123 -0
  80. package/src/build/route-trie.ts +43 -0
  81. package/src/build/route-types/codegen.ts +4 -4
  82. package/src/build/route-types/include-resolution.ts +1 -1
  83. package/src/build/route-types/per-module-writer.ts +7 -4
  84. package/src/build/route-types/router-processing.ts +55 -14
  85. package/src/build/route-types/scan-filter.ts +1 -1
  86. package/src/build/route-types/source-scan.ts +118 -0
  87. package/src/build/runtime-discovery.ts +9 -20
  88. package/src/cache/cache-scope.ts +28 -42
  89. package/src/cache/cf/cf-cache-store.ts +49 -6
  90. package/src/client.tsx +9 -30
  91. package/src/context-var.ts +5 -5
  92. package/src/decode-loader-results.ts +36 -0
  93. package/src/errors.ts +30 -4
  94. package/src/handle.ts +32 -14
  95. package/src/host/index.ts +2 -2
  96. package/src/host/router.ts +129 -57
  97. package/src/host/types.ts +31 -2
  98. package/src/host/utils.ts +1 -1
  99. package/src/href-client.ts +136 -20
  100. package/src/index.rsc.ts +7 -6
  101. package/src/index.ts +14 -8
  102. package/src/loader-store.ts +500 -0
  103. package/src/loader.rsc.ts +25 -7
  104. package/src/loader.ts +16 -9
  105. package/src/missing-id-error.ts +68 -0
  106. package/src/prerender.ts +27 -6
  107. package/src/response-utils.ts +9 -0
  108. package/src/reverse.ts +16 -13
  109. package/src/route-content-wrapper.tsx +6 -28
  110. package/src/route-definition/dsl-helpers.ts +238 -263
  111. package/src/route-definition/helper-factories.ts +29 -139
  112. package/src/route-definition/helpers-types.ts +37 -14
  113. package/src/route-definition/use-item-types.ts +32 -0
  114. package/src/route-types.ts +19 -41
  115. package/src/router/basename.ts +14 -0
  116. package/src/router/content-negotiation.ts +15 -2
  117. package/src/router/error-handling.ts +1 -1
  118. package/src/router/find-match.ts +54 -6
  119. package/src/router/intercept-resolution.ts +4 -18
  120. package/src/router/lazy-includes.ts +35 -16
  121. package/src/router/loader-resolution.ts +79 -36
  122. package/src/router/manifest.ts +19 -6
  123. package/src/router/match-handlers.ts +62 -20
  124. package/src/router/match-middleware/cache-lookup.ts +44 -91
  125. package/src/router/match-middleware/cache-store.ts +3 -2
  126. package/src/router/match-result.ts +32 -30
  127. package/src/router/metrics.ts +1 -1
  128. package/src/router/middleware-types.ts +1 -1
  129. package/src/router/middleware.ts +46 -78
  130. package/src/router/pattern-matching.ts +15 -2
  131. package/src/router/prerender-match.ts +1 -1
  132. package/src/router/preview-match.ts +3 -1
  133. package/src/router/request-classification.ts +4 -28
  134. package/src/router/revalidation.ts +43 -1
  135. package/src/router/router-interfaces.ts +45 -28
  136. package/src/router/router-options.ts +40 -1
  137. package/src/router/router-registry.ts +2 -5
  138. package/src/router/segment-resolution/fresh.ts +19 -6
  139. package/src/router/segment-resolution/revalidation.ts +19 -6
  140. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  141. package/src/router/telemetry.ts +99 -0
  142. package/src/router/trie-matching.ts +22 -3
  143. package/src/router/types.ts +8 -0
  144. package/src/router.ts +51 -28
  145. package/src/rsc/handler-context.ts +2 -2
  146. package/src/rsc/handler.ts +20 -65
  147. package/src/rsc/helpers.ts +22 -2
  148. package/src/rsc/index.ts +1 -1
  149. package/src/rsc/manifest-init.ts +28 -41
  150. package/src/rsc/origin-guard.ts +28 -10
  151. package/src/rsc/response-error.ts +79 -12
  152. package/src/rsc/response-route-handler.ts +43 -60
  153. package/src/rsc/rsc-rendering.ts +27 -53
  154. package/src/rsc/runtime-warnings.ts +9 -10
  155. package/src/rsc/server-action.ts +13 -37
  156. package/src/rsc/ssr-setup.ts +16 -0
  157. package/src/rsc/types.ts +2 -2
  158. package/src/runtime-env.ts +18 -0
  159. package/src/search-params.ts +4 -4
  160. package/src/segment-system.tsx +64 -49
  161. package/src/serialize.ts +243 -0
  162. package/src/server/context.ts +150 -51
  163. package/src/server/cookie-store.ts +28 -4
  164. package/src/server/request-context.ts +57 -9
  165. package/src/static-handler.ts +25 -3
  166. package/src/testing/cache-status.ts +166 -0
  167. package/src/testing/collect-handle.ts +63 -0
  168. package/src/testing/dispatch.ts +581 -0
  169. package/src/testing/dom.entry.ts +22 -0
  170. package/src/testing/e2e/fixture.ts +188 -0
  171. package/src/testing/e2e/index.ts +149 -0
  172. package/src/testing/e2e/matchers.ts +51 -0
  173. package/src/testing/e2e/page-helpers.ts +272 -0
  174. package/src/testing/e2e/parity.ts +326 -0
  175. package/src/testing/e2e/server.ts +195 -0
  176. package/src/testing/flight-matchers.ts +110 -0
  177. package/src/testing/flight-normalize.ts +38 -0
  178. package/src/testing/flight-runtime.d.ts +57 -0
  179. package/src/testing/flight-tree.ts +682 -0
  180. package/src/testing/flight.entry.ts +51 -0
  181. package/src/testing/flight.ts +234 -0
  182. package/src/testing/generated-routes.ts +223 -0
  183. package/src/testing/index.ts +106 -0
  184. package/src/testing/internal/context.ts +304 -0
  185. package/src/testing/internal/flight-client-globals.ts +30 -0
  186. package/src/testing/internal/seed-vars.ts +42 -0
  187. package/src/testing/render-handler.ts +323 -0
  188. package/src/testing/render-route.tsx +590 -0
  189. package/src/testing/run-loader.ts +363 -0
  190. package/src/testing/run-middleware.ts +205 -0
  191. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  192. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  193. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  194. package/src/testing/vitest-stubs/version.ts +5 -0
  195. package/src/testing/vitest.ts +285 -0
  196. package/src/types/global-namespace.ts +39 -26
  197. package/src/types/handler-context.ts +56 -11
  198. package/src/types/index.ts +1 -0
  199. package/src/types/loader-types.ts +6 -3
  200. package/src/types/segments.ts +18 -1
  201. package/src/urls/include-helper.ts +10 -53
  202. package/src/urls/index.ts +1 -5
  203. package/src/urls/path-helper-types.ts +11 -3
  204. package/src/urls/path-helper.ts +17 -52
  205. package/src/urls/pattern-types.ts +36 -19
  206. package/src/urls/response-types.ts +20 -19
  207. package/src/urls/type-extraction.ts +58 -139
  208. package/src/urls/urls-function.ts +1 -5
  209. package/src/use-loader.tsx +413 -42
  210. package/src/vite/debug.ts +1 -0
  211. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  212. package/src/vite/discovery/discover-routers.ts +75 -72
  213. package/src/vite/discovery/discovery-errors.ts +194 -0
  214. package/src/vite/discovery/prerender-collection.ts +19 -25
  215. package/src/vite/discovery/route-types-writer.ts +40 -84
  216. package/src/vite/discovery/state.ts +33 -0
  217. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  218. package/src/vite/index.ts +2 -0
  219. package/src/vite/plugin-types.ts +67 -0
  220. package/src/vite/plugins/cjs-to-esm.ts +3 -7
  221. package/src/vite/plugins/client-ref-hashing.ts +12 -1
  222. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
  223. package/src/vite/plugins/expose-action-id.ts +2 -2
  224. package/src/vite/plugins/expose-id-utils.ts +12 -8
  225. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  226. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  227. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  228. package/src/vite/plugins/expose-internal-ids.ts +47 -67
  229. package/src/vite/plugins/performance-tracks.ts +12 -16
  230. package/src/vite/plugins/use-cache-transform.ts +13 -11
  231. package/src/vite/plugins/version-injector.ts +2 -12
  232. package/src/vite/plugins/version-plugin.ts +59 -2
  233. package/src/vite/plugins/virtual-entries.ts +2 -2
  234. package/src/vite/rango.ts +67 -15
  235. package/src/vite/router-discovery.ts +208 -63
  236. package/src/vite/utils/ast-handler-extract.ts +15 -15
  237. package/src/vite/utils/bundle-analysis.ts +4 -2
  238. package/src/vite/utils/client-chunks.ts +190 -0
  239. package/src/vite/utils/forward-user-plugins.ts +193 -0
  240. package/src/vite/utils/manifest-utils.ts +8 -59
  241. package/src/vite/utils/shared-utils.ts +107 -26
  242. package/src/browser/action-response-classifier.ts +0 -99
@@ -21,8 +21,8 @@ function hashId(filePath, exportName) {
21
21
  function makeStubId(filePath, exportName, isBuild) {
22
22
  return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
23
23
  }
24
- function hashInlineId(filePath, lineNumber, index) {
25
- const input = index !== void 0 && index > 0 ? `${filePath}:${lineNumber}:${index}` : `${filePath}:${lineNumber}`;
24
+ function hashInlineId(filePath, fnName, index) {
25
+ const input = `${filePath}:${fnName}:${index}`;
26
26
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
27
27
  }
28
28
  function buildExportMap(program) {
@@ -200,7 +200,8 @@ var NS = {
200
200
  prerender: "rango:prerender",
201
201
  build: "rango:build",
202
202
  dev: "rango:dev",
203
- transform: "rango:transform"
203
+ transform: "rango:transform",
204
+ chunks: "rango:chunks"
204
205
  };
205
206
  if (process.env.INTERNAL_RANGO_DEBUG) {
206
207
  const existing = debugFactory.disable();
@@ -292,7 +293,7 @@ function getRscPluginApi(config) {
292
293
  );
293
294
  if (plugin) {
294
295
  console.warn(
295
- `[rsc-router:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). Consider updating the name lookup if the plugin was renamed.`
296
+ `[rango:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). Consider updating the name lookup if the plugin was renamed.`
296
297
  );
297
298
  }
298
299
  }
@@ -393,7 +394,7 @@ function exposeActionId() {
393
394
  }
394
395
  if (!rscPluginApi) {
395
396
  throw new Error(
396
- "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
397
+ "[rango] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
397
398
  );
398
399
  }
399
400
  if (!isBuild) return;
@@ -465,7 +466,7 @@ function exposeActionId() {
465
466
 
466
467
  // src/vite/plugins/expose-internal-ids.ts
467
468
  import { parseAst as parseAst2 } from "vite";
468
- import MagicString4 from "magic-string";
469
+ import MagicString3 from "magic-string";
469
470
  import path4 from "node:path";
470
471
 
471
472
  // src/vite/utils/ast-handler-extract.ts
@@ -475,7 +476,7 @@ function isDirectivePrologueStatement(node) {
475
476
  function findImportInsertionPos(code, parseAst4) {
476
477
  let program;
477
478
  try {
478
- program = parseAst4(code, { jsx: true });
479
+ program = parseAst4(code, { lang: "tsx" });
479
480
  } catch {
480
481
  return 0;
481
482
  }
@@ -515,7 +516,7 @@ function walkNode(node, parent, ancestors, enter) {
515
516
  function findHandlerCalls(code, fnName, parseAst4) {
516
517
  let program;
517
518
  try {
518
- program = parseAst4(code, { jsx: true });
519
+ program = parseAst4(code, { lang: "tsx" });
519
520
  } catch {
520
521
  return [];
521
522
  }
@@ -589,7 +590,7 @@ function getImportedLocalNamesFromProgram(program, importedName) {
589
590
  }
590
591
  function getImportedLocalNames(code, importedName, parseAst4) {
591
592
  try {
592
- const program = parseAst4(code, { jsx: true });
593
+ const program = parseAst4(code, { lang: "tsx" });
593
594
  return getImportedLocalNamesFromProgram(program, importedName);
594
595
  } catch {
595
596
  return /* @__PURE__ */ new Set();
@@ -598,7 +599,7 @@ function getImportedLocalNames(code, importedName, parseAst4) {
598
599
  function extractImportDeclarations(code, parseAst4) {
599
600
  let program;
600
601
  try {
601
- program = parseAst4(code, { jsx: true });
602
+ program = parseAst4(code, { lang: "tsx" });
602
603
  } catch {
603
604
  return [];
604
605
  }
@@ -653,7 +654,7 @@ function isSafeVariableDeclaration(node, handlerNames) {
653
654
  function extractModuleLevelDeclarations(code, parseAst4, handlerNames) {
654
655
  let program;
655
656
  try {
656
- program = parseAst4(code, { jsx: true });
657
+ program = parseAst4(code, { lang: "tsx" });
657
658
  } catch {
658
659
  return [];
659
660
  }
@@ -697,14 +698,12 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
697
698
  parseAst4,
698
699
  handlerNames
699
700
  );
700
- const lineCounts = /* @__PURE__ */ new Map();
701
701
  const importStatements = [];
702
- for (const site of inlineSites) {
703
- const lineCount = lineCounts.get(site.lineNumber) ?? 0;
704
- lineCounts.set(site.lineNumber, lineCount + 1);
705
- const hash = hashInlineId(filePath, site.lineNumber, lineCount);
702
+ for (const [siteIndex, site] of inlineSites.entries()) {
703
+ const hash = hashInlineId(filePath, fnName, siteIndex);
706
704
  const exportName = `__sh_${hash}`;
707
- const virtualId = `\0${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
705
+ const idSuffix = `${filePath}:${fnName}:${siteIndex}`;
706
+ const virtualId = `\0${virtualPrefix}${idSuffix}`;
708
707
  const handlerCode = code.slice(site.callStart, site.callEnd);
709
708
  virtualRegistry.set(virtualId, {
710
709
  originalModuleId: moduleId,
@@ -714,7 +713,7 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
714
713
  exportName
715
714
  });
716
715
  s.overwrite(site.callStart, site.callEnd, exportName);
717
- const importId = `${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
716
+ const importId = `${virtualPrefix}${idSuffix}`;
718
717
  importStatements.push(`import { ${exportName} } from "${importId}";`);
719
718
  }
720
719
  if (importStatements.length > 0) {
@@ -746,6 +745,83 @@ var STRICT_CREATE_CONFIGS = [
746
745
 
747
746
  // src/vite/plugins/expose-ids/export-analysis.ts
748
747
  import { parseAst } from "vite";
748
+
749
+ // src/build/route-types/source-scan.ts
750
+ function isLineTerminator(ch) {
751
+ const c = ch.charCodeAt(0);
752
+ return c === 10 || c === 13 || c === 8232 || c === 8233;
753
+ }
754
+ function makeCodeClassifier(code) {
755
+ const n = code.length;
756
+ let i = 0;
757
+ let skipStart = -1;
758
+ let skipEnd = -1;
759
+ return (q) => {
760
+ if (q >= skipStart && q < skipEnd) return false;
761
+ while (i < n && i <= q) {
762
+ const c = code[i];
763
+ const d = i + 1 < n ? code[i + 1] : "";
764
+ let end = -1;
765
+ if (c === "/" && d === "/") {
766
+ let j = i + 2;
767
+ while (j < n && !isLineTerminator(code[j])) j++;
768
+ end = j;
769
+ } else if (c === "/" && d === "*") {
770
+ let j = i + 2;
771
+ while (j < n && !(code[j] === "*" && code[j + 1] === "/")) j++;
772
+ end = Math.min(n, j + 2);
773
+ } else if (c === '"' || c === "'" || c === "`") {
774
+ let j = i + 1;
775
+ while (j < n) {
776
+ if (code[j] === "\\") {
777
+ j += 2;
778
+ continue;
779
+ }
780
+ if (code[j] === c) {
781
+ j++;
782
+ break;
783
+ }
784
+ j++;
785
+ }
786
+ end = j;
787
+ }
788
+ if (end >= 0) {
789
+ if (q < end) {
790
+ skipStart = i;
791
+ skipEnd = end;
792
+ return false;
793
+ }
794
+ i = end;
795
+ } else {
796
+ i++;
797
+ }
798
+ }
799
+ return true;
800
+ };
801
+ }
802
+ function firstCodeMatchIndex(code, pattern) {
803
+ const inCode = makeCodeClassifier(code);
804
+ pattern.lastIndex = 0;
805
+ let m;
806
+ while ((m = pattern.exec(code)) !== null) {
807
+ if (inCode(m.index)) return m.index;
808
+ if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
809
+ }
810
+ return -1;
811
+ }
812
+ function codeMatchIndices(code, pattern) {
813
+ const inCode = makeCodeClassifier(code);
814
+ const indices = [];
815
+ pattern.lastIndex = 0;
816
+ let m;
817
+ while ((m = pattern.exec(code)) !== null) {
818
+ if (inCode(m.index)) indices.push(m.index);
819
+ if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
820
+ }
821
+ return indices;
822
+ }
823
+
824
+ // src/vite/plugins/expose-ids/export-analysis.ts
749
825
  function isExportOnlyFile(code, bindings) {
750
826
  if (bindings.length === 0) return false;
751
827
  const knownLocals = /* @__PURE__ */ new Set();
@@ -774,12 +850,30 @@ function isExportOnlyFile(code, bindings) {
774
850
  }
775
851
  return true;
776
852
  }
777
- function countCreateCallsForNames(code, fnNames) {
778
- const pattern = new RegExp(
853
+ function createCallPattern(fnNames) {
854
+ return new RegExp(
779
855
  `\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
780
856
  "g"
781
857
  );
782
- return (code.match(pattern) || []).length;
858
+ }
859
+ function countCreateCallsForNames(code, fnNames) {
860
+ return codeMatchIndices(code, createCallPattern(fnNames)).length;
861
+ }
862
+ function offsetToLineColumn(code, index) {
863
+ let line = 1;
864
+ let lineStart = 0;
865
+ const end = Math.min(index, code.length);
866
+ for (let i = 0; i < end; i++) {
867
+ if (code[i] === "\n") {
868
+ line++;
869
+ lineStart = i + 1;
870
+ }
871
+ }
872
+ return { line, column: index - lineStart + 1 };
873
+ }
874
+ function findUnsupportedCreateCallSites(code, fnNames, supportedBindings) {
875
+ const supported = new Set(supportedBindings.map((b) => b.callExprStart));
876
+ return codeMatchIndices(code, createCallPattern(fnNames)).filter((index) => !supported.has(index)).map((index) => offsetToLineColumn(code, index));
783
877
  }
784
878
  function getImportedFnNames(code, importedName) {
785
879
  const importPattern = /import\s*\{([^}]*)\}\s*from\s*["']@rangojs\/router(?:\/[^"']*)?["']/g;
@@ -810,6 +904,17 @@ function getCalledIdentifierFromCall(callExpr) {
810
904
  }
811
905
  return null;
812
906
  }
907
+ function unwrapSignatureWrappedCall(init, fnNameSet) {
908
+ if (init?.type !== "CallExpression") return init;
909
+ const directId = getCalledIdentifierFromCall(init);
910
+ if (directId && fnNameSet.has(directId)) return init;
911
+ const firstArg = init.arguments?.[0];
912
+ if (firstArg?.type === "CallExpression") {
913
+ const innerId = getCalledIdentifierFromCall(firstArg);
914
+ if (innerId && fnNameSet.has(innerId)) return firstArg;
915
+ }
916
+ return init;
917
+ }
813
918
  function collectCreateExportBindingsFallback(code, fnNames) {
814
919
  const alternation = fnNames.map(escapeRegExp).join("|");
815
920
  const exportConstPattern = new RegExp(
@@ -869,7 +974,7 @@ function collectCreateExportBindingsFallback(code, fnNames) {
869
974
  function collectCreateExportBindings(code, fnNames, program) {
870
975
  if (!program) {
871
976
  try {
872
- program = parseAst(code, { jsx: true });
977
+ program = parseAst(code, { lang: "tsx" });
873
978
  } catch {
874
979
  return collectCreateExportBindingsFallback(code, fnNames);
875
980
  }
@@ -882,16 +987,16 @@ function collectCreateExportBindings(code, fnNames, program) {
882
987
  return;
883
988
  }
884
989
  for (const decl of varDecl.declarations ?? []) {
885
- const calledIdentifier = getCalledIdentifierFromCall(decl?.init);
886
- if (decl?.id?.type !== "Identifier" || decl?.init?.type !== "CallExpression" || !calledIdentifier || !fnNameSet.has(calledIdentifier)) {
990
+ const callExpr = unwrapSignatureWrappedCall(decl?.init, fnNameSet);
991
+ const calledIdentifier = getCalledIdentifierFromCall(callExpr);
992
+ if (decl?.id?.type !== "Identifier" || callExpr?.type !== "CallExpression" || !calledIdentifier || !fnNameSet.has(calledIdentifier)) {
887
993
  continue;
888
994
  }
889
995
  const localName = decl.id.name;
890
996
  const exportNames = exportMap.get(localName) ?? [];
891
997
  if (exportNames.length === 0) continue;
892
- const callStart = decl.init.start;
893
- const callEnd = decl.init.end;
894
- const calleeEnd = decl.init.callee.end;
998
+ const callEnd = callExpr.end;
999
+ const calleeEnd = callExpr.callee.end;
895
1000
  let openParenPos = -1;
896
1001
  for (let i = calleeEnd; i < callEnd; i++) {
897
1002
  if (code[i] === "(") {
@@ -905,10 +1010,10 @@ function collectCreateExportBindings(code, fnNames, program) {
905
1010
  bindings.push({
906
1011
  localName,
907
1012
  exportNames,
908
- callExprStart: decl.init.start,
1013
+ callExprStart: callExpr.start,
909
1014
  callOpenParenPos: openParenPos,
910
1015
  callCloseParenPos: closeParenPos,
911
- argCount: decl.init.arguments?.length ?? 0,
1016
+ argCount: callExpr.arguments?.length ?? 0,
912
1017
  statementEnd
913
1018
  });
914
1019
  }
@@ -927,9 +1032,20 @@ function collectCreateExportBindings(code, fnNames, program) {
927
1032
  }
928
1033
  return bindings;
929
1034
  }
930
- function buildUnsupportedShapeWarning(filePath, fnName) {
931
- return [
932
- `[rsc-router] Unsupported ${fnName} shape in "${filePath}".`,
1035
+ function buildUnsupportedShapeWarning(filePath, fnName, sites = []) {
1036
+ const lines = [`[rango] Unsupported ${fnName} shape in "${filePath}".`];
1037
+ if (sites.length === 1) {
1038
+ const s = sites[0];
1039
+ lines.push(
1040
+ `The ${fnName}(...) call at ${filePath}:${s.line}:${s.column} has no stable $$id injected \u2014 it is not in a supported shape.`
1041
+ );
1042
+ } else if (sites.length > 1) {
1043
+ lines.push(
1044
+ `These ${fnName}(...) calls have no stable $$id injected \u2014 they are not in a supported shape:`
1045
+ );
1046
+ for (const s of sites) lines.push(` - ${filePath}:${s.line}:${s.column}`);
1047
+ }
1048
+ lines.push(
933
1049
  `Supported shapes are:`,
934
1050
  ` - export const X = ${fnName}(...)`,
935
1051
  ` - const X = ${fnName}(...); export { X }`,
@@ -937,7 +1053,8 @@ function buildUnsupportedShapeWarning(filePath, fnName) {
937
1053
  `Potentially unsupported forms include:`,
938
1054
  ` - export let/var X = ${fnName}(...)`,
939
1055
  ` - inline ${fnName}(...) calls`
940
- ].join("\n");
1056
+ );
1057
+ return lines.join("\n");
941
1058
  }
942
1059
 
943
1060
  // src/vite/plugins/expose-ids/loader-transform.ts
@@ -951,7 +1068,7 @@ function generateClientLoaderStubs(bindings, code, filePath, isBuild) {
951
1068
  const lines = [];
952
1069
  for (const binding of bindings) {
953
1070
  for (const name of binding.exportNames) {
954
- const loaderId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1071
+ const loaderId = makeStubId(filePath, name, isBuild);
955
1072
  lines.push(
956
1073
  `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`
957
1074
  );
@@ -963,7 +1080,7 @@ function transformLoaders(bindings, s, filePath, isBuild) {
963
1080
  let hasChanges = false;
964
1081
  for (const binding of bindings) {
965
1082
  const exportName = binding.exportNames[0];
966
- const loaderId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1083
+ const loaderId = makeStubId(filePath, exportName, isBuild);
967
1084
  const paramInjection = binding.argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
968
1085
  s.appendLeft(binding.callCloseParenPos, paramInjection);
969
1086
  const propInjection = `
@@ -975,7 +1092,6 @@ ${binding.localName}.$$id = "${loaderId}";`;
975
1092
  }
976
1093
 
977
1094
  // src/vite/plugins/expose-ids/handler-transform.ts
978
- import MagicString2 from "magic-string";
979
1095
  function analyzeCreateHandleArgs(code, startPos, endPos) {
980
1096
  const content = code.slice(startPos, endPos).trim();
981
1097
  return { hasArgs: content.length > 0 };
@@ -989,7 +1105,7 @@ function transformHandles(bindings, s, code, filePath, isBuild) {
989
1105
  binding.callOpenParenPos + 1,
990
1106
  binding.callCloseParenPos
991
1107
  );
992
- const handleId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1108
+ const handleId = makeStubId(filePath, exportName, isBuild);
993
1109
  let paramInjection;
994
1110
  if (!args.hasArgs) {
995
1111
  paramInjection = `undefined, "${handleId}"`;
@@ -1008,7 +1124,7 @@ function transformLocationState(bindings, s, filePath, isBuild) {
1008
1124
  let hasChanges = false;
1009
1125
  for (const binding of bindings) {
1010
1126
  const exportName = binding.exportNames[0];
1011
- const stateKey = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1127
+ const stateKey = makeStubId(filePath, exportName, isBuild);
1012
1128
  const propInjection = `
1013
1129
  ${binding.localName}.__rsc_ls_key = "__rsc_ls_${stateKey}";`;
1014
1130
  s.appendRight(binding.statementEnd, propInjection);
@@ -1020,7 +1136,7 @@ function generateWholeFileStubs(cfg, bindings, code, filePath, isBuild) {
1020
1136
  if (!isExportOnlyFile(code, bindings)) return null;
1021
1137
  const exportNames = bindings.flatMap((b) => b.exportNames);
1022
1138
  const stubs = exportNames.map((name) => {
1023
- const handlerId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1139
+ const handlerId = makeStubId(filePath, name, isBuild);
1024
1140
  return `export const ${name} = { __brand: "${cfg.brand}", $$id: "${handlerId}" };`;
1025
1141
  });
1026
1142
  return { code: stubs.join("\n") + "\n", map: null };
@@ -1029,7 +1145,7 @@ function stubHandlerExprs(cfg, bindings, s, filePath, isBuild) {
1029
1145
  let hasChanges = false;
1030
1146
  for (const binding of bindings) {
1031
1147
  const exportName = binding.exportNames[0];
1032
- const handlerId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1148
+ const handlerId = makeStubId(filePath, exportName, isBuild);
1033
1149
  s.overwrite(
1034
1150
  binding.callExprStart,
1035
1151
  binding.callCloseParenPos + 1,
@@ -1043,7 +1159,7 @@ function transformHandlerIds(cfg, bindings, s, filePath, isBuild) {
1043
1159
  let hasChanges = false;
1044
1160
  for (const binding of bindings) {
1045
1161
  const exportName = binding.exportNames[0];
1046
- const handlerId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1162
+ const handlerId = makeStubId(filePath, exportName, isBuild);
1047
1163
  let paramInjection;
1048
1164
  if (binding.argCount === 0) {
1049
1165
  paramInjection = `undefined, "${handlerId}"`;
@@ -1062,7 +1178,7 @@ ${binding.localName}.$$id = "${handlerId}";`;
1062
1178
  }
1063
1179
 
1064
1180
  // src/vite/plugins/expose-ids/router-transform.ts
1065
- import MagicString3 from "magic-string";
1181
+ import MagicString2 from "magic-string";
1066
1182
  import path3 from "node:path";
1067
1183
  import { createHash } from "node:crypto";
1068
1184
  var debug2 = createRangoDebugger(NS.transform);
@@ -1072,10 +1188,10 @@ function transformRouter(code, filePath, routerFnNames, absolutePath) {
1072
1188
  "g"
1073
1189
  );
1074
1190
  let match;
1075
- const s = new MagicString3(code);
1191
+ const s = new MagicString2(code);
1076
1192
  let changed = false;
1077
- const basename3 = path3.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
1078
- const routeNamesImport = `./${basename3}.named-routes.gen.js`;
1193
+ const basename2 = path3.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
1194
+ const routeNamesImport = `./${basename2}.named-routes.gen.js`;
1079
1195
  const routeNamesVar = `__rsc_rn`;
1080
1196
  while ((match = pat.exec(code)) !== null) {
1081
1197
  const callStart = match.index;
@@ -1335,7 +1451,7 @@ ${lazyImports.join(",\n")}
1335
1451
  }
1336
1452
  if (_cachedAst !== void 0 || _astParseFailed) return _cachedAst;
1337
1453
  try {
1338
- _cachedAst = parseAst2(code, { jsx: true });
1454
+ _cachedAst = parseAst2(code, { lang: "tsx" });
1339
1455
  } catch {
1340
1456
  _astParseFailed = true;
1341
1457
  }
@@ -1358,13 +1474,16 @@ ${lazyImports.join(",\n")}
1358
1474
  const hasCode = cfg.fnName === "createLoader" ? hasLoaderCode : cfg.fnName === "createHandle" ? hasHandleCode : hasLocationStateCode;
1359
1475
  if (!hasCode) continue;
1360
1476
  const fnNames = getFnNames(cfg.fnName);
1361
- const totalCalls = countCreateCallsForNames(code, fnNames);
1362
- const supportedBindings = getBindings(code, fnNames).length;
1363
- if (totalCalls <= supportedBindings) continue;
1477
+ const sites = findUnsupportedCreateCallSites(
1478
+ code,
1479
+ fnNames,
1480
+ getBindings(code, fnNames)
1481
+ );
1482
+ if (sites.length === 0) continue;
1364
1483
  const warnKey = `${id}::${cfg.fnName}`;
1365
1484
  if (unsupportedShapeWarnings.has(warnKey)) continue;
1366
1485
  unsupportedShapeWarnings.add(warnKey);
1367
- this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
1486
+ this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName, sites));
1368
1487
  }
1369
1488
  if (hasLoaderCode && isRscEnv) {
1370
1489
  const fnNames = getFnNames("createLoader");
@@ -1401,15 +1520,6 @@ ${lazyImports.join(",\n")}
1401
1520
  );
1402
1521
  if (wholeFile) return wholeFile;
1403
1522
  }
1404
- if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1405
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1406
- const exportNames = getBindings(code, fnNames).map(
1407
- (b) => b.exportNames[0]
1408
- );
1409
- if (exportNames.length > 0) {
1410
- prerenderHandlerModules.set(id, exportNames);
1411
- }
1412
- }
1413
1523
  let changed = false;
1414
1524
  const handlerConfigs = [
1415
1525
  hasStaticHandlerCode && STATIC_CONFIG,
@@ -1422,7 +1532,7 @@ ${lazyImports.join(",\n")}
1422
1532
  const totalCalls = countCreateCallsForNames(code, fnNames);
1423
1533
  const supportedBindings = getBindings(code, fnNames).length;
1424
1534
  if (totalCalls > supportedBindings) {
1425
- const iterS = new MagicString4(code);
1535
+ const iterS = new MagicString3(code);
1426
1536
  const result = transformInlineHandlers(
1427
1537
  cfg.fnName,
1428
1538
  VIRTUAL_HANDLER_PREFIX,
@@ -1587,16 +1697,24 @@ ${lazyImports.join(",\n")}
1587
1697
  return { code: lines.join("\n") + "\n", map: null };
1588
1698
  }
1589
1699
  }
1590
- if (hasStaticHandlerCode && isRscEnv && isBuild) {
1591
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1592
- const exportNames = getBindings(code, fnNames).map(
1593
- (b) => b.exportNames[0]
1594
- );
1595
- if (exportNames.length > 0) {
1596
- staticHandlerModules.set(id, exportNames);
1700
+ if (isRscEnv && isBuild) {
1701
+ const trackTypes = [
1702
+ [
1703
+ hasPrerenderHandlerCode,
1704
+ PRERENDER_CONFIG,
1705
+ prerenderHandlerModules
1706
+ ],
1707
+ [hasStaticHandlerCode, STATIC_CONFIG, staticHandlerModules]
1708
+ ];
1709
+ for (const [has2, cfg, trackMap] of trackTypes) {
1710
+ if (!has2) continue;
1711
+ const exportNames = getBindings(code, getFnNames(cfg.fnName)).map(
1712
+ (b) => b.exportNames[0]
1713
+ );
1714
+ if (exportNames.length > 0) trackMap.set(id, exportNames);
1597
1715
  }
1598
1716
  }
1599
- const s = new MagicString4(code);
1717
+ const s = new MagicString3(code);
1600
1718
  if (hasLoaderCode) {
1601
1719
  const fnNames = getFnNames("createLoader");
1602
1720
  changed = transformLoaders(
@@ -1625,41 +1743,13 @@ ${lazyImports.join(",\n")}
1625
1743
  isBuild
1626
1744
  ) || changed;
1627
1745
  }
1628
- if (hasPrerenderHandlerCode) {
1629
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1630
- const bindings = getBindings(code, fnNames);
1631
- if (isRscEnv) {
1632
- changed = transformHandlerIds(
1633
- PRERENDER_CONFIG,
1634
- bindings,
1635
- s,
1636
- filePath,
1637
- isBuild
1638
- ) || changed;
1639
- } else {
1640
- changed = stubHandlerExprs(
1641
- PRERENDER_CONFIG,
1642
- bindings,
1643
- s,
1644
- filePath,
1645
- isBuild
1646
- ) || changed;
1647
- }
1648
- }
1649
- if (hasStaticHandlerCode) {
1650
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1651
- const bindings = getBindings(code, fnNames);
1652
- if (isRscEnv) {
1653
- changed = transformHandlerIds(
1654
- STATIC_CONFIG,
1655
- bindings,
1656
- s,
1657
- filePath,
1658
- isBuild
1659
- ) || changed;
1660
- } else {
1661
- changed = stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) || changed;
1662
- }
1746
+ const finalHandlerConfigs = [
1747
+ hasPrerenderHandlerCode && PRERENDER_CONFIG,
1748
+ hasStaticHandlerCode && STATIC_CONFIG
1749
+ ].filter((c) => !!c);
1750
+ for (const cfg of finalHandlerConfigs) {
1751
+ const bindings = getBindings(code, getFnNames(cfg.fnName));
1752
+ changed = (isRscEnv ? transformHandlerIds(cfg, bindings, s, filePath, isBuild) : stubHandlerExprs(cfg, bindings, s, filePath, isBuild)) || changed;
1663
1753
  }
1664
1754
  if (!changed) return;
1665
1755
  return {
@@ -1675,10 +1765,11 @@ ${lazyImports.join(",\n")}
1675
1765
 
1676
1766
  // src/vite/plugins/use-cache-transform.ts
1677
1767
  import path5 from "node:path";
1678
- import MagicString5 from "magic-string";
1768
+ import MagicString4 from "magic-string";
1679
1769
  var debug4 = createRangoDebugger(NS.transform);
1680
1770
  var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
1681
1771
  var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
1772
+ var USE_CACHE_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
1682
1773
  function useCacheTransform() {
1683
1774
  let projectRoot = "";
1684
1775
  let isBuild = false;
@@ -1716,7 +1807,7 @@ function useCacheTransform() {
1716
1807
  let ast;
1717
1808
  try {
1718
1809
  const { parseAst: parseAst4 } = await import("vite");
1719
- ast = parseAst4(code);
1810
+ ast = parseAst4(code, { lang: "tsx" });
1720
1811
  } catch {
1721
1812
  return;
1722
1813
  }
@@ -1772,7 +1863,7 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
1772
1863
  );
1773
1864
  }
1774
1865
  if (exportNames.length === 0) {
1775
- const s = new MagicString5(code);
1866
+ const s = new MagicString4(code);
1776
1867
  const directive2 = findFileLevelDirective(ast);
1777
1868
  if (directive2) {
1778
1869
  s.overwrite(
@@ -1807,7 +1898,7 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
1807
1898
  function transformFunctionLevelUseCache(code, ast, filePath, sourceId, isBuild, transformHoistInlineDirective) {
1808
1899
  try {
1809
1900
  const { output, names } = transformHoistInlineDirective(code, ast, {
1810
- directive: /^use cache(:\s*[\w-]+)?$/,
1901
+ directive: USE_CACHE_DIRECTIVE_RE,
1811
1902
  runtime: (value, name, meta) => {
1812
1903
  const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1813
1904
  const profileMatch = meta.directiveMatch[1];
@@ -1837,14 +1928,13 @@ function findFileLevelDirective(ast) {
1837
1928
  }
1838
1929
  return null;
1839
1930
  }
1840
- var VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
1841
1931
  var NEAR_MISS_RE = /^use cache:\s*.+$/;
1842
1932
  function warnOnNearMissDirectives(ast, fileId, warn) {
1843
1933
  const visit = (node) => {
1844
1934
  if (!node || typeof node !== "object") return;
1845
1935
  if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
1846
1936
  const value = node.expression.value;
1847
- if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !VALID_DIRECTIVE_RE.test(value)) {
1937
+ if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !USE_CACHE_DIRECTIVE_RE.test(value)) {
1848
1938
  const profilePart = value.slice("use cache:".length).trim();
1849
1939
  warn(
1850
1940
  `[rango:use-cache] "${value}" in ${fileId} has an invalid profile name "${profilePart}". Profile names must match [a-zA-Z0-9_-]+. This directive will be ignored.`
@@ -1938,7 +2028,7 @@ import {
1938
2028
  import { createElement, StrictMode } from "react";
1939
2029
  import { hydrateRoot } from "react-dom/client";
1940
2030
  import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
1941
- import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
2031
+ import { initBrowserApp, Rango } from "@rangojs/router/browser";
1942
2032
 
1943
2033
  async function initializeApp() {
1944
2034
  const deps = {
@@ -1953,7 +2043,7 @@ async function initializeApp() {
1953
2043
 
1954
2044
  hydrateRoot(
1955
2045
  document,
1956
- createElement(StrictMode, null, createElement(RSCRouter))
2046
+ createElement(StrictMode, null, createElement(Rango))
1957
2047
  );
1958
2048
  }
1959
2049
 
@@ -2040,7 +2130,7 @@ import { resolve } from "node:path";
2040
2130
  // package.json
2041
2131
  var package_default = {
2042
2132
  name: "@rangojs/router",
2043
- version: "0.0.0-experimental.d20dd405",
2133
+ version: "0.0.0-experimental.d98a8e9d",
2044
2134
  description: "Django-inspired RSC router with composable URL patterns",
2045
2135
  keywords: [
2046
2136
  "react",
@@ -2166,6 +2256,31 @@ var package_default = {
2166
2256
  "./host/testing": {
2167
2257
  types: "./src/host/testing.ts",
2168
2258
  default: "./src/host/testing.ts"
2259
+ },
2260
+ "./testing": {
2261
+ types: "./src/testing/index.ts",
2262
+ default: "./src/testing/index.ts"
2263
+ },
2264
+ "./testing/vitest": {
2265
+ types: "./src/testing/vitest.ts",
2266
+ default: "./dist/testing/vitest.js"
2267
+ },
2268
+ "./testing/dom": {
2269
+ types: "./src/testing/dom.entry.ts",
2270
+ default: "./src/testing/dom.entry.ts"
2271
+ },
2272
+ "./testing/e2e": {
2273
+ types: "./src/testing/e2e/index.ts",
2274
+ default: "./src/testing/e2e/index.ts"
2275
+ },
2276
+ "./testing/flight": {
2277
+ types: "./src/testing/flight.entry.ts",
2278
+ "react-server": "./src/testing/flight.entry.ts",
2279
+ default: "./src/testing/flight.entry.ts"
2280
+ },
2281
+ "./testing/flight-matchers": {
2282
+ types: "./src/testing/flight-matchers.ts",
2283
+ default: "./src/testing/flight-matchers.ts"
2169
2284
  }
2170
2285
  },
2171
2286
  publishConfig: {
@@ -2173,47 +2288,66 @@ var package_default = {
2173
2288
  tag: "experimental"
2174
2289
  },
2175
2290
  scripts: {
2176
- build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2291
+ build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2177
2292
  prepublishOnly: "pnpm build",
2178
- typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit",
2293
+ typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
2179
2294
  test: "playwright test",
2180
2295
  "test:ui": "playwright test --ui",
2296
+ "test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
2181
2297
  "test:unit": "vitest run",
2182
- "test:unit:watch": "vitest"
2298
+ "test:unit:watch": "vitest",
2299
+ "test:unit:rsc": "vitest run --config vitest.rsc.config.ts"
2183
2300
  },
2184
2301
  dependencies: {
2185
2302
  "@types/debug": "^4.1.12",
2186
- "@vitejs/plugin-rsc": "^0.5.23",
2303
+ "@vitejs/plugin-rsc": "^0.5.26",
2187
2304
  debug: "^4.4.1",
2188
2305
  "magic-string": "^0.30.17",
2189
2306
  picomatch: "^4.0.3",
2190
- "rsc-html-stream": "^0.0.7"
2307
+ "rsc-html-stream": "^0.0.7",
2308
+ tinyexec: "^0.3.2"
2191
2309
  },
2192
2310
  devDependencies: {
2193
2311
  "@playwright/test": "^1.49.1",
2312
+ "@shared/e2e": "workspace:*",
2313
+ "@testing-library/dom": "^10.4.1",
2314
+ "@testing-library/react": "^16.3.2",
2194
2315
  "@types/node": "^24.10.1",
2195
2316
  "@types/react": "catalog:",
2196
2317
  "@types/react-dom": "catalog:",
2197
2318
  esbuild: "^0.27.0",
2319
+ "happy-dom": "^20.10.1",
2198
2320
  jiti: "^2.6.1",
2199
2321
  react: "catalog:",
2200
2322
  "react-dom": "catalog:",
2201
- tinyexec: "^0.3.2",
2202
2323
  typescript: "^5.3.0",
2203
2324
  vitest: "^4.0.0"
2204
2325
  },
2205
2326
  peerDependencies: {
2206
- "@cloudflare/vite-plugin": "^1.25.0",
2207
- "@vitejs/plugin-rsc": "^0.5.23",
2208
- react: "^18.0.0 || ^19.0.0",
2209
- vite: "^7.3.0"
2327
+ "@cloudflare/vite-plugin": "^1.38.0",
2328
+ "@playwright/test": "^1.49.1",
2329
+ "@testing-library/react": ">=16",
2330
+ "@vitejs/plugin-rsc": "^0.5.26",
2331
+ react: ">=19.2.6 <20",
2332
+ "react-dom": ">=19.2.6 <20",
2333
+ vite: "^8.0.0",
2334
+ vitest: ">=3"
2210
2335
  },
2211
2336
  peerDependenciesMeta: {
2212
2337
  "@cloudflare/vite-plugin": {
2213
2338
  optional: true
2214
2339
  },
2340
+ "@playwright/test": {
2341
+ optional: true
2342
+ },
2343
+ "@testing-library/react": {
2344
+ optional: true
2345
+ },
2215
2346
  vite: {
2216
2347
  optional: true
2348
+ },
2349
+ vitest: {
2350
+ optional: true
2217
2351
  }
2218
2352
  }
2219
2353
  };
@@ -2399,7 +2533,7 @@ ${objectBody}
2399
2533
  } as const;
2400
2534
 
2401
2535
  declare global {
2402
- namespace RSCRouter {
2536
+ namespace Rango {
2403
2537
  interface GeneratedRouteMap extends Readonly<typeof NamedRoutes> {}
2404
2538
  }
2405
2539
  }
@@ -2634,7 +2768,7 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2634
2768
  const realPath = resolve2(filePath);
2635
2769
  const key = variableName ? `${realPath}:${variableName}` : realPath;
2636
2770
  if (visited.has(key)) {
2637
- console.warn(`[rsc-router] Circular include detected, skipping: ${key}`);
2771
+ console.warn(`[rango] Circular include detected, skipping: ${key}`);
2638
2772
  return { routes: {}, searchSchemas: {} };
2639
2773
  }
2640
2774
  visited.add(key);
@@ -2695,6 +2829,7 @@ function countPublicRouteEntries(source) {
2695
2829
  return count;
2696
2830
  }
2697
2831
  var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
2832
+ var ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
2698
2833
  function isRoutableSourceFile(name) {
2699
2834
  return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
2700
2835
  }
@@ -2704,7 +2839,7 @@ function findRouterFilesRecursive(dir, filter, results) {
2704
2839
  entries = readdirSync(dir, { withFileTypes: true });
2705
2840
  } catch (err) {
2706
2841
  console.warn(
2707
- `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
2842
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
2708
2843
  );
2709
2844
  return;
2710
2845
  }
@@ -2722,7 +2857,7 @@ function findRouterFilesRecursive(dir, filter, results) {
2722
2857
  if (filter && !filter(fullPath)) continue;
2723
2858
  try {
2724
2859
  const source = readFileSync2(fullPath, "utf-8");
2725
- if (ROUTER_CALL_PATTERN.test(source)) {
2860
+ if (ROUTER_CALL_PATTERN.test(source) && firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0) {
2726
2861
  routerFilesInDir.push(fullPath);
2727
2862
  }
2728
2863
  } catch {
@@ -2760,7 +2895,7 @@ function findNestedRouterConflict(routerFiles) {
2760
2895
  }
2761
2896
  return null;
2762
2897
  }
2763
- function formatNestedRouterConflictError(conflict, prefix = "[rsc-router]") {
2898
+ function formatNestedRouterConflictError(conflict, prefix = "[rango]") {
2764
2899
  return `${prefix} Nested router roots are not supported.
2765
2900
  Router root: ${conflict.ancestor}
2766
2901
  Nested router: ${conflict.nested}
@@ -2856,19 +2991,38 @@ function extractBasenameFromRouter(code) {
2856
2991
  visit(sourceFile);
2857
2992
  return result;
2858
2993
  }
2859
- function applyBasenameToRoutes(result, basename3) {
2994
+ function applyBasenameToRoutes(result, basename2) {
2860
2995
  const prefixed = {};
2861
2996
  for (const [name, pattern] of Object.entries(result.routes)) {
2862
2997
  if (pattern === "/") {
2863
- prefixed[name] = basename3;
2864
- } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2865
- prefixed[name] = basename3 + pattern.slice(1);
2998
+ prefixed[name] = basename2;
2999
+ } else if (basename2.endsWith("/") && pattern.startsWith("/")) {
3000
+ prefixed[name] = basename2 + pattern.slice(1);
2866
3001
  } else {
2867
- prefixed[name] = basename3 + pattern;
3002
+ prefixed[name] = basename2 + pattern;
2868
3003
  }
2869
3004
  }
2870
3005
  return { routes: prefixed, searchSchemas: result.searchSchemas };
2871
3006
  }
3007
+ function genFileTsPath(sourceFile) {
3008
+ const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
3009
+ return join(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
3010
+ }
3011
+ function resolveSearchSchemas(publicRouteNames, runtimeSchemas, sourceFile) {
3012
+ if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
3013
+ return runtimeSchemas;
3014
+ }
3015
+ const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
3016
+ if (Object.keys(staticParsed.searchSchemas).length === 0) {
3017
+ return runtimeSchemas;
3018
+ }
3019
+ const filtered = {};
3020
+ for (const name of publicRouteNames) {
3021
+ const schema = staticParsed.searchSchemas[name];
3022
+ if (schema) filtered[name] = schema;
3023
+ }
3024
+ return Object.keys(filtered).length > 0 ? filtered : runtimeSchemas;
3025
+ }
2872
3026
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2873
3027
  let routerSource;
2874
3028
  try {
@@ -2881,7 +3035,7 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2881
3035
  return { routes: {}, searchSchemas: {} };
2882
3036
  }
2883
3037
  const rawBasename = extractBasenameFromRouter(routerSource);
2884
- const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
3038
+ const basename2 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2885
3039
  let result;
2886
3040
  if (extraction.kind === "inline") {
2887
3041
  result = buildCombinedRouteMapWithSearch(
@@ -2906,8 +3060,8 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2906
3060
  result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2907
3061
  }
2908
3062
  }
2909
- if (basename3) {
2910
- result = applyBasenameToRoutes(result, basename3);
3063
+ if (basename2) {
3064
+ result = applyBasenameToRoutes(result, basename2);
2911
3065
  }
2912
3066
  return result;
2913
3067
  }
@@ -2922,7 +3076,7 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2922
3076
  if (existsSync3(oldCombinedPath)) {
2923
3077
  unlinkSync(oldCombinedPath);
2924
3078
  console.log(
2925
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
3079
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
2926
3080
  );
2927
3081
  }
2928
3082
  } catch {
@@ -2944,18 +3098,12 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2944
3098
  }
2945
3099
  if (!extractUrlsFromRouter(routerSource)) continue;
2946
3100
  }
2947
- const routerBasename = pathBasename(routerFilePath).replace(
2948
- /\.(tsx?|jsx?)$/,
2949
- ""
2950
- );
2951
- const outPath = join(
2952
- dirname2(routerFilePath),
2953
- `${routerBasename}.named-routes.gen.ts`
2954
- );
3101
+ const outPath = genFileTsPath(routerFilePath);
2955
3102
  const existing = existsSync3(outPath) ? readFileSync2(outPath, "utf-8") : null;
2956
3103
  if (Object.keys(result.routes).length === 0) {
2957
3104
  if (!existing) {
2958
3105
  const emptySource = generateRouteTypesSource({});
3106
+ opts?.onWrite?.(outPath, emptySource);
2959
3107
  writeFileSync(outPath, emptySource);
2960
3108
  }
2961
3109
  continue;
@@ -2975,9 +3123,10 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2975
3123
  continue;
2976
3124
  }
2977
3125
  }
3126
+ opts?.onWrite?.(outPath, source);
2978
3127
  writeFileSync(outPath, source);
2979
3128
  console.log(
2980
- `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
3129
+ `[rango] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
2981
3130
  );
2982
3131
  }
2983
3132
  }
@@ -2994,7 +3143,7 @@ function normalizeModuleId(id) {
2994
3143
  function getClientModuleSignature(source) {
2995
3144
  let program;
2996
3145
  try {
2997
- program = parseAst3(source, { jsx: true });
3146
+ program = parseAst3(source, { lang: "tsx" });
2998
3147
  } catch {
2999
3148
  return void 0;
3000
3149
  }
@@ -3077,11 +3226,12 @@ function createVersionPlugin() {
3077
3226
  let currentVersion = buildVersion;
3078
3227
  let isDev = false;
3079
3228
  let server = null;
3229
+ let resolvedCacheDir;
3080
3230
  const clientModuleSignatures = /* @__PURE__ */ new Map();
3081
3231
  let versionCounter = 0;
3082
3232
  const bumpVersion = (reason) => {
3083
3233
  currentVersion = Date.now().toString(16) + String(++versionCounter);
3084
- console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
3234
+ console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
3085
3235
  const rscEnv = server?.environments?.rsc;
3086
3236
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
3087
3237
  "\0" + VIRTUAL_IDS.version
@@ -3095,6 +3245,7 @@ function createVersionPlugin() {
3095
3245
  enforce: "pre",
3096
3246
  configResolved(config) {
3097
3247
  isDev = config.command === "serve";
3248
+ resolvedCacheDir = config.cacheDir ? String(config.cacheDir).replace(/\\/g, "/") : void 0;
3098
3249
  },
3099
3250
  configureServer(devServer) {
3100
3251
  server = devServer;
@@ -3136,6 +3287,7 @@ function createVersionPlugin() {
3136
3287
  if (!isDev) return;
3137
3288
  const isRscModule = this.environment?.name === "rsc";
3138
3289
  if (!isRscModule) return;
3290
+ if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
3139
3291
  if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
3140
3292
  return;
3141
3293
  }
@@ -3165,6 +3317,17 @@ function createVersionPlugin() {
3165
3317
  }
3166
3318
  };
3167
3319
  }
3320
+ function isViteDepCachePath(filePath, cacheDir) {
3321
+ if (!filePath) return false;
3322
+ const normalized = filePath.replace(/\\/g, "/");
3323
+ if (cacheDir) {
3324
+ const normalizedCacheDir = cacheDir.replace(/\\/g, "/").replace(/\/+$/, "");
3325
+ if (normalized === normalizedCacheDir || normalized.startsWith(normalizedCacheDir + "/")) {
3326
+ return true;
3327
+ }
3328
+ }
3329
+ return /\/node_modules\/\.vite[^/]*\//.test(normalized) || normalized.includes("/.vite-isolated/");
3330
+ }
3168
3331
 
3169
3332
  // src/vite/utils/shared-utils.ts
3170
3333
  import * as Vite from "vite";
@@ -3193,22 +3356,18 @@ function patchRsdwClientDebugInfoRecovery(code) {
3193
3356
  };
3194
3357
  }
3195
3358
  function performanceTracksOptimizeDepsPlugin() {
3359
+ const RSDW_CLIENT_RE = /react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
3196
3360
  return {
3197
3361
  name: "@rangojs/router:performance-tracks-optimize-deps",
3198
- setup(build) {
3199
- build.onLoad(
3200
- {
3201
- filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
3202
- },
3203
- async (args) => {
3204
- const code = await readFile(args.path, "utf8");
3205
- const patched = patchRsdwClientDebugInfoRecovery(code);
3206
- return {
3207
- contents: patched.code,
3208
- loader: "js"
3209
- };
3210
- }
3211
- );
3362
+ // Vite 8 optimizes deps with Rolldown (Rollup-style plugin pipeline), so the
3363
+ // pre-bundled RSDW client is patched via load() rather than esbuild's onLoad.
3364
+ // Returning code overrides Rolldown's default filesystem read for the module.
3365
+ async load(id) {
3366
+ const cleanId = id.split("?")[0] ?? id;
3367
+ if (!RSDW_CLIENT_RE.test(cleanId)) return null;
3368
+ const code = await readFile(cleanId, "utf8");
3369
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3370
+ return { code: patched.code };
3212
3371
  }
3213
3372
  };
3214
3373
  }
@@ -3235,24 +3394,27 @@ function performanceTracksPlugin() {
3235
3394
  }
3236
3395
 
3237
3396
  // src/vite/utils/shared-utils.ts
3238
- var versionEsbuildPlugin = {
3397
+ function resolveRscEntryFromConfig(config) {
3398
+ const entries = config.environments?.["rsc"]?.optimizeDeps?.entries;
3399
+ if (typeof entries === "string") return entries;
3400
+ if (Array.isArray(entries) && entries.length > 0) return entries[0];
3401
+ return void 0;
3402
+ }
3403
+ var versionRolldownPlugin = {
3239
3404
  name: "@rangojs/router-version",
3240
- setup(build) {
3241
- build.onResolve({ filter: /^rsc-router:version$/ }, (args) => ({
3242
- path: args.path,
3243
- namespace: "@rangojs/router-virtual"
3244
- }));
3245
- build.onLoad(
3246
- { filter: /.*/, namespace: "@rangojs/router-virtual" },
3247
- () => ({
3248
- contents: `export const VERSION = "dev";`,
3249
- loader: "js"
3250
- })
3251
- );
3405
+ resolveId(id) {
3406
+ if (id === VIRTUAL_IDS.version) return "\0" + VIRTUAL_IDS.version;
3407
+ return void 0;
3408
+ },
3409
+ load(id) {
3410
+ if (id === "\0" + VIRTUAL_IDS.version) {
3411
+ return getVirtualVersionContent("dev");
3412
+ }
3413
+ return void 0;
3252
3414
  }
3253
3415
  };
3254
- var sharedEsbuildOptions = {
3255
- plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
3416
+ var sharedRolldownOptions = {
3417
+ plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()]
3256
3418
  };
3257
3419
  function createVirtualEntriesPlugin(entries, routerPathRef) {
3258
3420
  const virtualModules = {};
@@ -3294,8 +3456,29 @@ function createVirtualEntriesPlugin(entries, routerPathRef) {
3294
3456
  }
3295
3457
  };
3296
3458
  }
3459
+ function isContentHashedAssetConflict(message) {
3460
+ if (!message) return false;
3461
+ const match = /The emitted file "?([^"\s]+)"? overwrites a previously emitted file/.exec(
3462
+ message
3463
+ );
3464
+ if (!match) return false;
3465
+ const fileName = match[1];
3466
+ const base = fileName.slice(fileName.lastIndexOf("/") + 1);
3467
+ const dot = base.lastIndexOf(".");
3468
+ if (dot <= 0) return false;
3469
+ const stem = base.slice(0, dot);
3470
+ const HASH_LEN = 8;
3471
+ if (stem.length < HASH_LEN + 1 || stem[stem.length - HASH_LEN - 1] !== "-") {
3472
+ return false;
3473
+ }
3474
+ const hash = stem.slice(-HASH_LEN);
3475
+ return /^[A-Za-z0-9_-]+$/.test(hash) && /[A-Z0-9]/.test(hash);
3476
+ }
3297
3477
  function onwarn(warning, defaultHandler) {
3298
- if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.code === "SOURCEMAP_ERROR" || warning.code === "EMPTY_BUNDLE") {
3478
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.code === "SOURCEMAP_ERROR" || warning.code === "EMPTY_BUNDLE" || warning.code === "INEFFECTIVE_DYNAMIC_IMPORT") {
3479
+ return;
3480
+ }
3481
+ if (warning.code === "FILE_NAME_CONFLICT" && isContentHashedAssetConflict(warning.message)) {
3299
3482
  return;
3300
3483
  }
3301
3484
  if (warning.message?.includes("Sourcemap is likely to be incorrect")) {
@@ -3314,12 +3497,138 @@ function getManualChunks(id) {
3314
3497
  return "react";
3315
3498
  }
3316
3499
  const packageName = getPublishedPackageName();
3317
- if (normalized.includes(`node_modules/${packageName}/`) || normalized.includes("packages/rsc-router/") || normalized.includes("packages/rangojs-router/")) {
3500
+ if (normalized.includes(`node_modules/${packageName}/`) || /\/packages\/(rsc-router|rangojs-router)\/(src|dist)\//.test(normalized)) {
3318
3501
  return "router";
3319
3502
  }
3320
3503
  return void 0;
3321
3504
  }
3322
3505
 
3506
+ // src/vite/plugins/client-ref-hashing.ts
3507
+ import { relative } from "node:path";
3508
+ import { createHash as createHash2 } from "node:crypto";
3509
+ var debug7 = createRangoDebugger(NS.transform);
3510
+ var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3511
+ var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3512
+ var FS_PREFIX = "/@fs/";
3513
+ function hashRefKey(relativeId) {
3514
+ return createHash2("sha256").update(relativeId).digest("hex").slice(0, 12);
3515
+ }
3516
+ function computeProductionHash(projectRoot, refKey) {
3517
+ let toHash;
3518
+ if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
3519
+ toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
3520
+ } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
3521
+ const absPath = decodeURIComponent(
3522
+ refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3523
+ );
3524
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3525
+ } else if (refKey.startsWith(FS_PREFIX)) {
3526
+ const absPath = refKey.slice(FS_PREFIX.length - 1);
3527
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3528
+ } else if (refKey.startsWith("/")) {
3529
+ toHash = refKey.slice(1);
3530
+ } else {
3531
+ return refKey;
3532
+ }
3533
+ return hashRefKey(toHash);
3534
+ }
3535
+ var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
3536
+ function transformClientRefs(code, projectRoot) {
3537
+ if (!code.includes("registerClientReference")) return null;
3538
+ let hasReplacement = false;
3539
+ const result = code.replace(
3540
+ REGISTER_CLIENT_REF_RE,
3541
+ (match, refKey) => {
3542
+ const hash = computeProductionHash(projectRoot, refKey);
3543
+ if (hash === refKey) return match;
3544
+ hasReplacement = true;
3545
+ return match.replace(`"${refKey}"`, `"${hash}"`);
3546
+ }
3547
+ );
3548
+ return hasReplacement ? result : null;
3549
+ }
3550
+ function hashClientRefs(projectRoot) {
3551
+ const counter = createCounter(debug7, "hash-client-refs");
3552
+ return {
3553
+ name: "@rangojs/router:hash-client-refs",
3554
+ // Run after the RSC plugin's transform (default enforce is normal)
3555
+ enforce: "post",
3556
+ applyToEnvironment(env) {
3557
+ return env.name === "rsc";
3558
+ },
3559
+ buildEnd() {
3560
+ counter?.flush();
3561
+ },
3562
+ transform(code, id) {
3563
+ const start = counter ? performance.now() : 0;
3564
+ try {
3565
+ const result = transformClientRefs(code, projectRoot);
3566
+ if (result === null) return;
3567
+ return { code: result, map: null };
3568
+ } finally {
3569
+ counter?.record(id, performance.now() - start);
3570
+ }
3571
+ }
3572
+ };
3573
+ }
3574
+
3575
+ // src/vite/utils/client-chunks.ts
3576
+ var debugChunks = createRangoDebugger(NS.chunks);
3577
+ function isSharedRuntime(meta) {
3578
+ return [meta.id, meta.normalizedId].some(
3579
+ (path6) => path6.includes("/node_modules/") || /\/@rangojs\/router\//.test(path6) || /\/packages\/(rangojs-router|rsc-router)\/(src|dist)\//.test(path6)
3580
+ );
3581
+ }
3582
+ function sanitizeGroup(name) {
3583
+ return name.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "app";
3584
+ }
3585
+ var ROUTE_ROOT_DIRS = /* @__PURE__ */ new Set([
3586
+ "routes",
3587
+ "route",
3588
+ "pages",
3589
+ "page",
3590
+ "app",
3591
+ "features",
3592
+ "feature",
3593
+ "views",
3594
+ "view",
3595
+ "handlers",
3596
+ "urls",
3597
+ "modules",
3598
+ "screens",
3599
+ "sections"
3600
+ ]);
3601
+ function directoryClientChunks(meta, ctx) {
3602
+ if (isSharedRuntime(meta)) {
3603
+ return void 0;
3604
+ }
3605
+ if (ctx?.fallbackRefs.size && ctx.fallbackRefs.has(hashRefKey(meta.normalizedId))) {
3606
+ debugChunks?.("fallback %s -> app-fallback", meta.normalizedId);
3607
+ return "app-fallback";
3608
+ }
3609
+ const segments = meta.normalizedId.split("/").filter(Boolean);
3610
+ const dirCount = segments.length - 1;
3611
+ if (dirCount >= 1) {
3612
+ for (let i = 0; i < dirCount - 1; i++) {
3613
+ if (ROUTE_ROOT_DIRS.has(segments[i].toLowerCase())) {
3614
+ const group = `app-${sanitizeGroup(segments[i + 1])}`;
3615
+ debugChunks?.("split %s -> %s", meta.normalizedId, group);
3616
+ return group;
3617
+ }
3618
+ }
3619
+ }
3620
+ debugChunks?.(
3621
+ "shared %s (no route-root marker; inherits default grouping)",
3622
+ meta.normalizedId
3623
+ );
3624
+ return void 0;
3625
+ }
3626
+ function resolveClientChunks(option, ctx) {
3627
+ if (!option) return void 0;
3628
+ if (option === true) return (meta) => directoryClientChunks(meta, ctx);
3629
+ return option;
3630
+ }
3631
+
3323
3632
  // src/vite/utils/banner.ts
3324
3633
  var rangoVersion = package_default.version;
3325
3634
  var _bannerPrinted = false;
@@ -3356,15 +3665,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
3356
3665
  enforce: "pre",
3357
3666
  configResolved(config) {
3358
3667
  let entryPath = rscEntryPath;
3359
- if (!entryPath) {
3360
- const rscEnvConfig = config.environments?.["rsc"];
3361
- const entries = rscEnvConfig?.optimizeDeps?.entries;
3362
- if (typeof entries === "string") {
3363
- entryPath = entries;
3364
- } else if (Array.isArray(entries) && entries.length > 0) {
3365
- entryPath = entries[0];
3366
- }
3367
- }
3668
+ if (!entryPath) entryPath = resolveRscEntryFromConfig(config);
3368
3669
  if (entryPath) {
3369
3670
  resolvedEntryPath = resolve4(config.root, entryPath);
3370
3671
  }
@@ -3412,23 +3713,23 @@ function createVersionInjectorPlugin(rscEntryPath) {
3412
3713
  }
3413
3714
 
3414
3715
  // src/vite/plugins/cjs-to-esm.ts
3415
- var debug7 = createRangoDebugger(NS.transform);
3716
+ var debug8 = createRangoDebugger(NS.transform);
3416
3717
  function createCjsToEsmPlugin() {
3417
3718
  return {
3418
3719
  name: "@rangojs/router:cjs-to-esm",
3419
3720
  enforce: "pre",
3420
3721
  transform(code, id) {
3421
- const cleanId = id.split("?")[0];
3422
- if (cleanId.includes("vendor/react-server-dom/client.browser.js") || cleanId.includes("vendor\\react-server-dom\\client.browser.js")) {
3722
+ const cleanId = id.split("?")[0].replaceAll("\\", "/");
3723
+ if (cleanId.includes("vendor/react-server-dom/client.browser.js")) {
3423
3724
  const isProd = process.env.NODE_ENV === "production";
3424
3725
  const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
3425
- debug7?.("cjs-to-esm entry redirect %s", id);
3726
+ debug8?.("cjs-to-esm entry redirect %s", id);
3426
3727
  return {
3427
3728
  code: `export * from "${cjsFile}";`,
3428
3729
  map: null
3429
3730
  };
3430
3731
  }
3431
- if ((cleanId.includes("vendor/react-server-dom/cjs/") || cleanId.includes("vendor\\react-server-dom\\cjs\\")) && cleanId.includes("client.browser")) {
3732
+ if (cleanId.includes("vendor/react-server-dom/cjs/") && cleanId.includes("client.browser")) {
3432
3733
  let transformed = code;
3433
3734
  const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
3434
3735
  const license = licenseMatch ? licenseMatch[0] : "";
@@ -3458,7 +3759,7 @@ function createCjsToEsmPlugin() {
3458
3759
  "export const $1 ="
3459
3760
  );
3460
3761
  transformed = license + "\n" + transformed;
3461
- debug7?.("cjs-to-esm body rewrite %s", id);
3762
+ debug8?.("cjs-to-esm body rewrite %s", id);
3462
3763
  return {
3463
3764
  code: transformed,
3464
3765
  map: null
@@ -3547,7 +3848,7 @@ function createCloudflareProtocolStubPlugin() {
3547
3848
  if (!code.includes(CF_PREFIX)) return null;
3548
3849
  let ast;
3549
3850
  try {
3550
- ast = this.parse(code);
3851
+ ast = this.parse(code, { lang: "tsx" });
3551
3852
  } catch {
3552
3853
  return null;
3553
3854
  }
@@ -3607,72 +3908,6 @@ function walk(node, visit) {
3607
3908
  }
3608
3909
  }
3609
3910
 
3610
- // src/vite/plugins/client-ref-hashing.ts
3611
- import { relative } from "node:path";
3612
- import { createHash as createHash2 } from "node:crypto";
3613
- var debug8 = createRangoDebugger(NS.transform);
3614
- var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3615
- var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3616
- var FS_PREFIX = "/@fs/";
3617
- function computeProductionHash(projectRoot, refKey) {
3618
- let toHash;
3619
- if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
3620
- toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
3621
- } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
3622
- const absPath = decodeURIComponent(
3623
- refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3624
- );
3625
- toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3626
- } else if (refKey.startsWith(FS_PREFIX)) {
3627
- const absPath = refKey.slice(FS_PREFIX.length - 1);
3628
- toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3629
- } else if (refKey.startsWith("/")) {
3630
- toHash = refKey.slice(1);
3631
- } else {
3632
- return refKey;
3633
- }
3634
- return createHash2("sha256").update(toHash).digest("hex").slice(0, 12);
3635
- }
3636
- var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
3637
- function transformClientRefs(code, projectRoot) {
3638
- if (!code.includes("registerClientReference")) return null;
3639
- let hasReplacement = false;
3640
- const result = code.replace(
3641
- REGISTER_CLIENT_REF_RE,
3642
- (match, refKey) => {
3643
- const hash = computeProductionHash(projectRoot, refKey);
3644
- if (hash === refKey) return match;
3645
- hasReplacement = true;
3646
- return match.replace(`"${refKey}"`, `"${hash}"`);
3647
- }
3648
- );
3649
- return hasReplacement ? result : null;
3650
- }
3651
- function hashClientRefs(projectRoot) {
3652
- const counter = createCounter(debug8, "hash-client-refs");
3653
- return {
3654
- name: "@rangojs/router:hash-client-refs",
3655
- // Run after the RSC plugin's transform (default enforce is normal)
3656
- enforce: "post",
3657
- applyToEnvironment(env) {
3658
- return env.name === "rsc";
3659
- },
3660
- buildEnd() {
3661
- counter?.flush();
3662
- },
3663
- transform(code, id) {
3664
- const start = counter ? performance.now() : 0;
3665
- try {
3666
- const result = transformClientRefs(code, projectRoot);
3667
- if (result === null) return;
3668
- return { code: result, map: null };
3669
- } finally {
3670
- counter?.record(id, performance.now() - start);
3671
- }
3672
- }
3673
- };
3674
- }
3675
-
3676
3911
  // src/vite/utils/bundle-analysis.ts
3677
3912
  function findMatchingParenInBundle(code, openParenPos) {
3678
3913
  let depth = 1;
@@ -3703,7 +3938,7 @@ function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detec
3703
3938
  if (detectPassthrough) {
3704
3939
  const eFnName = escapeRegExp(fnName);
3705
3940
  const callStartRe = new RegExp(
3706
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3941
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3707
3942
  );
3708
3943
  const callStart = callStartRe.exec(chunkCode);
3709
3944
  if (callStart) {
@@ -3728,7 +3963,7 @@ function evictHandlerCode(code, exports, fnName, brand) {
3728
3963
  if (passthrough) continue;
3729
3964
  const eName = escapeRegExp(name);
3730
3965
  const callStartRe = new RegExp(
3731
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3966
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3732
3967
  );
3733
3968
  const startMatch = callStartRe.exec(modified);
3734
3969
  if (!startMatch) continue;
@@ -3763,6 +3998,8 @@ function createDiscoveryState(entryPath, opts) {
3763
3998
  projectRoot: "",
3764
3999
  isBuildMode: false,
3765
4000
  userResolveAlias: void 0,
4001
+ userRunnerConfig: void 0,
4002
+ userResolvePlugins: [],
3766
4003
  scanFilter: void 0,
3767
4004
  cachedRouterFiles: void 0,
3768
4005
  opts,
@@ -3784,7 +4021,8 @@ function createDiscoveryState(entryPath, opts) {
3784
4021
  devServerOrigin: null,
3785
4022
  devServer: null,
3786
4023
  selfWrittenGenFiles: /* @__PURE__ */ new Map(),
3787
- SELF_WRITE_WINDOW_MS: 5e3
4024
+ SELF_WRITE_WINDOW_MS: 5e3,
4025
+ lastDiscoveryError: null
3788
4026
  };
3789
4027
  }
3790
4028
 
@@ -3822,11 +4060,14 @@ function checkSelfGenWrite(state, filePath, consume) {
3822
4060
  }
3823
4061
  }
3824
4062
 
3825
- // src/vite/utils/manifest-utils.ts
4063
+ // src/build/prefix-tree-utils.ts
3826
4064
  function flattenLeafEntries(prefixTree, routeManifest, result) {
3827
- function visit(node) {
4065
+ function visit(node, ancestorStaticPrefixes) {
3828
4066
  const children = node.children || {};
3829
4067
  if (Object.keys(children).length === 0 && node.routes && node.routes.length > 0) {
4068
+ if (ancestorStaticPrefixes.has(node.staticPrefix)) {
4069
+ return;
4070
+ }
3830
4071
  const routes = {};
3831
4072
  for (const name of node.routes) {
3832
4073
  if (name in routeManifest) {
@@ -3835,13 +4076,15 @@ function flattenLeafEntries(prefixTree, routeManifest, result) {
3835
4076
  }
3836
4077
  result.push({ staticPrefix: node.staticPrefix, routes });
3837
4078
  } else {
4079
+ const nextAncestors = new Set(ancestorStaticPrefixes);
4080
+ nextAncestors.add(node.staticPrefix);
3838
4081
  for (const child of Object.values(children)) {
3839
- visit(child);
4082
+ visit(child, nextAncestors);
3840
4083
  }
3841
4084
  }
3842
4085
  }
3843
4086
  for (const node of Object.values(prefixTree)) {
3844
- visit(node);
4087
+ visit(node, /* @__PURE__ */ new Set());
3845
4088
  }
3846
4089
  }
3847
4090
  function buildRouteToStaticPrefix(prefixTree, result) {
@@ -3858,6 +4101,8 @@ function buildRouteToStaticPrefix(prefixTree, result) {
3858
4101
  visit(node);
3859
4102
  }
3860
4103
  }
4104
+
4105
+ // src/vite/utils/manifest-utils.ts
3861
4106
  function jsonParseExpression(value) {
3862
4107
  const json = JSON.stringify(value);
3863
4108
  const escaped = json.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
@@ -4068,7 +4313,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4068
4313
  const progressInterval = totalDynamic > 0 ? setInterval(() => {
4069
4314
  const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4070
4315
  console.log(
4071
- `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4316
+ `[rango] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4072
4317
  );
4073
4318
  }, 5e3) : void 0;
4074
4319
  try {
@@ -4105,7 +4350,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4105
4350
  get env() {
4106
4351
  if (buildEnv !== void 0) return buildEnv;
4107
4352
  throw new Error(
4108
- "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
4353
+ "[rango] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
4109
4354
  );
4110
4355
  }
4111
4356
  };
@@ -4146,7 +4391,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4146
4391
  resolvedRoutes++;
4147
4392
  if (err.name === "Skip") {
4148
4393
  console.log(
4149
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
4394
+ `[rango] SKIP route "${routeName}" - ${err.message}`
4150
4395
  );
4151
4396
  notifyOnError(
4152
4397
  registry,
@@ -4159,14 +4404,14 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4159
4404
  continue;
4160
4405
  }
4161
4406
  console.error(
4162
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
4407
+ `[rango] Failed to get params for prerender route "${routeName}": ${err.message}`
4163
4408
  );
4164
4409
  notifyOnError(registry, err, "prerender", routeName);
4165
4410
  throw err;
4166
4411
  }
4167
4412
  } else {
4168
4413
  console.warn(
4169
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
4414
+ `[rango] Dynamic prerender route "${routeName}" has no getParams(), skipping`
4170
4415
  );
4171
4416
  }
4172
4417
  }
@@ -4177,7 +4422,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4177
4422
  clearInterval(progressInterval);
4178
4423
  const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4179
4424
  console.log(
4180
- `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4425
+ `[rango] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4181
4426
  );
4182
4427
  }
4183
4428
  }
@@ -4191,7 +4436,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4191
4436
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
4192
4437
  const concurrencyNote = maxConcurrency > 1 ? ` (concurrency: ${maxConcurrency})` : "";
4193
4438
  console.log(
4194
- `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
4439
+ `[rango] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
4195
4440
  );
4196
4441
  debug9?.(
4197
4442
  "prerender loop: %d entries, max concurrency %d",
@@ -4224,7 +4469,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4224
4469
  if (result.passthrough) {
4225
4470
  const elapsed2 = (performance.now() - startUrl).toFixed(0);
4226
4471
  console.log(
4227
- `[rsc-router] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
4472
+ `[rango] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
4228
4473
  );
4229
4474
  doneCount++;
4230
4475
  break;
@@ -4257,7 +4502,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4257
4502
  }
4258
4503
  const elapsed = (performance.now() - startUrl).toFixed(0);
4259
4504
  console.log(
4260
- `[rsc-router] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`
4505
+ `[rango] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`
4261
4506
  );
4262
4507
  doneCount++;
4263
4508
  break;
@@ -4265,7 +4510,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4265
4510
  if (err.name === "Skip") {
4266
4511
  const elapsed2 = (performance.now() - startUrl).toFixed(0);
4267
4512
  console.log(
4268
- `[rsc-router] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4513
+ `[rango] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4269
4514
  );
4270
4515
  skipCount++;
4271
4516
  notifyOnError(
@@ -4280,7 +4525,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4280
4525
  }
4281
4526
  const elapsed = (performance.now() - startUrl).toFixed(0);
4282
4527
  console.error(
4283
- `[rsc-router] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
4528
+ `[rango] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
4284
4529
  );
4285
4530
  notifyOnError(
4286
4531
  registry,
@@ -4302,7 +4547,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4302
4547
  const parts = [`${doneCount} done`];
4303
4548
  if (skipCount > 0) parts.push(`${skipCount} skipped`);
4304
4549
  console.log(
4305
- `[rsc-router] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
4550
+ `[rango] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
4306
4551
  );
4307
4552
  debug9?.(
4308
4553
  "expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
@@ -4328,16 +4573,14 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4328
4573
  totalStaticCount += exportNames.length;
4329
4574
  }
4330
4575
  const startStatic = performance.now();
4331
- console.log(
4332
- `[rsc-router] Rendering ${totalStaticCount} static handler(s)...`
4333
- );
4576
+ console.log(`[rango] Rendering ${totalStaticCount} static handler(s)...`);
4334
4577
  for (const [moduleId, exportNames] of state.resolvedStaticModules) {
4335
4578
  let mod;
4336
4579
  try {
4337
4580
  mod = await rscEnv.runner.import(moduleId);
4338
4581
  } catch (err) {
4339
4582
  console.error(
4340
- `[rsc-router] Failed to import static module ${moduleId}: ${err.message}`
4583
+ `[rango] Failed to import static module ${moduleId}: ${err.message}`
4341
4584
  );
4342
4585
  notifyOnError(registry, err, "static");
4343
4586
  throw err;
@@ -4367,9 +4610,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4367
4610
  exportValue
4368
4611
  );
4369
4612
  const elapsed = (performance.now() - startHandler).toFixed(0);
4370
- console.log(
4371
- `[rsc-router] OK ${name.padEnd(40)} (${elapsed}ms)`
4372
- );
4613
+ console.log(`[rango] OK ${name.padEnd(40)} (${elapsed}ms)`);
4373
4614
  staticDone++;
4374
4615
  handled = true;
4375
4616
  break;
@@ -4378,7 +4619,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4378
4619
  if (err.name === "Skip") {
4379
4620
  const elapsed2 = (performance.now() - startHandler).toFixed(0);
4380
4621
  console.log(
4381
- `[rsc-router] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4622
+ `[rango] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4382
4623
  );
4383
4624
  staticSkip++;
4384
4625
  notifyOnError(registry, err, "static", void 0, void 0, true);
@@ -4387,16 +4628,14 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4387
4628
  }
4388
4629
  const elapsed = (performance.now() - startHandler).toFixed(0);
4389
4630
  console.error(
4390
- `[rsc-router] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
4631
+ `[rango] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
4391
4632
  );
4392
4633
  notifyOnError(registry, err, "static");
4393
4634
  throw err;
4394
4635
  }
4395
4636
  }
4396
4637
  if (!handled) {
4397
- console.warn(
4398
- `[rsc-router] No router could render static handler "${name}"`
4399
- );
4638
+ console.warn(`[rango] No router could render static handler "${name}"`);
4400
4639
  }
4401
4640
  }
4402
4641
  }
@@ -4407,7 +4646,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4407
4646
  const staticParts = [`${staticDone} done`];
4408
4647
  if (staticSkip > 0) staticParts.push(`${staticSkip} skipped`);
4409
4648
  console.log(
4410
- `[rsc-router] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
4649
+ `[rango] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
4411
4650
  );
4412
4651
  debug9?.(
4413
4652
  "renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
@@ -4418,6 +4657,80 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4418
4657
  );
4419
4658
  }
4420
4659
 
4660
+ // src/vite/discovery/discovery-errors.ts
4661
+ function indent(text, pad) {
4662
+ return text.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n");
4663
+ }
4664
+ async function invokeLazyMount(loader, context, errors) {
4665
+ try {
4666
+ await loader();
4667
+ } catch (error) {
4668
+ errors.push({ context, error });
4669
+ }
4670
+ }
4671
+ function isLazyMount(route) {
4672
+ return !!route && route.kind === "lazy" && typeof route.handler === "function";
4673
+ }
4674
+ async function resolveHostRouterHandlers(hostRegistry) {
4675
+ const errors = [];
4676
+ for (const [hostId, entry] of hostRegistry) {
4677
+ for (const route of entry.routes) {
4678
+ if (isLazyMount(route)) {
4679
+ await invokeLazyMount(
4680
+ route.handler,
4681
+ `host "${hostId}" route handler`,
4682
+ errors
4683
+ );
4684
+ }
4685
+ }
4686
+ if (isLazyMount(entry.fallback)) {
4687
+ await invokeLazyMount(
4688
+ entry.fallback.handler,
4689
+ `host "${hostId}" fallback handler`,
4690
+ errors
4691
+ );
4692
+ }
4693
+ }
4694
+ return errors;
4695
+ }
4696
+ function formatNoRoutersError(entryPath, errors) {
4697
+ const base = `[rango] No routers found in registry after importing ${entryPath}`;
4698
+ if (errors.length === 0) {
4699
+ return base;
4700
+ }
4701
+ const formatted = errors.map(({ context, error }) => {
4702
+ const err = error instanceof Error ? error : new Error(String(error));
4703
+ const detail = err.stack ?? err.message;
4704
+ return ` - while resolving ${context}:
4705
+ ${indent(detail, " ")}`;
4706
+ }).join("\n");
4707
+ return `${base}
4708
+
4709
+ ${errors.length} error(s) were caught during host-router discovery and likely explain why no routers were registered:
4710
+ ${formatted}`;
4711
+ }
4712
+ function toCause(errors) {
4713
+ if (errors.length === 0) return void 0;
4714
+ if (errors.length === 1) return errors[0].error;
4715
+ return new AggregateError(
4716
+ errors.map((e) => e.error),
4717
+ "Multiple host-router handlers failed during discovery"
4718
+ );
4719
+ }
4720
+ var DiscoveryError = class _DiscoveryError extends Error {
4721
+ constructor(entryPath, caught) {
4722
+ super(formatNoRoutersError(entryPath, caught));
4723
+ const cause = toCause(caught);
4724
+ if (cause !== void 0) {
4725
+ this.cause = cause;
4726
+ }
4727
+ this.name = "DiscoveryError";
4728
+ this.entryPath = entryPath;
4729
+ this.caught = caught;
4730
+ Object.setPrototypeOf(this, _DiscoveryError.prototype);
4731
+ }
4732
+ };
4733
+
4421
4734
  // src/vite/discovery/discover-routers.ts
4422
4735
  var debug10 = createRangoDebugger(NS.discovery);
4423
4736
  async function discoverRouters(state, rscEnv) {
@@ -4434,27 +4747,17 @@ async function discoverRouters(state, rscEnv) {
4434
4747
  );
4435
4748
  let registry = serverMod.RouterRegistry;
4436
4749
  if (!registry || registry.size === 0) {
4750
+ const discoveryErrors = [];
4437
4751
  try {
4438
4752
  const hostRegistry = serverMod.HostRouterRegistry;
4439
4753
  if (hostRegistry && hostRegistry.size > 0) {
4440
4754
  console.log(
4441
- `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
4755
+ `[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
4442
4756
  );
4443
- for (const [, entry] of hostRegistry) {
4444
- for (const route of entry.routes) {
4445
- if (typeof route.handler === "function") {
4446
- try {
4447
- await route.handler();
4448
- } catch {
4449
- }
4450
- }
4451
- }
4452
- if (entry.fallback && typeof entry.fallback.handler === "function") {
4453
- try {
4454
- await entry.fallback.handler();
4455
- } catch {
4456
- }
4457
- }
4757
+ const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
4758
+ discoveryErrors.push(...handlerErrors);
4759
+ for (const { context, error } of handlerErrors) {
4760
+ debug10?.("caught error while resolving %s: %O", context, error);
4458
4761
  }
4459
4762
  const freshServerMod = await rscEnv.runner.import(
4460
4763
  "@rangojs/router/server"
@@ -4465,12 +4768,11 @@ async function discoverRouters(state, rscEnv) {
4465
4768
  registry = freshRegistry;
4466
4769
  }
4467
4770
  }
4468
- } catch {
4771
+ } catch (error) {
4772
+ discoveryErrors.push({ context: "host-router discovery", error });
4469
4773
  }
4470
4774
  if (!registry || registry.size === 0) {
4471
- throw new Error(
4472
- `[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`
4473
- );
4775
+ throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
4474
4776
  }
4475
4777
  }
4476
4778
  const buildMod = await timed(
@@ -4498,6 +4800,15 @@ async function discoverRouters(state, rscEnv) {
4498
4800
  let mergedRouteTrailingSlash = {};
4499
4801
  let routerMountIndex = 0;
4500
4802
  const allManifests = [];
4803
+ const clientChunkCtx = state.opts?.clientChunkCtx;
4804
+ const collectClientFallbackRef = clientChunkCtx ? (refKey) => clientChunkCtx.fallbackRefs.add(
4805
+ computeProductionHash(state.projectRoot, refKey)
4806
+ ) : void 0;
4807
+ const collectFromBoundaryNode = (node) => {
4808
+ if (collectClientFallbackRef && buildMod.collectFallbackClientRefs) {
4809
+ buildMod.collectFallbackClientRefs(node, collectClientFallbackRef);
4810
+ }
4811
+ };
4501
4812
  const manifestGenStart = debug10 ? performance.now() : 0;
4502
4813
  for (const [id, router] of registry) {
4503
4814
  if (!router.urlpatterns || !generateManifestFull) {
@@ -4506,10 +4817,18 @@ async function discoverRouters(state, rscEnv) {
4506
4817
  const manifest = generateManifestFull(
4507
4818
  router.urlpatterns,
4508
4819
  routerMountIndex,
4509
- router.__basename ? { urlPrefix: router.__basename } : void 0
4820
+ {
4821
+ ...router.__basename ? { urlPrefix: router.__basename } : {},
4822
+ ...collectClientFallbackRef ? { collectClientFallbackRef } : {}
4823
+ }
4510
4824
  );
4511
4825
  routerMountIndex++;
4512
4826
  allManifests.push({ id, manifest });
4827
+ if (collectClientFallbackRef) {
4828
+ collectFromBoundaryNode(router.__defaultErrorBoundary);
4829
+ collectFromBoundaryNode(router.__defaultNotFoundBoundary);
4830
+ collectFromBoundaryNode(router.__notFound);
4831
+ }
4513
4832
  const routeCount = Object.keys(manifest.routeManifest).length;
4514
4833
  const staticRoutes = Object.values(manifest.routeManifest).filter(
4515
4834
  (p) => !p.includes(":") && !p.includes("*")
@@ -4560,7 +4879,7 @@ async function discoverRouters(state, rscEnv) {
4560
4879
  );
4561
4880
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
4562
4881
  console.log(
4563
- `[rsc-router] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
4882
+ `[rango] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
4564
4883
  );
4565
4884
  }
4566
4885
  if (registry.size > 1) {
@@ -4569,7 +4888,7 @@ async function discoverRouters(state, rscEnv) {
4569
4888
  );
4570
4889
  if (autoIds.length > 1) {
4571
4890
  console.warn(
4572
- `[rsc-router] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). In multi-router setups, each createRouter() must have an explicit \`id\` option to ensure per-router manifest data is matched correctly at runtime. Example: createRouter({ id: "site", ... })`
4891
+ `[rango] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). In multi-router setups, each createRouter() must have an explicit \`id\` option to ensure per-router manifest data is matched correctly at runtime. Example: createRouter({ id: "site", ... })`
4573
4892
  );
4574
4893
  }
4575
4894
  }
@@ -4614,31 +4933,17 @@ async function discoverRouters(state, rscEnv) {
4614
4933
  newMergedRouteManifest,
4615
4934
  mergedRouteAncestry,
4616
4935
  routeToStaticPrefix,
4617
- Object.keys(mergedRouteTrailingSlash).length > 0 ? mergedRouteTrailingSlash : void 0,
4618
- prerenderRouteNames.size > 0 ? prerenderRouteNames : void 0,
4619
- passthroughRouteNames.size > 0 ? passthroughRouteNames : void 0,
4620
- Object.keys(mergedResponseTypeRoutes).length > 0 ? mergedResponseTypeRoutes : void 0
4936
+ mergedRouteTrailingSlash,
4937
+ prerenderRouteNames,
4938
+ passthroughRouteNames,
4939
+ mergedResponseTypeRoutes
4621
4940
  );
4941
+ const buildPerRouterTrie = buildMod.buildPerRouterTrie;
4622
4942
  for (const { id, manifest } of allManifests) {
4623
- if (!manifest._routeAncestry || Object.keys(manifest._routeAncestry).length === 0)
4624
- continue;
4625
- const perRouterStaticPrefix = {};
4626
- for (const name of Object.keys(manifest.routeManifest)) {
4627
- perRouterStaticPrefix[name] = "";
4943
+ const perRouterTrie = buildPerRouterTrie ? buildPerRouterTrie(manifest) : null;
4944
+ if (perRouterTrie) {
4945
+ newPerRouterTrieMap.set(id, perRouterTrie);
4628
4946
  }
4629
- buildRouteToStaticPrefix(manifest.prefixTree, perRouterStaticPrefix);
4630
- const perRouterPrerenderNames = manifest.prerenderRoutes ? new Set(manifest.prerenderRoutes) : void 0;
4631
- const perRouterPassthroughNames = manifest.passthroughRoutes ? new Set(manifest.passthroughRoutes) : void 0;
4632
- const perRouterTrie = buildRouteTrie(
4633
- manifest.routeManifest,
4634
- manifest._routeAncestry,
4635
- perRouterStaticPrefix,
4636
- manifest.routeTrailingSlash && Object.keys(manifest.routeTrailingSlash).length > 0 ? manifest.routeTrailingSlash : void 0,
4637
- perRouterPrerenderNames && perRouterPrerenderNames.size > 0 ? perRouterPrerenderNames : void 0,
4638
- perRouterPassthroughNames && perRouterPassthroughNames.size > 0 ? perRouterPassthroughNames : void 0,
4639
- manifest.responseTypeRoutes && Object.keys(manifest.responseTypeRoutes).length > 0 ? manifest.responseTypeRoutes : void 0
4640
- );
4641
- newPerRouterTrieMap.set(id, perRouterTrie);
4642
4947
  }
4643
4948
  }
4644
4949
  }
@@ -4659,7 +4964,7 @@ async function discoverRouters(state, rscEnv) {
4659
4964
  }
4660
4965
 
4661
4966
  // src/vite/discovery/route-types-writer.ts
4662
- import { dirname as dirname3, basename, join as join2, resolve as resolve6 } from "node:path";
4967
+ import { dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
4663
4968
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
4664
4969
  function filterUserNamedRoutes(manifest) {
4665
4970
  const filtered = {};
@@ -4670,39 +4975,20 @@ function filterUserNamedRoutes(manifest) {
4670
4975
  }
4671
4976
  return filtered;
4672
4977
  }
4978
+ function writeGenFileIfChanged(state, outPath, source, opts) {
4979
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4980
+ if (existing === source) return;
4981
+ markSelfGenWrite(state, outPath, source);
4982
+ writeFileSync3(outPath, source);
4983
+ if (opts?.log) console.log(`[rango] Generated route types -> ${outPath}`);
4984
+ }
4673
4985
  function writeCombinedRouteTypesWithTracking(state, opts) {
4674
4986
  const routerFiles = state.cachedRouterFiles ?? findRouterFiles(state.projectRoot, state.scanFilter);
4675
4987
  state.cachedRouterFiles = routerFiles;
4676
- const preContent = /* @__PURE__ */ new Map();
4677
- for (const routerFilePath of routerFiles) {
4678
- const routerDir = dirname3(routerFilePath);
4679
- const routerBasename = basename(routerFilePath).replace(
4680
- /\.(tsx?|jsx?)$/,
4681
- ""
4682
- );
4683
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4684
- try {
4685
- preContent.set(outPath, readFileSync4(outPath, "utf-8"));
4686
- } catch {
4687
- }
4688
- }
4689
- writeCombinedRouteTypes(state.projectRoot, routerFiles, opts);
4690
- for (const routerFilePath of routerFiles) {
4691
- const routerDir = dirname3(routerFilePath);
4692
- const routerBasename = basename(routerFilePath).replace(
4693
- /\.(tsx?|jsx?)$/,
4694
- ""
4695
- );
4696
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4697
- if (!existsSync5(outPath)) continue;
4698
- try {
4699
- const content = readFileSync4(outPath, "utf-8");
4700
- if (content !== preContent.get(outPath)) {
4701
- markSelfGenWrite(state, outPath, content);
4702
- }
4703
- } catch {
4704
- }
4705
- }
4988
+ writeCombinedRouteTypes(state.projectRoot, routerFiles, {
4989
+ ...opts,
4990
+ onWrite: (outPath, content) => markSelfGenWrite(state, outPath, content)
4991
+ });
4706
4992
  }
4707
4993
  function writeRouteTypesFiles(state) {
4708
4994
  if (state.perRouterManifests.length === 0) return;
@@ -4714,7 +5000,7 @@ function writeRouteTypesFiles(state) {
4714
5000
  if (existsSync5(oldCombinedPath)) {
4715
5001
  unlinkSync2(oldCombinedPath);
4716
5002
  console.log(
4717
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
5003
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
4718
5004
  );
4719
5005
  }
4720
5006
  } catch {
@@ -4728,39 +5014,23 @@ function writeRouteTypesFiles(state) {
4728
5014
  if (!sourceFile) continue;
4729
5015
  if (sourceFile.includes("node_modules")) {
4730
5016
  throw new Error(
4731
- `[rsc-router] Router "${id}" has sourceFile inside node_modules: ${sourceFile}
5017
+ `[rango] Router "${id}" has sourceFile inside node_modules: ${sourceFile}
4732
5018
  This means createRouter() stack trace parsing matched a Vite internal frame.
4733
5019
  Set an explicit \`id\` on createRouter() or check the call site.`
4734
5020
  );
4735
5021
  }
4736
- const routerDir = dirname3(sourceFile);
4737
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4738
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
5022
+ const outPath = genFileTsPath(sourceFile);
4739
5023
  const userRoutes = filterUserNamedRoutes(routeManifest);
4740
- let effectiveSearchSchemas = routeSearchSchemas;
4741
- if ((!effectiveSearchSchemas || Object.keys(effectiveSearchSchemas).length === 0) && sourceFile) {
4742
- const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
4743
- if (Object.keys(staticParsed.searchSchemas).length > 0) {
4744
- const filtered = {};
4745
- for (const name of Object.keys(userRoutes)) {
4746
- const schema = staticParsed.searchSchemas[name];
4747
- if (schema) filtered[name] = schema;
4748
- }
4749
- if (Object.keys(filtered).length > 0) {
4750
- effectiveSearchSchemas = filtered;
4751
- }
4752
- }
4753
- }
5024
+ const effectiveSearchSchemas = resolveSearchSchemas(
5025
+ Object.keys(userRoutes),
5026
+ routeSearchSchemas,
5027
+ sourceFile
5028
+ );
4754
5029
  const source = generateRouteTypesSource(
4755
5030
  userRoutes,
4756
5031
  effectiveSearchSchemas && Object.keys(effectiveSearchSchemas).length > 0 ? effectiveSearchSchemas : void 0
4757
5032
  );
4758
- const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4759
- if (existing !== source) {
4760
- markSelfGenWrite(state, outPath, source);
4761
- writeFileSync3(outPath, source);
4762
- console.log(`[rsc-router] Generated route types -> ${outPath}`);
4763
- }
5033
+ writeGenFileIfChanged(state, outPath, source, { log: true });
4764
5034
  }
4765
5035
  }
4766
5036
  function supplementGenFilesWithRuntimeRoutes(state) {
@@ -4798,23 +5068,17 @@ function supplementGenFilesWithRuntimeRoutes(state) {
4798
5068
  }
4799
5069
  }
4800
5070
  }
4801
- const routerDir = dirname3(sourceFile);
4802
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4803
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
5071
+ const outPath = genFileTsPath(sourceFile);
4804
5072
  const source = generateRouteTypesSource(
4805
5073
  mergedRoutes,
4806
5074
  Object.keys(mergedSearchSchemas).length > 0 ? mergedSearchSchemas : void 0
4807
5075
  );
4808
- const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4809
- if (existing !== source) {
4810
- markSelfGenWrite(state, outPath, source);
4811
- writeFileSync3(outPath, source);
4812
- }
5076
+ writeGenFileIfChanged(state, outPath, source);
4813
5077
  }
4814
5078
  }
4815
5079
 
4816
5080
  // src/vite/discovery/virtual-module-codegen.ts
4817
- import { dirname as dirname4, basename as basename2, join as join3 } from "node:path";
5081
+ import { dirname as dirname4, basename, join as join3 } from "node:path";
4818
5082
  function generateRoutesManifestModule(state) {
4819
5083
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
4820
5084
  if (hasManifest) {
@@ -4825,7 +5089,7 @@ function generateRoutesManifestModule(state) {
4825
5089
  for (const entry of state.perRouterManifests) {
4826
5090
  if (entry.sourceFile) {
4827
5091
  const routerDir = dirname4(entry.sourceFile);
4828
- const routerBasename = basename2(entry.sourceFile).replace(
5092
+ const routerBasename = basename(entry.sourceFile).replace(
4829
5093
  /\.(tsx?|jsx?)$/,
4830
5094
  ""
4831
5095
  );
@@ -4846,7 +5110,7 @@ function generateRoutesManifestModule(state) {
4846
5110
  }
4847
5111
  }
4848
5112
  const lines = [
4849
- `import { setCachedManifest, setPrecomputedEntries, setRouteTrie, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
5113
+ `import { setCachedManifest, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
4850
5114
  ...genFileImports,
4851
5115
  // Clear stale per-router cached data (manifest, trie, precomputed entries)
4852
5116
  // before re-populating. In Cloudflare dev mode, program reloads re-evaluate
@@ -4882,18 +5146,6 @@ function generateRoutesManifestModule(state) {
4882
5146
  );
4883
5147
  }
4884
5148
  }
4885
- if (state.isBuildMode) {
4886
- if (state.mergedPrecomputedEntries && state.mergedPrecomputedEntries.length > 0) {
4887
- lines.push(
4888
- `setPrecomputedEntries(${jsonParseExpression(state.mergedPrecomputedEntries)});`
4889
- );
4890
- }
4891
- if (state.mergedRouteTrie) {
4892
- lines.push(
4893
- `setRouteTrie(${jsonParseExpression(state.mergedRouteTrie)});`
4894
- );
4895
- }
4896
- }
4897
5149
  for (const routerId of state.perRouterManifestDataMap.keys()) {
4898
5150
  lines.push(
4899
5151
  `registerRouterManifestLoader(${JSON.stringify(routerId)}, () => import(${JSON.stringify(VIRTUAL_ROUTES_MANIFEST_ID + "/" + routerId)}));`
@@ -4922,7 +5174,7 @@ function generatePerRouterModule(state, routerId) {
4922
5174
  const lines = [];
4923
5175
  if (routerEntry?.sourceFile) {
4924
5176
  const routerDir = dirname4(routerEntry.sourceFile);
4925
- const routerBasename = basename2(routerEntry.sourceFile).replace(
5177
+ const routerBasename = basename(routerEntry.sourceFile).replace(
4926
5178
  /\.(tsx?|jsx?)$/,
4927
5179
  ""
4928
5180
  );
@@ -4993,12 +5245,12 @@ function postprocessBundle(state) {
4993
5245
  writeFileSync4(chunkPath, result.code);
4994
5246
  const savedKB = (result.savedBytes / 1024).toFixed(1);
4995
5247
  console.log(
4996
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
5248
+ `[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4997
5249
  );
4998
5250
  }
4999
5251
  } catch (replaceErr) {
5000
5252
  console.warn(
5001
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
5253
+ `[rango] Failed to evict ${target.label}: ${replaceErr.message}`
5002
5254
  );
5003
5255
  }
5004
5256
  }
@@ -5036,11 +5288,11 @@ function postprocessBundle(state) {
5036
5288
  writeFileSync4(rscEntryPath, injection + rscCode);
5037
5289
  const totalKB = (totalBytes / 1024).toFixed(1);
5038
5290
  console.log(
5039
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
5291
+ `[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
5040
5292
  );
5041
5293
  } catch (err) {
5042
5294
  throw new Error(
5043
- `[rsc-router] Failed to write prerender assets: ${err.message}`
5295
+ `[rango] Failed to write prerender assets: ${err.message}`
5044
5296
  );
5045
5297
  }
5046
5298
  }
@@ -5074,11 +5326,11 @@ function postprocessBundle(state) {
5074
5326
  writeFileSync4(rscEntryPath, injection + rscCode);
5075
5327
  const totalKB = (totalBytes / 1024).toFixed(1);
5076
5328
  console.log(
5077
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
5329
+ `[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
5078
5330
  );
5079
5331
  } catch (err) {
5080
5332
  throw new Error(
5081
- `[rsc-router] Failed to write static assets: ${err.message}`
5333
+ `[rango] Failed to write static assets: ${err.message}`
5082
5334
  );
5083
5335
  }
5084
5336
  }
@@ -5159,6 +5411,57 @@ function createDiscoveryGate(s, debug11) {
5159
5411
  };
5160
5412
  }
5161
5413
 
5414
+ // src/vite/utils/forward-user-plugins.ts
5415
+ function isDenied(name) {
5416
+ return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
5417
+ }
5418
+ function hasResolutionHooks(p) {
5419
+ return Boolean(p.resolveId || p.load);
5420
+ }
5421
+ function stripToResolutionHooks(p) {
5422
+ const stripped = { name: p.name };
5423
+ if (p.enforce) stripped.enforce = p.enforce;
5424
+ if (p.applyToEnvironment)
5425
+ stripped.applyToEnvironment = p.applyToEnvironment;
5426
+ if (p.resolveId) stripped.resolveId = p.resolveId;
5427
+ if (p.load) stripped.load = p.load;
5428
+ return stripped;
5429
+ }
5430
+ function selectForwardableResolvePlugins(plugins) {
5431
+ if (!plugins) return [];
5432
+ const forwarded = [];
5433
+ for (const p of plugins) {
5434
+ const name = p?.name;
5435
+ if (!name || isDenied(name)) continue;
5436
+ if (!hasResolutionHooks(p)) continue;
5437
+ forwarded.push(stripToResolutionHooks(p));
5438
+ }
5439
+ return forwarded;
5440
+ }
5441
+ function pickForwardedRunnerConfig(config) {
5442
+ const r = config.resolve ?? {};
5443
+ const resolve10 = {};
5444
+ if (r.alias !== void 0) resolve10.alias = r.alias;
5445
+ if (r.dedupe !== void 0) resolve10.dedupe = r.dedupe;
5446
+ if (r.conditions !== void 0) resolve10.conditions = r.conditions;
5447
+ if (r.mainFields !== void 0) resolve10.mainFields = r.mainFields;
5448
+ if (r.extensions !== void 0) resolve10.extensions = r.extensions;
5449
+ if (r.preserveSymlinks !== void 0)
5450
+ resolve10.preserveSymlinks = r.preserveSymlinks;
5451
+ if (r.tsconfigPaths !== void 0) resolve10.tsconfigPaths = r.tsconfigPaths;
5452
+ const userOxc = config.oxc;
5453
+ const userJsx = userOxc && typeof userOxc === "object" && typeof userOxc.jsx === "object" && userOxc.jsx !== null ? userOxc.jsx : {};
5454
+ const oxc = userOxc && typeof userOxc === "object" ? {
5455
+ ...userOxc,
5456
+ jsx: { ...userJsx, runtime: "automatic", importSource: "react" }
5457
+ } : { jsx: { runtime: "automatic", importSource: "react" } };
5458
+ return {
5459
+ resolve: resolve10,
5460
+ define: config.define,
5461
+ oxc
5462
+ };
5463
+ }
5464
+
5162
5465
  // src/vite/router-discovery.ts
5163
5466
  var debugDiscovery = createRangoDebugger(NS.discovery);
5164
5467
  var debugRoutes = createRangoDebugger(NS.routes);
@@ -5174,21 +5477,29 @@ function ensureCloudflareProtocolLoaderRegistered() {
5174
5477
  );
5175
5478
  } catch (err) {
5176
5479
  console.warn(
5177
- `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5480
+ `[rango] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5178
5481
  );
5179
5482
  }
5180
5483
  }
5181
5484
  async function createTempRscServer(state, options = {}) {
5182
5485
  ensureCloudflareProtocolLoaderRegistered();
5183
5486
  const { default: rsc } = await import("@vitejs/plugin-rsc");
5487
+ const runnerConfig = state.userRunnerConfig;
5488
+ const resolveConfig = runnerConfig?.resolve ?? {
5489
+ alias: state.userResolveAlias
5490
+ };
5491
+ const oxcConfig = runnerConfig?.oxc ?? {
5492
+ jsx: { runtime: "automatic", importSource: "react" }
5493
+ };
5184
5494
  return createViteServer({
5185
5495
  root: state.projectRoot,
5186
5496
  configFile: false,
5187
5497
  server: { middlewareMode: true },
5188
5498
  appType: "custom",
5189
5499
  logLevel: "silent",
5190
- resolve: { alias: state.userResolveAlias },
5191
- esbuild: { jsx: "automatic", jsxImportSource: "react" },
5500
+ resolve: resolveConfig,
5501
+ ...runnerConfig?.define ? { define: runnerConfig.define } : {},
5502
+ oxc: oxcConfig,
5192
5503
  ...options.cacheDir && { cacheDir: options.cacheDir },
5193
5504
  plugins: [
5194
5505
  rsc({
@@ -5206,7 +5517,11 @@ async function createTempRscServer(state, options = {}) {
5206
5517
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
5207
5518
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
5208
5519
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
5209
- exposeRouterId()
5520
+ exposeRouterId(),
5521
+ // Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
5522
+ // to resolveId/load and placed last so framework resolution runs first;
5523
+ // Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
5524
+ ...state.userResolvePlugins
5210
5525
  ]
5211
5526
  });
5212
5527
  }
@@ -5215,7 +5530,7 @@ async function resolveBuildEnv(option, factoryCtx) {
5215
5530
  if (option === "auto") {
5216
5531
  if (factoryCtx.preset !== "cloudflare") {
5217
5532
  throw new Error(
5218
- '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
5533
+ '[rango] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
5219
5534
  );
5220
5535
  }
5221
5536
  try {
@@ -5231,7 +5546,7 @@ async function resolveBuildEnv(option, factoryCtx) {
5231
5546
  };
5232
5547
  } catch (err) {
5233
5548
  throw new Error(
5234
- `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
5549
+ `[rango] buildEnv: "auto" requires wrangler to be installed.
5235
5550
  Install it with: pnpm add -D wrangler
5236
5551
  ${err.message}`
5237
5552
  );
@@ -5262,7 +5577,7 @@ async function releaseBuildEnv(s) {
5262
5577
  try {
5263
5578
  await s.buildEnvDispose();
5264
5579
  } catch (err) {
5265
- console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
5580
+ console.warn(`[rango] buildEnv dispose failed: ${err.message}`);
5266
5581
  }
5267
5582
  s.buildEnvDispose = null;
5268
5583
  }
@@ -5289,17 +5604,16 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5289
5604
  viteCommand = config.command;
5290
5605
  viteMode = config.mode;
5291
5606
  s.userResolveAlias = config.resolve.alias;
5607
+ s.userRunnerConfig = pickForwardedRunnerConfig(config);
5608
+ s.userResolvePlugins = selectForwardableResolvePlugins(
5609
+ config.plugins
5610
+ );
5292
5611
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
5293
5612
  s.resolvedEntryPath = opts.routerPathRef.path;
5294
5613
  }
5295
5614
  if (!s.resolvedEntryPath) {
5296
- const rscEnvConfig = config.environments?.["rsc"];
5297
- const entries = rscEnvConfig?.optimizeDeps?.entries;
5298
- if (typeof entries === "string") {
5299
- s.resolvedEntryPath = entries;
5300
- } else if (Array.isArray(entries) && entries.length > 0) {
5301
- s.resolvedEntryPath = entries[0];
5302
- }
5615
+ const entry = resolveRscEntryFromConfig(config);
5616
+ if (entry) s.resolvedEntryPath = entry;
5303
5617
  }
5304
5618
  if (opts?.staticRouteTypesGeneration !== false) {
5305
5619
  s.cachedRouterFiles = findRouterFiles(s.projectRoot, s.scanFilter);
@@ -5426,9 +5740,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5426
5740
  "getOrCreateTempServer: FAILED message=%s",
5427
5741
  err.message
5428
5742
  );
5429
- console.warn(
5430
- `[rsc-router] Failed to create temp runner: ${err.message}`
5431
- );
5743
+ console.warn(`[rango] Failed to create temp runner: ${err.message}`);
5432
5744
  }
5433
5745
  return null;
5434
5746
  }
@@ -5515,7 +5827,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5515
5827
  }
5516
5828
  } catch (err) {
5517
5829
  console.warn(
5518
- `[rsc-router] Cloudflare dev discovery failed: ${err.message}
5830
+ `[rango] Cloudflare dev discovery failed: ${err.message}
5519
5831
  ${err.stack}`
5520
5832
  );
5521
5833
  }
@@ -5559,7 +5871,7 @@ ${err.stack}`
5559
5871
  );
5560
5872
  } catch (err) {
5561
5873
  console.warn(
5562
- `[rsc-router] Router discovery failed: ${err.message}
5874
+ `[rango] Router discovery failed: ${err.message}
5563
5875
  ${err.stack}`
5564
5876
  );
5565
5877
  } finally {
@@ -5592,20 +5904,15 @@ ${err.stack}`
5592
5904
  if (s.mergedRouteTrie && serverMod.setRouteTrie) {
5593
5905
  serverMod.setRouteTrie(s.mergedRouteTrie);
5594
5906
  }
5595
- if (serverMod.setRouterManifest) {
5596
- for (const [routerId, manifest] of s.perRouterManifestDataMap) {
5597
- serverMod.setRouterManifest(routerId, manifest);
5598
- }
5599
- }
5600
- if (serverMod.setRouterTrie) {
5601
- for (const [routerId, trie] of s.perRouterTrieMap) {
5602
- serverMod.setRouterTrie(routerId, trie);
5603
- }
5604
- }
5605
- if (serverMod.setRouterPrecomputedEntries) {
5606
- for (const [routerId, entries] of s.perRouterPrecomputedMap) {
5607
- serverMod.setRouterPrecomputedEntries(routerId, entries);
5608
- }
5907
+ const perRouterSetters = [
5908
+ [s.perRouterManifestDataMap, "setRouterManifest"],
5909
+ [s.perRouterTrieMap, "setRouterTrie"],
5910
+ [s.perRouterPrecomputedMap, "setRouterPrecomputedEntries"]
5911
+ ];
5912
+ for (const [map, fn] of perRouterSetters) {
5913
+ const setter = serverMod[fn];
5914
+ if (typeof setter !== "function") continue;
5915
+ for (const [routerId, value] of map) setter(routerId, value);
5609
5916
  }
5610
5917
  };
5611
5918
  server.middlewares.use("/__rsc_prerender", async (req, res) => {
@@ -5639,7 +5946,7 @@ ${err.stack}`
5639
5946
  registry = serverMod.RouterRegistry ?? null;
5640
5947
  } catch (err) {
5641
5948
  console.warn(
5642
- `[rsc-router] Dev prerender module refresh failed: ${err.message}`
5949
+ `[rango] Dev prerender module refresh failed: ${err.message}`
5643
5950
  );
5644
5951
  res.statusCode = 500;
5645
5952
  res.end(`Prerender handler error: ${err.message}`);
@@ -5697,7 +6004,7 @@ ${err.stack}`
5697
6004
  return;
5698
6005
  } catch (err) {
5699
6006
  console.warn(
5700
- `[rsc-router] Dev prerender failed for ${pathname}: ${err.message}`
6007
+ `[rango] Dev prerender failed for ${pathname}: ${err.message}`
5701
6008
  );
5702
6009
  }
5703
6010
  }
@@ -5768,9 +6075,25 @@ ${err.stack}`
5768
6075
  () => writeRouteTypesFiles(s)
5769
6076
  );
5770
6077
  }
6078
+ if (s.lastDiscoveryError) {
6079
+ debugDiscovery?.(
6080
+ "hmr: cleared lastDiscoveryError (%s) after successful rediscovery",
6081
+ s.lastDiscoveryError.message
6082
+ );
6083
+ s.lastDiscoveryError = null;
6084
+ }
6085
+ if (rscEnv && !rscEnv.runner) forceCloudflareWorkerReload(rscEnv);
5771
6086
  } catch (err) {
6087
+ s.lastDiscoveryError = {
6088
+ message: err?.message ?? String(err),
6089
+ at: Date.now()
6090
+ };
5772
6091
  console.warn(
5773
- `[rsc-router] Runtime re-discovery failed: ${err.message}`
6092
+ `[rango] Runtime re-discovery failed: ${err.message}`
6093
+ );
6094
+ debugDiscovery?.(
6095
+ "hmr: lastDiscoveryError set (%s) \u2014 manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
6096
+ err?.message
5774
6097
  );
5775
6098
  } finally {
5776
6099
  debugDiscovery?.(
@@ -5780,6 +6103,25 @@ ${err.stack}`
5780
6103
  }
5781
6104
  });
5782
6105
  };
6106
+ const forceCloudflareWorkerReload = (rscEnv) => {
6107
+ if (!rscEnv?.hot) return;
6108
+ try {
6109
+ const graph = rscEnv.moduleGraph;
6110
+ if (graph?.invalidateAll) {
6111
+ graph.invalidateAll();
6112
+ debugDiscovery?.("hmr: invalidated workerd rsc module graph");
6113
+ }
6114
+ rscEnv.hot.send({ type: "full-reload" });
6115
+ debugDiscovery?.(
6116
+ "hmr: forced workerd rsc env reload (full-reload)"
6117
+ );
6118
+ } catch (err) {
6119
+ debugDiscovery?.(
6120
+ "hmr: workerd reload failed: %s",
6121
+ err?.message ?? err
6122
+ );
6123
+ }
6124
+ };
5783
6125
  const scheduleRouteRegeneration = () => {
5784
6126
  clearTimeout(routeChangeTimer);
5785
6127
  routeChangeTimer = setTimeout(() => {
@@ -5799,9 +6141,7 @@ ${err.stack}`
5799
6141
  }
5800
6142
  }
5801
6143
  } catch (err) {
5802
- console.error(
5803
- `[rsc-router] Route regeneration error: ${err.message}`
5804
- );
6144
+ console.error(`[rango] Route regeneration error: ${err.message}`);
5805
6145
  }
5806
6146
  debugDiscovery?.(
5807
6147
  "watcher: regenerated gen files (%sms)",
@@ -5810,7 +6150,7 @@ ${err.stack}`
5810
6150
  if (s.perRouterManifests.length > 0) {
5811
6151
  refreshRuntimeDiscovery().catch((err) => {
5812
6152
  console.warn(
5813
- `[rsc-router] Runtime re-discovery error: ${err.message}`
6153
+ `[rango] Runtime re-discovery error: ${err.message}`
5814
6154
  );
5815
6155
  resolveDiscoveryGate();
5816
6156
  });
@@ -5819,23 +6159,56 @@ ${err.stack}`
5819
6159
  };
5820
6160
  const handleRouteFileChange = (filePath) => {
5821
6161
  if (maybeHandleGeneratedRouteFileMutation(filePath)) return;
5822
- if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx"))
6162
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx")) {
6163
+ if (s.lastDiscoveryError) {
6164
+ debugDiscovery?.(
6165
+ "watcher: skip non-source %s [LASTERR %s]",
6166
+ filePath,
6167
+ s.lastDiscoveryError.message
6168
+ );
6169
+ }
5823
6170
  return;
5824
- if (s.scanFilter && !s.scanFilter(filePath)) return;
6171
+ }
6172
+ if (s.scanFilter && !s.scanFilter(filePath)) {
6173
+ if (s.lastDiscoveryError) {
6174
+ debugDiscovery?.(
6175
+ "watcher: skip scan-filter %s [LASTERR %s]",
6176
+ filePath,
6177
+ s.lastDiscoveryError.message
6178
+ );
6179
+ }
6180
+ return;
6181
+ }
6182
+ const inRecoveryMode = !!s.lastDiscoveryError;
5825
6183
  try {
5826
6184
  const source = readFileSync6(filePath, "utf-8");
5827
6185
  const trimmed = source.trimStart();
5828
- if (trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'"))
5829
- return;
5830
- const hasUrls = source.includes("urls(");
5831
- const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
5832
- if (!hasUrls && !hasCreateRouter) return;
5833
- debugDiscovery?.(
5834
- "watcher: %s matches (urls=%s, router=%s)",
5835
- filePath,
5836
- hasUrls,
5837
- hasCreateRouter
5838
- );
6186
+ const isUseClient = trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'");
6187
+ if (!inRecoveryMode && isUseClient) return;
6188
+ let hasUrls = source.includes("urls(");
6189
+ let hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
6190
+ if (hasUrls) hasUrls = firstCodeMatchIndex(source, /urls\(/g) >= 0;
6191
+ if (hasCreateRouter) {
6192
+ hasCreateRouter = firstCodeMatchIndex(source, /\bcreateRouter\s*[<(]/g) >= 0;
6193
+ }
6194
+ if (!inRecoveryMode && !hasUrls && !hasCreateRouter) return;
6195
+ if (inRecoveryMode) {
6196
+ debugDiscovery?.(
6197
+ "watcher: recovery rediscovery for %s (urls=%s, router=%s, useClient=%s) [LASTERR %s]",
6198
+ filePath,
6199
+ hasUrls,
6200
+ hasCreateRouter,
6201
+ isUseClient,
6202
+ s.lastDiscoveryError.message
6203
+ );
6204
+ } else {
6205
+ debugDiscovery?.(
6206
+ "watcher: %s matches (urls=%s, router=%s)",
6207
+ filePath,
6208
+ hasUrls,
6209
+ hasCreateRouter
6210
+ );
6211
+ }
5839
6212
  if (hasCreateRouter) {
5840
6213
  const nestedRouterConflict = findNestedRouterConflict([
5841
6214
  ...s.cachedRouterFiles ?? [],
@@ -5853,7 +6226,15 @@ ${err.stack}`
5853
6226
  gate.noteRouteEvent();
5854
6227
  }
5855
6228
  scheduleRouteRegeneration();
5856
- } catch {
6229
+ } catch (readErr) {
6230
+ if (s.lastDiscoveryError) {
6231
+ debugDiscovery?.(
6232
+ "watcher: read error %s: %s [LASTERR %s]",
6233
+ filePath,
6234
+ readErr?.message,
6235
+ s.lastDiscoveryError.message
6236
+ );
6237
+ }
5857
6238
  }
5858
6239
  };
5859
6240
  server.watcher.on("add", handleRouteFileChange);
@@ -5899,7 +6280,7 @@ ${err.stack}`
5899
6280
  const rscEnv = tempServer.environments?.rsc;
5900
6281
  if (!rscEnv?.runner) {
5901
6282
  console.warn(
5902
- "[rsc-router] RSC environment runner not available during build, skipping manifest generation"
6283
+ "[rango] RSC environment runner not available during build, skipping manifest generation"
5903
6284
  );
5904
6285
  return;
5905
6286
  }
@@ -5931,8 +6312,9 @@ ${err.stack}`
5931
6312
  ${err.stack}` : null
5932
6313
  ].filter(Boolean).join("\n");
5933
6314
  throw new Error(
5934
- `[rsc-router] Build-time router discovery failed:
5935
- ${details}`
6315
+ `[rango] Build-time router discovery failed:
6316
+ ${details}`,
6317
+ { cause: err }
5936
6318
  );
5937
6319
  } finally {
5938
6320
  delete globalThis.__rscRouterDiscoveryActive;
@@ -5960,7 +6342,7 @@ ${details}`
5960
6342
  // `consumeSelfGenWrite` inside `maybeHandleGeneratedRouteFileMutation`),
5961
6343
  // AND vite's own HMR pipeline (which invalidates the gen file's
5962
6344
  // importers and triggers a second workerd full reload — visible to the
5963
- // user as a duplicate "[RSCRouter] HMR: version changed" on the client).
6345
+ // user as a duplicate "[Rango] HMR: version changed" on the client).
5964
6346
  //
5965
6347
  // `peekSelfGenWrite` is the authoritative filter: its map only contains
5966
6348
  // paths that `markSelfGenWrite` has registered, so it natively works
@@ -6120,6 +6502,10 @@ async function rango(options) {
6120
6502
  const resolvedOptions = options ?? { preset: "node" };
6121
6503
  const preset = resolvedOptions.preset ?? "node";
6122
6504
  const showBanner = resolvedOptions.banner ?? true;
6505
+ const clientChunksOption = resolvedOptions.clientChunks ?? true;
6506
+ const useBuiltInClientChunks = clientChunksOption === true;
6507
+ const clientChunkCtx = useBuiltInClientChunks ? { fallbackRefs: /* @__PURE__ */ new Set() } : void 0;
6508
+ const clientChunks = resolveClientChunks(clientChunksOption, clientChunkCtx);
6123
6509
  debugConfig?.("rango(%s) setup start", preset);
6124
6510
  const plugins = [];
6125
6511
  const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
@@ -6155,10 +6541,18 @@ async function rango(options) {
6155
6541
  // This ensures the same Context instance is used by both browser entry and RSC proxy modules
6156
6542
  optimizeDeps: {
6157
6543
  exclude: excludeDeps,
6158
- esbuildOptions: sharedEsbuildOptions
6544
+ rolldownOptions: sharedRolldownOptions
6159
6545
  },
6160
6546
  resolve: {
6161
- alias: rangoAliases
6547
+ alias: rangoAliases,
6548
+ // Force a single React/React-DOM copy across all three RSC
6549
+ // environments. RSC requires exactly one react/react-dom instance
6550
+ // per environment runtime; consumer install topologies (pnpm
6551
+ // strict layout, experimental React pins, third-party "use client"
6552
+ // packages) can otherwise resolve duplicate copies, causing
6553
+ // "Invalid hook call" / lost context. Child environments inherit
6554
+ // this root dedupe, and Vite merges it with any consumer dedupe.
6555
+ dedupe: ["react", "react-dom"]
6162
6556
  },
6163
6557
  build: {
6164
6558
  rollupOptions: { onwarn }
@@ -6167,6 +6561,14 @@ async function rango(options) {
6167
6561
  client: {
6168
6562
  build: {
6169
6563
  rollupOptions: {
6564
+ // FILE_NAME_CONFLICT (and any other client-build warning) is
6565
+ // emitted by the CLIENT environment build, which consults THIS
6566
+ // env's onwarn -- Vite 8's environment builds do NOT propagate
6567
+ // the top-level build.rollupOptions.onwarn into the client env.
6568
+ // Wire it here so the suppression runs where the conflicts
6569
+ // originate (the top-level handler is invoked 0x for these; the
6570
+ // client-env handler is invoked for all of them).
6571
+ onwarn,
6170
6572
  output: {
6171
6573
  manualChunks: getManualChunks
6172
6574
  }
@@ -6177,7 +6579,7 @@ async function rango(options) {
6177
6579
  optimizeDeps: {
6178
6580
  include: [nested("rsc-html-stream/client")],
6179
6581
  exclude: excludeDeps,
6180
- esbuildOptions: sharedEsbuildOptions
6582
+ rolldownOptions: sharedRolldownOptions
6181
6583
  }
6182
6584
  },
6183
6585
  ssr: {
@@ -6185,10 +6587,6 @@ async function rango(options) {
6185
6587
  build: {
6186
6588
  outDir: "./dist/rsc/ssr"
6187
6589
  },
6188
- resolve: {
6189
- // Ensure single React instance in SSR child environment
6190
- dedupe: ["react", "react-dom"]
6191
- },
6192
6590
  // Pre-bundle SSR entry and React for proper module linking with childEnvironments
6193
6591
  // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
6194
6592
  optimizeDeps: {
@@ -6206,7 +6604,7 @@ async function rango(options) {
6206
6604
  )
6207
6605
  ],
6208
6606
  exclude: excludeDeps,
6209
- esbuildOptions: sharedEsbuildOptions
6607
+ rolldownOptions: sharedRolldownOptions
6210
6608
  }
6211
6609
  },
6212
6610
  rsc: {
@@ -6223,7 +6621,7 @@ async function rango(options) {
6223
6621
  )
6224
6622
  ],
6225
6623
  exclude: excludeDeps,
6226
- esbuildOptions: sharedEsbuildOptions
6624
+ rolldownOptions: sharedRolldownOptions
6227
6625
  }
6228
6626
  }
6229
6627
  }
@@ -6241,7 +6639,8 @@ async function rango(options) {
6241
6639
  plugins.push(
6242
6640
  rsc({
6243
6641
  entries: finalEntries,
6244
- serverHandler: false
6642
+ serverHandler: false,
6643
+ clientChunks
6245
6644
  })
6246
6645
  );
6247
6646
  plugins.push(clientRefDedup());
@@ -6259,7 +6658,7 @@ async function rango(options) {
6259
6658
  const list = candidates.map(
6260
6659
  (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
6261
6660
  ).join("\n");
6262
- throw new Error(`[rsc-router] Multiple routers found:
6661
+ throw new Error(`[rango] Multiple routers found:
6263
6662
  ${list}`);
6264
6663
  }
6265
6664
  }
@@ -6278,18 +6677,34 @@ ${list}`);
6278
6677
  return {
6279
6678
  optimizeDeps: {
6280
6679
  exclude: excludeDeps,
6281
- esbuildOptions: sharedEsbuildOptions
6680
+ rolldownOptions: sharedRolldownOptions
6282
6681
  },
6283
6682
  build: {
6284
6683
  rollupOptions: { onwarn }
6285
6684
  },
6286
6685
  resolve: {
6287
- alias: rangoAliases
6686
+ alias: rangoAliases,
6687
+ // Force a single React/React-DOM copy across all three RSC
6688
+ // environments. RSC requires exactly one react/react-dom instance
6689
+ // per environment runtime; consumer install topologies (pnpm
6690
+ // strict layout, experimental React pins, third-party "use client"
6691
+ // packages) can otherwise resolve duplicate copies, causing
6692
+ // "Invalid hook call" / lost context. Child environments inherit
6693
+ // this root dedupe, and Vite merges it with any consumer dedupe.
6694
+ dedupe: ["react", "react-dom"]
6288
6695
  },
6289
6696
  environments: {
6290
6697
  client: {
6291
6698
  build: {
6292
6699
  rollupOptions: {
6700
+ // FILE_NAME_CONFLICT (and any other client-build warning) is
6701
+ // emitted by the CLIENT environment build, which consults THIS
6702
+ // env's onwarn -- Vite 8's environment builds do NOT propagate
6703
+ // the top-level build.rollupOptions.onwarn into the client env.
6704
+ // Wire it here so the suppression runs where the conflicts
6705
+ // originate (the top-level handler is invoked 0x for these; the
6706
+ // client-env handler is invoked for all of them).
6707
+ onwarn,
6293
6708
  output: {
6294
6709
  manualChunks: getManualChunks
6295
6710
  }
@@ -6304,7 +6719,7 @@ ${list}`);
6304
6719
  nested("rsc-html-stream/client")
6305
6720
  ],
6306
6721
  exclude: excludeDeps,
6307
- esbuildOptions: sharedEsbuildOptions,
6722
+ rolldownOptions: sharedRolldownOptions,
6308
6723
  entries: [VIRTUAL_IDS.browser]
6309
6724
  }
6310
6725
  },
@@ -6323,7 +6738,7 @@ ${list}`);
6323
6738
  )
6324
6739
  ],
6325
6740
  exclude: excludeDeps,
6326
- esbuildOptions: sharedEsbuildOptions
6741
+ rolldownOptions: sharedRolldownOptions
6327
6742
  }
6328
6743
  },
6329
6744
  rsc: {
@@ -6337,7 +6752,7 @@ ${list}`);
6337
6752
  "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6338
6753
  )
6339
6754
  ],
6340
- esbuildOptions: sharedEsbuildOptions
6755
+ rolldownOptions: sharedRolldownOptions
6341
6756
  }
6342
6757
  }
6343
6758
  }
@@ -6354,7 +6769,7 @@ ${list}`);
6354
6769
  if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
6355
6770
  hasWarnedDuplicate = true;
6356
6771
  console.warn(
6357
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
6772
+ "[rango] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
6358
6773
  );
6359
6774
  }
6360
6775
  }
@@ -6363,7 +6778,8 @@ ${list}`);
6363
6778
  plugins.push(performanceTracksPlugin());
6364
6779
  plugins.push(
6365
6780
  rsc({
6366
- entries: finalEntries
6781
+ entries: finalEntries,
6782
+ clientChunks
6367
6783
  })
6368
6784
  );
6369
6785
  plugins.push(clientRefDedup());
@@ -6402,7 +6818,8 @@ ${list}`);
6402
6818
  routerPathRef: discoveryRouterRef,
6403
6819
  enableBuildPrerender: prerenderEnabled,
6404
6820
  buildEnv: options?.buildEnv,
6405
- preset
6821
+ preset,
6822
+ clientChunkCtx
6406
6823
  })
6407
6824
  );
6408
6825
  debugConfig?.(