@rangojs/router 0.0.0-experimental.fb4fdc18 → 0.0.0-experimental.fce7fbd1

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 (214) hide show
  1. package/README.md +9 -9
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/testing/vitest.js +48 -0
  4. package/dist/vite/index.js +914 -485
  5. package/package.json +55 -11
  6. package/skills/bundle-analysis/SKILL.md +159 -0
  7. package/skills/cache-guide/SKILL.md +220 -30
  8. package/skills/caching/SKILL.md +116 -8
  9. package/skills/composability/SKILL.md +27 -2
  10. package/skills/document-cache/SKILL.md +78 -55
  11. package/skills/handler-use/SKILL.md +3 -1
  12. package/skills/hooks/SKILL.md +214 -18
  13. package/skills/host-router/SKILL.md +45 -20
  14. package/skills/intercept/SKILL.md +26 -4
  15. package/skills/layout/SKILL.md +6 -7
  16. package/skills/links/SKILL.md +173 -17
  17. package/skills/loader/SKILL.md +149 -6
  18. package/skills/middleware/SKILL.md +13 -9
  19. package/skills/migrate-nextjs/SKILL.md +1 -1
  20. package/skills/mime-routes/SKILL.md +27 -0
  21. package/skills/observability/SKILL.md +137 -0
  22. package/skills/parallel/SKILL.md +5 -6
  23. package/skills/prerender/SKILL.md +14 -33
  24. package/skills/rango/SKILL.md +242 -26
  25. package/skills/react-compiler/SKILL.md +168 -0
  26. package/skills/response-routes/SKILL.md +58 -9
  27. package/skills/route/SKILL.md +13 -4
  28. package/skills/router-setup/SKILL.md +3 -3
  29. package/skills/server-actions/SKILL.md +53 -41
  30. package/skills/testing/SKILL.md +599 -0
  31. package/skills/typesafety/SKILL.md +310 -26
  32. package/skills/use-cache/SKILL.md +34 -5
  33. package/skills/view-transitions/SKILL.md +294 -0
  34. package/src/__augment-tests__/augment.ts +81 -0
  35. package/src/__augment-tests__/augmented.check.ts +117 -0
  36. package/src/browser/action-coordinator.ts +53 -36
  37. package/src/browser/event-controller.ts +42 -66
  38. package/src/browser/history-state.ts +21 -0
  39. package/src/browser/index.ts +3 -3
  40. package/src/browser/navigation-bridge.ts +6 -6
  41. package/src/browser/navigation-client.ts +12 -15
  42. package/src/browser/navigation-store.ts +7 -8
  43. package/src/browser/navigation-transaction.ts +10 -28
  44. package/src/browser/partial-update.ts +9 -19
  45. package/src/browser/react/NavigationProvider.tsx +29 -40
  46. package/src/browser/react/index.ts +3 -0
  47. package/src/browser/react/location-state-shared.ts +175 -4
  48. package/src/browser/react/location-state.ts +39 -13
  49. package/src/browser/react/use-handle.ts +17 -9
  50. package/src/browser/react/use-params.ts +3 -4
  51. package/src/browser/react/use-reverse.ts +106 -0
  52. package/src/browser/react/use-router.ts +14 -1
  53. package/src/browser/response-adapter.ts +25 -0
  54. package/src/browser/rsc-router.tsx +30 -16
  55. package/src/browser/scroll-restoration.ts +22 -14
  56. package/src/browser/segment-structure-assert.ts +2 -2
  57. package/src/browser/server-action-bridge.ts +23 -30
  58. package/src/browser/types.ts +2 -0
  59. package/src/build/collect-fallback-refs.ts +107 -0
  60. package/src/build/generate-manifest.ts +60 -35
  61. package/src/build/generate-route-types.ts +2 -0
  62. package/src/build/index.ts +2 -0
  63. package/src/build/route-types/codegen.ts +4 -4
  64. package/src/build/route-types/include-resolution.ts +1 -1
  65. package/src/build/route-types/per-module-writer.ts +7 -4
  66. package/src/build/route-types/router-processing.ts +55 -14
  67. package/src/build/route-types/scan-filter.ts +1 -1
  68. package/src/build/route-types/source-scan.ts +118 -0
  69. package/src/build/runtime-discovery.ts +9 -20
  70. package/src/cache/cache-scope.ts +28 -42
  71. package/src/cache/cf/cf-cache-store.ts +49 -6
  72. package/src/client.rsc.tsx +3 -0
  73. package/src/client.tsx +10 -8
  74. package/src/context-var.ts +5 -5
  75. package/src/decode-loader-results.ts +36 -0
  76. package/src/errors.ts +30 -1
  77. package/src/handle.ts +26 -13
  78. package/src/host/index.ts +2 -2
  79. package/src/host/router.ts +129 -57
  80. package/src/host/types.ts +31 -2
  81. package/src/host/utils.ts +1 -1
  82. package/src/href-client.ts +140 -20
  83. package/src/index.rsc.ts +6 -4
  84. package/src/index.ts +13 -6
  85. package/src/loader-store.ts +500 -0
  86. package/src/loader.rsc.ts +2 -5
  87. package/src/loader.ts +3 -10
  88. package/src/missing-id-error.ts +68 -0
  89. package/src/prerender.ts +4 -4
  90. package/src/response-utils.ts +9 -0
  91. package/src/reverse.ts +65 -41
  92. package/src/route-content-wrapper.tsx +6 -28
  93. package/src/route-definition/dsl-helpers.ts +238 -263
  94. package/src/route-definition/helper-factories.ts +29 -139
  95. package/src/route-definition/helpers-types.ts +37 -14
  96. package/src/route-definition/use-item-types.ts +32 -0
  97. package/src/route-types.ts +19 -41
  98. package/src/router/basename.ts +14 -0
  99. package/src/router/content-negotiation.ts +15 -2
  100. package/src/router/error-handling.ts +1 -1
  101. package/src/router/handler-context.ts +4 -42
  102. package/src/router/intercept-resolution.ts +4 -18
  103. package/src/router/lazy-includes.ts +2 -2
  104. package/src/router/loader-resolution.ts +16 -2
  105. package/src/router/match-handlers.ts +62 -20
  106. package/src/router/match-middleware/cache-lookup.ts +44 -91
  107. package/src/router/match-middleware/cache-store.ts +3 -2
  108. package/src/router/match-result.ts +32 -30
  109. package/src/router/metrics.ts +1 -1
  110. package/src/router/middleware-types.ts +1 -1
  111. package/src/router/middleware.ts +46 -78
  112. package/src/router/prerender-match.ts +1 -1
  113. package/src/router/preview-match.ts +3 -1
  114. package/src/router/request-classification.ts +4 -28
  115. package/src/router/revalidation.ts +43 -1
  116. package/src/router/router-interfaces.ts +45 -28
  117. package/src/router/router-options.ts +40 -1
  118. package/src/router/router-registry.ts +2 -5
  119. package/src/router/segment-resolution/fresh.ts +19 -6
  120. package/src/router/segment-resolution/revalidation.ts +19 -6
  121. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  122. package/src/router/substitute-pattern-params.ts +56 -0
  123. package/src/router/telemetry.ts +99 -0
  124. package/src/router/types.ts +8 -0
  125. package/src/router.ts +37 -21
  126. package/src/rsc/handler-context.ts +2 -2
  127. package/src/rsc/handler.ts +20 -65
  128. package/src/rsc/helpers.ts +22 -2
  129. package/src/rsc/index.ts +1 -1
  130. package/src/rsc/origin-guard.ts +28 -10
  131. package/src/rsc/response-route-handler.ts +32 -52
  132. package/src/rsc/rsc-rendering.ts +27 -53
  133. package/src/rsc/runtime-warnings.ts +9 -10
  134. package/src/rsc/server-action.ts +13 -37
  135. package/src/rsc/ssr-setup.ts +16 -0
  136. package/src/rsc/types.ts +2 -2
  137. package/src/search-params.ts +4 -4
  138. package/src/segment-system.tsx +121 -65
  139. package/src/serialize.ts +243 -0
  140. package/src/server/context.ts +118 -51
  141. package/src/server/cookie-store.ts +28 -4
  142. package/src/server/request-context.ts +10 -0
  143. package/src/static-handler.ts +1 -1
  144. package/src/testing/cache-status.ts +166 -0
  145. package/src/testing/collect-handle.ts +63 -0
  146. package/src/testing/dispatch.ts +440 -0
  147. package/src/testing/dom.entry.ts +22 -0
  148. package/src/testing/e2e/fixture.ts +154 -0
  149. package/src/testing/e2e/index.ts +149 -0
  150. package/src/testing/e2e/matchers.ts +51 -0
  151. package/src/testing/e2e/page-helpers.ts +272 -0
  152. package/src/testing/e2e/parity.ts +306 -0
  153. package/src/testing/e2e/server.ts +183 -0
  154. package/src/testing/flight-matchers.ts +104 -0
  155. package/src/testing/flight-runtime.d.ts +21 -0
  156. package/src/testing/flight.entry.ts +22 -0
  157. package/src/testing/flight.ts +182 -0
  158. package/src/testing/generated-routes.ts +223 -0
  159. package/src/testing/index.ts +105 -0
  160. package/src/testing/internal/context.ts +193 -0
  161. package/src/testing/render-route.tsx +536 -0
  162. package/src/testing/run-loader.ts +296 -0
  163. package/src/testing/run-middleware.ts +170 -0
  164. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  165. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  166. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  167. package/src/testing/vitest-stubs/version.ts +5 -0
  168. package/src/testing/vitest.ts +183 -0
  169. package/src/types/global-namespace.ts +39 -26
  170. package/src/types/handler-context.ts +56 -11
  171. package/src/types/index.ts +1 -0
  172. package/src/types/segments.ts +18 -1
  173. package/src/urls/include-helper.ts +10 -53
  174. package/src/urls/index.ts +0 -3
  175. package/src/urls/path-helper-types.ts +11 -3
  176. package/src/urls/path-helper.ts +17 -52
  177. package/src/urls/pattern-types.ts +36 -19
  178. package/src/urls/response-types.ts +20 -19
  179. package/src/urls/type-extraction.ts +26 -116
  180. package/src/urls/urls-function.ts +1 -5
  181. package/src/use-loader.tsx +413 -42
  182. package/src/vite/debug.ts +1 -0
  183. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  184. package/src/vite/discovery/discover-routers.ts +70 -48
  185. package/src/vite/discovery/discovery-errors.ts +194 -0
  186. package/src/vite/discovery/prerender-collection.ts +19 -25
  187. package/src/vite/discovery/route-types-writer.ts +40 -84
  188. package/src/vite/discovery/state.ts +33 -0
  189. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  190. package/src/vite/index.ts +2 -0
  191. package/src/vite/plugin-types.ts +67 -0
  192. package/src/vite/plugins/cjs-to-esm.ts +3 -7
  193. package/src/vite/plugins/client-ref-hashing.ts +12 -1
  194. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
  195. package/src/vite/plugins/expose-action-id.ts +2 -2
  196. package/src/vite/plugins/expose-id-utils.ts +12 -8
  197. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  198. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  199. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  200. package/src/vite/plugins/expose-internal-ids.ts +47 -67
  201. package/src/vite/plugins/performance-tracks.ts +12 -16
  202. package/src/vite/plugins/use-cache-transform.ts +13 -11
  203. package/src/vite/plugins/version-injector.ts +2 -12
  204. package/src/vite/plugins/version-plugin.ts +59 -2
  205. package/src/vite/plugins/virtual-entries.ts +2 -2
  206. package/src/vite/rango.ts +67 -15
  207. package/src/vite/router-discovery.ts +208 -63
  208. package/src/vite/utils/ast-handler-extract.ts +15 -15
  209. package/src/vite/utils/bundle-analysis.ts +4 -2
  210. package/src/vite/utils/client-chunks.ts +190 -0
  211. package/src/vite/utils/forward-user-plugins.ts +193 -0
  212. package/src/vite/utils/manifest-utils.ts +21 -5
  213. package/src/vite/utils/shared-utils.ts +107 -26
  214. 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.fb4fdc18",
2133
+ version: "0.0.0-experimental.fce7fbd1",
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
 
@@ -3824,9 +4062,12 @@ function checkSelfGenWrite(state, filePath, consume) {
3824
4062
 
3825
4063
  // src/vite/utils/manifest-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) {
@@ -4068,7 +4311,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4068
4311
  const progressInterval = totalDynamic > 0 ? setInterval(() => {
4069
4312
  const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4070
4313
  console.log(
4071
- `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4314
+ `[rango] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4072
4315
  );
4073
4316
  }, 5e3) : void 0;
4074
4317
  try {
@@ -4105,7 +4348,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4105
4348
  get env() {
4106
4349
  if (buildEnv !== void 0) return buildEnv;
4107
4350
  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."
4351
+ "[rango] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
4109
4352
  );
4110
4353
  }
4111
4354
  };
@@ -4146,7 +4389,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4146
4389
  resolvedRoutes++;
4147
4390
  if (err.name === "Skip") {
4148
4391
  console.log(
4149
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
4392
+ `[rango] SKIP route "${routeName}" - ${err.message}`
4150
4393
  );
4151
4394
  notifyOnError(
4152
4395
  registry,
@@ -4159,14 +4402,14 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4159
4402
  continue;
4160
4403
  }
4161
4404
  console.error(
4162
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
4405
+ `[rango] Failed to get params for prerender route "${routeName}": ${err.message}`
4163
4406
  );
4164
4407
  notifyOnError(registry, err, "prerender", routeName);
4165
4408
  throw err;
4166
4409
  }
4167
4410
  } else {
4168
4411
  console.warn(
4169
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
4412
+ `[rango] Dynamic prerender route "${routeName}" has no getParams(), skipping`
4170
4413
  );
4171
4414
  }
4172
4415
  }
@@ -4177,7 +4420,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4177
4420
  clearInterval(progressInterval);
4178
4421
  const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4179
4422
  console.log(
4180
- `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4423
+ `[rango] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4181
4424
  );
4182
4425
  }
4183
4426
  }
@@ -4191,7 +4434,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4191
4434
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
4192
4435
  const concurrencyNote = maxConcurrency > 1 ? ` (concurrency: ${maxConcurrency})` : "";
4193
4436
  console.log(
4194
- `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
4437
+ `[rango] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
4195
4438
  );
4196
4439
  debug9?.(
4197
4440
  "prerender loop: %d entries, max concurrency %d",
@@ -4224,7 +4467,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4224
4467
  if (result.passthrough) {
4225
4468
  const elapsed2 = (performance.now() - startUrl).toFixed(0);
4226
4469
  console.log(
4227
- `[rsc-router] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
4470
+ `[rango] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
4228
4471
  );
4229
4472
  doneCount++;
4230
4473
  break;
@@ -4257,7 +4500,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4257
4500
  }
4258
4501
  const elapsed = (performance.now() - startUrl).toFixed(0);
4259
4502
  console.log(
4260
- `[rsc-router] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`
4503
+ `[rango] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`
4261
4504
  );
4262
4505
  doneCount++;
4263
4506
  break;
@@ -4265,7 +4508,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4265
4508
  if (err.name === "Skip") {
4266
4509
  const elapsed2 = (performance.now() - startUrl).toFixed(0);
4267
4510
  console.log(
4268
- `[rsc-router] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4511
+ `[rango] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4269
4512
  );
4270
4513
  skipCount++;
4271
4514
  notifyOnError(
@@ -4280,7 +4523,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4280
4523
  }
4281
4524
  const elapsed = (performance.now() - startUrl).toFixed(0);
4282
4525
  console.error(
4283
- `[rsc-router] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
4526
+ `[rango] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
4284
4527
  );
4285
4528
  notifyOnError(
4286
4529
  registry,
@@ -4302,7 +4545,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4302
4545
  const parts = [`${doneCount} done`];
4303
4546
  if (skipCount > 0) parts.push(`${skipCount} skipped`);
4304
4547
  console.log(
4305
- `[rsc-router] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
4548
+ `[rango] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
4306
4549
  );
4307
4550
  debug9?.(
4308
4551
  "expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
@@ -4328,16 +4571,14 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4328
4571
  totalStaticCount += exportNames.length;
4329
4572
  }
4330
4573
  const startStatic = performance.now();
4331
- console.log(
4332
- `[rsc-router] Rendering ${totalStaticCount} static handler(s)...`
4333
- );
4574
+ console.log(`[rango] Rendering ${totalStaticCount} static handler(s)...`);
4334
4575
  for (const [moduleId, exportNames] of state.resolvedStaticModules) {
4335
4576
  let mod;
4336
4577
  try {
4337
4578
  mod = await rscEnv.runner.import(moduleId);
4338
4579
  } catch (err) {
4339
4580
  console.error(
4340
- `[rsc-router] Failed to import static module ${moduleId}: ${err.message}`
4581
+ `[rango] Failed to import static module ${moduleId}: ${err.message}`
4341
4582
  );
4342
4583
  notifyOnError(registry, err, "static");
4343
4584
  throw err;
@@ -4367,9 +4608,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4367
4608
  exportValue
4368
4609
  );
4369
4610
  const elapsed = (performance.now() - startHandler).toFixed(0);
4370
- console.log(
4371
- `[rsc-router] OK ${name.padEnd(40)} (${elapsed}ms)`
4372
- );
4611
+ console.log(`[rango] OK ${name.padEnd(40)} (${elapsed}ms)`);
4373
4612
  staticDone++;
4374
4613
  handled = true;
4375
4614
  break;
@@ -4378,7 +4617,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4378
4617
  if (err.name === "Skip") {
4379
4618
  const elapsed2 = (performance.now() - startHandler).toFixed(0);
4380
4619
  console.log(
4381
- `[rsc-router] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4620
+ `[rango] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4382
4621
  );
4383
4622
  staticSkip++;
4384
4623
  notifyOnError(registry, err, "static", void 0, void 0, true);
@@ -4387,16 +4626,14 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4387
4626
  }
4388
4627
  const elapsed = (performance.now() - startHandler).toFixed(0);
4389
4628
  console.error(
4390
- `[rsc-router] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
4629
+ `[rango] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
4391
4630
  );
4392
4631
  notifyOnError(registry, err, "static");
4393
4632
  throw err;
4394
4633
  }
4395
4634
  }
4396
4635
  if (!handled) {
4397
- console.warn(
4398
- `[rsc-router] No router could render static handler "${name}"`
4399
- );
4636
+ console.warn(`[rango] No router could render static handler "${name}"`);
4400
4637
  }
4401
4638
  }
4402
4639
  }
@@ -4407,7 +4644,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4407
4644
  const staticParts = [`${staticDone} done`];
4408
4645
  if (staticSkip > 0) staticParts.push(`${staticSkip} skipped`);
4409
4646
  console.log(
4410
- `[rsc-router] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
4647
+ `[rango] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
4411
4648
  );
4412
4649
  debug9?.(
4413
4650
  "renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
@@ -4418,6 +4655,80 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4418
4655
  );
4419
4656
  }
4420
4657
 
4658
+ // src/vite/discovery/discovery-errors.ts
4659
+ function indent(text, pad) {
4660
+ return text.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n");
4661
+ }
4662
+ async function invokeLazyMount(loader, context, errors) {
4663
+ try {
4664
+ await loader();
4665
+ } catch (error) {
4666
+ errors.push({ context, error });
4667
+ }
4668
+ }
4669
+ function isLazyMount(route) {
4670
+ return !!route && route.kind === "lazy" && typeof route.handler === "function";
4671
+ }
4672
+ async function resolveHostRouterHandlers(hostRegistry) {
4673
+ const errors = [];
4674
+ for (const [hostId, entry] of hostRegistry) {
4675
+ for (const route of entry.routes) {
4676
+ if (isLazyMount(route)) {
4677
+ await invokeLazyMount(
4678
+ route.handler,
4679
+ `host "${hostId}" route handler`,
4680
+ errors
4681
+ );
4682
+ }
4683
+ }
4684
+ if (isLazyMount(entry.fallback)) {
4685
+ await invokeLazyMount(
4686
+ entry.fallback.handler,
4687
+ `host "${hostId}" fallback handler`,
4688
+ errors
4689
+ );
4690
+ }
4691
+ }
4692
+ return errors;
4693
+ }
4694
+ function formatNoRoutersError(entryPath, errors) {
4695
+ const base = `[rango] No routers found in registry after importing ${entryPath}`;
4696
+ if (errors.length === 0) {
4697
+ return base;
4698
+ }
4699
+ const formatted = errors.map(({ context, error }) => {
4700
+ const err = error instanceof Error ? error : new Error(String(error));
4701
+ const detail = err.stack ?? err.message;
4702
+ return ` - while resolving ${context}:
4703
+ ${indent(detail, " ")}`;
4704
+ }).join("\n");
4705
+ return `${base}
4706
+
4707
+ ${errors.length} error(s) were caught during host-router discovery and likely explain why no routers were registered:
4708
+ ${formatted}`;
4709
+ }
4710
+ function toCause(errors) {
4711
+ if (errors.length === 0) return void 0;
4712
+ if (errors.length === 1) return errors[0].error;
4713
+ return new AggregateError(
4714
+ errors.map((e) => e.error),
4715
+ "Multiple host-router handlers failed during discovery"
4716
+ );
4717
+ }
4718
+ var DiscoveryError = class _DiscoveryError extends Error {
4719
+ constructor(entryPath, caught) {
4720
+ super(formatNoRoutersError(entryPath, caught));
4721
+ const cause = toCause(caught);
4722
+ if (cause !== void 0) {
4723
+ this.cause = cause;
4724
+ }
4725
+ this.name = "DiscoveryError";
4726
+ this.entryPath = entryPath;
4727
+ this.caught = caught;
4728
+ Object.setPrototypeOf(this, _DiscoveryError.prototype);
4729
+ }
4730
+ };
4731
+
4421
4732
  // src/vite/discovery/discover-routers.ts
4422
4733
  var debug10 = createRangoDebugger(NS.discovery);
4423
4734
  async function discoverRouters(state, rscEnv) {
@@ -4434,27 +4745,17 @@ async function discoverRouters(state, rscEnv) {
4434
4745
  );
4435
4746
  let registry = serverMod.RouterRegistry;
4436
4747
  if (!registry || registry.size === 0) {
4748
+ const discoveryErrors = [];
4437
4749
  try {
4438
4750
  const hostRegistry = serverMod.HostRouterRegistry;
4439
4751
  if (hostRegistry && hostRegistry.size > 0) {
4440
4752
  console.log(
4441
- `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
4753
+ `[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
4442
4754
  );
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
- }
4755
+ const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
4756
+ discoveryErrors.push(...handlerErrors);
4757
+ for (const { context, error } of handlerErrors) {
4758
+ debug10?.("caught error while resolving %s: %O", context, error);
4458
4759
  }
4459
4760
  const freshServerMod = await rscEnv.runner.import(
4460
4761
  "@rangojs/router/server"
@@ -4465,12 +4766,11 @@ async function discoverRouters(state, rscEnv) {
4465
4766
  registry = freshRegistry;
4466
4767
  }
4467
4768
  }
4468
- } catch {
4769
+ } catch (error) {
4770
+ discoveryErrors.push({ context: "host-router discovery", error });
4469
4771
  }
4470
4772
  if (!registry || registry.size === 0) {
4471
- throw new Error(
4472
- `[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`
4473
- );
4773
+ throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
4474
4774
  }
4475
4775
  }
4476
4776
  const buildMod = await timed(
@@ -4498,6 +4798,15 @@ async function discoverRouters(state, rscEnv) {
4498
4798
  let mergedRouteTrailingSlash = {};
4499
4799
  let routerMountIndex = 0;
4500
4800
  const allManifests = [];
4801
+ const clientChunkCtx = state.opts?.clientChunkCtx;
4802
+ const collectClientFallbackRef = clientChunkCtx ? (refKey) => clientChunkCtx.fallbackRefs.add(
4803
+ computeProductionHash(state.projectRoot, refKey)
4804
+ ) : void 0;
4805
+ const collectFromBoundaryNode = (node) => {
4806
+ if (collectClientFallbackRef && buildMod.collectFallbackClientRefs) {
4807
+ buildMod.collectFallbackClientRefs(node, collectClientFallbackRef);
4808
+ }
4809
+ };
4501
4810
  const manifestGenStart = debug10 ? performance.now() : 0;
4502
4811
  for (const [id, router] of registry) {
4503
4812
  if (!router.urlpatterns || !generateManifestFull) {
@@ -4506,10 +4815,18 @@ async function discoverRouters(state, rscEnv) {
4506
4815
  const manifest = generateManifestFull(
4507
4816
  router.urlpatterns,
4508
4817
  routerMountIndex,
4509
- router.__basename ? { urlPrefix: router.__basename } : void 0
4818
+ {
4819
+ ...router.__basename ? { urlPrefix: router.__basename } : {},
4820
+ ...collectClientFallbackRef ? { collectClientFallbackRef } : {}
4821
+ }
4510
4822
  );
4511
4823
  routerMountIndex++;
4512
4824
  allManifests.push({ id, manifest });
4825
+ if (collectClientFallbackRef) {
4826
+ collectFromBoundaryNode(router.__defaultErrorBoundary);
4827
+ collectFromBoundaryNode(router.__defaultNotFoundBoundary);
4828
+ collectFromBoundaryNode(router.__notFound);
4829
+ }
4513
4830
  const routeCount = Object.keys(manifest.routeManifest).length;
4514
4831
  const staticRoutes = Object.values(manifest.routeManifest).filter(
4515
4832
  (p) => !p.includes(":") && !p.includes("*")
@@ -4560,7 +4877,7 @@ async function discoverRouters(state, rscEnv) {
4560
4877
  );
4561
4878
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
4562
4879
  console.log(
4563
- `[rsc-router] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
4880
+ `[rango] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
4564
4881
  );
4565
4882
  }
4566
4883
  if (registry.size > 1) {
@@ -4569,7 +4886,7 @@ async function discoverRouters(state, rscEnv) {
4569
4886
  );
4570
4887
  if (autoIds.length > 1) {
4571
4888
  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", ... })`
4889
+ `[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
4890
  );
4574
4891
  }
4575
4892
  }
@@ -4614,10 +4931,10 @@ async function discoverRouters(state, rscEnv) {
4614
4931
  newMergedRouteManifest,
4615
4932
  mergedRouteAncestry,
4616
4933
  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
4934
+ mergedRouteTrailingSlash,
4935
+ prerenderRouteNames,
4936
+ passthroughRouteNames,
4937
+ mergedResponseTypeRoutes
4621
4938
  );
4622
4939
  for (const { id, manifest } of allManifests) {
4623
4940
  if (!manifest._routeAncestry || Object.keys(manifest._routeAncestry).length === 0)
@@ -4633,10 +4950,10 @@ async function discoverRouters(state, rscEnv) {
4633
4950
  manifest.routeManifest,
4634
4951
  manifest._routeAncestry,
4635
4952
  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
4953
+ manifest.routeTrailingSlash,
4954
+ perRouterPrerenderNames,
4955
+ perRouterPassthroughNames,
4956
+ manifest.responseTypeRoutes
4640
4957
  );
4641
4958
  newPerRouterTrieMap.set(id, perRouterTrie);
4642
4959
  }
@@ -4659,7 +4976,7 @@ async function discoverRouters(state, rscEnv) {
4659
4976
  }
4660
4977
 
4661
4978
  // src/vite/discovery/route-types-writer.ts
4662
- import { dirname as dirname3, basename, join as join2, resolve as resolve6 } from "node:path";
4979
+ import { dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
4663
4980
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
4664
4981
  function filterUserNamedRoutes(manifest) {
4665
4982
  const filtered = {};
@@ -4670,39 +4987,20 @@ function filterUserNamedRoutes(manifest) {
4670
4987
  }
4671
4988
  return filtered;
4672
4989
  }
4990
+ function writeGenFileIfChanged(state, outPath, source, opts) {
4991
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4992
+ if (existing === source) return;
4993
+ markSelfGenWrite(state, outPath, source);
4994
+ writeFileSync3(outPath, source);
4995
+ if (opts?.log) console.log(`[rango] Generated route types -> ${outPath}`);
4996
+ }
4673
4997
  function writeCombinedRouteTypesWithTracking(state, opts) {
4674
4998
  const routerFiles = state.cachedRouterFiles ?? findRouterFiles(state.projectRoot, state.scanFilter);
4675
4999
  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
- }
5000
+ writeCombinedRouteTypes(state.projectRoot, routerFiles, {
5001
+ ...opts,
5002
+ onWrite: (outPath, content) => markSelfGenWrite(state, outPath, content)
5003
+ });
4706
5004
  }
4707
5005
  function writeRouteTypesFiles(state) {
4708
5006
  if (state.perRouterManifests.length === 0) return;
@@ -4714,7 +5012,7 @@ function writeRouteTypesFiles(state) {
4714
5012
  if (existsSync5(oldCombinedPath)) {
4715
5013
  unlinkSync2(oldCombinedPath);
4716
5014
  console.log(
4717
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
5015
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
4718
5016
  );
4719
5017
  }
4720
5018
  } catch {
@@ -4728,39 +5026,23 @@ function writeRouteTypesFiles(state) {
4728
5026
  if (!sourceFile) continue;
4729
5027
  if (sourceFile.includes("node_modules")) {
4730
5028
  throw new Error(
4731
- `[rsc-router] Router "${id}" has sourceFile inside node_modules: ${sourceFile}
5029
+ `[rango] Router "${id}" has sourceFile inside node_modules: ${sourceFile}
4732
5030
  This means createRouter() stack trace parsing matched a Vite internal frame.
4733
5031
  Set an explicit \`id\` on createRouter() or check the call site.`
4734
5032
  );
4735
5033
  }
4736
- const routerDir = dirname3(sourceFile);
4737
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4738
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
5034
+ const outPath = genFileTsPath(sourceFile);
4739
5035
  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
- }
5036
+ const effectiveSearchSchemas = resolveSearchSchemas(
5037
+ Object.keys(userRoutes),
5038
+ routeSearchSchemas,
5039
+ sourceFile
5040
+ );
4754
5041
  const source = generateRouteTypesSource(
4755
5042
  userRoutes,
4756
5043
  effectiveSearchSchemas && Object.keys(effectiveSearchSchemas).length > 0 ? effectiveSearchSchemas : void 0
4757
5044
  );
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
- }
5045
+ writeGenFileIfChanged(state, outPath, source, { log: true });
4764
5046
  }
4765
5047
  }
4766
5048
  function supplementGenFilesWithRuntimeRoutes(state) {
@@ -4798,23 +5080,17 @@ function supplementGenFilesWithRuntimeRoutes(state) {
4798
5080
  }
4799
5081
  }
4800
5082
  }
4801
- const routerDir = dirname3(sourceFile);
4802
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4803
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
5083
+ const outPath = genFileTsPath(sourceFile);
4804
5084
  const source = generateRouteTypesSource(
4805
5085
  mergedRoutes,
4806
5086
  Object.keys(mergedSearchSchemas).length > 0 ? mergedSearchSchemas : void 0
4807
5087
  );
4808
- const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4809
- if (existing !== source) {
4810
- markSelfGenWrite(state, outPath, source);
4811
- writeFileSync3(outPath, source);
4812
- }
5088
+ writeGenFileIfChanged(state, outPath, source);
4813
5089
  }
4814
5090
  }
4815
5091
 
4816
5092
  // src/vite/discovery/virtual-module-codegen.ts
4817
- import { dirname as dirname4, basename as basename2, join as join3 } from "node:path";
5093
+ import { dirname as dirname4, basename, join as join3 } from "node:path";
4818
5094
  function generateRoutesManifestModule(state) {
4819
5095
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
4820
5096
  if (hasManifest) {
@@ -4825,7 +5101,7 @@ function generateRoutesManifestModule(state) {
4825
5101
  for (const entry of state.perRouterManifests) {
4826
5102
  if (entry.sourceFile) {
4827
5103
  const routerDir = dirname4(entry.sourceFile);
4828
- const routerBasename = basename2(entry.sourceFile).replace(
5104
+ const routerBasename = basename(entry.sourceFile).replace(
4829
5105
  /\.(tsx?|jsx?)$/,
4830
5106
  ""
4831
5107
  );
@@ -4846,7 +5122,7 @@ function generateRoutesManifestModule(state) {
4846
5122
  }
4847
5123
  }
4848
5124
  const lines = [
4849
- `import { setCachedManifest, setPrecomputedEntries, setRouteTrie, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
5125
+ `import { setCachedManifest, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
4850
5126
  ...genFileImports,
4851
5127
  // Clear stale per-router cached data (manifest, trie, precomputed entries)
4852
5128
  // before re-populating. In Cloudflare dev mode, program reloads re-evaluate
@@ -4882,18 +5158,6 @@ function generateRoutesManifestModule(state) {
4882
5158
  );
4883
5159
  }
4884
5160
  }
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
5161
  for (const routerId of state.perRouterManifestDataMap.keys()) {
4898
5162
  lines.push(
4899
5163
  `registerRouterManifestLoader(${JSON.stringify(routerId)}, () => import(${JSON.stringify(VIRTUAL_ROUTES_MANIFEST_ID + "/" + routerId)}));`
@@ -4922,7 +5186,7 @@ function generatePerRouterModule(state, routerId) {
4922
5186
  const lines = [];
4923
5187
  if (routerEntry?.sourceFile) {
4924
5188
  const routerDir = dirname4(routerEntry.sourceFile);
4925
- const routerBasename = basename2(routerEntry.sourceFile).replace(
5189
+ const routerBasename = basename(routerEntry.sourceFile).replace(
4926
5190
  /\.(tsx?|jsx?)$/,
4927
5191
  ""
4928
5192
  );
@@ -4993,12 +5257,12 @@ function postprocessBundle(state) {
4993
5257
  writeFileSync4(chunkPath, result.code);
4994
5258
  const savedKB = (result.savedBytes / 1024).toFixed(1);
4995
5259
  console.log(
4996
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
5260
+ `[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4997
5261
  );
4998
5262
  }
4999
5263
  } catch (replaceErr) {
5000
5264
  console.warn(
5001
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
5265
+ `[rango] Failed to evict ${target.label}: ${replaceErr.message}`
5002
5266
  );
5003
5267
  }
5004
5268
  }
@@ -5036,11 +5300,11 @@ function postprocessBundle(state) {
5036
5300
  writeFileSync4(rscEntryPath, injection + rscCode);
5037
5301
  const totalKB = (totalBytes / 1024).toFixed(1);
5038
5302
  console.log(
5039
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
5303
+ `[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
5040
5304
  );
5041
5305
  } catch (err) {
5042
5306
  throw new Error(
5043
- `[rsc-router] Failed to write prerender assets: ${err.message}`
5307
+ `[rango] Failed to write prerender assets: ${err.message}`
5044
5308
  );
5045
5309
  }
5046
5310
  }
@@ -5074,11 +5338,11 @@ function postprocessBundle(state) {
5074
5338
  writeFileSync4(rscEntryPath, injection + rscCode);
5075
5339
  const totalKB = (totalBytes / 1024).toFixed(1);
5076
5340
  console.log(
5077
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
5341
+ `[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
5078
5342
  );
5079
5343
  } catch (err) {
5080
5344
  throw new Error(
5081
- `[rsc-router] Failed to write static assets: ${err.message}`
5345
+ `[rango] Failed to write static assets: ${err.message}`
5082
5346
  );
5083
5347
  }
5084
5348
  }
@@ -5159,6 +5423,57 @@ function createDiscoveryGate(s, debug11) {
5159
5423
  };
5160
5424
  }
5161
5425
 
5426
+ // src/vite/utils/forward-user-plugins.ts
5427
+ function isDenied(name) {
5428
+ return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
5429
+ }
5430
+ function hasResolutionHooks(p) {
5431
+ return Boolean(p.resolveId || p.load);
5432
+ }
5433
+ function stripToResolutionHooks(p) {
5434
+ const stripped = { name: p.name };
5435
+ if (p.enforce) stripped.enforce = p.enforce;
5436
+ if (p.applyToEnvironment)
5437
+ stripped.applyToEnvironment = p.applyToEnvironment;
5438
+ if (p.resolveId) stripped.resolveId = p.resolveId;
5439
+ if (p.load) stripped.load = p.load;
5440
+ return stripped;
5441
+ }
5442
+ function selectForwardableResolvePlugins(plugins) {
5443
+ if (!plugins) return [];
5444
+ const forwarded = [];
5445
+ for (const p of plugins) {
5446
+ const name = p?.name;
5447
+ if (!name || isDenied(name)) continue;
5448
+ if (!hasResolutionHooks(p)) continue;
5449
+ forwarded.push(stripToResolutionHooks(p));
5450
+ }
5451
+ return forwarded;
5452
+ }
5453
+ function pickForwardedRunnerConfig(config) {
5454
+ const r = config.resolve ?? {};
5455
+ const resolve10 = {};
5456
+ if (r.alias !== void 0) resolve10.alias = r.alias;
5457
+ if (r.dedupe !== void 0) resolve10.dedupe = r.dedupe;
5458
+ if (r.conditions !== void 0) resolve10.conditions = r.conditions;
5459
+ if (r.mainFields !== void 0) resolve10.mainFields = r.mainFields;
5460
+ if (r.extensions !== void 0) resolve10.extensions = r.extensions;
5461
+ if (r.preserveSymlinks !== void 0)
5462
+ resolve10.preserveSymlinks = r.preserveSymlinks;
5463
+ if (r.tsconfigPaths !== void 0) resolve10.tsconfigPaths = r.tsconfigPaths;
5464
+ const userOxc = config.oxc;
5465
+ const userJsx = userOxc && typeof userOxc === "object" && typeof userOxc.jsx === "object" && userOxc.jsx !== null ? userOxc.jsx : {};
5466
+ const oxc = userOxc && typeof userOxc === "object" ? {
5467
+ ...userOxc,
5468
+ jsx: { ...userJsx, runtime: "automatic", importSource: "react" }
5469
+ } : { jsx: { runtime: "automatic", importSource: "react" } };
5470
+ return {
5471
+ resolve: resolve10,
5472
+ define: config.define,
5473
+ oxc
5474
+ };
5475
+ }
5476
+
5162
5477
  // src/vite/router-discovery.ts
5163
5478
  var debugDiscovery = createRangoDebugger(NS.discovery);
5164
5479
  var debugRoutes = createRangoDebugger(NS.routes);
@@ -5174,21 +5489,29 @@ function ensureCloudflareProtocolLoaderRegistered() {
5174
5489
  );
5175
5490
  } catch (err) {
5176
5491
  console.warn(
5177
- `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5492
+ `[rango] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5178
5493
  );
5179
5494
  }
5180
5495
  }
5181
5496
  async function createTempRscServer(state, options = {}) {
5182
5497
  ensureCloudflareProtocolLoaderRegistered();
5183
5498
  const { default: rsc } = await import("@vitejs/plugin-rsc");
5499
+ const runnerConfig = state.userRunnerConfig;
5500
+ const resolveConfig = runnerConfig?.resolve ?? {
5501
+ alias: state.userResolveAlias
5502
+ };
5503
+ const oxcConfig = runnerConfig?.oxc ?? {
5504
+ jsx: { runtime: "automatic", importSource: "react" }
5505
+ };
5184
5506
  return createViteServer({
5185
5507
  root: state.projectRoot,
5186
5508
  configFile: false,
5187
5509
  server: { middlewareMode: true },
5188
5510
  appType: "custom",
5189
5511
  logLevel: "silent",
5190
- resolve: { alias: state.userResolveAlias },
5191
- esbuild: { jsx: "automatic", jsxImportSource: "react" },
5512
+ resolve: resolveConfig,
5513
+ ...runnerConfig?.define ? { define: runnerConfig.define } : {},
5514
+ oxc: oxcConfig,
5192
5515
  ...options.cacheDir && { cacheDir: options.cacheDir },
5193
5516
  plugins: [
5194
5517
  rsc({
@@ -5206,7 +5529,11 @@ async function createTempRscServer(state, options = {}) {
5206
5529
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
5207
5530
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
5208
5531
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
5209
- exposeRouterId()
5532
+ exposeRouterId(),
5533
+ // Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
5534
+ // to resolveId/load and placed last so framework resolution runs first;
5535
+ // Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
5536
+ ...state.userResolvePlugins
5210
5537
  ]
5211
5538
  });
5212
5539
  }
@@ -5215,7 +5542,7 @@ async function resolveBuildEnv(option, factoryCtx) {
5215
5542
  if (option === "auto") {
5216
5543
  if (factoryCtx.preset !== "cloudflare") {
5217
5544
  throw new Error(
5218
- '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
5545
+ '[rango] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
5219
5546
  );
5220
5547
  }
5221
5548
  try {
@@ -5231,7 +5558,7 @@ async function resolveBuildEnv(option, factoryCtx) {
5231
5558
  };
5232
5559
  } catch (err) {
5233
5560
  throw new Error(
5234
- `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
5561
+ `[rango] buildEnv: "auto" requires wrangler to be installed.
5235
5562
  Install it with: pnpm add -D wrangler
5236
5563
  ${err.message}`
5237
5564
  );
@@ -5262,7 +5589,7 @@ async function releaseBuildEnv(s) {
5262
5589
  try {
5263
5590
  await s.buildEnvDispose();
5264
5591
  } catch (err) {
5265
- console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
5592
+ console.warn(`[rango] buildEnv dispose failed: ${err.message}`);
5266
5593
  }
5267
5594
  s.buildEnvDispose = null;
5268
5595
  }
@@ -5289,17 +5616,16 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5289
5616
  viteCommand = config.command;
5290
5617
  viteMode = config.mode;
5291
5618
  s.userResolveAlias = config.resolve.alias;
5619
+ s.userRunnerConfig = pickForwardedRunnerConfig(config);
5620
+ s.userResolvePlugins = selectForwardableResolvePlugins(
5621
+ config.plugins
5622
+ );
5292
5623
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
5293
5624
  s.resolvedEntryPath = opts.routerPathRef.path;
5294
5625
  }
5295
5626
  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
- }
5627
+ const entry = resolveRscEntryFromConfig(config);
5628
+ if (entry) s.resolvedEntryPath = entry;
5303
5629
  }
5304
5630
  if (opts?.staticRouteTypesGeneration !== false) {
5305
5631
  s.cachedRouterFiles = findRouterFiles(s.projectRoot, s.scanFilter);
@@ -5426,9 +5752,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5426
5752
  "getOrCreateTempServer: FAILED message=%s",
5427
5753
  err.message
5428
5754
  );
5429
- console.warn(
5430
- `[rsc-router] Failed to create temp runner: ${err.message}`
5431
- );
5755
+ console.warn(`[rango] Failed to create temp runner: ${err.message}`);
5432
5756
  }
5433
5757
  return null;
5434
5758
  }
@@ -5515,7 +5839,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5515
5839
  }
5516
5840
  } catch (err) {
5517
5841
  console.warn(
5518
- `[rsc-router] Cloudflare dev discovery failed: ${err.message}
5842
+ `[rango] Cloudflare dev discovery failed: ${err.message}
5519
5843
  ${err.stack}`
5520
5844
  );
5521
5845
  }
@@ -5559,7 +5883,7 @@ ${err.stack}`
5559
5883
  );
5560
5884
  } catch (err) {
5561
5885
  console.warn(
5562
- `[rsc-router] Router discovery failed: ${err.message}
5886
+ `[rango] Router discovery failed: ${err.message}
5563
5887
  ${err.stack}`
5564
5888
  );
5565
5889
  } finally {
@@ -5592,20 +5916,15 @@ ${err.stack}`
5592
5916
  if (s.mergedRouteTrie && serverMod.setRouteTrie) {
5593
5917
  serverMod.setRouteTrie(s.mergedRouteTrie);
5594
5918
  }
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
- }
5919
+ const perRouterSetters = [
5920
+ [s.perRouterManifestDataMap, "setRouterManifest"],
5921
+ [s.perRouterTrieMap, "setRouterTrie"],
5922
+ [s.perRouterPrecomputedMap, "setRouterPrecomputedEntries"]
5923
+ ];
5924
+ for (const [map, fn] of perRouterSetters) {
5925
+ const setter = serverMod[fn];
5926
+ if (typeof setter !== "function") continue;
5927
+ for (const [routerId, value] of map) setter(routerId, value);
5609
5928
  }
5610
5929
  };
5611
5930
  server.middlewares.use("/__rsc_prerender", async (req, res) => {
@@ -5639,7 +5958,7 @@ ${err.stack}`
5639
5958
  registry = serverMod.RouterRegistry ?? null;
5640
5959
  } catch (err) {
5641
5960
  console.warn(
5642
- `[rsc-router] Dev prerender module refresh failed: ${err.message}`
5961
+ `[rango] Dev prerender module refresh failed: ${err.message}`
5643
5962
  );
5644
5963
  res.statusCode = 500;
5645
5964
  res.end(`Prerender handler error: ${err.message}`);
@@ -5697,7 +6016,7 @@ ${err.stack}`
5697
6016
  return;
5698
6017
  } catch (err) {
5699
6018
  console.warn(
5700
- `[rsc-router] Dev prerender failed for ${pathname}: ${err.message}`
6019
+ `[rango] Dev prerender failed for ${pathname}: ${err.message}`
5701
6020
  );
5702
6021
  }
5703
6022
  }
@@ -5768,9 +6087,25 @@ ${err.stack}`
5768
6087
  () => writeRouteTypesFiles(s)
5769
6088
  );
5770
6089
  }
6090
+ if (s.lastDiscoveryError) {
6091
+ debugDiscovery?.(
6092
+ "hmr: cleared lastDiscoveryError (%s) after successful rediscovery",
6093
+ s.lastDiscoveryError.message
6094
+ );
6095
+ s.lastDiscoveryError = null;
6096
+ }
6097
+ if (rscEnv && !rscEnv.runner) forceCloudflareWorkerReload(rscEnv);
5771
6098
  } catch (err) {
6099
+ s.lastDiscoveryError = {
6100
+ message: err?.message ?? String(err),
6101
+ at: Date.now()
6102
+ };
5772
6103
  console.warn(
5773
- `[rsc-router] Runtime re-discovery failed: ${err.message}`
6104
+ `[rango] Runtime re-discovery failed: ${err.message}`
6105
+ );
6106
+ debugDiscovery?.(
6107
+ "hmr: lastDiscoveryError set (%s) \u2014 manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
6108
+ err?.message
5774
6109
  );
5775
6110
  } finally {
5776
6111
  debugDiscovery?.(
@@ -5780,6 +6115,25 @@ ${err.stack}`
5780
6115
  }
5781
6116
  });
5782
6117
  };
6118
+ const forceCloudflareWorkerReload = (rscEnv) => {
6119
+ if (!rscEnv?.hot) return;
6120
+ try {
6121
+ const graph = rscEnv.moduleGraph;
6122
+ if (graph?.invalidateAll) {
6123
+ graph.invalidateAll();
6124
+ debugDiscovery?.("hmr: invalidated workerd rsc module graph");
6125
+ }
6126
+ rscEnv.hot.send({ type: "full-reload" });
6127
+ debugDiscovery?.(
6128
+ "hmr: forced workerd rsc env reload (full-reload)"
6129
+ );
6130
+ } catch (err) {
6131
+ debugDiscovery?.(
6132
+ "hmr: workerd reload failed: %s",
6133
+ err?.message ?? err
6134
+ );
6135
+ }
6136
+ };
5783
6137
  const scheduleRouteRegeneration = () => {
5784
6138
  clearTimeout(routeChangeTimer);
5785
6139
  routeChangeTimer = setTimeout(() => {
@@ -5799,9 +6153,7 @@ ${err.stack}`
5799
6153
  }
5800
6154
  }
5801
6155
  } catch (err) {
5802
- console.error(
5803
- `[rsc-router] Route regeneration error: ${err.message}`
5804
- );
6156
+ console.error(`[rango] Route regeneration error: ${err.message}`);
5805
6157
  }
5806
6158
  debugDiscovery?.(
5807
6159
  "watcher: regenerated gen files (%sms)",
@@ -5810,7 +6162,7 @@ ${err.stack}`
5810
6162
  if (s.perRouterManifests.length > 0) {
5811
6163
  refreshRuntimeDiscovery().catch((err) => {
5812
6164
  console.warn(
5813
- `[rsc-router] Runtime re-discovery error: ${err.message}`
6165
+ `[rango] Runtime re-discovery error: ${err.message}`
5814
6166
  );
5815
6167
  resolveDiscoveryGate();
5816
6168
  });
@@ -5819,23 +6171,56 @@ ${err.stack}`
5819
6171
  };
5820
6172
  const handleRouteFileChange = (filePath) => {
5821
6173
  if (maybeHandleGeneratedRouteFileMutation(filePath)) return;
5822
- if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx"))
6174
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx")) {
6175
+ if (s.lastDiscoveryError) {
6176
+ debugDiscovery?.(
6177
+ "watcher: skip non-source %s [LASTERR %s]",
6178
+ filePath,
6179
+ s.lastDiscoveryError.message
6180
+ );
6181
+ }
5823
6182
  return;
5824
- if (s.scanFilter && !s.scanFilter(filePath)) return;
6183
+ }
6184
+ if (s.scanFilter && !s.scanFilter(filePath)) {
6185
+ if (s.lastDiscoveryError) {
6186
+ debugDiscovery?.(
6187
+ "watcher: skip scan-filter %s [LASTERR %s]",
6188
+ filePath,
6189
+ s.lastDiscoveryError.message
6190
+ );
6191
+ }
6192
+ return;
6193
+ }
6194
+ const inRecoveryMode = !!s.lastDiscoveryError;
5825
6195
  try {
5826
6196
  const source = readFileSync6(filePath, "utf-8");
5827
6197
  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
- );
6198
+ const isUseClient = trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'");
6199
+ if (!inRecoveryMode && isUseClient) return;
6200
+ let hasUrls = source.includes("urls(");
6201
+ let hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
6202
+ if (hasUrls) hasUrls = firstCodeMatchIndex(source, /urls\(/g) >= 0;
6203
+ if (hasCreateRouter) {
6204
+ hasCreateRouter = firstCodeMatchIndex(source, /\bcreateRouter\s*[<(]/g) >= 0;
6205
+ }
6206
+ if (!inRecoveryMode && !hasUrls && !hasCreateRouter) return;
6207
+ if (inRecoveryMode) {
6208
+ debugDiscovery?.(
6209
+ "watcher: recovery rediscovery for %s (urls=%s, router=%s, useClient=%s) [LASTERR %s]",
6210
+ filePath,
6211
+ hasUrls,
6212
+ hasCreateRouter,
6213
+ isUseClient,
6214
+ s.lastDiscoveryError.message
6215
+ );
6216
+ } else {
6217
+ debugDiscovery?.(
6218
+ "watcher: %s matches (urls=%s, router=%s)",
6219
+ filePath,
6220
+ hasUrls,
6221
+ hasCreateRouter
6222
+ );
6223
+ }
5839
6224
  if (hasCreateRouter) {
5840
6225
  const nestedRouterConflict = findNestedRouterConflict([
5841
6226
  ...s.cachedRouterFiles ?? [],
@@ -5853,7 +6238,15 @@ ${err.stack}`
5853
6238
  gate.noteRouteEvent();
5854
6239
  }
5855
6240
  scheduleRouteRegeneration();
5856
- } catch {
6241
+ } catch (readErr) {
6242
+ if (s.lastDiscoveryError) {
6243
+ debugDiscovery?.(
6244
+ "watcher: read error %s: %s [LASTERR %s]",
6245
+ filePath,
6246
+ readErr?.message,
6247
+ s.lastDiscoveryError.message
6248
+ );
6249
+ }
5857
6250
  }
5858
6251
  };
5859
6252
  server.watcher.on("add", handleRouteFileChange);
@@ -5899,7 +6292,7 @@ ${err.stack}`
5899
6292
  const rscEnv = tempServer.environments?.rsc;
5900
6293
  if (!rscEnv?.runner) {
5901
6294
  console.warn(
5902
- "[rsc-router] RSC environment runner not available during build, skipping manifest generation"
6295
+ "[rango] RSC environment runner not available during build, skipping manifest generation"
5903
6296
  );
5904
6297
  return;
5905
6298
  }
@@ -5931,8 +6324,9 @@ ${err.stack}`
5931
6324
  ${err.stack}` : null
5932
6325
  ].filter(Boolean).join("\n");
5933
6326
  throw new Error(
5934
- `[rsc-router] Build-time router discovery failed:
5935
- ${details}`
6327
+ `[rango] Build-time router discovery failed:
6328
+ ${details}`,
6329
+ { cause: err }
5936
6330
  );
5937
6331
  } finally {
5938
6332
  delete globalThis.__rscRouterDiscoveryActive;
@@ -5960,7 +6354,7 @@ ${details}`
5960
6354
  // `consumeSelfGenWrite` inside `maybeHandleGeneratedRouteFileMutation`),
5961
6355
  // AND vite's own HMR pipeline (which invalidates the gen file's
5962
6356
  // importers and triggers a second workerd full reload — visible to the
5963
- // user as a duplicate "[RSCRouter] HMR: version changed" on the client).
6357
+ // user as a duplicate "[Rango] HMR: version changed" on the client).
5964
6358
  //
5965
6359
  // `peekSelfGenWrite` is the authoritative filter: its map only contains
5966
6360
  // paths that `markSelfGenWrite` has registered, so it natively works
@@ -6120,6 +6514,10 @@ async function rango(options) {
6120
6514
  const resolvedOptions = options ?? { preset: "node" };
6121
6515
  const preset = resolvedOptions.preset ?? "node";
6122
6516
  const showBanner = resolvedOptions.banner ?? true;
6517
+ const clientChunksOption = resolvedOptions.clientChunks ?? true;
6518
+ const useBuiltInClientChunks = clientChunksOption === true;
6519
+ const clientChunkCtx = useBuiltInClientChunks ? { fallbackRefs: /* @__PURE__ */ new Set() } : void 0;
6520
+ const clientChunks = resolveClientChunks(clientChunksOption, clientChunkCtx);
6123
6521
  debugConfig?.("rango(%s) setup start", preset);
6124
6522
  const plugins = [];
6125
6523
  const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
@@ -6155,10 +6553,18 @@ async function rango(options) {
6155
6553
  // This ensures the same Context instance is used by both browser entry and RSC proxy modules
6156
6554
  optimizeDeps: {
6157
6555
  exclude: excludeDeps,
6158
- esbuildOptions: sharedEsbuildOptions
6556
+ rolldownOptions: sharedRolldownOptions
6159
6557
  },
6160
6558
  resolve: {
6161
- alias: rangoAliases
6559
+ alias: rangoAliases,
6560
+ // Force a single React/React-DOM copy across all three RSC
6561
+ // environments. RSC requires exactly one react/react-dom instance
6562
+ // per environment runtime; consumer install topologies (pnpm
6563
+ // strict layout, experimental React pins, third-party "use client"
6564
+ // packages) can otherwise resolve duplicate copies, causing
6565
+ // "Invalid hook call" / lost context. Child environments inherit
6566
+ // this root dedupe, and Vite merges it with any consumer dedupe.
6567
+ dedupe: ["react", "react-dom"]
6162
6568
  },
6163
6569
  build: {
6164
6570
  rollupOptions: { onwarn }
@@ -6167,6 +6573,14 @@ async function rango(options) {
6167
6573
  client: {
6168
6574
  build: {
6169
6575
  rollupOptions: {
6576
+ // FILE_NAME_CONFLICT (and any other client-build warning) is
6577
+ // emitted by the CLIENT environment build, which consults THIS
6578
+ // env's onwarn -- Vite 8's environment builds do NOT propagate
6579
+ // the top-level build.rollupOptions.onwarn into the client env.
6580
+ // Wire it here so the suppression runs where the conflicts
6581
+ // originate (the top-level handler is invoked 0x for these; the
6582
+ // client-env handler is invoked for all of them).
6583
+ onwarn,
6170
6584
  output: {
6171
6585
  manualChunks: getManualChunks
6172
6586
  }
@@ -6177,7 +6591,7 @@ async function rango(options) {
6177
6591
  optimizeDeps: {
6178
6592
  include: [nested("rsc-html-stream/client")],
6179
6593
  exclude: excludeDeps,
6180
- esbuildOptions: sharedEsbuildOptions
6594
+ rolldownOptions: sharedRolldownOptions
6181
6595
  }
6182
6596
  },
6183
6597
  ssr: {
@@ -6185,10 +6599,6 @@ async function rango(options) {
6185
6599
  build: {
6186
6600
  outDir: "./dist/rsc/ssr"
6187
6601
  },
6188
- resolve: {
6189
- // Ensure single React instance in SSR child environment
6190
- dedupe: ["react", "react-dom"]
6191
- },
6192
6602
  // Pre-bundle SSR entry and React for proper module linking with childEnvironments
6193
6603
  // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
6194
6604
  optimizeDeps: {
@@ -6206,7 +6616,7 @@ async function rango(options) {
6206
6616
  )
6207
6617
  ],
6208
6618
  exclude: excludeDeps,
6209
- esbuildOptions: sharedEsbuildOptions
6619
+ rolldownOptions: sharedRolldownOptions
6210
6620
  }
6211
6621
  },
6212
6622
  rsc: {
@@ -6223,7 +6633,7 @@ async function rango(options) {
6223
6633
  )
6224
6634
  ],
6225
6635
  exclude: excludeDeps,
6226
- esbuildOptions: sharedEsbuildOptions
6636
+ rolldownOptions: sharedRolldownOptions
6227
6637
  }
6228
6638
  }
6229
6639
  }
@@ -6241,7 +6651,8 @@ async function rango(options) {
6241
6651
  plugins.push(
6242
6652
  rsc({
6243
6653
  entries: finalEntries,
6244
- serverHandler: false
6654
+ serverHandler: false,
6655
+ clientChunks
6245
6656
  })
6246
6657
  );
6247
6658
  plugins.push(clientRefDedup());
@@ -6259,7 +6670,7 @@ async function rango(options) {
6259
6670
  const list = candidates.map(
6260
6671
  (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
6261
6672
  ).join("\n");
6262
- throw new Error(`[rsc-router] Multiple routers found:
6673
+ throw new Error(`[rango] Multiple routers found:
6263
6674
  ${list}`);
6264
6675
  }
6265
6676
  }
@@ -6278,18 +6689,34 @@ ${list}`);
6278
6689
  return {
6279
6690
  optimizeDeps: {
6280
6691
  exclude: excludeDeps,
6281
- esbuildOptions: sharedEsbuildOptions
6692
+ rolldownOptions: sharedRolldownOptions
6282
6693
  },
6283
6694
  build: {
6284
6695
  rollupOptions: { onwarn }
6285
6696
  },
6286
6697
  resolve: {
6287
- alias: rangoAliases
6698
+ alias: rangoAliases,
6699
+ // Force a single React/React-DOM copy across all three RSC
6700
+ // environments. RSC requires exactly one react/react-dom instance
6701
+ // per environment runtime; consumer install topologies (pnpm
6702
+ // strict layout, experimental React pins, third-party "use client"
6703
+ // packages) can otherwise resolve duplicate copies, causing
6704
+ // "Invalid hook call" / lost context. Child environments inherit
6705
+ // this root dedupe, and Vite merges it with any consumer dedupe.
6706
+ dedupe: ["react", "react-dom"]
6288
6707
  },
6289
6708
  environments: {
6290
6709
  client: {
6291
6710
  build: {
6292
6711
  rollupOptions: {
6712
+ // FILE_NAME_CONFLICT (and any other client-build warning) is
6713
+ // emitted by the CLIENT environment build, which consults THIS
6714
+ // env's onwarn -- Vite 8's environment builds do NOT propagate
6715
+ // the top-level build.rollupOptions.onwarn into the client env.
6716
+ // Wire it here so the suppression runs where the conflicts
6717
+ // originate (the top-level handler is invoked 0x for these; the
6718
+ // client-env handler is invoked for all of them).
6719
+ onwarn,
6293
6720
  output: {
6294
6721
  manualChunks: getManualChunks
6295
6722
  }
@@ -6304,7 +6731,7 @@ ${list}`);
6304
6731
  nested("rsc-html-stream/client")
6305
6732
  ],
6306
6733
  exclude: excludeDeps,
6307
- esbuildOptions: sharedEsbuildOptions,
6734
+ rolldownOptions: sharedRolldownOptions,
6308
6735
  entries: [VIRTUAL_IDS.browser]
6309
6736
  }
6310
6737
  },
@@ -6323,7 +6750,7 @@ ${list}`);
6323
6750
  )
6324
6751
  ],
6325
6752
  exclude: excludeDeps,
6326
- esbuildOptions: sharedEsbuildOptions
6753
+ rolldownOptions: sharedRolldownOptions
6327
6754
  }
6328
6755
  },
6329
6756
  rsc: {
@@ -6337,7 +6764,7 @@ ${list}`);
6337
6764
  "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6338
6765
  )
6339
6766
  ],
6340
- esbuildOptions: sharedEsbuildOptions
6767
+ rolldownOptions: sharedRolldownOptions
6341
6768
  }
6342
6769
  }
6343
6770
  }
@@ -6354,7 +6781,7 @@ ${list}`);
6354
6781
  if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
6355
6782
  hasWarnedDuplicate = true;
6356
6783
  console.warn(
6357
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
6784
+ "[rango] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
6358
6785
  );
6359
6786
  }
6360
6787
  }
@@ -6363,7 +6790,8 @@ ${list}`);
6363
6790
  plugins.push(performanceTracksPlugin());
6364
6791
  plugins.push(
6365
6792
  rsc({
6366
- entries: finalEntries
6793
+ entries: finalEntries,
6794
+ clientChunks
6367
6795
  })
6368
6796
  );
6369
6797
  plugins.push(clientRefDedup());
@@ -6402,7 +6830,8 @@ ${list}`);
6402
6830
  routerPathRef: discoveryRouterRef,
6403
6831
  enableBuildPrerender: prerenderEnabled,
6404
6832
  buildEnv: options?.buildEnv,
6405
- preset
6833
+ preset,
6834
+ clientChunkCtx
6406
6835
  })
6407
6836
  );
6408
6837
  debugConfig?.(