@rangojs/router 0.0.0-experimental.259 → 0.0.0-experimental.26

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 (225) hide show
  1. package/README.md +294 -28
  2. package/dist/bin/rango.js +355 -47
  3. package/dist/vite/index.js +1658 -1239
  4. package/package.json +3 -3
  5. package/skills/cache-guide/SKILL.md +9 -5
  6. package/skills/caching/SKILL.md +4 -4
  7. package/skills/document-cache/SKILL.md +2 -2
  8. package/skills/hooks/SKILL.md +40 -29
  9. package/skills/host-router/SKILL.md +218 -0
  10. package/skills/intercept/SKILL.md +79 -0
  11. package/skills/layout/SKILL.md +62 -2
  12. package/skills/loader/SKILL.md +229 -15
  13. package/skills/middleware/SKILL.md +109 -30
  14. package/skills/parallel/SKILL.md +57 -2
  15. package/skills/prerender/SKILL.md +189 -19
  16. package/skills/rango/SKILL.md +1 -2
  17. package/skills/response-routes/SKILL.md +3 -3
  18. package/skills/route/SKILL.md +44 -3
  19. package/skills/router-setup/SKILL.md +80 -3
  20. package/skills/theme/SKILL.md +5 -4
  21. package/skills/typesafety/SKILL.md +59 -16
  22. package/skills/use-cache/SKILL.md +16 -2
  23. package/src/__internal.ts +1 -1
  24. package/src/bin/rango.ts +56 -19
  25. package/src/browser/action-coordinator.ts +97 -0
  26. package/src/browser/event-controller.ts +29 -48
  27. package/src/browser/history-state.ts +80 -0
  28. package/src/browser/intercept-utils.ts +1 -1
  29. package/src/browser/link-interceptor.ts +19 -3
  30. package/src/browser/merge-segment-loaders.ts +9 -2
  31. package/src/browser/navigation-bridge.ts +66 -443
  32. package/src/browser/navigation-client.ts +34 -62
  33. package/src/browser/navigation-store.ts +4 -33
  34. package/src/browser/navigation-transaction.ts +295 -0
  35. package/src/browser/partial-update.ts +103 -151
  36. package/src/browser/prefetch/cache.ts +67 -0
  37. package/src/browser/prefetch/fetch.ts +137 -0
  38. package/src/browser/prefetch/observer.ts +65 -0
  39. package/src/browser/prefetch/policy.ts +42 -0
  40. package/src/browser/prefetch/queue.ts +88 -0
  41. package/src/browser/rango-state.ts +112 -0
  42. package/src/browser/react/Link.tsx +154 -44
  43. package/src/browser/react/NavigationProvider.tsx +32 -0
  44. package/src/browser/react/context.ts +6 -0
  45. package/src/browser/react/filter-segment-order.ts +11 -0
  46. package/src/browser/react/index.ts +2 -6
  47. package/src/browser/react/location-state-shared.ts +29 -11
  48. package/src/browser/react/location-state.ts +6 -4
  49. package/src/browser/react/nonce-context.ts +23 -0
  50. package/src/browser/react/shallow-equal.ts +27 -0
  51. package/src/browser/react/use-action.ts +23 -45
  52. package/src/browser/react/use-client-cache.ts +5 -3
  53. package/src/browser/react/use-handle.ts +21 -64
  54. package/src/browser/react/use-navigation.ts +7 -32
  55. package/src/browser/react/use-params.ts +5 -34
  56. package/src/browser/react/use-pathname.ts +2 -3
  57. package/src/browser/react/use-router.ts +3 -6
  58. package/src/browser/react/use-search-params.ts +2 -1
  59. package/src/browser/react/use-segments.ts +75 -114
  60. package/src/browser/response-adapter.ts +73 -0
  61. package/src/browser/rsc-router.tsx +46 -22
  62. package/src/browser/scroll-restoration.ts +10 -7
  63. package/src/browser/server-action-bridge.ts +458 -405
  64. package/src/browser/types.ts +21 -35
  65. package/src/browser/validate-redirect-origin.ts +29 -0
  66. package/src/build/generate-manifest.ts +38 -13
  67. package/src/build/generate-route-types.ts +4 -0
  68. package/src/build/index.ts +1 -0
  69. package/src/build/route-trie.ts +19 -3
  70. package/src/build/route-types/codegen.ts +13 -4
  71. package/src/build/route-types/include-resolution.ts +13 -0
  72. package/src/build/route-types/per-module-writer.ts +15 -3
  73. package/src/build/route-types/router-processing.ts +170 -18
  74. package/src/build/runtime-discovery.ts +13 -1
  75. package/src/cache/background-task.ts +34 -0
  76. package/src/cache/cache-key-utils.ts +44 -0
  77. package/src/cache/cache-policy.ts +125 -0
  78. package/src/cache/cache-runtime.ts +136 -123
  79. package/src/cache/cache-scope.ts +76 -83
  80. package/src/cache/cf/cf-cache-store.ts +12 -7
  81. package/src/cache/document-cache.ts +93 -69
  82. package/src/cache/handle-capture.ts +81 -0
  83. package/src/cache/index.ts +0 -15
  84. package/src/cache/memory-segment-store.ts +43 -69
  85. package/src/cache/profile-registry.ts +43 -8
  86. package/src/cache/read-through-swr.ts +134 -0
  87. package/src/cache/segment-codec.ts +140 -117
  88. package/src/cache/taint.ts +30 -3
  89. package/src/cache/types.ts +1 -115
  90. package/src/client.rsc.tsx +0 -1
  91. package/src/client.tsx +53 -76
  92. package/src/errors.ts +6 -1
  93. package/src/handle.ts +1 -1
  94. package/src/handles/MetaTags.tsx +5 -2
  95. package/src/host/cookie-handler.ts +8 -3
  96. package/src/host/index.ts +0 -3
  97. package/src/host/router.ts +14 -1
  98. package/src/href-client.ts +3 -1
  99. package/src/index.rsc.ts +53 -10
  100. package/src/index.ts +73 -43
  101. package/src/loader.rsc.ts +12 -4
  102. package/src/loader.ts +8 -0
  103. package/src/prerender/store.ts +60 -18
  104. package/src/prerender.ts +76 -18
  105. package/src/reverse.ts +11 -7
  106. package/src/root-error-boundary.tsx +30 -26
  107. package/src/route-definition/dsl-helpers.ts +9 -6
  108. package/src/route-definition/index.ts +0 -3
  109. package/src/route-definition/redirect.ts +15 -3
  110. package/src/route-map-builder.ts +38 -2
  111. package/src/route-name.ts +53 -0
  112. package/src/route-types.ts +7 -0
  113. package/src/router/content-negotiation.ts +1 -1
  114. package/src/router/debug-manifest.ts +16 -3
  115. package/src/router/handler-context.ts +96 -17
  116. package/src/router/intercept-resolution.ts +6 -4
  117. package/src/router/lazy-includes.ts +4 -0
  118. package/src/router/loader-resolution.ts +6 -11
  119. package/src/router/logging.ts +100 -3
  120. package/src/router/manifest.ts +32 -3
  121. package/src/router/match-api.ts +62 -54
  122. package/src/router/match-context.ts +3 -0
  123. package/src/router/match-handlers.ts +185 -11
  124. package/src/router/match-middleware/background-revalidation.ts +65 -85
  125. package/src/router/match-middleware/cache-lookup.ts +78 -10
  126. package/src/router/match-middleware/cache-store.ts +2 -0
  127. package/src/router/match-pipelines.ts +8 -43
  128. package/src/router/match-result.ts +0 -9
  129. package/src/router/metrics.ts +233 -13
  130. package/src/router/middleware-types.ts +34 -39
  131. package/src/router/middleware.ts +290 -130
  132. package/src/router/pattern-matching.ts +61 -10
  133. package/src/router/prerender-match.ts +36 -6
  134. package/src/router/preview-match.ts +7 -1
  135. package/src/router/revalidation.ts +61 -2
  136. package/src/router/router-context.ts +15 -0
  137. package/src/router/router-interfaces.ts +158 -40
  138. package/src/router/router-options.ts +223 -1
  139. package/src/router/router-registry.ts +5 -2
  140. package/src/router/segment-resolution/fresh.ts +165 -242
  141. package/src/router/segment-resolution/helpers.ts +263 -0
  142. package/src/router/segment-resolution/loader-cache.ts +102 -98
  143. package/src/router/segment-resolution/revalidation.ts +394 -272
  144. package/src/router/segment-resolution/static-store.ts +2 -2
  145. package/src/router/segment-resolution.ts +1 -3
  146. package/src/router/segment-wrappers.ts +3 -0
  147. package/src/router/telemetry-otel.ts +299 -0
  148. package/src/router/telemetry.ts +300 -0
  149. package/src/router/timeout.ts +148 -0
  150. package/src/router/trie-matching.ts +20 -2
  151. package/src/router/types.ts +7 -1
  152. package/src/router.ts +203 -18
  153. package/src/rsc/handler-context.ts +13 -2
  154. package/src/rsc/handler.ts +489 -438
  155. package/src/rsc/helpers.ts +125 -5
  156. package/src/rsc/index.ts +0 -20
  157. package/src/rsc/loader-fetch.ts +84 -42
  158. package/src/rsc/manifest-init.ts +3 -2
  159. package/src/rsc/origin-guard.ts +141 -0
  160. package/src/rsc/progressive-enhancement.ts +245 -19
  161. package/src/rsc/response-route-handler.ts +347 -0
  162. package/src/rsc/rsc-rendering.ts +47 -43
  163. package/src/rsc/runtime-warnings.ts +42 -0
  164. package/src/rsc/server-action.ts +166 -66
  165. package/src/rsc/ssr-setup.ts +128 -0
  166. package/src/rsc/types.ts +20 -2
  167. package/src/search-params.ts +38 -23
  168. package/src/server/context.ts +61 -7
  169. package/src/server/cookie-store.ts +190 -0
  170. package/src/server/fetchable-loader-store.ts +11 -6
  171. package/src/server/handle-store.ts +84 -12
  172. package/src/server/loader-registry.ts +11 -46
  173. package/src/server/request-context.ts +275 -49
  174. package/src/server.ts +6 -0
  175. package/src/ssr/index.tsx +67 -28
  176. package/src/static-handler.ts +7 -0
  177. package/src/theme/ThemeProvider.tsx +6 -1
  178. package/src/theme/index.ts +4 -18
  179. package/src/theme/theme-context.ts +1 -28
  180. package/src/theme/theme-script.ts +2 -1
  181. package/src/types/cache-types.ts +6 -1
  182. package/src/types/error-types.ts +3 -0
  183. package/src/types/global-namespace.ts +22 -0
  184. package/src/types/handler-context.ts +103 -16
  185. package/src/types/index.ts +1 -1
  186. package/src/types/loader-types.ts +9 -6
  187. package/src/types/route-config.ts +17 -26
  188. package/src/types/route-entry.ts +28 -0
  189. package/src/types/segments.ts +0 -5
  190. package/src/urls/include-helper.ts +49 -8
  191. package/src/urls/index.ts +1 -0
  192. package/src/urls/path-helper-types.ts +30 -12
  193. package/src/urls/path-helper.ts +17 -2
  194. package/src/urls/pattern-types.ts +21 -1
  195. package/src/urls/response-types.ts +29 -7
  196. package/src/urls/type-extraction.ts +23 -15
  197. package/src/use-loader.tsx +27 -9
  198. package/src/vite/discovery/bundle-postprocess.ts +32 -52
  199. package/src/vite/discovery/discover-routers.ts +52 -26
  200. package/src/vite/discovery/prerender-collection.ts +58 -41
  201. package/src/vite/discovery/route-types-writer.ts +7 -7
  202. package/src/vite/discovery/state.ts +7 -7
  203. package/src/vite/discovery/virtual-module-codegen.ts +5 -2
  204. package/src/vite/index.ts +10 -51
  205. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  206. package/src/vite/plugins/client-ref-hashing.ts +3 -3
  207. package/src/vite/plugins/expose-internal-ids.ts +4 -3
  208. package/src/vite/plugins/refresh-cmd.ts +65 -0
  209. package/src/vite/plugins/use-cache-transform.ts +91 -3
  210. package/src/vite/plugins/version-plugin.ts +188 -18
  211. package/src/vite/rango.ts +61 -36
  212. package/src/vite/router-discovery.ts +173 -100
  213. package/src/vite/utils/prerender-utils.ts +81 -0
  214. package/src/vite/utils/shared-utils.ts +19 -9
  215. package/skills/testing/SKILL.md +0 -226
  216. package/src/browser/lru-cache.ts +0 -61
  217. package/src/browser/react/prefetch.ts +0 -27
  218. package/src/browser/request-controller.ts +0 -164
  219. package/src/cache/memory-store.ts +0 -253
  220. package/src/href-context.ts +0 -33
  221. package/src/route-definition/route-function.ts +0 -119
  222. package/src/router.gen.ts +0 -6
  223. package/src/static-handler.gen.ts +0 -5
  224. package/src/urls.gen.ts +0 -8
  225. /package/{CLAUDE.md → AGENTS.md} +0 -0
@@ -1,3 +1,7 @@
1
+ // src/vite/rango.ts
2
+ import { readFileSync as readFileSync7 } from "node:fs";
3
+ import { resolve as resolve9 } from "node:path";
4
+
1
5
  // src/vite/plugins/expose-action-id.ts
2
6
  import MagicString from "magic-string";
3
7
  import path2 from "node:path";
@@ -353,10 +357,10 @@ import path4 from "node:path";
353
357
  function isDirectivePrologueStatement(node) {
354
358
  return node?.type === "ExpressionStatement" && typeof node.directive === "string";
355
359
  }
356
- function findImportInsertionPos(code, parseAst3) {
360
+ function findImportInsertionPos(code, parseAst4) {
357
361
  let program;
358
362
  try {
359
- program = parseAst3(code, { jsx: true });
363
+ program = parseAst4(code, { jsx: true });
360
364
  } catch {
361
365
  return 0;
362
366
  }
@@ -393,10 +397,10 @@ function walkNode(node, parent, ancestors, enter) {
393
397
  }
394
398
  ancestors.pop();
395
399
  }
396
- function findHandlerCalls(code, fnName, parseAst3) {
400
+ function findHandlerCalls(code, fnName, parseAst4) {
397
401
  let program;
398
402
  try {
399
- program = parseAst3(code, { jsx: true });
403
+ program = parseAst4(code, { jsx: true });
400
404
  } catch {
401
405
  return [];
402
406
  }
@@ -468,18 +472,18 @@ function getImportedLocalNamesFromProgram(program, importedName) {
468
472
  }
469
473
  return localNames;
470
474
  }
471
- function getImportedLocalNames(code, importedName, parseAst3) {
475
+ function getImportedLocalNames(code, importedName, parseAst4) {
472
476
  try {
473
- const program = parseAst3(code, { jsx: true });
477
+ const program = parseAst4(code, { jsx: true });
474
478
  return getImportedLocalNamesFromProgram(program, importedName);
475
479
  } catch {
476
480
  return /* @__PURE__ */ new Set();
477
481
  }
478
482
  }
479
- function extractImportDeclarations(code, parseAst3) {
483
+ function extractImportDeclarations(code, parseAst4) {
480
484
  let program;
481
485
  try {
482
- program = parseAst3(code, { jsx: true });
486
+ program = parseAst4(code, { jsx: true });
483
487
  } catch {
484
488
  return [];
485
489
  }
@@ -531,10 +535,10 @@ function isSafeVariableDeclaration(node, handlerNames) {
531
535
  (d) => isSafeDeclaratorInit(d.init) && !(d.init?.type === "CallExpression" && d.init.callee?.type === "Identifier" && handlerNames.has(d.init.callee.name))
532
536
  );
533
537
  }
534
- function extractModuleLevelDeclarations(code, parseAst3, handlerNames) {
538
+ function extractModuleLevelDeclarations(code, parseAst4, handlerNames) {
535
539
  let program;
536
540
  try {
537
- program = parseAst3(code, { jsx: true });
541
+ program = parseAst4(code, { jsx: true });
538
542
  } catch {
539
543
  return [];
540
544
  }
@@ -565,17 +569,17 @@ function extractModuleLevelDeclarations(code, parseAst3, handlerNames) {
565
569
  }
566
570
  return declarations;
567
571
  }
568
- function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtualRegistry, moduleId, parseAst3) {
569
- const sites = findHandlerCalls(code, fnName, parseAst3);
572
+ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtualRegistry, moduleId, parseAst4) {
573
+ const sites = findHandlerCalls(code, fnName, parseAst4);
570
574
  const inlineSites = sites.filter((site) => site.exportInfo === null);
571
575
  if (inlineSites.length === 0) return false;
572
- const imports = extractImportDeclarations(code, parseAst3);
573
- const staticNames = getImportedLocalNames(code, "Static", parseAst3);
574
- const prerenderNames = getImportedLocalNames(code, "Prerender", parseAst3);
576
+ const imports = extractImportDeclarations(code, parseAst4);
577
+ const staticNames = getImportedLocalNames(code, "Static", parseAst4);
578
+ const prerenderNames = getImportedLocalNames(code, "Prerender", parseAst4);
575
579
  const handlerNames = /* @__PURE__ */ new Set([...staticNames, ...prerenderNames]);
576
580
  const declarations = extractModuleLevelDeclarations(
577
581
  code,
578
- parseAst3,
582
+ parseAst4,
579
583
  handlerNames
580
584
  );
581
585
  const lineCounts = /* @__PURE__ */ new Map();
@@ -600,7 +604,7 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
600
604
  }
601
605
  if (importStatements.length > 0) {
602
606
  const importBlock = importStatements.join("\n") + "\n";
603
- const insertionPos = findImportInsertionPos(code, parseAst3);
607
+ const insertionPos = findImportInsertionPos(code, parseAst4);
604
608
  if (insertionPos === 0) {
605
609
  s.prepend(importBlock);
606
610
  } else {
@@ -1101,6 +1105,7 @@ ${lazyImports.join(",\n")}
1101
1105
  async buildStart() {
1102
1106
  if (!isBuild) return;
1103
1107
  const fs2 = await import("node:fs/promises");
1108
+ const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage"]);
1104
1109
  async function scanDir(dir) {
1105
1110
  const results = [];
1106
1111
  try {
@@ -1108,7 +1113,7 @@ ${lazyImports.join(",\n")}
1108
1113
  for (const entry of entries) {
1109
1114
  const fullPath = path4.join(dir, entry.name);
1110
1115
  if (entry.isDirectory()) {
1111
- if (entry.name !== "node_modules") {
1116
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
1112
1117
  results.push(...await scanDir(fullPath));
1113
1118
  }
1114
1119
  } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
@@ -1120,8 +1125,7 @@ ${lazyImports.join(",\n")}
1120
1125
  return results;
1121
1126
  }
1122
1127
  try {
1123
- const srcDir = path4.join(projectRoot, "src");
1124
- const files = await scanDir(srcDir);
1128
+ const files = await scanDir(projectRoot);
1125
1129
  for (const filePath of files) {
1126
1130
  const content = await fs2.readFile(filePath, "utf-8");
1127
1131
  if (!content.includes("createLoader")) continue;
@@ -1397,6 +1401,237 @@ ${lazyImports.join(",\n")}
1397
1401
  };
1398
1402
  }
1399
1403
 
1404
+ // src/vite/plugins/use-cache-transform.ts
1405
+ import path5 from "node:path";
1406
+ import MagicString5 from "magic-string";
1407
+ var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
1408
+ var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
1409
+ function useCacheTransform() {
1410
+ let projectRoot = "";
1411
+ let isBuild = false;
1412
+ let rscTransforms = null;
1413
+ return {
1414
+ name: "@rangojs/router:use-cache",
1415
+ enforce: "post",
1416
+ configResolved(config) {
1417
+ projectRoot = config.root;
1418
+ isBuild = config.command === "build";
1419
+ },
1420
+ async transform(code, id) {
1421
+ if (this.environment?.name !== "rsc") return;
1422
+ if (!code.includes("use cache")) return;
1423
+ if (id.includes("/node_modules/") || id.startsWith("\0")) return;
1424
+ if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
1425
+ if (!rscTransforms) {
1426
+ try {
1427
+ rscTransforms = await import("@vitejs/plugin-rsc/transforms");
1428
+ } catch {
1429
+ return;
1430
+ }
1431
+ }
1432
+ const {
1433
+ hasDirective,
1434
+ transformWrapExport,
1435
+ transformHoistInlineDirective
1436
+ } = rscTransforms;
1437
+ let ast;
1438
+ try {
1439
+ const { parseAst: parseAst4 } = await import("vite");
1440
+ ast = parseAst4(code);
1441
+ } catch {
1442
+ return;
1443
+ }
1444
+ const filePath = normalizePath(path5.relative(projectRoot, id));
1445
+ const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
1446
+ if (hasDirective(ast.body, "use cache")) {
1447
+ return transformFileLevelUseCache(
1448
+ code,
1449
+ ast,
1450
+ filePath,
1451
+ id,
1452
+ isBuild,
1453
+ isLayoutOrTemplate,
1454
+ transformWrapExport
1455
+ );
1456
+ }
1457
+ const functionResult = transformFunctionLevelUseCache(
1458
+ code,
1459
+ ast,
1460
+ filePath,
1461
+ id,
1462
+ isBuild,
1463
+ transformHoistInlineDirective
1464
+ );
1465
+ warnOnNearMissDirectives(ast, id, this.warn.bind(this));
1466
+ if (functionResult) return functionResult;
1467
+ }
1468
+ };
1469
+ }
1470
+ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLayoutOrTemplate, transformWrapExport) {
1471
+ const nonFunctionExports = [];
1472
+ const { exportNames, output } = transformWrapExport(code, ast, {
1473
+ runtime: (value, name) => {
1474
+ const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1475
+ return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, "default")`;
1476
+ },
1477
+ rejectNonAsyncFunction: false,
1478
+ filter: (name, meta) => {
1479
+ if (name === "default" && isLayoutOrTemplate) return false;
1480
+ if (meta.isFunction === false) {
1481
+ nonFunctionExports.push(name);
1482
+ return false;
1483
+ }
1484
+ return true;
1485
+ }
1486
+ });
1487
+ if (nonFunctionExports.length > 0) {
1488
+ throw new Error(
1489
+ `[rango:use-cache] File-level "use cache" in ${sourceId} cannot wrap non-function export${nonFunctionExports.length > 1 ? "s" : ""}: ${nonFunctionExports.map((n) => `"${n}"`).join(", ")}. Only function exports can be cached. Either remove "use cache" from the file level and add it inside individual functions, or move the non-function exports to a separate module.`
1490
+ );
1491
+ }
1492
+ if (exportNames.length === 0) {
1493
+ const s = new MagicString5(code);
1494
+ const directive2 = findFileLevelDirective(ast);
1495
+ if (directive2) {
1496
+ s.overwrite(
1497
+ directive2.start,
1498
+ directive2.end,
1499
+ `/* "use cache" -- wrapped by rango */`
1500
+ );
1501
+ return {
1502
+ code: s.toString(),
1503
+ map: s.generateMap({ source: sourceId, hires: "boundary" })
1504
+ };
1505
+ }
1506
+ return;
1507
+ }
1508
+ output.prepend(
1509
+ `import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};
1510
+ `
1511
+ );
1512
+ const directive = findFileLevelDirective(ast);
1513
+ if (directive) {
1514
+ output.overwrite(
1515
+ directive.start,
1516
+ directive.end,
1517
+ `/* "use cache" -- wrapped by rango */`
1518
+ );
1519
+ }
1520
+ return {
1521
+ code: output.toString(),
1522
+ map: output.generateMap({ source: sourceId, hires: "boundary" })
1523
+ };
1524
+ }
1525
+ function transformFunctionLevelUseCache(code, ast, filePath, sourceId, isBuild, transformHoistInlineDirective) {
1526
+ try {
1527
+ const { output, names } = transformHoistInlineDirective(code, ast, {
1528
+ directive: /^use cache(:\s*[\w-]+)?$/,
1529
+ runtime: (value, name, meta) => {
1530
+ const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1531
+ const profileMatch = meta.directiveMatch[1];
1532
+ const profileName = profileMatch ? profileMatch.replace(/^:\s*/, "").trim() : "default";
1533
+ return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, ${JSON.stringify(profileName)})`;
1534
+ },
1535
+ rejectNonAsyncFunction: false
1536
+ });
1537
+ if (names.length === 0) return;
1538
+ output.prepend(
1539
+ `import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};
1540
+ `
1541
+ );
1542
+ return {
1543
+ code: output.toString(),
1544
+ map: output.generateMap({ source: sourceId, hires: "boundary" })
1545
+ };
1546
+ } catch {
1547
+ return;
1548
+ }
1549
+ }
1550
+ function findFileLevelDirective(ast) {
1551
+ for (const node of ast.body ?? []) {
1552
+ if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string" && node.expression.value.startsWith("use cache")) {
1553
+ return { start: node.start, end: node.end };
1554
+ }
1555
+ }
1556
+ return null;
1557
+ }
1558
+ var VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
1559
+ var NEAR_MISS_RE = /^use cache:\s*.+$/;
1560
+ function warnOnNearMissDirectives(ast, fileId, warn) {
1561
+ const visit = (node) => {
1562
+ if (!node || typeof node !== "object") return;
1563
+ if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
1564
+ const value = node.expression.value;
1565
+ if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !VALID_DIRECTIVE_RE.test(value)) {
1566
+ const profilePart = value.slice("use cache:".length).trim();
1567
+ warn(
1568
+ `[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.`
1569
+ );
1570
+ }
1571
+ }
1572
+ for (const key of Object.keys(node)) {
1573
+ const child = node[key];
1574
+ if (Array.isArray(child)) {
1575
+ for (const item of child) {
1576
+ visit(item);
1577
+ }
1578
+ } else if (child && typeof child === "object" && child.type) {
1579
+ visit(child);
1580
+ }
1581
+ }
1582
+ };
1583
+ for (const node of ast.body ?? []) {
1584
+ visit(node);
1585
+ }
1586
+ }
1587
+
1588
+ // src/vite/plugins/client-ref-dedup.ts
1589
+ var CLIENT_IN_SERVER_PROXY_PREFIX = "virtual:vite-rsc/client-in-server-package-proxy/";
1590
+ function extractPackageName(absolutePath) {
1591
+ const marker = "/node_modules/";
1592
+ const idx = absolutePath.lastIndexOf(marker);
1593
+ if (idx === -1) return null;
1594
+ const afterModules = absolutePath.slice(idx + marker.length);
1595
+ if (afterModules.startsWith("@")) {
1596
+ const parts = afterModules.split("/");
1597
+ if (parts.length < 2 || !parts[1]) return null;
1598
+ return `${parts[0]}/${parts[1]}`;
1599
+ }
1600
+ const name = afterModules.split("/")[0];
1601
+ return name || null;
1602
+ }
1603
+ function clientRefDedup() {
1604
+ let clientExclude = [];
1605
+ return {
1606
+ name: "@rangojs/router:client-ref-dedup",
1607
+ enforce: "pre",
1608
+ apply: "serve",
1609
+ configResolved(config) {
1610
+ const clientEnv = config.environments?.["client"];
1611
+ clientExclude = clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
1612
+ },
1613
+ resolveId(source, importer, options) {
1614
+ if (this.environment?.name !== "client") return;
1615
+ if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
1616
+ if (!source.includes("/node_modules/")) return;
1617
+ if (!importer) return;
1618
+ const packageName = extractPackageName(source);
1619
+ if (!packageName) return;
1620
+ if (clientExclude.includes(packageName)) return;
1621
+ return `\0rango:dedup/${packageName}`;
1622
+ },
1623
+ load(id) {
1624
+ if (!id.startsWith("\0rango:dedup/")) return;
1625
+ const packageName = id.slice("\0rango:dedup/".length);
1626
+ return [
1627
+ `export * from ${JSON.stringify(packageName)};`,
1628
+ `import * as __all__ from ${JSON.stringify(packageName)};`,
1629
+ `export default __all__.default;`
1630
+ ].join("\n");
1631
+ }
1632
+ };
1633
+ }
1634
+
1400
1635
  // src/vite/plugins/virtual-entries.ts
1401
1636
  var VIRTUAL_ENTRY_BROWSER = `
1402
1637
  import {
@@ -1503,309 +1738,66 @@ function getVirtualVersionContent(version) {
1503
1738
  return `export const VERSION = ${JSON.stringify(version)};`;
1504
1739
  }
1505
1740
 
1506
- // src/vite/plugins/version-plugin.ts
1507
- function createVersionPlugin() {
1508
- const buildVersion = Date.now().toString(16);
1509
- let currentVersion = buildVersion;
1510
- let isDev = false;
1511
- let server = null;
1512
- return {
1513
- name: "@rangojs/router:version",
1514
- enforce: "pre",
1515
- configResolved(config) {
1516
- isDev = config.command === "serve";
1741
+ // src/vite/utils/package-resolution.ts
1742
+ import { existsSync } from "node:fs";
1743
+ import { resolve } from "node:path";
1744
+
1745
+ // package.json
1746
+ var package_default = {
1747
+ name: "@rangojs/router",
1748
+ version: "0.0.0-experimental.26",
1749
+ description: "Django-inspired RSC router with composable URL patterns",
1750
+ keywords: [
1751
+ "react",
1752
+ "react-server-components",
1753
+ "router",
1754
+ "rsc",
1755
+ "vite"
1756
+ ],
1757
+ homepage: "https://github.com/ivogt/vite-rsc#readme",
1758
+ bugs: {
1759
+ url: "https://github.com/ivogt/vite-rsc/issues"
1760
+ },
1761
+ license: "MIT",
1762
+ author: "Ivo Todorov",
1763
+ repository: {
1764
+ type: "git",
1765
+ url: "git+https://github.com/ivogt/vite-rsc.git",
1766
+ directory: "packages/rangojs-router"
1767
+ },
1768
+ bin: {
1769
+ rango: "./dist/bin/rango.js"
1770
+ },
1771
+ files: [
1772
+ "src",
1773
+ "!src/**/__tests__",
1774
+ "!src/**/__mocks__",
1775
+ "!src/**/*.test.ts",
1776
+ "!src/**/*.test.tsx",
1777
+ "dist",
1778
+ "skills",
1779
+ "AGENTS.md",
1780
+ "README.md"
1781
+ ],
1782
+ type: "module",
1783
+ exports: {
1784
+ ".": {
1785
+ types: "./src/index.rsc.ts",
1786
+ "react-server": "./src/index.rsc.ts",
1787
+ default: "./src/index.ts"
1517
1788
  },
1518
- configureServer(devServer) {
1519
- server = devServer;
1789
+ "./server": {
1790
+ types: "./src/server.ts",
1791
+ import: "./src/server.ts"
1520
1792
  },
1521
- resolveId(id) {
1522
- if (id === VIRTUAL_IDS.version) {
1523
- return "\0" + id;
1524
- }
1525
- return null;
1793
+ "./client": {
1794
+ types: "./src/client.tsx",
1795
+ "react-server": "./src/client.rsc.tsx",
1796
+ default: "./src/client.tsx"
1526
1797
  },
1527
- load(id) {
1528
- if (id === "\0" + VIRTUAL_IDS.version) {
1529
- return getVirtualVersionContent(currentVersion);
1530
- }
1531
- return null;
1532
- },
1533
- // Track RSC module changes and update version
1534
- hotUpdate(ctx) {
1535
- if (!isDev) return;
1536
- const isRscModule = this.environment?.name === "rsc";
1537
- if (isRscModule && ctx.modules.length > 0) {
1538
- currentVersion = Date.now().toString(16);
1539
- console.log(
1540
- `[rsc-router] RSC module changed, version updated: ${currentVersion}`
1541
- );
1542
- if (server) {
1543
- const rscEnv = server.environments?.rsc;
1544
- if (rscEnv?.moduleGraph) {
1545
- const versionMod = rscEnv.moduleGraph.getModuleById(
1546
- "\0" + VIRTUAL_IDS.version
1547
- );
1548
- if (versionMod) {
1549
- rscEnv.moduleGraph.invalidateModule(versionMod);
1550
- }
1551
- }
1552
- }
1553
- }
1554
- }
1555
- };
1556
- }
1557
-
1558
- // src/vite/plugins/virtual-stub-plugin.ts
1559
- function createVirtualStubPlugin() {
1560
- const STUB_PREFIXES = [
1561
- "virtual:rsc-router/",
1562
- "virtual:entry-",
1563
- "virtual:vite-rsc/"
1564
- ];
1565
- return {
1566
- name: "@rangojs/router:virtual-stubs",
1567
- resolveId(id) {
1568
- if (STUB_PREFIXES.some((p) => id.startsWith(p))) {
1569
- return "\0stub:" + id;
1570
- }
1571
- return null;
1572
- },
1573
- load(id) {
1574
- if (id.startsWith("\0stub:")) {
1575
- return "export default {}";
1576
- }
1577
- return null;
1578
- }
1579
- };
1580
- }
1581
-
1582
- // src/vite/plugins/client-ref-hashing.ts
1583
- import { relative, posix } from "node:path";
1584
- import { createHash as createHash2 } from "node:crypto";
1585
- var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
1586
- var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
1587
- var FS_PREFIX = "/@fs/";
1588
- function computeProductionHash(projectRoot, refKey) {
1589
- let toHash;
1590
- if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
1591
- toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
1592
- } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
1593
- const absPath = decodeURIComponent(
1594
- refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
1595
- );
1596
- toHash = posix.normalize(relative(projectRoot, absPath));
1597
- } else if (refKey.startsWith(FS_PREFIX)) {
1598
- const absPath = refKey.slice(FS_PREFIX.length - 1);
1599
- toHash = posix.normalize(relative(projectRoot, absPath));
1600
- } else if (refKey.startsWith("/")) {
1601
- toHash = refKey.slice(1);
1602
- } else {
1603
- return refKey;
1604
- }
1605
- return createHash2("sha256").update(toHash).digest("hex").slice(0, 12);
1606
- }
1607
- var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
1608
- function transformClientRefs(code, projectRoot) {
1609
- if (!code.includes("registerClientReference")) return null;
1610
- let hasReplacement = false;
1611
- const result = code.replace(
1612
- REGISTER_CLIENT_REF_RE,
1613
- (match, refKey) => {
1614
- const hash = computeProductionHash(projectRoot, refKey);
1615
- if (hash === refKey) return match;
1616
- hasReplacement = true;
1617
- return match.replace(`"${refKey}"`, `"${hash}"`);
1618
- }
1619
- );
1620
- return hasReplacement ? result : null;
1621
- }
1622
- function hashClientRefs(projectRoot) {
1623
- return {
1624
- name: "@rangojs/router:hash-client-refs",
1625
- // Run after the RSC plugin's transform (default enforce is normal)
1626
- enforce: "post",
1627
- applyToEnvironment(env) {
1628
- return env.name === "rsc";
1629
- },
1630
- transform(code, _id) {
1631
- const result = transformClientRefs(code, projectRoot);
1632
- if (result === null) return;
1633
- return { code: result, map: null };
1634
- }
1635
- };
1636
- }
1637
-
1638
- // src/vite/plugins/version-injector.ts
1639
- import { resolve } from "node:path";
1640
- import * as Vite from "vite";
1641
- function createVersionInjectorPlugin(rscEntryPath) {
1642
- let resolvedEntryPath = "";
1643
- return {
1644
- name: "@rangojs/router:version-injector",
1645
- enforce: "pre",
1646
- configResolved(config) {
1647
- let entryPath = rscEntryPath;
1648
- if (!entryPath) {
1649
- const rscEnvConfig = config.environments?.["rsc"];
1650
- const entries = rscEnvConfig?.optimizeDeps?.entries;
1651
- if (typeof entries === "string") {
1652
- entryPath = entries;
1653
- } else if (Array.isArray(entries) && entries.length > 0) {
1654
- entryPath = entries[0];
1655
- }
1656
- }
1657
- if (entryPath) {
1658
- resolvedEntryPath = resolve(config.root, entryPath);
1659
- }
1660
- },
1661
- transform(code, id) {
1662
- if (!resolvedEntryPath) return null;
1663
- const normalizedId = Vite.normalizePath(id);
1664
- const normalizedEntry = Vite.normalizePath(resolvedEntryPath);
1665
- if (normalizedId !== normalizedEntry) {
1666
- return null;
1667
- }
1668
- const prepend = [];
1669
- let newCode = code;
1670
- if (!code.includes("virtual:rsc-router/routes-manifest")) {
1671
- prepend.push(`import "virtual:rsc-router/routes-manifest";`);
1672
- }
1673
- const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
1674
- if (needsVersion) {
1675
- prepend.push(`import { VERSION } from "@rangojs/router:version";`);
1676
- newCode = newCode.replace(
1677
- /createRSCHandler\s*\(\s*\{/,
1678
- "createRSCHandler({\n version: VERSION,"
1679
- );
1680
- }
1681
- if (prepend.length === 0 && newCode === code) return null;
1682
- newCode = prepend.join("\n") + (prepend.length > 0 ? "\n" : "") + newCode;
1683
- return {
1684
- code: newCode,
1685
- map: null
1686
- };
1687
- }
1688
- };
1689
- }
1690
-
1691
- // src/vite/plugins/cjs-to-esm.ts
1692
- function createCjsToEsmPlugin() {
1693
- return {
1694
- name: "@rangojs/router:cjs-to-esm",
1695
- enforce: "pre",
1696
- transform(code, id) {
1697
- const cleanId = id.split("?")[0];
1698
- if (cleanId.includes("vendor/react-server-dom/client.browser.js") || cleanId.includes("vendor\\react-server-dom\\client.browser.js")) {
1699
- const isProd = process.env.NODE_ENV === "production";
1700
- const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
1701
- return {
1702
- code: `export * from "${cjsFile}";`,
1703
- map: null
1704
- };
1705
- }
1706
- if ((cleanId.includes("vendor/react-server-dom/cjs/") || cleanId.includes("vendor\\react-server-dom\\cjs\\")) && cleanId.includes("client.browser")) {
1707
- let transformed = code;
1708
- const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
1709
- const license = licenseMatch ? licenseMatch[0] : "";
1710
- if (license) {
1711
- transformed = transformed.slice(license.length);
1712
- }
1713
- transformed = transformed.replace(/^\s*["']use strict["'];\s*/, "");
1714
- transformed = transformed.replace(
1715
- /^\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
1716
- ""
1717
- );
1718
- transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
1719
- transformed = transformed.replace(
1720
- /var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
1721
- 'import React from "react";\nimport ReactDOM from "react-dom";\nvar '
1722
- );
1723
- transformed = transformed.replace(
1724
- /var\s+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
1725
- 'import ReactDOM from "react-dom";\nvar '
1726
- );
1727
- transformed = transformed.replace(
1728
- /exports\.(\w+)\s*=\s*function\s*\(/g,
1729
- "export function $1("
1730
- );
1731
- transformed = transformed.replace(
1732
- /exports\.(\w+)\s*=/g,
1733
- "export const $1 ="
1734
- );
1735
- transformed = license + "\n" + transformed;
1736
- return {
1737
- code: transformed,
1738
- map: null
1739
- };
1740
- }
1741
- return null;
1742
- }
1743
- };
1744
- }
1745
-
1746
- // src/vite/utils/shared-utils.ts
1747
- import * as Vite2 from "vite";
1748
-
1749
- // src/vite/utils/package-resolution.ts
1750
- import { existsSync } from "node:fs";
1751
- import { resolve as resolve2 } from "node:path";
1752
-
1753
- // package.json
1754
- var package_default = {
1755
- name: "@rangojs/router",
1756
- version: "0.0.0-experimental.259",
1757
- description: "Django-inspired RSC router with composable URL patterns",
1758
- keywords: [
1759
- "react",
1760
- "react-server-components",
1761
- "router",
1762
- "rsc",
1763
- "vite"
1764
- ],
1765
- homepage: "https://github.com/ivogt/vite-rsc#readme",
1766
- bugs: {
1767
- url: "https://github.com/ivogt/vite-rsc/issues"
1768
- },
1769
- license: "MIT",
1770
- author: "Ivo Todorov",
1771
- repository: {
1772
- type: "git",
1773
- url: "git+https://github.com/ivogt/vite-rsc.git",
1774
- directory: "packages/rangojs-router"
1775
- },
1776
- bin: {
1777
- rango: "./dist/bin/rango.js"
1778
- },
1779
- files: [
1780
- "src",
1781
- "!src/**/__tests__",
1782
- "!src/**/__mocks__",
1783
- "!src/**/*.test.ts",
1784
- "!src/**/*.test.tsx",
1785
- "dist",
1786
- "skills",
1787
- "CLAUDE.md",
1788
- "README.md"
1789
- ],
1790
- type: "module",
1791
- exports: {
1792
- ".": {
1793
- types: "./src/index.rsc.ts",
1794
- "react-server": "./src/index.rsc.ts",
1795
- default: "./src/index.ts"
1796
- },
1797
- "./server": {
1798
- types: "./src/server.ts",
1799
- import: "./src/server.ts"
1800
- },
1801
- "./client": {
1802
- types: "./src/client.tsx",
1803
- "react-server": "./src/client.rsc.tsx",
1804
- default: "./src/client.tsx"
1805
- },
1806
- "./browser": {
1807
- types: "./src/browser/index.ts",
1808
- default: "./src/browser/index.ts"
1798
+ "./browser": {
1799
+ types: "./src/browser/index.ts",
1800
+ default: "./src/browser/index.ts"
1809
1801
  },
1810
1802
  "./ssr": {
1811
1803
  types: "./src/ssr/index.tsx",
@@ -1914,7 +1906,7 @@ var package_default = {
1914
1906
  vitest: "^4.0.0"
1915
1907
  },
1916
1908
  peerDependencies: {
1917
- "@cloudflare/vite-plugin": "^1.25.6",
1909
+ "@cloudflare/vite-plugin": "^1.25.0",
1918
1910
  "@vitejs/plugin-rsc": "^0.5.14",
1919
1911
  react: "^18.0.0 || ^19.0.0",
1920
1912
  vite: "^7.3.0"
@@ -1936,7 +1928,7 @@ function getPublishedPackageName() {
1936
1928
  }
1937
1929
  function isInstalledFromNpm() {
1938
1930
  const packageName = getPublishedPackageName();
1939
- return existsSync(resolve2(process.cwd(), "node_modules", packageName));
1931
+ return existsSync(resolve(process.cwd(), "node_modules", packageName));
1940
1932
  }
1941
1933
  function isWorkspaceDevelopment() {
1942
1934
  return !isInstalledFromNpm();
@@ -1971,413 +1963,122 @@ function getPackageAliases() {
1971
1963
  return aliases;
1972
1964
  }
1973
1965
 
1974
- // src/vite/utils/shared-utils.ts
1975
- var versionEsbuildPlugin = {
1976
- name: "@rangojs/router-version",
1977
- setup(build) {
1978
- build.onResolve({ filter: /^rsc-router:version$/ }, (args) => ({
1979
- path: args.path,
1980
- namespace: "@rangojs/router-virtual"
1981
- }));
1982
- build.onLoad(
1983
- { filter: /.*/, namespace: "@rangojs/router-virtual" },
1984
- () => ({
1985
- contents: `export const VERSION = "dev";`,
1986
- loader: "js"
1987
- })
1988
- );
1989
- }
1990
- };
1991
- var sharedEsbuildOptions = {
1992
- plugins: [versionEsbuildPlugin]
1993
- };
1994
- function createVirtualEntriesPlugin(entries, routerPath) {
1995
- const virtualModules = {};
1996
- if (entries.client === VIRTUAL_IDS.browser) {
1997
- virtualModules[VIRTUAL_IDS.browser] = VIRTUAL_ENTRY_BROWSER;
1998
- }
1999
- if (entries.ssr === VIRTUAL_IDS.ssr) {
2000
- virtualModules[VIRTUAL_IDS.ssr] = VIRTUAL_ENTRY_SSR;
2001
- }
2002
- if (entries.rsc === VIRTUAL_IDS.rsc && routerPath) {
2003
- const absoluteRouterPath = routerPath.startsWith(".") ? "/" + routerPath.slice(2) : routerPath;
2004
- virtualModules[VIRTUAL_IDS.rsc] = getVirtualEntryRSC(absoluteRouterPath);
2005
- }
2006
- return {
2007
- name: "@rangojs/router:virtual-entries",
2008
- enforce: "pre",
2009
- resolveId(id) {
2010
- if (id in virtualModules) {
2011
- return "\0" + id;
2012
- }
2013
- if (id.startsWith("\0") && id.slice(1) in virtualModules) {
2014
- return id;
2015
- }
2016
- return null;
2017
- },
2018
- load(id) {
2019
- if (id.startsWith("\0virtual:rsc-router/")) {
2020
- const virtualId = id.slice(1);
2021
- if (virtualId in virtualModules) {
2022
- return virtualModules[virtualId];
2023
- }
2024
- }
2025
- return null;
2026
- }
2027
- };
2028
- }
2029
- function onwarn(warning, defaultHandler) {
2030
- if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.code === "SOURCEMAP_ERROR" || warning.code === "EMPTY_BUNDLE") {
2031
- return;
2032
- }
2033
- if (warning.message?.includes("Sourcemap is likely to be incorrect")) {
2034
- return;
2035
- }
2036
- if (warning.plugin === "vite:reporter" && warning.message?.includes(
2037
- "dynamic import will not move module into another chunk"
2038
- )) {
2039
- return;
1966
+ // src/build/route-types/param-extraction.ts
1967
+ function extractParamsFromPattern(pattern) {
1968
+ const params = {};
1969
+ const regex = /:([a-zA-Z_$][\w$]*)(?:\([^)]+\))?(\?)?/g;
1970
+ let match;
1971
+ while ((match = regex.exec(pattern)) !== null) {
1972
+ params[match[1]] = match[2] ? "string?" : "string";
2040
1973
  }
2041
- defaultHandler(warning);
1974
+ return Object.keys(params).length > 0 ? params : void 0;
2042
1975
  }
2043
- function getManualChunks(id) {
2044
- const normalized = Vite2.normalizePath(id);
2045
- if (normalized.includes("node_modules/react/") || normalized.includes("node_modules/react-dom/") || normalized.includes("node_modules/react-server-dom-webpack/") || normalized.includes("node_modules/@vitejs/plugin-rsc/")) {
2046
- return "react";
2047
- }
2048
- const packageName = getPublishedPackageName();
2049
- if (normalized.includes(`node_modules/${packageName}/`) || normalized.includes("packages/rsc-router/") || normalized.includes("packages/rangojs-router/")) {
2050
- return "router";
1976
+ function formatRouteEntry(key, pattern, _params, search) {
1977
+ const hasSearch = search && Object.keys(search).length > 0;
1978
+ if (!hasSearch) {
1979
+ return ` ${key}: "${pattern}",`;
2051
1980
  }
2052
- return void 0;
1981
+ const searchBody = Object.entries(search).map(([k, v]) => `${k}: "${v}"`).join(", ");
1982
+ return ` ${key}: { path: "${pattern}", search: { ${searchBody} } },`;
2053
1983
  }
2054
1984
 
2055
- // src/vite/utils/manifest-utils.ts
2056
- function flattenLeafEntries(prefixTree, routeManifest, result) {
2057
- function visit(node) {
2058
- const children = node.children || {};
2059
- if (Object.keys(children).length === 0 && node.routes && node.routes.length > 0) {
2060
- const routes = {};
2061
- for (const name of node.routes) {
2062
- if (name in routeManifest) {
2063
- routes[name] = routeManifest[name];
2064
- }
2065
- }
2066
- result.push({ staticPrefix: node.staticPrefix, routes });
2067
- } else {
2068
- for (const child of Object.values(children)) {
2069
- visit(child);
2070
- }
2071
- }
2072
- }
2073
- for (const node of Object.values(prefixTree)) {
2074
- visit(node);
2075
- }
1985
+ // src/build/route-types/ast-route-extraction.ts
1986
+ import ts2 from "typescript";
1987
+
1988
+ // src/build/route-types/ast-helpers.ts
1989
+ import ts from "typescript";
1990
+ function getStringValue(node) {
1991
+ if (ts.isStringLiteral(node)) return node.text;
1992
+ if (ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
1993
+ return null;
2076
1994
  }
2077
- function buildRouteToStaticPrefix(prefixTree, result) {
2078
- function visit(node) {
2079
- const sp = node.staticPrefix || "";
2080
- for (const name of node.routes || []) {
2081
- result[name] = sp;
2082
- }
2083
- for (const child of Object.values(node.children || {})) {
2084
- visit(child);
2085
- }
2086
- }
2087
- for (const node of Object.values(prefixTree)) {
2088
- visit(node);
1995
+ function extractObjectStringProperties(node) {
1996
+ const result = {};
1997
+ for (const prop of node.properties) {
1998
+ if (!ts.isPropertyAssignment(prop)) continue;
1999
+ const key = ts.isIdentifier(prop.name) ? prop.name.text : ts.isStringLiteral(prop.name) ? prop.name.text : null;
2000
+ if (!key) continue;
2001
+ const val = getStringValue(prop.initializer);
2002
+ if (val !== null) result[key] = val;
2089
2003
  }
2090
- }
2091
- function jsonParseExpression(value) {
2092
- const json = JSON.stringify(value);
2093
- const escaped = json.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2094
- return `JSON.parse('${escaped}')`;
2004
+ return result;
2095
2005
  }
2096
2006
 
2097
- // src/vite/utils/bundle-analysis.ts
2098
- function findMatchingParenInBundle(code, openParenPos) {
2099
- let depth = 1;
2100
- let pos = openParenPos;
2101
- while (pos < code.length && depth > 0) {
2102
- const skipped = skipStringOrComment(code, pos);
2103
- if (skipped > pos) {
2104
- pos = skipped;
2105
- continue;
2007
+ // src/build/route-types/ast-route-extraction.ts
2008
+ function extractRoutesFromSource(code) {
2009
+ const sourceFile = ts2.createSourceFile(
2010
+ "input.tsx",
2011
+ code,
2012
+ ts2.ScriptTarget.Latest,
2013
+ true,
2014
+ ts2.ScriptKind.TSX
2015
+ );
2016
+ const routes = [];
2017
+ function visit(node) {
2018
+ if (ts2.isCallExpression(node)) {
2019
+ const callee = node.expression;
2020
+ const isPath = ts2.isIdentifier(callee) && callee.text === "path" || ts2.isPropertyAccessExpression(callee) && ts2.isIdentifier(callee.expression) && callee.expression.text === "path";
2021
+ if (isPath && node.arguments.length >= 1) {
2022
+ const route = extractRouteFromCallExpression(node);
2023
+ if (route) routes.push(route);
2024
+ }
2106
2025
  }
2107
- if (code[pos] === "(") depth++;
2108
- else if (code[pos] === ")") depth--;
2109
- pos++;
2026
+ ts2.forEachChild(node, visit);
2110
2027
  }
2111
- return depth === 0 ? pos : -1;
2028
+ visit(sourceFile);
2029
+ return routes;
2112
2030
  }
2113
- function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detectPassthrough) {
2114
- const handlers = [];
2115
- for (const [, handlerNames] of handlerModules) {
2116
- for (const name of handlerNames) {
2117
- const eName = escapeRegExp(name);
2118
- const idPattern = new RegExp(
2119
- `(?<![a-zA-Z0-9_])${eName}\\.\\$\\$id\\s*=\\s*"([^"]+)"`
2120
- );
2121
- const match = chunkCode.match(idPattern);
2122
- if (!match) continue;
2123
- let isPassthrough = false;
2124
- if (detectPassthrough) {
2125
- const eFnName = escapeRegExp(fnName);
2126
- const callStartRe = new RegExp(
2127
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
2128
- );
2129
- const callStart = callStartRe.exec(chunkCode);
2130
- if (callStart) {
2131
- const afterOpen = callStart.index + callStart[0].length;
2132
- const closePos = findMatchingParenInBundle(chunkCode, afterOpen);
2133
- if (closePos !== -1) {
2134
- const callBody = chunkCode.slice(callStart.index, closePos);
2135
- isPassthrough = /passthrough\s*:\s*(!0|true)/.test(callBody);
2136
- }
2031
+ function extractRouteFromCallExpression(node) {
2032
+ const patternNode = node.arguments[0];
2033
+ const pattern = getStringValue(patternNode);
2034
+ if (pattern === null) return null;
2035
+ let name = null;
2036
+ let search;
2037
+ for (let i = 1; i < node.arguments.length; i++) {
2038
+ const arg = node.arguments[i];
2039
+ if (ts2.isObjectLiteralExpression(arg)) {
2040
+ for (const prop of arg.properties) {
2041
+ if (!ts2.isPropertyAssignment(prop)) continue;
2042
+ const propName = ts2.isIdentifier(prop.name) ? prop.name.text : null;
2043
+ if (propName === "name") {
2044
+ name = getStringValue(prop.initializer);
2045
+ } else if (propName === "search" && ts2.isObjectLiteralExpression(prop.initializer)) {
2046
+ search = extractObjectStringProperties(prop.initializer);
2137
2047
  }
2138
2048
  }
2139
- handlers.push({ name, handlerId: match[1], passthrough: isPassthrough });
2140
2049
  }
2141
2050
  }
2142
- return handlers;
2143
- }
2144
- function evictHandlerCode(code, exports, fnName, brand) {
2145
- const originalSize = Buffer.byteLength(code);
2146
- let modified = code;
2147
- const eFnName = escapeRegExp(fnName);
2148
- for (const { name, handlerId, passthrough } of exports) {
2149
- if (passthrough) continue;
2150
- const eName = escapeRegExp(name);
2151
- const callStartRe = new RegExp(
2152
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
2153
- );
2154
- const startMatch = callStartRe.exec(modified);
2155
- if (!startMatch) continue;
2156
- const afterOpen = startMatch.index + startMatch[0].length;
2157
- const closePos = findMatchingParenInBundle(modified, afterOpen);
2158
- if (closePos === -1) continue;
2159
- let rangeEnd = closePos;
2160
- while (rangeEnd < modified.length && /\s/.test(modified[rangeEnd]))
2161
- rangeEnd++;
2162
- if (modified[rangeEnd] === ";") rangeEnd++;
2163
- const matched = modified.slice(startMatch.index, rangeEnd);
2164
- if (!matched.includes(handlerId)) continue;
2165
- const stub = `const ${name} = { __brand: "${brand}", $$id: "${handlerId}" };`;
2166
- modified = modified.slice(0, startMatch.index) + stub + modified.slice(rangeEnd);
2167
- modified = modified.replace(
2168
- new RegExp(`\\n${eName}\\.\\$\\$id\\s*=\\s*"[^"]+";`),
2169
- ""
2170
- );
2171
- }
2172
- if (modified === code) return null;
2051
+ if (!name) return null;
2052
+ const params = extractParamsFromPattern(pattern);
2173
2053
  return {
2174
- code: modified,
2175
- savedBytes: originalSize - Buffer.byteLength(modified)
2054
+ name,
2055
+ pattern,
2056
+ ...params ? { params } : {},
2057
+ ...search && Object.keys(search).length > 0 ? { search } : {}
2176
2058
  };
2177
2059
  }
2178
2060
 
2179
- // src/vite/utils/prerender-utils.ts
2180
- function escapeRegExp2(str) {
2181
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2182
- }
2183
- function encodePathParam(value) {
2184
- return String(value).split("/").map((segment) => encodeURIComponent(segment)).join("/");
2185
- }
2186
- async function runWithConcurrency(items, concurrency, fn) {
2187
- const limit = Math.max(1, Math.min(concurrency, items.length));
2188
- if (limit <= 1) {
2189
- for (const item of items) await fn(item);
2190
- return;
2191
- }
2192
- let nextIndex = 0;
2193
- async function worker() {
2194
- while (nextIndex < items.length) {
2195
- const idx = nextIndex++;
2196
- await fn(items[idx]);
2197
- }
2198
- }
2199
- await Promise.all(Array.from({ length: limit }, () => worker()));
2200
- }
2201
- function groupByConcurrency(entries) {
2202
- const map = /* @__PURE__ */ new Map();
2203
- for (const entry of entries) {
2204
- const key = entry.concurrency;
2205
- let group = map.get(key);
2206
- if (!group) {
2207
- group = [];
2208
- map.set(key, group);
2209
- }
2210
- group.push(entry);
2211
- }
2212
- return Array.from(map.entries(), ([concurrency, items]) => ({
2213
- concurrency,
2214
- entries: items
2215
- }));
2216
- }
2217
- function notifyOnError(registry, error, phase, routeKey, pathname, skipped) {
2218
- for (const [, routerInstance] of registry) {
2219
- const onError = routerInstance.onError;
2220
- if (!onError) continue;
2221
- const errorObj = error instanceof Error ? error : new Error(String(error));
2222
- const syntheticUrl = new URL("http://prerender" + (pathname || "/"));
2223
- const context = {
2224
- error: errorObj,
2225
- phase,
2226
- request: new Request(syntheticUrl),
2227
- url: syntheticUrl,
2228
- pathname: syntheticUrl.pathname,
2229
- method: "GET",
2230
- routeKey,
2231
- metadata: skipped ? { skipped: true } : void 0
2232
- };
2233
- try {
2234
- const result = onError(context);
2235
- if (result instanceof Promise) {
2236
- result.catch((cbErr) => {
2237
- console.error(`[Build.onError] Callback error:`, cbErr);
2238
- });
2239
- }
2240
- } catch (cbErr) {
2241
- console.error(`[Build.onError] Callback error:`, cbErr);
2242
- }
2243
- break;
2244
- }
2245
- }
2246
-
2247
- // src/vite/utils/banner.ts
2248
- var rangoVersion = package_default.version;
2249
- var _bannerPrinted = false;
2250
- function printBanner(mode, preset, version) {
2251
- if (_bannerPrinted) return;
2252
- _bannerPrinted = true;
2253
- const dim = "\x1B[2m";
2254
- const bold = "\x1B[1m";
2255
- const reset = "\x1B[0m";
2256
- const banner = `
2257
- ${dim} \u2726 \u2726 \u2727. . .${reset}
2258
- ${dim} \u2571${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2571 \u2726 *${reset}
2259
- ${dim} ${reset}${bold}\u2551 \u2551${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2727. \u2571${reset}
2260
- ${dim} ${reset}${bold}\u2554\u2557 \u2551 \u2551 \u2551 \u2551${reset}${dim} * \u2571${reset}
2261
- ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2551 \u2551 \u2551 \u2566\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557${reset}${dim} \u2727 \u2726${reset}
2262
- ${dim} ${reset}${bold}\u2550\u2563\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
2263
- ${dim} ${reset}${bold}\u2551\u255A\u2550\u255D \u2554\u2550\u2550\u2550\u255D \u2569\u255A\u2550\u2569 \u2569\u255D\u255A\u255D\u255A\u2550\u255D\u255A\u2550\u255D${reset}${dim} \u2726 . *${reset}
2264
- ${dim} ${reset}${bold}\u255A\u2550\u2550\u2557 \u2551${reset}${dim} * RSC Wrangler \u2727 \u2726${reset}
2265
- ${dim} * ${reset}${bold}\u2551 \u2560\u2550${reset}${dim} * \u2727. \u2571${reset}
2266
- ${bold}\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
2267
-
2268
- v${version} \xB7 ${preset} \xB7 ${mode}
2269
- `;
2270
- console.log(banner);
2271
- }
2272
-
2273
- // src/vite/router-discovery.ts
2274
- import { createServer as createViteServer } from "vite";
2275
- import { readFileSync as readFileSync6 } from "node:fs";
2276
-
2277
- // src/build/route-types/param-extraction.ts
2278
- function extractParamsFromPattern(pattern) {
2279
- const params = {};
2280
- const regex = /:([a-zA-Z_$][\w$]*)(?:\([^)]+\))?(\?)?/g;
2281
- let match;
2282
- while ((match = regex.exec(pattern)) !== null) {
2283
- params[match[1]] = match[2] ? "string?" : "string";
2284
- }
2285
- return Object.keys(params).length > 0 ? params : void 0;
2286
- }
2287
- function formatRouteEntry(key, pattern, _params, search) {
2288
- const hasSearch = search && Object.keys(search).length > 0;
2289
- if (!hasSearch) {
2290
- return ` ${key}: "${pattern}",`;
2291
- }
2292
- const searchBody = Object.entries(search).map(([k, v]) => `${k}: "${v}"`).join(", ");
2293
- return ` ${key}: { path: "${pattern}", search: { ${searchBody} } },`;
2294
- }
2295
-
2296
- // src/build/route-types/ast-route-extraction.ts
2297
- import ts2 from "typescript";
2298
-
2299
- // src/build/route-types/ast-helpers.ts
2300
- import ts from "typescript";
2301
- function getStringValue(node) {
2302
- if (ts.isStringLiteral(node)) return node.text;
2303
- if (ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
2304
- return null;
2305
- }
2306
- function extractObjectStringProperties(node) {
2307
- const result = {};
2308
- for (const prop of node.properties) {
2309
- if (!ts.isPropertyAssignment(prop)) continue;
2310
- const key = ts.isIdentifier(prop.name) ? prop.name.text : ts.isStringLiteral(prop.name) ? prop.name.text : null;
2311
- if (!key) continue;
2312
- const val = getStringValue(prop.initializer);
2313
- if (val !== null) result[key] = val;
2314
- }
2315
- return result;
2316
- }
2317
-
2318
- // src/build/route-types/ast-route-extraction.ts
2319
- function extractRoutesFromSource(code) {
2320
- const sourceFile = ts2.createSourceFile(
2321
- "input.tsx",
2322
- code,
2323
- ts2.ScriptTarget.Latest,
2324
- true,
2325
- ts2.ScriptKind.TSX
2326
- );
2327
- const routes = [];
2328
- function visit(node) {
2329
- if (ts2.isCallExpression(node)) {
2330
- const callee = node.expression;
2331
- const isPath = ts2.isIdentifier(callee) && callee.text === "path" || ts2.isPropertyAccessExpression(callee) && ts2.isIdentifier(callee.expression) && callee.expression.text === "path";
2332
- if (isPath && node.arguments.length >= 1) {
2333
- const route = extractRouteFromCallExpression(node);
2334
- if (route) routes.push(route);
2335
- }
2336
- }
2337
- ts2.forEachChild(node, visit);
2338
- }
2339
- visit(sourceFile);
2340
- return routes;
2341
- }
2342
- function extractRouteFromCallExpression(node) {
2343
- const patternNode = node.arguments[0];
2344
- const pattern = getStringValue(patternNode);
2345
- if (pattern === null) return null;
2346
- let name = null;
2347
- let search;
2348
- for (let i = 1; i < node.arguments.length; i++) {
2349
- const arg = node.arguments[i];
2350
- if (ts2.isObjectLiteralExpression(arg)) {
2351
- for (const prop of arg.properties) {
2352
- if (!ts2.isPropertyAssignment(prop)) continue;
2353
- const propName = ts2.isIdentifier(prop.name) ? prop.name.text : null;
2354
- if (propName === "name") {
2355
- name = getStringValue(prop.initializer);
2356
- } else if (propName === "search" && ts2.isObjectLiteralExpression(prop.initializer)) {
2357
- search = extractObjectStringProperties(prop.initializer);
2358
- }
2359
- }
2360
- }
2361
- }
2362
- if (!name) return null;
2363
- const params = extractParamsFromPattern(pattern);
2364
- return {
2365
- name,
2366
- pattern,
2367
- ...params ? { params } : {},
2368
- ...search && Object.keys(search).length > 0 ? { search } : {}
2369
- };
2061
+ // src/route-name.ts
2062
+ var AUTO_GENERATED_ROUTE_PREFIX = "$path_";
2063
+ var INTERNAL_INCLUDE_SCOPE_PREFIX = "$prefix_";
2064
+ function isAutoGeneratedRouteName(name) {
2065
+ return name.split(".").some((segment) => {
2066
+ return segment.startsWith(AUTO_GENERATED_ROUTE_PREFIX) || segment.startsWith(INTERNAL_INCLUDE_SCOPE_PREFIX);
2067
+ });
2370
2068
  }
2371
2069
 
2372
2070
  // src/build/route-types/codegen.ts
2373
2071
  function generateRouteTypesSource(routeManifest, searchSchemas) {
2374
- const entries = Object.entries(routeManifest).sort(
2375
- ([a], [b]) => a.localeCompare(b)
2376
- );
2072
+ const entries = Object.entries(routeManifest).filter(([name]) => !isAutoGeneratedRouteName(name)).sort(([a], [b]) => a.localeCompare(b));
2073
+ const filteredSearchSchemas = searchSchemas ? Object.fromEntries(
2074
+ Object.entries(searchSchemas).filter(
2075
+ ([name]) => !isAutoGeneratedRouteName(name)
2076
+ )
2077
+ ) : void 0;
2377
2078
  const objectBody = entries.map(([name, pattern]) => {
2378
2079
  const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
2379
2080
  const params = extractParamsFromPattern(pattern);
2380
- const search = searchSchemas?.[name];
2081
+ const search = filteredSearchSchemas?.[name];
2381
2082
  return formatRouteEntry(key, pattern, params, search);
2382
2083
  }).join("\n");
2383
2084
  return `// Auto-generated by @rangojs/router - do not edit
@@ -2394,8 +2095,7 @@ declare global {
2394
2095
  }
2395
2096
 
2396
2097
  // src/build/route-types/scan-filter.ts
2397
- import { join, relative as relative2 } from "node:path";
2398
- import { readdirSync } from "node:fs";
2098
+ import { join, relative } from "node:path";
2399
2099
  import picomatch from "picomatch";
2400
2100
  var DEFAULT_EXCLUDE_PATTERNS = [
2401
2101
  "**/__tests__/**",
@@ -2414,42 +2114,19 @@ function createScanFilter(root, opts) {
2414
2114
  const includeMatcher = hasInclude ? picomatch(include) : null;
2415
2115
  const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
2416
2116
  return (absolutePath) => {
2417
- const rel = relative2(root, absolutePath);
2117
+ const rel = relative(root, absolutePath);
2418
2118
  if (excludeMatcher && excludeMatcher(rel)) return false;
2419
2119
  if (includeMatcher) return includeMatcher(rel);
2420
2120
  return true;
2421
2121
  };
2422
2122
  }
2423
- function findTsFiles(dir, filter) {
2424
- const results = [];
2425
- let entries;
2426
- try {
2427
- entries = readdirSync(dir, { withFileTypes: true });
2428
- } catch (err) {
2429
- console.warn(
2430
- `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
2431
- );
2432
- return results;
2433
- }
2434
- for (const entry of entries) {
2435
- const fullPath = join(dir, entry.name);
2436
- if (entry.isDirectory()) {
2437
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2438
- results.push(...findTsFiles(fullPath, filter));
2439
- } else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx")) && !entry.name.includes(".gen.")) {
2440
- if (filter && !filter(fullPath)) continue;
2441
- results.push(fullPath);
2442
- }
2443
- }
2444
- return results;
2445
- }
2446
2123
 
2447
2124
  // src/build/route-types/per-module-writer.ts
2448
2125
  import ts4 from "typescript";
2449
2126
 
2450
2127
  // src/build/route-types/include-resolution.ts
2451
2128
  import { readFileSync, existsSync as existsSync2 } from "node:fs";
2452
- import { dirname, resolve as resolve3 } from "node:path";
2129
+ import { dirname, resolve as resolve2 } from "node:path";
2453
2130
  import ts3 from "typescript";
2454
2131
  function extractNamePrefixFromInclude(node) {
2455
2132
  if (node.arguments.length >= 3) {
@@ -2544,14 +2221,14 @@ function resolveImportPath(importSpec, fromFile) {
2544
2221
  else if (base.endsWith(".mjs")) base = base.slice(0, -4);
2545
2222
  else if (base.endsWith(".jsx")) base = base.slice(0, -4);
2546
2223
  const candidates = [
2547
- resolve3(dir, base + ".ts"),
2548
- resolve3(dir, base + ".tsx"),
2549
- resolve3(dir, base + ".js"),
2550
- resolve3(dir, base + ".jsx"),
2551
- resolve3(dir, base + "/index.ts"),
2552
- resolve3(dir, base + "/index.tsx"),
2553
- resolve3(dir, base + "/index.js"),
2554
- resolve3(dir, base + "/index.jsx")
2224
+ resolve2(dir, base + ".ts"),
2225
+ resolve2(dir, base + ".tsx"),
2226
+ resolve2(dir, base + ".js"),
2227
+ resolve2(dir, base + ".jsx"),
2228
+ resolve2(dir, base + "/index.ts"),
2229
+ resolve2(dir, base + "/index.tsx"),
2230
+ resolve2(dir, base + "/index.js"),
2231
+ resolve2(dir, base + "/index.jsx")
2555
2232
  ];
2556
2233
  for (const candidate of candidates) {
2557
2234
  if (existsSync2(candidate)) return candidate;
@@ -2643,6 +2320,9 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2643
2320
  diagnosticsOut
2644
2321
  );
2645
2322
  }
2323
+ if (namePrefix === null) {
2324
+ continue;
2325
+ }
2646
2326
  for (const [name, pattern] of Object.entries(childResult.routes)) {
2647
2327
  const prefixedName = namePrefix ? `${namePrefix}.${name}` : name;
2648
2328
  let prefixedPattern;
@@ -2663,7 +2343,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2663
2343
  }
2664
2344
  function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
2665
2345
  visited = visited ?? /* @__PURE__ */ new Set();
2666
- const realPath = resolve3(filePath);
2346
+ const realPath = resolve2(filePath);
2667
2347
  const key = variableName ? `${realPath}:${variableName}` : realPath;
2668
2348
  if (visited.has(key)) {
2669
2349
  console.warn(`[rsc-router] Circular include detected, skipping: ${key}`);
@@ -2693,13 +2373,108 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2693
2373
  searchSchemas,
2694
2374
  diagnosticsOut
2695
2375
  );
2376
+ visited.delete(key);
2696
2377
  return { routes, searchSchemas };
2697
2378
  }
2698
2379
 
2699
2380
  // src/build/route-types/router-processing.ts
2700
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, unlinkSync } from "node:fs";
2701
- import { join as join2, dirname as dirname2, resolve as resolve4, basename as pathBasename } from "node:path";
2381
+ import {
2382
+ readFileSync as readFileSync2,
2383
+ writeFileSync,
2384
+ existsSync as existsSync3,
2385
+ unlinkSync,
2386
+ readdirSync
2387
+ } from "node:fs";
2388
+ import {
2389
+ join as join2,
2390
+ dirname as dirname2,
2391
+ resolve as resolve3,
2392
+ sep,
2393
+ basename as pathBasename
2394
+ } from "node:path";
2702
2395
  import ts5 from "typescript";
2396
+ function countPublicRouteEntries(source) {
2397
+ const matches = source.matchAll(/^\s+(?:"([^"]+)"|([a-zA-Z_$][^:]*)):\s*["{]/gm) ?? [];
2398
+ let count = 0;
2399
+ for (const match of matches) {
2400
+ const routeName = match[1] || match[2];
2401
+ if (routeName && !isAutoGeneratedRouteName(routeName.trim())) {
2402
+ count++;
2403
+ }
2404
+ }
2405
+ return count;
2406
+ }
2407
+ var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
2408
+ function isRoutableSourceFile(name) {
2409
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.");
2410
+ }
2411
+ function findRouterFilesRecursive(dir, filter, results) {
2412
+ let entries;
2413
+ try {
2414
+ entries = readdirSync(dir, { withFileTypes: true });
2415
+ } catch (err) {
2416
+ console.warn(
2417
+ `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
2418
+ );
2419
+ return;
2420
+ }
2421
+ const childDirs = [];
2422
+ const routerFilesInDir = [];
2423
+ for (const entry of entries) {
2424
+ const fullPath = join2(dir, entry.name);
2425
+ if (entry.isDirectory()) {
2426
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2427
+ childDirs.push(fullPath);
2428
+ continue;
2429
+ }
2430
+ if (!isRoutableSourceFile(entry.name)) continue;
2431
+ if (filter && !filter(fullPath)) continue;
2432
+ try {
2433
+ const source = readFileSync2(fullPath, "utf-8");
2434
+ if (ROUTER_CALL_PATTERN.test(source)) {
2435
+ routerFilesInDir.push(fullPath);
2436
+ }
2437
+ } catch {
2438
+ continue;
2439
+ }
2440
+ }
2441
+ if (routerFilesInDir.length > 0) {
2442
+ results.push(...routerFilesInDir);
2443
+ return;
2444
+ }
2445
+ for (const childDir of childDirs) {
2446
+ findRouterFilesRecursive(childDir, filter, results);
2447
+ }
2448
+ }
2449
+ function findNestedRouterConflict(routerFiles) {
2450
+ const routerDirs = [
2451
+ ...new Set(routerFiles.map((filePath) => dirname2(resolve3(filePath))))
2452
+ ].sort((a, b) => a.length - b.length);
2453
+ for (let i = 0; i < routerDirs.length; i++) {
2454
+ const ancestorDir = routerDirs[i];
2455
+ const prefix = ancestorDir.endsWith(sep) ? ancestorDir : `${ancestorDir}${sep}`;
2456
+ for (let j = i + 1; j < routerDirs.length; j++) {
2457
+ const nestedDir = routerDirs[j];
2458
+ if (!nestedDir.startsWith(prefix)) continue;
2459
+ const ancestorFile = routerFiles.find(
2460
+ (filePath) => dirname2(resolve3(filePath)) === ancestorDir
2461
+ );
2462
+ const nestedFile = routerFiles.find(
2463
+ (filePath) => dirname2(resolve3(filePath)) === nestedDir
2464
+ );
2465
+ if (ancestorFile && nestedFile) {
2466
+ return { ancestor: ancestorFile, nested: nestedFile };
2467
+ }
2468
+ }
2469
+ }
2470
+ return null;
2471
+ }
2472
+ function formatNestedRouterConflictError(conflict, prefix = "[rsc-router]") {
2473
+ return `${prefix} Nested router roots are not supported.
2474
+ Router root: ${conflict.ancestor}
2475
+ Nested router: ${conflict.nested}
2476
+ Move the nested router into a sibling directory or configure it as a separate app root.`;
2477
+ }
2703
2478
  function extractUrlsVariableFromRouter(code) {
2704
2479
  const sourceFile = ts5.createSourceFile(
2705
2480
  "router.tsx",
@@ -2761,95 +2536,657 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2761
2536
  if (!targetFile) {
2762
2537
  return { routes: {}, searchSchemas: {} };
2763
2538
  }
2764
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2765
- }
2766
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2539
+ return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2540
+ }
2541
+ return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2542
+ }
2543
+ function findRouterFiles(root, filter) {
2544
+ const result = [];
2545
+ findRouterFilesRecursive(root, filter, result);
2546
+ return result;
2547
+ }
2548
+ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2549
+ try {
2550
+ const oldCombinedPath = join2(root, "src", "named-routes.gen.ts");
2551
+ if (existsSync3(oldCombinedPath)) {
2552
+ unlinkSync(oldCombinedPath);
2553
+ console.log(
2554
+ `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
2555
+ );
2556
+ }
2557
+ } catch {
2558
+ }
2559
+ const routerFilePaths = knownRouterFiles ?? findRouterFiles(root);
2560
+ if (routerFilePaths.length === 0) return;
2561
+ const nestedRouterConflict = findNestedRouterConflict(routerFilePaths);
2562
+ if (nestedRouterConflict) {
2563
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
2564
+ }
2565
+ for (const routerFilePath of routerFilePaths) {
2566
+ let routerSource;
2567
+ try {
2568
+ routerSource = readFileSync2(routerFilePath, "utf-8");
2569
+ } catch {
2570
+ continue;
2571
+ }
2572
+ const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2573
+ if (!urlsVarName) continue;
2574
+ let result;
2575
+ const imported = resolveImportedVariable(routerSource, urlsVarName);
2576
+ if (imported) {
2577
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2578
+ if (!targetFile) continue;
2579
+ result = buildCombinedRouteMapWithSearch(
2580
+ targetFile,
2581
+ imported.exportedName
2582
+ );
2583
+ } else {
2584
+ result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2585
+ }
2586
+ const routerBasename = pathBasename(routerFilePath).replace(
2587
+ /\.(tsx?|jsx?)$/,
2588
+ ""
2589
+ );
2590
+ const outPath = join2(
2591
+ dirname2(routerFilePath),
2592
+ `${routerBasename}.named-routes.gen.ts`
2593
+ );
2594
+ const existing = existsSync3(outPath) ? readFileSync2(outPath, "utf-8") : null;
2595
+ if (Object.keys(result.routes).length === 0) {
2596
+ if (!existing) {
2597
+ const emptySource = generateRouteTypesSource({});
2598
+ writeFileSync(outPath, emptySource);
2599
+ }
2600
+ continue;
2601
+ }
2602
+ const hasSearchSchemas = Object.keys(result.searchSchemas).length > 0;
2603
+ const source = generateRouteTypesSource(
2604
+ result.routes,
2605
+ hasSearchSchemas ? result.searchSchemas : void 0
2606
+ );
2607
+ if (existing !== source) {
2608
+ if (opts?.preserveIfLarger && existing) {
2609
+ const existingCount = countPublicRouteEntries(existing);
2610
+ const newCount = Object.keys(result.routes).filter(
2611
+ (name) => !isAutoGeneratedRouteName(name)
2612
+ ).length;
2613
+ if (existingCount > newCount) {
2614
+ continue;
2615
+ }
2616
+ }
2617
+ writeFileSync(outPath, source);
2618
+ console.log(
2619
+ `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
2620
+ );
2621
+ }
2622
+ }
2623
+ }
2624
+
2625
+ // src/vite/plugins/version-plugin.ts
2626
+ import { parseAst as parseAst3 } from "vite";
2627
+ function isCodeModule(id) {
2628
+ return /\.(tsx?|jsx?)($|\?)/.test(id);
2629
+ }
2630
+ function normalizeModuleId(id) {
2631
+ return id.split("?", 1)[0];
2632
+ }
2633
+ function getClientModuleSignature(source) {
2634
+ let program;
2635
+ try {
2636
+ program = parseAst3(source, { jsx: true });
2637
+ } catch {
2638
+ return void 0;
2639
+ }
2640
+ let isUseClient = false;
2641
+ for (const node of program.body ?? []) {
2642
+ if (node?.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
2643
+ if (node.expression.value === "use client") {
2644
+ isUseClient = true;
2645
+ }
2646
+ continue;
2647
+ }
2648
+ break;
2649
+ }
2650
+ if (!isUseClient) return void 0;
2651
+ const exports = /* @__PURE__ */ new Set();
2652
+ let hasDefault = false;
2653
+ let hasExportAll = false;
2654
+ const collectBindingNames = (pattern) => {
2655
+ if (!pattern) return;
2656
+ if (pattern.type === "Identifier") {
2657
+ exports.add(pattern.name);
2658
+ } else if (pattern.type === "ObjectPattern") {
2659
+ for (const prop of pattern.properties ?? []) {
2660
+ if (prop?.type === "RestElement") {
2661
+ collectBindingNames(prop.argument);
2662
+ } else {
2663
+ collectBindingNames(prop?.value);
2664
+ }
2665
+ }
2666
+ } else if (pattern.type === "ArrayPattern") {
2667
+ for (const el of pattern.elements ?? []) {
2668
+ if (el?.type === "RestElement") {
2669
+ collectBindingNames(el.argument);
2670
+ } else {
2671
+ collectBindingNames(el);
2672
+ }
2673
+ }
2674
+ }
2675
+ };
2676
+ const collectDeclarationNames = (declaration) => {
2677
+ if (!declaration) return;
2678
+ if (declaration.type === "VariableDeclaration") {
2679
+ for (const decl of declaration.declarations ?? []) {
2680
+ collectBindingNames(decl?.id);
2681
+ }
2682
+ return;
2683
+ }
2684
+ collectBindingNames(declaration.id);
2685
+ };
2686
+ for (const node of program.body ?? []) {
2687
+ if (node?.type === "ExportDefaultDeclaration") {
2688
+ hasDefault = true;
2689
+ continue;
2690
+ }
2691
+ if (node?.type === "ExportAllDeclaration") {
2692
+ hasExportAll = true;
2693
+ continue;
2694
+ }
2695
+ if (node?.type !== "ExportNamedDeclaration") continue;
2696
+ collectDeclarationNames(node.declaration);
2697
+ for (const specifier of node.specifiers ?? []) {
2698
+ const exportedName = specifier?.exported?.name ?? specifier?.exported?.value;
2699
+ if (exportedName === "default") {
2700
+ hasDefault = true;
2701
+ } else if (typeof exportedName === "string") {
2702
+ exports.add(exportedName);
2703
+ }
2704
+ }
2705
+ }
2706
+ return {
2707
+ key: JSON.stringify({
2708
+ default: hasDefault,
2709
+ exportAll: hasExportAll,
2710
+ exports: [...exports].sort()
2711
+ })
2712
+ };
2713
+ }
2714
+ function createVersionPlugin() {
2715
+ const buildVersion = Date.now().toString(16);
2716
+ let currentVersion = buildVersion;
2717
+ let isDev = false;
2718
+ let server = null;
2719
+ const clientModuleSignatures = /* @__PURE__ */ new Map();
2720
+ const bumpVersion = (reason) => {
2721
+ currentVersion = Date.now().toString(16);
2722
+ console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
2723
+ const rscEnv = server?.environments?.rsc;
2724
+ const versionMod = rscEnv?.moduleGraph?.getModuleById(
2725
+ "\0" + VIRTUAL_IDS.version
2726
+ );
2727
+ if (versionMod) {
2728
+ rscEnv.moduleGraph.invalidateModule(versionMod);
2729
+ }
2730
+ };
2731
+ return {
2732
+ name: "@rangojs/router:version",
2733
+ enforce: "pre",
2734
+ configResolved(config) {
2735
+ isDev = config.command === "serve";
2736
+ },
2737
+ configureServer(devServer) {
2738
+ server = devServer;
2739
+ devServer.watcher.on("unlink", (filePath) => {
2740
+ if (!isDev) return;
2741
+ if (!clientModuleSignatures.has(filePath)) return;
2742
+ clientModuleSignatures.delete(filePath);
2743
+ bumpVersion("Client module removed");
2744
+ });
2745
+ },
2746
+ resolveId(id) {
2747
+ if (id === VIRTUAL_IDS.version) {
2748
+ return "\0" + id;
2749
+ }
2750
+ return null;
2751
+ },
2752
+ load(id) {
2753
+ if (id === "\0" + VIRTUAL_IDS.version) {
2754
+ return getVirtualVersionContent(currentVersion);
2755
+ }
2756
+ return null;
2757
+ },
2758
+ transform(code, id) {
2759
+ if (!isDev || !isCodeModule(id)) return null;
2760
+ const normalizedId = normalizeModuleId(id);
2761
+ if (!code.includes("use client") && !clientModuleSignatures.has(normalizedId)) {
2762
+ return null;
2763
+ }
2764
+ const signature = getClientModuleSignature(code);
2765
+ if (signature) {
2766
+ clientModuleSignatures.set(normalizedId, signature);
2767
+ } else {
2768
+ clientModuleSignatures.delete(normalizedId);
2769
+ }
2770
+ return null;
2771
+ },
2772
+ // Track RSC module changes and update version
2773
+ async hotUpdate(ctx) {
2774
+ if (!isDev) return;
2775
+ const isRscModule = this.environment?.name === "rsc";
2776
+ if (!isRscModule) return;
2777
+ if (isCodeModule(ctx.file)) {
2778
+ const filePath = normalizeModuleId(ctx.file);
2779
+ const previousSignature = clientModuleSignatures.get(filePath);
2780
+ try {
2781
+ const source = await ctx.read();
2782
+ const nextSignature = getClientModuleSignature(source);
2783
+ if (nextSignature) {
2784
+ clientModuleSignatures.set(filePath, nextSignature);
2785
+ if (previousSignature && previousSignature.key === nextSignature.key) {
2786
+ return;
2787
+ }
2788
+ } else {
2789
+ clientModuleSignatures.delete(filePath);
2790
+ if (!previousSignature) {
2791
+ if (ctx.modules.length === 0) return;
2792
+ }
2793
+ }
2794
+ } catch {
2795
+ }
2796
+ } else {
2797
+ if (ctx.modules.length === 0) return;
2798
+ }
2799
+ bumpVersion("RSC module changed");
2800
+ }
2801
+ };
2802
+ }
2803
+
2804
+ // src/vite/utils/shared-utils.ts
2805
+ import * as Vite from "vite";
2806
+ var versionEsbuildPlugin = {
2807
+ name: "@rangojs/router-version",
2808
+ setup(build) {
2809
+ build.onResolve({ filter: /^rsc-router:version$/ }, (args) => ({
2810
+ path: args.path,
2811
+ namespace: "@rangojs/router-virtual"
2812
+ }));
2813
+ build.onLoad(
2814
+ { filter: /.*/, namespace: "@rangojs/router-virtual" },
2815
+ () => ({
2816
+ contents: `export const VERSION = "dev";`,
2817
+ loader: "js"
2818
+ })
2819
+ );
2820
+ }
2821
+ };
2822
+ var sharedEsbuildOptions = {
2823
+ plugins: [versionEsbuildPlugin]
2824
+ };
2825
+ function createVirtualEntriesPlugin(entries, routerPathRef) {
2826
+ const virtualModules = {};
2827
+ if (entries.client === VIRTUAL_IDS.browser) {
2828
+ virtualModules[VIRTUAL_IDS.browser] = VIRTUAL_ENTRY_BROWSER;
2829
+ }
2830
+ if (entries.ssr === VIRTUAL_IDS.ssr) {
2831
+ virtualModules[VIRTUAL_IDS.ssr] = VIRTUAL_ENTRY_SSR;
2832
+ }
2833
+ const knownIds = new Set(Object.keys(virtualModules));
2834
+ if (entries.rsc === VIRTUAL_IDS.rsc) {
2835
+ knownIds.add(VIRTUAL_IDS.rsc);
2836
+ }
2837
+ return {
2838
+ name: "@rangojs/router:virtual-entries",
2839
+ enforce: "pre",
2840
+ resolveId(id) {
2841
+ if (knownIds.has(id)) {
2842
+ return "\0" + id;
2843
+ }
2844
+ if (id.startsWith("\0") && knownIds.has(id.slice(1))) {
2845
+ return id;
2846
+ }
2847
+ return null;
2848
+ },
2849
+ load(id) {
2850
+ if (id.startsWith("\0virtual:rsc-router/")) {
2851
+ const virtualId = id.slice(1);
2852
+ if (virtualId in virtualModules) {
2853
+ return virtualModules[virtualId];
2854
+ }
2855
+ if (virtualId === VIRTUAL_IDS.rsc && routerPathRef?.path) {
2856
+ const raw = routerPathRef.path.startsWith(".") ? "/" + routerPathRef.path.slice(2) : routerPathRef.path;
2857
+ const absoluteRouterPath = raw.replaceAll("\\", "/");
2858
+ return getVirtualEntryRSC(absoluteRouterPath);
2859
+ }
2860
+ }
2861
+ return null;
2862
+ }
2863
+ };
2864
+ }
2865
+ function onwarn(warning, defaultHandler) {
2866
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.code === "SOURCEMAP_ERROR" || warning.code === "EMPTY_BUNDLE") {
2867
+ return;
2868
+ }
2869
+ if (warning.message?.includes("Sourcemap is likely to be incorrect")) {
2870
+ return;
2871
+ }
2872
+ if (warning.plugin === "vite:reporter" && warning.message?.includes(
2873
+ "dynamic import will not move module into another chunk"
2874
+ )) {
2875
+ return;
2876
+ }
2877
+ defaultHandler(warning);
2878
+ }
2879
+ function getManualChunks(id) {
2880
+ const normalized = Vite.normalizePath(id);
2881
+ if (normalized.includes("node_modules/react/") || normalized.includes("node_modules/react-dom/") || normalized.includes("node_modules/react-server-dom-webpack/") || normalized.includes("node_modules/@vitejs/plugin-rsc/")) {
2882
+ return "react";
2883
+ }
2884
+ const packageName = getPublishedPackageName();
2885
+ if (normalized.includes(`node_modules/${packageName}/`) || normalized.includes("packages/rsc-router/") || normalized.includes("packages/rangojs-router/")) {
2886
+ return "router";
2887
+ }
2888
+ return void 0;
2889
+ }
2890
+
2891
+ // src/vite/utils/banner.ts
2892
+ var rangoVersion = package_default.version;
2893
+ var _bannerPrinted = false;
2894
+ function printBanner(mode, preset, version) {
2895
+ if (_bannerPrinted) return;
2896
+ _bannerPrinted = true;
2897
+ const dim = "\x1B[2m";
2898
+ const bold = "\x1B[1m";
2899
+ const reset = "\x1B[0m";
2900
+ const banner = `
2901
+ ${dim} \u2726 \u2726 \u2727. . .${reset}
2902
+ ${dim} \u2571${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2571 \u2726 *${reset}
2903
+ ${dim} ${reset}${bold}\u2551 \u2551${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2727. \u2571${reset}
2904
+ ${dim} ${reset}${bold}\u2554\u2557 \u2551 \u2551 \u2551 \u2551${reset}${dim} * \u2571${reset}
2905
+ ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2551 \u2551 \u2551 \u2566\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557${reset}${dim} \u2727 \u2726${reset}
2906
+ ${dim} ${reset}${bold}\u2550\u2563\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
2907
+ ${dim} ${reset}${bold}\u2551\u255A\u2550\u255D \u2554\u2550\u2550\u2550\u255D \u2569\u255A\u2550\u2569 \u2569\u255D\u255A\u255D\u255A\u2550\u255D\u255A\u2550\u255D${reset}${dim} \u2726 . *${reset}
2908
+ ${dim} ${reset}${bold}\u255A\u2550\u2550\u2557 \u2551${reset}${dim} * RSC Wrangler \u2727 \u2726${reset}
2909
+ ${dim} * ${reset}${bold}\u2551 \u2560\u2550${reset}${dim} * \u2727. \u2571${reset}
2910
+ ${bold}\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
2911
+
2912
+ v${version} \xB7 ${preset} \xB7 ${mode}
2913
+ `;
2914
+ console.log(banner);
2915
+ }
2916
+
2917
+ // src/vite/plugins/version-injector.ts
2918
+ import { resolve as resolve4 } from "node:path";
2919
+ import * as Vite2 from "vite";
2920
+ function createVersionInjectorPlugin(rscEntryPath) {
2921
+ let resolvedEntryPath = "";
2922
+ return {
2923
+ name: "@rangojs/router:version-injector",
2924
+ enforce: "pre",
2925
+ configResolved(config) {
2926
+ let entryPath = rscEntryPath;
2927
+ if (!entryPath) {
2928
+ const rscEnvConfig = config.environments?.["rsc"];
2929
+ const entries = rscEnvConfig?.optimizeDeps?.entries;
2930
+ if (typeof entries === "string") {
2931
+ entryPath = entries;
2932
+ } else if (Array.isArray(entries) && entries.length > 0) {
2933
+ entryPath = entries[0];
2934
+ }
2935
+ }
2936
+ if (entryPath) {
2937
+ resolvedEntryPath = resolve4(config.root, entryPath);
2938
+ }
2939
+ },
2940
+ transform(code, id) {
2941
+ if (!resolvedEntryPath) return null;
2942
+ const normalizedId = Vite2.normalizePath(id);
2943
+ const normalizedEntry = Vite2.normalizePath(resolvedEntryPath);
2944
+ if (normalizedId !== normalizedEntry) {
2945
+ return null;
2946
+ }
2947
+ const prepend = [];
2948
+ let newCode = code;
2949
+ if (!code.includes("virtual:rsc-router/routes-manifest")) {
2950
+ prepend.push(`import "virtual:rsc-router/routes-manifest";`);
2951
+ }
2952
+ const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
2953
+ if (needsVersion) {
2954
+ prepend.push(`import { VERSION } from "@rangojs/router:version";`);
2955
+ newCode = newCode.replace(
2956
+ /createRSCHandler\s*\(\s*\{/,
2957
+ "createRSCHandler({\n version: VERSION,"
2958
+ );
2959
+ }
2960
+ if (prepend.length === 0 && newCode === code) return null;
2961
+ newCode = prepend.join("\n") + (prepend.length > 0 ? "\n" : "") + newCode;
2962
+ return {
2963
+ code: newCode,
2964
+ map: null
2965
+ };
2966
+ }
2967
+ };
2968
+ }
2969
+
2970
+ // src/vite/plugins/cjs-to-esm.ts
2971
+ function createCjsToEsmPlugin() {
2972
+ return {
2973
+ name: "@rangojs/router:cjs-to-esm",
2974
+ enforce: "pre",
2975
+ transform(code, id) {
2976
+ const cleanId = id.split("?")[0];
2977
+ if (cleanId.includes("vendor/react-server-dom/client.browser.js") || cleanId.includes("vendor\\react-server-dom\\client.browser.js")) {
2978
+ const isProd = process.env.NODE_ENV === "production";
2979
+ const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
2980
+ return {
2981
+ code: `export * from "${cjsFile}";`,
2982
+ map: null
2983
+ };
2984
+ }
2985
+ if ((cleanId.includes("vendor/react-server-dom/cjs/") || cleanId.includes("vendor\\react-server-dom\\cjs\\")) && cleanId.includes("client.browser")) {
2986
+ let transformed = code;
2987
+ const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
2988
+ const license = licenseMatch ? licenseMatch[0] : "";
2989
+ if (license) {
2990
+ transformed = transformed.slice(license.length);
2991
+ }
2992
+ transformed = transformed.replace(/^\s*["']use strict["'];\s*/, "");
2993
+ transformed = transformed.replace(
2994
+ /^\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
2995
+ ""
2996
+ );
2997
+ transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
2998
+ transformed = transformed.replace(
2999
+ /var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
3000
+ 'import React from "react";\nimport ReactDOM from "react-dom";\nvar '
3001
+ );
3002
+ transformed = transformed.replace(
3003
+ /var\s+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
3004
+ 'import ReactDOM from "react-dom";\nvar '
3005
+ );
3006
+ transformed = transformed.replace(
3007
+ /exports\.(\w+)\s*=\s*function\s*\(/g,
3008
+ "export function $1("
3009
+ );
3010
+ transformed = transformed.replace(
3011
+ /exports\.(\w+)\s*=/g,
3012
+ "export const $1 ="
3013
+ );
3014
+ transformed = license + "\n" + transformed;
3015
+ return {
3016
+ code: transformed,
3017
+ map: null
3018
+ };
3019
+ }
3020
+ return null;
3021
+ }
3022
+ };
3023
+ }
3024
+
3025
+ // src/vite/router-discovery.ts
3026
+ import { createServer as createViteServer } from "vite";
3027
+ import { resolve as resolve8 } from "node:path";
3028
+ import { readFileSync as readFileSync6 } from "node:fs";
3029
+
3030
+ // src/vite/plugins/virtual-stub-plugin.ts
3031
+ function createVirtualStubPlugin() {
3032
+ const STUB_PREFIXES = [
3033
+ "virtual:rsc-router/",
3034
+ "virtual:entry-",
3035
+ "virtual:vite-rsc/"
3036
+ ];
3037
+ return {
3038
+ name: "@rangojs/router:virtual-stubs",
3039
+ resolveId(id) {
3040
+ if (STUB_PREFIXES.some((p) => id.startsWith(p))) {
3041
+ return "\0stub:" + id;
3042
+ }
3043
+ return null;
3044
+ },
3045
+ load(id) {
3046
+ if (id.startsWith("\0stub:")) {
3047
+ return "export default {}";
3048
+ }
3049
+ return null;
3050
+ }
3051
+ };
3052
+ }
3053
+
3054
+ // src/vite/plugins/client-ref-hashing.ts
3055
+ import { relative as relative2 } from "node:path";
3056
+ import { createHash as createHash2 } from "node:crypto";
3057
+ var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3058
+ var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3059
+ var FS_PREFIX = "/@fs/";
3060
+ function computeProductionHash(projectRoot, refKey) {
3061
+ let toHash;
3062
+ if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
3063
+ toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
3064
+ } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
3065
+ const absPath = decodeURIComponent(
3066
+ refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3067
+ );
3068
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3069
+ } else if (refKey.startsWith(FS_PREFIX)) {
3070
+ const absPath = refKey.slice(FS_PREFIX.length - 1);
3071
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3072
+ } else if (refKey.startsWith("/")) {
3073
+ toHash = refKey.slice(1);
3074
+ } else {
3075
+ return refKey;
3076
+ }
3077
+ return createHash2("sha256").update(toHash).digest("hex").slice(0, 12);
3078
+ }
3079
+ var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
3080
+ function transformClientRefs(code, projectRoot) {
3081
+ if (!code.includes("registerClientReference")) return null;
3082
+ let hasReplacement = false;
3083
+ const result = code.replace(
3084
+ REGISTER_CLIENT_REF_RE,
3085
+ (match, refKey) => {
3086
+ const hash = computeProductionHash(projectRoot, refKey);
3087
+ if (hash === refKey) return match;
3088
+ hasReplacement = true;
3089
+ return match.replace(`"${refKey}"`, `"${hash}"`);
3090
+ }
3091
+ );
3092
+ return hasReplacement ? result : null;
3093
+ }
3094
+ function hashClientRefs(projectRoot) {
3095
+ return {
3096
+ name: "@rangojs/router:hash-client-refs",
3097
+ // Run after the RSC plugin's transform (default enforce is normal)
3098
+ enforce: "post",
3099
+ applyToEnvironment(env) {
3100
+ return env.name === "rsc";
3101
+ },
3102
+ transform(code, _id) {
3103
+ const result = transformClientRefs(code, projectRoot);
3104
+ if (result === null) return;
3105
+ return { code: result, map: null };
3106
+ }
3107
+ };
2767
3108
  }
2768
- function findRouterFiles(root, filter) {
2769
- const files = findTsFiles(root, filter);
2770
- const result = [];
2771
- for (const filePath of files) {
2772
- if (filePath.includes(".gen.")) continue;
2773
- try {
2774
- const source = readFileSync2(filePath, "utf-8");
2775
- if (/\bcreateRouter\s*[<(]/.test(source)) {
2776
- result.push(filePath);
2777
- }
2778
- } catch {
3109
+
3110
+ // src/vite/utils/bundle-analysis.ts
3111
+ function findMatchingParenInBundle(code, openParenPos) {
3112
+ let depth = 1;
3113
+ let pos = openParenPos;
3114
+ while (pos < code.length && depth > 0) {
3115
+ const skipped = skipStringOrComment(code, pos);
3116
+ if (skipped > pos) {
3117
+ pos = skipped;
2779
3118
  continue;
2780
3119
  }
3120
+ if (code[pos] === "(") depth++;
3121
+ else if (code[pos] === ")") depth--;
3122
+ pos++;
2781
3123
  }
2782
- return result;
3124
+ return depth === 0 ? pos : -1;
2783
3125
  }
2784
- function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2785
- try {
2786
- const oldCombinedPath = join2(root, "src", "named-routes.gen.ts");
2787
- if (existsSync3(oldCombinedPath)) {
2788
- unlinkSync(oldCombinedPath);
2789
- console.log(
2790
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
3126
+ function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detectPassthrough) {
3127
+ const handlers = [];
3128
+ for (const [, handlerNames] of handlerModules) {
3129
+ for (const name of handlerNames) {
3130
+ const eName = escapeRegExp(name);
3131
+ const idPattern = new RegExp(
3132
+ `(?<![a-zA-Z0-9_])${eName}\\.\\$\\$id\\s*=\\s*"([^"]+)"`
2791
3133
  );
3134
+ const match = chunkCode.match(idPattern);
3135
+ if (!match) continue;
3136
+ let isPassthrough = false;
3137
+ if (detectPassthrough) {
3138
+ const eFnName = escapeRegExp(fnName);
3139
+ const callStartRe = new RegExp(
3140
+ `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3141
+ );
3142
+ const callStart = callStartRe.exec(chunkCode);
3143
+ if (callStart) {
3144
+ const afterOpen = callStart.index + callStart[0].length;
3145
+ const closePos = findMatchingParenInBundle(chunkCode, afterOpen);
3146
+ if (closePos !== -1) {
3147
+ const callBody = chunkCode.slice(callStart.index, closePos);
3148
+ isPassthrough = /passthrough\s*:\s*(!0|true)/.test(callBody);
3149
+ }
3150
+ }
3151
+ }
3152
+ handlers.push({ name, handlerId: match[1], passthrough: isPassthrough });
2792
3153
  }
2793
- } catch {
2794
3154
  }
2795
- const routerFilePaths = knownRouterFiles ?? findRouterFiles(root);
2796
- if (routerFilePaths.length === 0) return;
2797
- for (const routerFilePath of routerFilePaths) {
2798
- let routerSource;
2799
- try {
2800
- routerSource = readFileSync2(routerFilePath, "utf-8");
2801
- } catch {
2802
- continue;
2803
- }
2804
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2805
- if (!urlsVarName) continue;
2806
- let result;
2807
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2808
- if (imported) {
2809
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2810
- if (!targetFile) continue;
2811
- result = buildCombinedRouteMapWithSearch(
2812
- targetFile,
2813
- imported.exportedName
2814
- );
2815
- } else {
2816
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2817
- }
2818
- const routerBasename = pathBasename(routerFilePath).replace(
2819
- /\.(tsx?|jsx?)$/,
2820
- ""
2821
- );
2822
- const outPath = join2(
2823
- dirname2(routerFilePath),
2824
- `${routerBasename}.named-routes.gen.ts`
3155
+ return handlers;
3156
+ }
3157
+ function evictHandlerCode(code, exports, fnName, brand) {
3158
+ const originalSize = Buffer.byteLength(code);
3159
+ let modified = code;
3160
+ const eFnName = escapeRegExp(fnName);
3161
+ for (const { name, handlerId, passthrough } of exports) {
3162
+ if (passthrough) continue;
3163
+ const eName = escapeRegExp(name);
3164
+ const callStartRe = new RegExp(
3165
+ `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
2825
3166
  );
2826
- const existing = existsSync3(outPath) ? readFileSync2(outPath, "utf-8") : null;
2827
- if (Object.keys(result.routes).length === 0) {
2828
- if (!existing) {
2829
- const emptySource = generateRouteTypesSource({});
2830
- writeFileSync(outPath, emptySource);
2831
- }
2832
- continue;
2833
- }
2834
- const hasSearchSchemas = Object.keys(result.searchSchemas).length > 0;
2835
- const source = generateRouteTypesSource(
2836
- result.routes,
2837
- hasSearchSchemas ? result.searchSchemas : void 0
3167
+ const startMatch = callStartRe.exec(modified);
3168
+ if (!startMatch) continue;
3169
+ const afterOpen = startMatch.index + startMatch[0].length;
3170
+ const closePos = findMatchingParenInBundle(modified, afterOpen);
3171
+ if (closePos === -1) continue;
3172
+ let rangeEnd = closePos;
3173
+ while (rangeEnd < modified.length && /\s/.test(modified[rangeEnd]))
3174
+ rangeEnd++;
3175
+ if (modified[rangeEnd] === ";") rangeEnd++;
3176
+ const matched = modified.slice(startMatch.index, rangeEnd);
3177
+ if (!matched.includes(handlerId)) continue;
3178
+ const stub = `const ${name} = { __brand: "${brand}", $$id: "${handlerId}" };`;
3179
+ modified = modified.slice(0, startMatch.index) + stub + modified.slice(rangeEnd);
3180
+ modified = modified.replace(
3181
+ new RegExp(`\\n${eName}\\.\\$\\$id\\s*=\\s*"[^"]+";`),
3182
+ ""
2838
3183
  );
2839
- if (existing !== source) {
2840
- if (opts?.preserveIfLarger && existing) {
2841
- const existingCount = (existing.match(/^\s+["a-zA-Z_$][^:]*:\s*["{]/gm) || []).length;
2842
- const newCount = Object.keys(result.routes).length;
2843
- if (existingCount > newCount) {
2844
- continue;
2845
- }
2846
- }
2847
- writeFileSync(outPath, source);
2848
- console.log(
2849
- `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
2850
- );
2851
- }
2852
3184
  }
3185
+ if (modified === code) return null;
3186
+ return {
3187
+ code: modified,
3188
+ savedBytes: originalSize - Buffer.byteLength(modified)
3189
+ };
2853
3190
  }
2854
3191
 
2855
3192
  // src/vite/discovery/state.ts
@@ -2870,8 +3207,8 @@ function createDiscoveryState(entryPath, opts) {
2870
3207
  perRouterTrieMap: /* @__PURE__ */ new Map(),
2871
3208
  perRouterPrecomputedMap: /* @__PURE__ */ new Map(),
2872
3209
  perRouterManifestDataMap: /* @__PURE__ */ new Map(),
2873
- prerenderCollectedData: null,
2874
- staticCollectedData: null,
3210
+ prerenderManifestEntries: null,
3211
+ staticManifestEntries: null,
2875
3212
  handlerChunkInfo: null,
2876
3213
  staticHandlerChunkInfo: null,
2877
3214
  rscEntryFileName: null,
@@ -2913,6 +3250,48 @@ function consumeSelfGenWrite(state, filePath) {
2913
3250
  }
2914
3251
  }
2915
3252
 
3253
+ // src/vite/utils/manifest-utils.ts
3254
+ function flattenLeafEntries(prefixTree, routeManifest, result) {
3255
+ function visit(node) {
3256
+ const children = node.children || {};
3257
+ if (Object.keys(children).length === 0 && node.routes && node.routes.length > 0) {
3258
+ const routes = {};
3259
+ for (const name of node.routes) {
3260
+ if (name in routeManifest) {
3261
+ routes[name] = routeManifest[name];
3262
+ }
3263
+ }
3264
+ result.push({ staticPrefix: node.staticPrefix, routes });
3265
+ } else {
3266
+ for (const child of Object.values(children)) {
3267
+ visit(child);
3268
+ }
3269
+ }
3270
+ }
3271
+ for (const node of Object.values(prefixTree)) {
3272
+ visit(node);
3273
+ }
3274
+ }
3275
+ function buildRouteToStaticPrefix(prefixTree, result) {
3276
+ function visit(node) {
3277
+ const sp = node.staticPrefix || "";
3278
+ for (const name of node.routes || []) {
3279
+ result[name] = sp;
3280
+ }
3281
+ for (const child of Object.values(node.children || {})) {
3282
+ visit(child);
3283
+ }
3284
+ }
3285
+ for (const node of Object.values(prefixTree)) {
3286
+ visit(node);
3287
+ }
3288
+ }
3289
+ function jsonParseExpression(value) {
3290
+ const json = JSON.stringify(value);
3291
+ const escaped = json.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
3292
+ return `JSON.parse('${escaped}')`;
3293
+ }
3294
+
2916
3295
  // src/context-var.ts
2917
3296
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2918
3297
  function contextSet(variables, keyOrVar, value) {
@@ -2922,11 +3301,132 @@ function contextSet(variables, keyOrVar, value) {
2922
3301
  `ctx.set(): "${keyOrVar}" is a reserved key and cannot be used as a variable name.`
2923
3302
  );
2924
3303
  }
2925
- variables[keyOrVar] = value;
2926
- } else {
2927
- variables[keyOrVar.key] = value;
3304
+ variables[keyOrVar] = value;
3305
+ } else {
3306
+ variables[keyOrVar.key] = value;
3307
+ }
3308
+ }
3309
+
3310
+ // src/vite/utils/prerender-utils.ts
3311
+ import { createHash as createHash4 } from "node:crypto";
3312
+ import {
3313
+ copyFileSync,
3314
+ existsSync as existsSync4,
3315
+ mkdirSync,
3316
+ rmSync,
3317
+ statSync,
3318
+ writeFileSync as writeFileSync2
3319
+ } from "node:fs";
3320
+ import { resolve as resolve5 } from "node:path";
3321
+ function escapeRegExp2(str) {
3322
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3323
+ }
3324
+ function encodePathParam(value) {
3325
+ return String(value).split("/").map((segment) => encodeURIComponent(segment)).join("/");
3326
+ }
3327
+ function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3328
+ let result = pattern;
3329
+ for (const [key, value] of Object.entries(params)) {
3330
+ const escaped = escapeRegExp2(key);
3331
+ result = result.replace(
3332
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3333
+ encode(value)
3334
+ );
3335
+ result = result.replace(`*${key}`, encode(value));
3336
+ }
3337
+ return result;
3338
+ }
3339
+ async function runWithConcurrency(items, concurrency, fn) {
3340
+ const limit = Math.max(1, Math.min(concurrency, items.length));
3341
+ if (limit <= 1) {
3342
+ for (const item of items) await fn(item);
3343
+ return;
3344
+ }
3345
+ let nextIndex = 0;
3346
+ async function worker() {
3347
+ while (nextIndex < items.length) {
3348
+ const idx = nextIndex++;
3349
+ await fn(items[idx]);
3350
+ }
3351
+ }
3352
+ await Promise.all(Array.from({ length: limit }, () => worker()));
3353
+ }
3354
+ function groupByConcurrency(entries) {
3355
+ const map = /* @__PURE__ */ new Map();
3356
+ for (const entry of entries) {
3357
+ const key = entry.concurrency;
3358
+ let group = map.get(key);
3359
+ if (!group) {
3360
+ group = [];
3361
+ map.set(key, group);
3362
+ }
3363
+ group.push(entry);
3364
+ }
3365
+ return Array.from(map.entries(), ([concurrency, items]) => ({
3366
+ concurrency,
3367
+ entries: items
3368
+ }));
3369
+ }
3370
+ function notifyOnError(registry, error, phase, routeKey, pathname, skipped) {
3371
+ for (const [, routerInstance] of registry) {
3372
+ const onError = routerInstance.onError;
3373
+ if (!onError) continue;
3374
+ const errorObj = error instanceof Error ? error : new Error(String(error));
3375
+ const syntheticUrl = new URL("http://prerender" + (pathname || "/"));
3376
+ const context = {
3377
+ error: errorObj,
3378
+ phase,
3379
+ request: new Request(syntheticUrl),
3380
+ url: syntheticUrl,
3381
+ pathname: syntheticUrl.pathname,
3382
+ method: "GET",
3383
+ routeKey,
3384
+ metadata: skipped ? { skipped: true } : void 0
3385
+ };
3386
+ try {
3387
+ const result = onError(context);
3388
+ if (result instanceof Promise) {
3389
+ result.catch((cbErr) => {
3390
+ console.error(`[Build.onError] Callback error:`, cbErr);
3391
+ });
3392
+ }
3393
+ } catch (cbErr) {
3394
+ console.error(`[Build.onError] Callback error:`, cbErr);
3395
+ }
3396
+ break;
2928
3397
  }
2929
3398
  }
3399
+ function getStagedAssetDir(projectRoot) {
3400
+ return resolve5(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
3401
+ }
3402
+ function resetStagedBuildAssets(projectRoot) {
3403
+ rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
3404
+ }
3405
+ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
3406
+ const stagedDir = getStagedAssetDir(projectRoot);
3407
+ mkdirSync(stagedDir, { recursive: true });
3408
+ const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
3409
+ const fileName = `${prefix}-${contentHash}.js`;
3410
+ const filePath = resolve5(stagedDir, fileName);
3411
+ if (!existsSync4(filePath)) {
3412
+ writeFileSync2(filePath, `export default ${exportValue};
3413
+ `);
3414
+ }
3415
+ return fileName;
3416
+ }
3417
+ function copyStagedBuildAssets(projectRoot, fileNames) {
3418
+ const stagedDir = getStagedAssetDir(projectRoot);
3419
+ const distAssetsDir = resolve5(projectRoot, "dist/rsc/assets");
3420
+ mkdirSync(distAssetsDir, { recursive: true });
3421
+ let totalBytes = 0;
3422
+ for (const fileName of new Set(fileNames)) {
3423
+ const stagedPath = resolve5(stagedDir, fileName);
3424
+ const distPath = resolve5(distAssetsDir, fileName);
3425
+ copyFileSync(stagedPath, distPath);
3426
+ totalBytes += statSync(stagedPath).size;
3427
+ }
3428
+ return totalBytes;
3429
+ }
2930
3430
 
2931
3431
  // src/vite/discovery/prerender-collection.ts
2932
3432
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
@@ -2939,18 +3439,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
2939
3439
  const getParamsReverse = (name, params) => {
2940
3440
  const pattern = allRoutes[name];
2941
3441
  if (!pattern) throw new Error(`Unknown route: "${name}"`);
2942
- let result = pattern;
2943
- if (params) {
2944
- for (const [key, value] of Object.entries(params)) {
2945
- const escaped = escapeRegExp2(key);
2946
- result = result.replace(
2947
- new RegExp(`:${escaped}(\\([^)]*\\))?`),
2948
- encodeURIComponent(value)
2949
- );
2950
- result = result.replace(`*${key}`, encodeURIComponent(value));
2951
- }
2952
- }
2953
- return result;
3442
+ if (!params) return pattern;
3443
+ return substituteRouteParams(pattern, params);
2954
3444
  };
2955
3445
  for (const { manifest } of allManifests) {
2956
3446
  if (!manifest.prerenderRoutes) continue;
@@ -2958,15 +3448,17 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
2958
3448
  for (const routeName of manifest.prerenderRoutes) {
2959
3449
  const pattern = manifest.routeManifest[routeName];
2960
3450
  if (!pattern) continue;
3451
+ const def = defs[routeName];
3452
+ const isPassthroughRoute = !!def?.options?.passthrough;
2961
3453
  const hasDynamic = pattern.includes(":") || pattern.includes("*");
2962
3454
  if (!hasDynamic) {
2963
3455
  entries.push({
2964
3456
  urlPath: pattern.replace(/\/$/, "") || "/",
2965
3457
  routeName,
2966
- concurrency: 1
3458
+ concurrency: 1,
3459
+ isPassthroughRoute
2967
3460
  });
2968
3461
  } else {
2969
- const def = defs[routeName];
2970
3462
  if (def?.getParams) {
2971
3463
  try {
2972
3464
  const buildVars = {};
@@ -2981,18 +3473,11 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
2981
3473
  const concurrency = def.options?.concurrency ?? 1;
2982
3474
  const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
2983
3475
  for (const params of paramsList) {
2984
- let url = pattern;
2985
- for (const [key, value] of Object.entries(
2986
- params
2987
- )) {
2988
- const encoded = encodePathParam(value);
2989
- const escaped = escapeRegExp2(key);
2990
- url = url.replace(
2991
- new RegExp(`:${escaped}(\\([^)]*\\))?`),
2992
- encoded
2993
- );
2994
- url = url.replace(`*${key}`, encoded);
2995
- }
3476
+ let url = substituteRouteParams(
3477
+ pattern,
3478
+ params,
3479
+ encodePathParam
3480
+ );
2996
3481
  if (url.includes("*")) {
2997
3482
  const wildcardValue = params["*"] ?? params.splat;
2998
3483
  if (wildcardValue !== void 0) {
@@ -3003,7 +3488,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3003
3488
  urlPath: url.replace(/\/$/, "") || "/",
3004
3489
  routeName,
3005
3490
  concurrency,
3006
- ...hasBuildVars ? { buildVars } : {}
3491
+ ...hasBuildVars ? { buildVars } : {},
3492
+ isPassthroughRoute
3007
3493
  });
3008
3494
  }
3009
3495
  } catch (err) {
@@ -3042,7 +3528,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3042
3528
  `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
3043
3529
  );
3044
3530
  const { hashParams } = await rscEnv.runner.import("@rangojs/router/build");
3045
- const collectedData = {};
3531
+ const manifestEntries = {};
3046
3532
  let doneCount = 0;
3047
3533
  let skipCount = 0;
3048
3534
  const startTotal = performance.now();
@@ -3059,22 +3545,43 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3059
3545
  const result = await routerInstance.matchForPrerender(
3060
3546
  entry.urlPath,
3061
3547
  {},
3062
- entry.buildVars
3548
+ entry.buildVars,
3549
+ entry.isPassthroughRoute
3063
3550
  );
3064
3551
  if (!result) continue;
3552
+ if (result.passthrough) {
3553
+ const elapsed2 = (performance.now() - startUrl).toFixed(0);
3554
+ console.log(
3555
+ `[rsc-router] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
3556
+ );
3557
+ doneCount++;
3558
+ break;
3559
+ }
3065
3560
  const paramHash = hashParams(result.params || {});
3066
- collectedData[`${result.routeName}/${paramHash}`] = {
3561
+ const mainKey = `${result.routeName}/${paramHash}`;
3562
+ const mainValue = JSON.stringify({
3067
3563
  segments: result.segments,
3068
3564
  handles: result.handles
3069
- };
3565
+ });
3566
+ manifestEntries[mainKey] = stageBuildAssetModule(
3567
+ state.projectRoot,
3568
+ "__pr",
3569
+ mainValue
3570
+ );
3070
3571
  if (result.interceptSegments?.length) {
3071
- collectedData[`${result.routeName}/${paramHash}/i`] = {
3572
+ const interceptKey = `${result.routeName}/${paramHash}/i`;
3573
+ const interceptValue = JSON.stringify({
3072
3574
  segments: [...result.segments, ...result.interceptSegments],
3073
3575
  handles: {
3074
3576
  ...result.handles,
3075
3577
  ...result.interceptHandles || {}
3076
3578
  }
3077
- };
3579
+ });
3580
+ manifestEntries[interceptKey] = stageBuildAssetModule(
3581
+ state.projectRoot,
3582
+ "__pr",
3583
+ interceptValue
3584
+ );
3078
3585
  }
3079
3586
  const elapsed = (performance.now() - startUrl).toFixed(0);
3080
3587
  console.log(
@@ -3118,7 +3625,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3118
3625
  }
3119
3626
  const totalElapsed = (performance.now() - startTotal).toFixed(0);
3120
3627
  if (doneCount > 0) {
3121
- state.prerenderCollectedData = collectedData;
3628
+ state.prerenderManifestEntries = manifestEntries;
3122
3629
  }
3123
3630
  const parts = [`${doneCount} done`];
3124
3631
  if (skipCount > 0) parts.push(`${skipCount} skipped`);
@@ -3129,7 +3636,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3129
3636
  async function renderStaticHandlers(state, rscEnv, registry) {
3130
3637
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode || !state.resolvedStaticModules?.size)
3131
3638
  return;
3132
- const collected = {};
3639
+ const manifestEntries = {};
3133
3640
  let staticDone = 0;
3134
3641
  let staticSkip = 0;
3135
3642
  let totalStaticCount = 0;
@@ -3166,7 +3673,13 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3166
3673
  def.$$routePrefix
3167
3674
  );
3168
3675
  if (result) {
3169
- collected[def.$$id] = result;
3676
+ const hasHandles = Object.keys(result.handles).length > 0;
3677
+ const exportValue = hasHandles ? JSON.stringify(result) : JSON.stringify(result.encoded);
3678
+ manifestEntries[def.$$id] = stageBuildAssetModule(
3679
+ state.projectRoot,
3680
+ "__st",
3681
+ exportValue
3682
+ );
3170
3683
  const elapsed = (performance.now() - startHandler).toFixed(0);
3171
3684
  console.log(
3172
3685
  `[rsc-router] OK ${name.padEnd(40)} (${elapsed}ms)`
@@ -3203,7 +3716,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3203
3716
  }
3204
3717
  const totalStaticElapsed = (performance.now() - startStatic).toFixed(0);
3205
3718
  if (staticDone > 0) {
3206
- state.staticCollectedData = collected;
3719
+ state.staticManifestEntries = manifestEntries;
3207
3720
  }
3208
3721
  const staticParts = [`${staticDone} done`];
3209
3722
  if (staticSkip > 0) staticParts.push(`${staticSkip} skipped`);
@@ -3220,8 +3733,7 @@ async function discoverRouters(state, rscEnv) {
3220
3733
  let registry = serverMod.RouterRegistry;
3221
3734
  if (!registry || registry.size === 0) {
3222
3735
  try {
3223
- const hostMod = await rscEnv.runner.import("@rangojs/router/host");
3224
- const hostRegistry = hostMod.HostRouterRegistry;
3736
+ const hostRegistry = serverMod.HostRouterRegistry;
3225
3737
  if (hostRegistry && hostRegistry.size > 0) {
3226
3738
  console.log(
3227
3739
  `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
@@ -3260,22 +3772,30 @@ async function discoverRouters(state, rscEnv) {
3260
3772
  }
3261
3773
  }
3262
3774
  const buildMod = await rscEnv.runner.import("@rangojs/router/build");
3263
- const generateManifest = buildMod.generateManifest;
3264
- state.mergedRouteManifest = {};
3265
- state.mergedPrecomputedEntries = [];
3266
- state.perRouterManifests = [];
3267
- state.perRouterManifestDataMap = /* @__PURE__ */ new Map();
3268
- state.perRouterPrecomputedMap = /* @__PURE__ */ new Map();
3269
- state.perRouterTrieMap = /* @__PURE__ */ new Map();
3775
+ const generateManifestFull = buildMod.generateManifestFull;
3776
+ const nestedRouterConflict = findNestedRouterConflict(
3777
+ [...registry.values()].map((router) => router.__sourceFile).filter(
3778
+ (sourceFile) => typeof sourceFile === "string"
3779
+ )
3780
+ );
3781
+ if (nestedRouterConflict) {
3782
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
3783
+ }
3784
+ const newMergedRouteManifest = {};
3785
+ const newMergedPrecomputedEntries = [];
3786
+ const newPerRouterManifests = [];
3787
+ const newPerRouterManifestDataMap = /* @__PURE__ */ new Map();
3788
+ const newPerRouterPrecomputedMap = /* @__PURE__ */ new Map();
3789
+ const newPerRouterTrieMap = /* @__PURE__ */ new Map();
3270
3790
  let mergedRouteAncestry = {};
3271
3791
  let mergedRouteTrailingSlash = {};
3272
3792
  let routerMountIndex = 0;
3273
3793
  const allManifests = [];
3274
3794
  for (const [id, router] of registry) {
3275
- if (!router.urlpatterns || !generateManifest) {
3795
+ if (!router.urlpatterns || !generateManifestFull) {
3276
3796
  continue;
3277
3797
  }
3278
- const manifest = generateManifest(router.urlpatterns, routerMountIndex);
3798
+ const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
3279
3799
  routerMountIndex++;
3280
3800
  allManifests.push({ id, manifest });
3281
3801
  const routeCount = Object.keys(manifest.routeManifest).length;
@@ -3283,7 +3803,7 @@ async function discoverRouters(state, rscEnv) {
3283
3803
  (p) => !p.includes(":") && !p.includes("*")
3284
3804
  ).length;
3285
3805
  const dynamicRoutes = routeCount - staticRoutes;
3286
- Object.assign(state.mergedRouteManifest, manifest.routeManifest);
3806
+ Object.assign(newMergedRouteManifest, manifest.routeManifest);
3287
3807
  let factoryOnlyPrefixes;
3288
3808
  if (router.__sourceFile) {
3289
3809
  const staticParsed = buildCombinedRouteMapForRouterFile(
@@ -3301,7 +3821,7 @@ async function discoverRouters(state, rscEnv) {
3301
3821
  }
3302
3822
  if (factoryOnlyPrefixes.size === 0) factoryOnlyPrefixes = void 0;
3303
3823
  }
3304
- state.perRouterManifests.push({
3824
+ newPerRouterManifests.push({
3305
3825
  id,
3306
3826
  routeManifest: manifest.routeManifest,
3307
3827
  routeSearchSchemas: manifest.routeSearchSchemas,
@@ -3317,16 +3837,16 @@ async function discoverRouters(state, rscEnv) {
3317
3837
  flattenLeafEntries(
3318
3838
  manifest.prefixTree,
3319
3839
  manifest.routeManifest,
3320
- state.mergedPrecomputedEntries
3840
+ newMergedPrecomputedEntries
3321
3841
  );
3322
- state.perRouterManifestDataMap.set(id, manifest.routeManifest);
3842
+ newPerRouterManifestDataMap.set(id, manifest.routeManifest);
3323
3843
  const routerPrecomputed = [];
3324
3844
  flattenLeafEntries(
3325
3845
  manifest.prefixTree,
3326
3846
  manifest.routeManifest,
3327
3847
  routerPrecomputed
3328
3848
  );
3329
- state.perRouterPrecomputedMap.set(id, routerPrecomputed);
3849
+ newPerRouterPrecomputedMap.set(id, routerPrecomputed);
3330
3850
  console.log(
3331
3851
  `[rsc-router] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
3332
3852
  );
@@ -3341,7 +3861,8 @@ async function discoverRouters(state, rscEnv) {
3341
3861
  );
3342
3862
  }
3343
3863
  }
3344
- if (state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0) {
3864
+ let newMergedRouteTrie = null;
3865
+ if (Object.keys(newMergedRouteManifest).length > 0) {
3345
3866
  const buildRouteTrie = buildMod.buildRouteTrie;
3346
3867
  if (buildRouteTrie && mergedRouteAncestry) {
3347
3868
  const routeToStaticPrefix = {};
@@ -3371,8 +3892,8 @@ async function discoverRouters(state, rscEnv) {
3371
3892
  Object.assign(mergedResponseTypeRoutes, manifest.responseTypeRoutes);
3372
3893
  }
3373
3894
  }
3374
- state.mergedRouteTrie = buildRouteTrie(
3375
- state.mergedRouteManifest,
3895
+ newMergedRouteTrie = buildRouteTrie(
3896
+ newMergedRouteManifest,
3376
3897
  mergedRouteAncestry,
3377
3898
  routeToStaticPrefix,
3378
3899
  Object.keys(mergedRouteTrailingSlash).length > 0 ? mergedRouteTrailingSlash : void 0,
@@ -3399,22 +3920,29 @@ async function discoverRouters(state, rscEnv) {
3399
3920
  perRouterPassthroughNames && perRouterPassthroughNames.size > 0 ? perRouterPassthroughNames : void 0,
3400
3921
  manifest.responseTypeRoutes && Object.keys(manifest.responseTypeRoutes).length > 0 ? manifest.responseTypeRoutes : void 0
3401
3922
  );
3402
- state.perRouterTrieMap.set(id, perRouterTrie);
3923
+ newPerRouterTrieMap.set(id, perRouterTrie);
3403
3924
  }
3404
3925
  }
3405
3926
  }
3927
+ state.mergedRouteManifest = newMergedRouteManifest;
3928
+ state.mergedPrecomputedEntries = newMergedPrecomputedEntries;
3929
+ state.perRouterManifests = newPerRouterManifests;
3930
+ state.perRouterManifestDataMap = newPerRouterManifestDataMap;
3931
+ state.perRouterPrecomputedMap = newPerRouterPrecomputedMap;
3932
+ state.perRouterTrieMap = newPerRouterTrieMap;
3933
+ state.mergedRouteTrie = newMergedRouteTrie;
3406
3934
  await expandPrerenderRoutes(state, rscEnv, registry, allManifests);
3407
3935
  await renderStaticHandlers(state, rscEnv, registry);
3408
3936
  return serverMod;
3409
3937
  }
3410
3938
 
3411
3939
  // src/vite/discovery/route-types-writer.ts
3412
- import { dirname as dirname3, basename, join as join3, resolve as resolve5 } from "node:path";
3413
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "node:fs";
3940
+ import { dirname as dirname3, basename, join as join3, resolve as resolve6 } from "node:path";
3941
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
3414
3942
  function filterUserNamedRoutes(manifest) {
3415
3943
  const filtered = {};
3416
3944
  for (const [name, pattern] of Object.entries(manifest)) {
3417
- if (!name.startsWith("$")) {
3945
+ if (!isAutoGeneratedRouteName(name)) {
3418
3946
  filtered[name] = pattern;
3419
3947
  }
3420
3948
  }
@@ -3444,7 +3972,7 @@ function writeCombinedRouteTypesWithTracking(state, opts) {
3444
3972
  ""
3445
3973
  );
3446
3974
  const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
3447
- if (!existsSync4(outPath)) continue;
3975
+ if (!existsSync5(outPath)) continue;
3448
3976
  try {
3449
3977
  const content = readFileSync4(outPath, "utf-8");
3450
3978
  if (content !== preContent.get(outPath)) {
@@ -3458,10 +3986,10 @@ function writeRouteTypesFiles(state) {
3458
3986
  if (state.perRouterManifests.length === 0) return;
3459
3987
  try {
3460
3988
  const entryDir = dirname3(
3461
- resolve5(state.projectRoot, state.resolvedEntryPath)
3989
+ resolve6(state.projectRoot, state.resolvedEntryPath)
3462
3990
  );
3463
3991
  const oldCombinedPath = join3(entryDir, "named-routes.gen.ts");
3464
- if (existsSync4(oldCombinedPath)) {
3992
+ if (existsSync5(oldCombinedPath)) {
3465
3993
  unlinkSync2(oldCombinedPath);
3466
3994
  console.log(
3467
3995
  `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
@@ -3505,10 +4033,10 @@ Set an explicit \`id\` on createRouter() or check the call site.`
3505
4033
  userRoutes,
3506
4034
  effectiveSearchSchemas && Object.keys(effectiveSearchSchemas).length > 0 ? effectiveSearchSchemas : void 0
3507
4035
  );
3508
- const existing = existsSync4(outPath) ? readFileSync4(outPath, "utf-8") : null;
4036
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
3509
4037
  if (existing !== source) {
3510
4038
  markSelfGenWrite(state, outPath, source);
3511
- writeFileSync2(outPath, source);
4039
+ writeFileSync3(outPath, source);
3512
4040
  console.log(`[rsc-router] Generated route types -> ${outPath}`);
3513
4041
  }
3514
4042
  }
@@ -3537,7 +4065,7 @@ function supplementGenFilesWithRuntimeRoutes(state) {
3537
4065
  ...staticParsed.searchSchemas
3538
4066
  };
3539
4067
  for (const [name, pattern] of Object.entries(routeManifest)) {
3540
- if (name.startsWith("$")) continue;
4068
+ if (isAutoGeneratedRouteName(name)) continue;
3541
4069
  const dotIdx = name.indexOf(".");
3542
4070
  if (dotIdx <= 0) continue;
3543
4071
  const prefix = name.substring(0, dotIdx + 1);
@@ -3555,10 +4083,10 @@ function supplementGenFilesWithRuntimeRoutes(state) {
3555
4083
  mergedRoutes,
3556
4084
  Object.keys(mergedSearchSchemas).length > 0 ? mergedSearchSchemas : void 0
3557
4085
  );
3558
- const existing = existsSync4(outPath) ? readFileSync4(outPath, "utf-8") : null;
4086
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
3559
4087
  if (existing !== source) {
3560
4088
  markSelfGenWrite(state, outPath, source);
3561
- writeFileSync2(outPath, source);
4089
+ writeFileSync3(outPath, source);
3562
4090
  }
3563
4091
  }
3564
4092
  }
@@ -3582,7 +4110,7 @@ function generateRoutesManifestModule(state) {
3582
4110
  const genPath = join4(
3583
4111
  routerDir,
3584
4112
  `${routerBasename}.named-routes.gen.js`
3585
- );
4113
+ ).replaceAll("\\", "/");
3586
4114
  const varName = `_r${varIdx++}`;
3587
4115
  genFileImports.push(
3588
4116
  `import { NamedRoutes as ${varName} } from ${JSON.stringify(genPath)};`
@@ -3676,7 +4204,10 @@ function generatePerRouterModule(state, routerId) {
3676
4204
  /\.(tsx?|jsx?)$/,
3677
4205
  ""
3678
4206
  );
3679
- const genPath = join4(routerDir, `${routerBasename}.named-routes.gen.js`);
4207
+ const genPath = join4(
4208
+ routerDir,
4209
+ `${routerBasename}.named-routes.gen.js`
4210
+ ).replaceAll("\\", "/");
3680
4211
  lines.push(`import { NamedRoutes as _r } from ${JSON.stringify(genPath)};`);
3681
4212
  lines.push(
3682
4213
  `function __flat(r) { const o = {}; for (const [k, v] of Object.entries(r)) o[k] = typeof v === "string" ? v : v.path; return o; }`
@@ -3700,14 +4231,13 @@ function generatePerRouterModule(state, routerId) {
3700
4231
  }
3701
4232
 
3702
4233
  // src/vite/discovery/bundle-postprocess.ts
3703
- import { resolve as resolve6 } from "node:path";
3704
- import { createHash as createHash4 } from "node:crypto";
3705
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync } from "node:fs";
4234
+ import { resolve as resolve7 } from "node:path";
4235
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "node:fs";
3706
4236
  function postprocessBundle(state) {
3707
- const hasPrerenderData = state.prerenderCollectedData && Object.keys(state.prerenderCollectedData).length > 0;
3708
- const hasStaticData = state.staticCollectedData && Object.keys(state.staticCollectedData).length > 0;
4237
+ const hasPrerenderData = state.prerenderManifestEntries && Object.keys(state.prerenderManifestEntries).length > 0;
4238
+ const hasStaticData = state.staticManifestEntries && Object.keys(state.staticManifestEntries).length > 0;
3709
4239
  if (!hasPrerenderData && !hasStaticData) return;
3710
- const rscEntryPath = resolve6(
4240
+ const rscEntryPath = resolve7(
3711
4241
  state.projectRoot,
3712
4242
  "dist/rsc",
3713
4243
  state.rscEntryFileName ?? "index.js"
@@ -3728,7 +4258,7 @@ function postprocessBundle(state) {
3728
4258
  ];
3729
4259
  for (const target of evictionTargets) {
3730
4260
  if (!target.info) continue;
3731
- const chunkPath = resolve6(
4261
+ const chunkPath = resolve7(
3732
4262
  state.projectRoot,
3733
4263
  "dist/rsc",
3734
4264
  target.info.fileName
@@ -3742,7 +4272,7 @@ function postprocessBundle(state) {
3742
4272
  target.brand
3743
4273
  );
3744
4274
  if (result) {
3745
- writeFileSync3(chunkPath, result.code);
4275
+ writeFileSync4(chunkPath, result.code);
3746
4276
  const savedKB = (result.savedBytes / 1024).toFixed(1);
3747
4277
  console.log(
3748
4278
  `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${target.info.fileName}`
@@ -3756,44 +4286,38 @@ function postprocessBundle(state) {
3756
4286
  }
3757
4287
  state.handlerChunkInfo = null;
3758
4288
  state.staticHandlerChunkInfo = null;
3759
- if (hasPrerenderData && existsSync5(rscEntryPath)) {
4289
+ if (hasPrerenderData && existsSync6(rscEntryPath)) {
3760
4290
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
3761
- if (!rscCode.includes("__PRERENDER_MANIFEST")) {
4291
+ if (!rscCode.includes("__prerender-manifest.js")) {
3762
4292
  try {
3763
- const assetsDir = resolve6(state.projectRoot, "dist/rsc/assets");
3764
- mkdirSync(assetsDir, { recursive: true });
3765
- const manifestEntries = [];
3766
- let totalBytes = 0;
3767
- for (const [key, entry] of Object.entries(
3768
- state.prerenderCollectedData
4293
+ let totalBytes = copyStagedBuildAssets(
4294
+ state.projectRoot,
4295
+ Object.values(state.prerenderManifestEntries)
4296
+ );
4297
+ const manifestMap = {};
4298
+ for (const [key, assetFileName] of Object.entries(
4299
+ state.prerenderManifestEntries
3769
4300
  )) {
3770
- const entryJson = JSON.stringify(entry);
3771
- const contentHash = createHash4("sha256").update(entryJson).digest("hex").slice(0, 8);
3772
- const assetFileName = `__pr-${contentHash}.js`;
3773
- const assetPath = resolve6(assetsDir, assetFileName);
3774
- const assetCode = `export default ${entryJson};
3775
- `;
3776
- writeFileSync3(assetPath, assetCode);
3777
- totalBytes += Buffer.byteLength(assetCode);
3778
- manifestEntries.push(
3779
- `${JSON.stringify(key)}:()=>import("./assets/${assetFileName}")`
3780
- );
4301
+ manifestMap[key] = `./assets/${assetFileName}`;
3781
4302
  }
3782
- const manifestCode = `const m={${manifestEntries.join(",")}};export default m;
3783
- `;
3784
- const manifestPath = resolve6(
4303
+ const manifestCode = [
4304
+ `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
4305
+ `export function loadPrerenderAsset(s){return import(s)}`,
4306
+ `export default m;`,
4307
+ ""
4308
+ ].join("\n");
4309
+ const manifestPath = resolve7(
3785
4310
  state.projectRoot,
3786
4311
  "dist/rsc/__prerender-manifest.js"
3787
4312
  );
3788
- writeFileSync3(manifestPath, manifestCode);
4313
+ writeFileSync4(manifestPath, manifestCode);
3789
4314
  totalBytes += Buffer.byteLength(manifestCode);
3790
- const injection = `import __pm from "./__prerender-manifest.js";
3791
- globalThis.__PRERENDER_MANIFEST = __pm;
4315
+ const injection = `globalThis.__loadPrerenderManifestModule = () => import("./__prerender-manifest.js");
3792
4316
  `;
3793
- writeFileSync3(rscEntryPath, injection + rscCode);
4317
+ writeFileSync4(rscEntryPath, injection + rscCode);
3794
4318
  const totalKB = (totalBytes / 1024).toFixed(1);
3795
4319
  console.log(
3796
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderCollectedData).length} entries)`
4320
+ `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
3797
4321
  );
3798
4322
  } catch (err) {
3799
4323
  throw new Error(
@@ -3802,44 +4326,36 @@ globalThis.__PRERENDER_MANIFEST = __pm;
3802
4326
  }
3803
4327
  }
3804
4328
  }
3805
- if (hasStaticData && existsSync5(rscEntryPath)) {
4329
+ if (hasStaticData && existsSync6(rscEntryPath)) {
3806
4330
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
3807
4331
  if (!rscCode.includes("__STATIC_MANIFEST")) {
3808
4332
  try {
3809
- const assetsDir = resolve6(state.projectRoot, "dist/rsc/assets");
3810
- mkdirSync(assetsDir, { recursive: true });
3811
4333
  const manifestEntries = [];
3812
- let totalBytes = 0;
3813
- for (const [handlerId, { encoded, handles }] of Object.entries(
3814
- state.staticCollectedData
4334
+ let totalBytes = copyStagedBuildAssets(
4335
+ state.projectRoot,
4336
+ Object.values(state.staticManifestEntries)
4337
+ );
4338
+ for (const [handlerId, assetFileName] of Object.entries(
4339
+ state.staticManifestEntries
3815
4340
  )) {
3816
- const contentHash = createHash4("sha256").update(encoded).digest("hex").slice(0, 8);
3817
- const assetFileName = `__st-${contentHash}.js`;
3818
- const assetPath = resolve6(assetsDir, assetFileName);
3819
- const hasHandles = Object.keys(handles).length > 0;
3820
- const exportValue = hasHandles ? JSON.stringify({ encoded, handles }) : JSON.stringify(encoded);
3821
- const assetCode = `export default ${exportValue};
3822
- `;
3823
- writeFileSync3(assetPath, assetCode);
3824
- totalBytes += Buffer.byteLength(assetCode);
3825
4341
  manifestEntries.push(
3826
4342
  `${JSON.stringify(handlerId)}:()=>import("./assets/${assetFileName}")`
3827
4343
  );
3828
4344
  }
3829
4345
  const manifestCode = `const m={${manifestEntries.join(",")}};globalThis.__STATIC_MANIFEST=m;export default m;
3830
4346
  `;
3831
- const manifestPath = resolve6(
4347
+ const manifestPath = resolve7(
3832
4348
  state.projectRoot,
3833
4349
  "dist/rsc/__static-manifest.js"
3834
4350
  );
3835
- writeFileSync3(manifestPath, manifestCode);
4351
+ writeFileSync4(manifestPath, manifestCode);
3836
4352
  totalBytes += Buffer.byteLength(manifestCode);
3837
4353
  const injection = `import "./__static-manifest.js";
3838
4354
  `;
3839
- writeFileSync3(rscEntryPath, injection + rscCode);
4355
+ writeFileSync4(rscEntryPath, injection + rscCode);
3840
4356
  const totalKB = (totalBytes / 1024).toFixed(1);
3841
4357
  console.log(
3842
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticCollectedData).length} entries)`
4358
+ `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
3843
4359
  );
3844
4360
  } catch (err) {
3845
4361
  throw new Error(
@@ -3851,6 +4367,36 @@ globalThis.__PRERENDER_MANIFEST = __pm;
3851
4367
  }
3852
4368
 
3853
4369
  // src/vite/router-discovery.ts
4370
+ async function createTempRscServer(state, options = {}) {
4371
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
4372
+ return createViteServer({
4373
+ root: state.projectRoot,
4374
+ configFile: false,
4375
+ server: { middlewareMode: true },
4376
+ appType: "custom",
4377
+ logLevel: "silent",
4378
+ resolve: { alias: state.userResolveAlias },
4379
+ esbuild: { jsx: "automatic", jsxImportSource: "react" },
4380
+ ...options.cacheDir && { cacheDir: options.cacheDir },
4381
+ plugins: [
4382
+ rsc({
4383
+ entries: {
4384
+ client: "virtual:entry-client",
4385
+ ssr: "virtual:entry-ssr",
4386
+ rsc: state.resolvedEntryPath
4387
+ }
4388
+ }),
4389
+ // hashClientRefs only in build mode — production bundles need hashed refs
4390
+ ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
4391
+ createVersionPlugin(),
4392
+ createVirtualStubPlugin(),
4393
+ // Dev prerender must use dev-mode IDs (path-based) to match the workerd
4394
+ // runtime. forceBuild produces hashed IDs for production bundle consistency.
4395
+ exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
4396
+ exposeRouterId()
4397
+ ]
4398
+ });
4399
+ }
3854
4400
  function createRouterDiscoveryPlugin(entryPath, opts) {
3855
4401
  const s = createDiscoveryState(entryPath, opts);
3856
4402
  return {
@@ -3887,6 +4433,9 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
3887
4433
  s.projectRoot = config.root;
3888
4434
  s.isBuildMode = config.command === "build";
3889
4435
  s.userResolveAlias = config.resolve.alias;
4436
+ if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
4437
+ s.resolvedEntryPath = opts.routerPathRef.path;
4438
+ }
3890
4439
  if (!s.resolvedEntryPath) {
3891
4440
  const rscEnvConfig = config.environments?.["rsc"];
3892
4441
  const entries = rscEnvConfig?.optimizeDeps?.entries;
@@ -3921,8 +4470,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
3921
4470
  if (globalThis.__rscRouterDiscoveryActive) return;
3922
4471
  s.devServer = server;
3923
4472
  let resolveDiscovery;
3924
- const discoveryPromise = new Promise((resolve7) => {
3925
- resolveDiscovery = resolve7;
4473
+ const discoveryPromise = new Promise((resolve10) => {
4474
+ resolveDiscovery = resolve10;
3926
4475
  });
3927
4476
  const getDevServerOrigin = () => server.resolvedUrls?.local?.[0]?.replace(/\/$/, "") || `http://localhost:${server.config.server.port || 5173}`;
3928
4477
  let prerenderTempServer = null;
@@ -3939,32 +4488,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
3939
4488
  return prerenderTempServer.environments?.rsc ?? null;
3940
4489
  }
3941
4490
  try {
3942
- const { default: rsc } = await import("@vitejs/plugin-rsc");
3943
- prerenderTempServer = await createViteServer({
3944
- root: s.projectRoot,
3945
- configFile: false,
3946
- server: { middlewareMode: true },
3947
- appType: "custom",
3948
- logLevel: "silent",
3949
- cacheDir: "node_modules/.vite_prerender",
3950
- resolve: { alias: s.userResolveAlias },
3951
- esbuild: { jsx: "automatic", jsxImportSource: "react" },
3952
- plugins: [
3953
- rsc({
3954
- entries: {
3955
- client: "virtual:entry-client",
3956
- ssr: "virtual:entry-ssr",
3957
- rsc: s.resolvedEntryPath
3958
- }
3959
- }),
3960
- createVersionPlugin(),
3961
- createVirtualStubPlugin(),
3962
- // Dev prerender must use dev-mode IDs (path-based) to match the
3963
- // workerd runtime. forceBuild would produce hashed IDs causing
3964
- // handle data key mismatches when replayed into the runtime store.
3965
- exposeInternalIds(),
3966
- exposeRouterId()
3967
- ]
4491
+ prerenderTempServer = await createTempRscServer(s, {
4492
+ cacheDir: "node_modules/.vite_prerender"
3968
4493
  });
3969
4494
  const tempRscEnv = prerenderTempServer.environments?.rsc;
3970
4495
  if (tempRscEnv?.runner) {
@@ -4008,34 +4533,10 @@ ${err.stack}`
4008
4533
  if (serverMod?.setManifestReadyPromise) {
4009
4534
  serverMod.setManifestReadyPromise(discoveryPromise);
4010
4535
  }
4011
- const serverModAfterDiscovery = await discoverRouters(s, rscEnv);
4012
- mainRegistry = serverModAfterDiscovery?.RouterRegistry ?? null;
4536
+ await discoverRouters(s, rscEnv);
4013
4537
  s.devServerOrigin = getDevServerOrigin();
4014
4538
  writeRouteTypesFiles(s);
4015
- if (s.mergedRouteManifest && serverMod?.setCachedManifest) {
4016
- serverMod.setCachedManifest(s.mergedRouteManifest);
4017
- }
4018
- if (s.mergedPrecomputedEntries && s.mergedPrecomputedEntries.length > 0 && serverMod?.setPrecomputedEntries) {
4019
- serverMod.setPrecomputedEntries(s.mergedPrecomputedEntries);
4020
- }
4021
- if (s.mergedRouteTrie && serverMod?.setRouteTrie) {
4022
- serverMod.setRouteTrie(s.mergedRouteTrie);
4023
- }
4024
- if (serverMod?.setRouterManifest) {
4025
- for (const [routerId, manifest] of s.perRouterManifestDataMap) {
4026
- serverMod.setRouterManifest(routerId, manifest);
4027
- }
4028
- }
4029
- if (serverMod?.setRouterTrie) {
4030
- for (const [routerId, trie] of s.perRouterTrieMap) {
4031
- serverMod.setRouterTrie(routerId, trie);
4032
- }
4033
- }
4034
- if (serverMod?.setRouterPrecomputedEntries) {
4035
- for (const [routerId, entries] of s.perRouterPrecomputedMap) {
4036
- serverMod.setRouterPrecomputedEntries(routerId, entries);
4037
- }
4038
- }
4539
+ await propagateDiscoveryState(rscEnv);
4039
4540
  } catch (err) {
4040
4541
  console.warn(
4041
4542
  `[rsc-router] Router discovery failed: ${err.message}
@@ -4045,10 +4546,42 @@ ${err.stack}`
4045
4546
  resolveDiscovery();
4046
4547
  }
4047
4548
  };
4048
- s.discoveryDone = new Promise((resolve7) => {
4049
- setTimeout(() => discover().then(resolve7, resolve7), 0);
4549
+ s.discoveryDone = new Promise((resolve10) => {
4550
+ setTimeout(() => discover().then(resolve10, resolve10), 0);
4050
4551
  });
4051
4552
  let mainRegistry = null;
4553
+ const propagateDiscoveryState = async (rscEnv) => {
4554
+ const serverMod = await rscEnv.runner.import("@rangojs/router/server");
4555
+ if (!serverMod) return;
4556
+ if (serverMod.clearAllRouterData) {
4557
+ serverMod.clearAllRouterData();
4558
+ }
4559
+ mainRegistry = serverMod.RouterRegistry ?? null;
4560
+ if (s.mergedRouteManifest && serverMod.setCachedManifest) {
4561
+ serverMod.setCachedManifest(s.mergedRouteManifest);
4562
+ }
4563
+ if (s.mergedPrecomputedEntries && s.mergedPrecomputedEntries.length > 0 && serverMod.setPrecomputedEntries) {
4564
+ serverMod.setPrecomputedEntries(s.mergedPrecomputedEntries);
4565
+ }
4566
+ if (s.mergedRouteTrie && serverMod.setRouteTrie) {
4567
+ serverMod.setRouteTrie(s.mergedRouteTrie);
4568
+ }
4569
+ if (serverMod.setRouterManifest) {
4570
+ for (const [routerId, manifest] of s.perRouterManifestDataMap) {
4571
+ serverMod.setRouterManifest(routerId, manifest);
4572
+ }
4573
+ }
4574
+ if (serverMod.setRouterTrie) {
4575
+ for (const [routerId, trie] of s.perRouterTrieMap) {
4576
+ serverMod.setRouterTrie(routerId, trie);
4577
+ }
4578
+ }
4579
+ if (serverMod.setRouterPrecomputedEntries) {
4580
+ for (const [routerId, entries] of s.perRouterPrecomputedMap) {
4581
+ serverMod.setRouterPrecomputedEntries(routerId, entries);
4582
+ }
4583
+ }
4584
+ };
4052
4585
  server.middlewares.use("/__rsc_prerender", async (req, res) => {
4053
4586
  if (s.discoveryDone) await s.discoveryDone;
4054
4587
  const url = new URL(req.url || "/", "http://localhost");
@@ -4071,11 +4604,20 @@ ${err.stack}`
4071
4604
  return;
4072
4605
  }
4073
4606
  const wantIntercept = url.searchParams.get("intercept") === "1";
4607
+ const wantRouteName = url.searchParams.get("routeName");
4608
+ const wantPassthrough = url.searchParams.get("passthrough") === "1";
4074
4609
  for (const [, routerInstance] of registry) {
4075
4610
  if (!routerInstance.matchForPrerender) continue;
4076
4611
  try {
4077
- const result = await routerInstance.matchForPrerender(pathname, {});
4612
+ const result = await routerInstance.matchForPrerender(
4613
+ pathname,
4614
+ {},
4615
+ void 0,
4616
+ wantPassthrough
4617
+ );
4078
4618
  if (!result) continue;
4619
+ if (result.passthrough) continue;
4620
+ if (wantRouteName && result.routeName !== wantRouteName) continue;
4079
4621
  res.setHeader("content-type", "application/json");
4080
4622
  let payload;
4081
4623
  if (wantIntercept && result.interceptSegments?.length) {
@@ -4112,10 +4654,29 @@ ${err.stack}`
4112
4654
  const maybeHandleGeneratedRouteFileMutation = (filePath) => {
4113
4655
  if (!isGeneratedRouteFile(filePath)) return false;
4114
4656
  if (consumeSelfGenWrite(s, filePath)) return true;
4657
+ const hasRunner = !!server.environments?.rsc?.runner;
4658
+ if (!hasRunner) return true;
4115
4659
  regenerateGeneratedRouteFiles();
4116
4660
  return true;
4117
4661
  };
4118
4662
  let routeChangeTimer;
4663
+ let runtimeRediscoveryInProgress = false;
4664
+ const refreshRuntimeDiscovery = async () => {
4665
+ const rscEnv = server.environments?.rsc;
4666
+ if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
4667
+ runtimeRediscoveryInProgress = true;
4668
+ try {
4669
+ await discoverRouters(s, rscEnv);
4670
+ writeRouteTypesFiles(s);
4671
+ await propagateDiscoveryState(rscEnv);
4672
+ } catch (err) {
4673
+ console.warn(
4674
+ `[rsc-router] Runtime re-discovery failed: ${err.message}`
4675
+ );
4676
+ } finally {
4677
+ runtimeRediscoveryInProgress = false;
4678
+ }
4679
+ };
4119
4680
  const scheduleRouteRegeneration = () => {
4120
4681
  clearTimeout(routeChangeTimer);
4121
4682
  routeChangeTimer = setTimeout(() => {
@@ -4130,6 +4691,13 @@ ${err.stack}`
4130
4691
  `[rsc-router] Route regeneration error: ${err.message}`
4131
4692
  );
4132
4693
  }
4694
+ if (s.perRouterManifests.length > 0) {
4695
+ refreshRuntimeDiscovery().catch((err) => {
4696
+ console.warn(
4697
+ `[rsc-router] Runtime re-discovery error: ${err.message}`
4698
+ );
4699
+ });
4700
+ }
4133
4701
  }, 100);
4134
4702
  };
4135
4703
  const handleRouteFileChange = (filePath) => {
@@ -4146,6 +4714,16 @@ ${err.stack}`
4146
4714
  const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
4147
4715
  if (!hasUrls && !hasCreateRouter) return;
4148
4716
  if (hasCreateRouter) {
4717
+ const nestedRouterConflict = findNestedRouterConflict([
4718
+ ...s.cachedRouterFiles ?? [],
4719
+ resolve8(filePath)
4720
+ ]);
4721
+ if (nestedRouterConflict) {
4722
+ server.config.logger.error(
4723
+ formatNestedRouterConflictError(nestedRouterConflict)
4724
+ );
4725
+ return;
4726
+ }
4149
4727
  s.cachedRouterFiles = void 0;
4150
4728
  }
4151
4729
  scheduleRouteRegeneration();
@@ -4156,6 +4734,8 @@ ${err.stack}`
4156
4734
  server.watcher.on("change", handleRouteFileChange);
4157
4735
  server.watcher.on("unlink", (filePath) => {
4158
4736
  if (!isGeneratedRouteFile(filePath)) return;
4737
+ const hasRunner = !!server.environments?.rsc?.runner;
4738
+ if (!hasRunner) return;
4159
4739
  regenerateGeneratedRouteFiles();
4160
4740
  });
4161
4741
  }
@@ -4166,43 +4746,13 @@ ${err.stack}`
4166
4746
  async buildStart() {
4167
4747
  if (!s.isBuildMode) return;
4168
4748
  if (s.mergedRouteManifest !== null) return;
4749
+ resetStagedBuildAssets(s.projectRoot);
4750
+ s.prerenderManifestEntries = null;
4751
+ s.staticManifestEntries = null;
4169
4752
  let tempServer = null;
4170
4753
  globalThis.__rscRouterDiscoveryActive = true;
4171
4754
  try {
4172
- const { default: rsc } = await import("@vitejs/plugin-rsc");
4173
- tempServer = await createViteServer({
4174
- root: s.projectRoot,
4175
- configFile: false,
4176
- server: { middlewareMode: true },
4177
- appType: "custom",
4178
- logLevel: "silent",
4179
- // Use the resolved aliases from the real config (includes user's path aliases
4180
- // like @/ -> src/ AND package aliases from rsc-router)
4181
- resolve: { alias: s.userResolveAlias },
4182
- // Enable automatic JSX runtime so .tsx files don't need `import React`.
4183
- // Without this, esbuild defaults to classic mode (React.createElement)
4184
- // which fails when lazy host-router handlers load sub-app modules with JSX.
4185
- esbuild: { jsx: "automatic", jsxImportSource: "react" },
4186
- plugins: [
4187
- rsc({
4188
- entries: {
4189
- client: "virtual:entry-client",
4190
- ssr: "virtual:entry-ssr",
4191
- rsc: s.resolvedEntryPath
4192
- }
4193
- }),
4194
- hashClientRefs(s.projectRoot),
4195
- createVersionPlugin(),
4196
- // Stub virtual modules that the RSC entry may import
4197
- // (e.g., virtual:rsc-router/routes-manifest, virtual:rsc-router/loader-manifest)
4198
- createVirtualStubPlugin(),
4199
- // Inject handle + router IDs so prerender-collected handle data uses
4200
- // the same hashed keys as the production client/SSR bundles, and
4201
- // build-time router IDs match runtime IDs across environments.
4202
- exposeInternalIds({ forceBuild: true }),
4203
- exposeRouterId()
4204
- ]
4205
- });
4755
+ tempServer = await createTempRscServer(s, { forceBuild: true });
4206
4756
  const rscEnv = tempServer.environments?.rsc;
4207
4757
  if (!rscEnv?.runner) {
4208
4758
  console.warn(
@@ -4321,152 +4871,6 @@ ${details}`
4321
4871
  };
4322
4872
  }
4323
4873
 
4324
- // src/vite/rango.ts
4325
- import { readFileSync as readFileSync7 } from "node:fs";
4326
-
4327
- // src/vite/plugins/use-cache-transform.ts
4328
- import path5 from "node:path";
4329
- import MagicString5 from "magic-string";
4330
- var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
4331
- var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
4332
- function useCacheTransform() {
4333
- let projectRoot = "";
4334
- let isBuild = false;
4335
- let rscTransforms = null;
4336
- return {
4337
- name: "@rangojs/router:use-cache",
4338
- enforce: "post",
4339
- configResolved(config) {
4340
- projectRoot = config.root;
4341
- isBuild = config.command === "build";
4342
- },
4343
- async transform(code, id) {
4344
- if (this.environment?.name !== "rsc") return;
4345
- if (!code.includes("use cache")) return;
4346
- if (id.includes("/node_modules/") || id.startsWith("\0")) return;
4347
- if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
4348
- if (!rscTransforms) {
4349
- try {
4350
- rscTransforms = await import("@vitejs/plugin-rsc/transforms");
4351
- } catch {
4352
- return;
4353
- }
4354
- }
4355
- const {
4356
- hasDirective,
4357
- transformWrapExport,
4358
- transformHoistInlineDirective
4359
- } = rscTransforms;
4360
- let ast;
4361
- try {
4362
- const { parseAst: parseAst3 } = await import("vite");
4363
- ast = parseAst3(code);
4364
- } catch {
4365
- return;
4366
- }
4367
- const filePath = normalizePath(path5.relative(projectRoot, id));
4368
- const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
4369
- if (hasDirective(ast.body, "use cache")) {
4370
- return transformFileLevelUseCache(
4371
- code,
4372
- ast,
4373
- filePath,
4374
- id,
4375
- isBuild,
4376
- isLayoutOrTemplate,
4377
- transformWrapExport
4378
- );
4379
- }
4380
- return transformFunctionLevelUseCache(
4381
- code,
4382
- ast,
4383
- filePath,
4384
- id,
4385
- isBuild,
4386
- transformHoistInlineDirective
4387
- );
4388
- }
4389
- };
4390
- }
4391
- function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLayoutOrTemplate, transformWrapExport) {
4392
- const { exportNames, output } = transformWrapExport(code, ast, {
4393
- runtime: (value, name) => {
4394
- const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
4395
- return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, "default")`;
4396
- },
4397
- rejectNonAsyncFunction: false,
4398
- filter: (name) => {
4399
- if (name === "default" && isLayoutOrTemplate) return false;
4400
- return true;
4401
- }
4402
- });
4403
- if (exportNames.length === 0) {
4404
- const s = new MagicString5(code);
4405
- const directive2 = findFileLevelDirective(ast);
4406
- if (directive2) {
4407
- s.overwrite(
4408
- directive2.start,
4409
- directive2.end,
4410
- `/* "use cache" -- wrapped by rango */`
4411
- );
4412
- return {
4413
- code: s.toString(),
4414
- map: s.generateMap({ source: sourceId, hires: "boundary" })
4415
- };
4416
- }
4417
- return;
4418
- }
4419
- output.prepend(
4420
- `import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};
4421
- `
4422
- );
4423
- const directive = findFileLevelDirective(ast);
4424
- if (directive) {
4425
- output.overwrite(
4426
- directive.start,
4427
- directive.end,
4428
- `/* "use cache" -- wrapped by rango */`
4429
- );
4430
- }
4431
- return {
4432
- code: output.toString(),
4433
- map: output.generateMap({ source: sourceId, hires: "boundary" })
4434
- };
4435
- }
4436
- function transformFunctionLevelUseCache(code, ast, filePath, sourceId, isBuild, transformHoistInlineDirective) {
4437
- try {
4438
- const { output, names } = transformHoistInlineDirective(code, ast, {
4439
- directive: /^use cache(:\s*\w+)?$/,
4440
- runtime: (value, name, meta) => {
4441
- const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
4442
- const profileMatch = meta.directiveMatch[1];
4443
- const profileName = profileMatch ? profileMatch.replace(/^:\s*/, "").trim() : "default";
4444
- return `__rango_registerCachedFunction(${value}, ${JSON.stringify(funcId)}, ${JSON.stringify(profileName)})`;
4445
- },
4446
- rejectNonAsyncFunction: false
4447
- });
4448
- if (names.length === 0) return;
4449
- output.prepend(
4450
- `import { registerCachedFunction as __rango_registerCachedFunction } from ${JSON.stringify(CACHE_RUNTIME_IMPORT)};
4451
- `
4452
- );
4453
- return {
4454
- code: output.toString(),
4455
- map: output.generateMap({ source: sourceId, hires: "boundary" })
4456
- };
4457
- } catch {
4458
- return;
4459
- }
4460
- }
4461
- function findFileLevelDirective(ast) {
4462
- for (const node of ast.body ?? []) {
4463
- if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string" && node.expression.value.startsWith("use cache")) {
4464
- return { start: node.start, end: node.end };
4465
- }
4466
- }
4467
- return null;
4468
- }
4469
-
4470
4874
  // src/vite/rango.ts
4471
4875
  async function rango(options) {
4472
4876
  const resolvedOptions = options ?? { preset: "node" };
@@ -4476,7 +4880,7 @@ async function rango(options) {
4476
4880
  const rangoAliases = getPackageAliases();
4477
4881
  const excludeDeps = getExcludeDeps();
4478
4882
  let rscEntryPath = null;
4479
- let routerPath;
4883
+ const routerRef = { path: void 0 };
4480
4884
  const prerenderEnabled = true;
4481
4885
  if (preset === "cloudflare") {
4482
4886
  const { default: rsc } = await import("@vitejs/plugin-rsc");
@@ -4573,35 +4977,39 @@ async function rango(options) {
4573
4977
  plugins.push(createVirtualEntriesPlugin(finalEntries));
4574
4978
  plugins.push(
4575
4979
  rsc({
4576
- get entries() {
4577
- return finalEntries;
4578
- },
4980
+ entries: finalEntries,
4579
4981
  serverHandler: false
4580
4982
  })
4581
4983
  );
4984
+ plugins.push(clientRefDedup());
4582
4985
  } else {
4583
4986
  const nodeOptions = resolvedOptions;
4584
- routerPath = nodeOptions.router;
4585
- if (!routerPath) {
4586
- const earlyFilter = createScanFilter(process.cwd(), {
4587
- include: resolvedOptions.include,
4588
- exclude: resolvedOptions.exclude
4589
- });
4590
- const candidates = findRouterFiles(process.cwd(), earlyFilter);
4591
- if (candidates.length === 1) {
4592
- const abs = candidates[0];
4593
- const rel = abs.startsWith(process.cwd()) ? "./" + abs.slice(process.cwd().length + 1) : abs;
4594
- routerPath = rel;
4595
- } else if (candidates.length > 1) {
4596
- const cwd = process.cwd();
4597
- const list = candidates.map(
4598
- (f) => " - " + (f.startsWith(cwd) ? f.slice(cwd.length + 1) : f)
4599
- ).join("\n");
4600
- throw new Error(
4601
- `[rsc-router] Multiple routers found. Specify \`router\` to choose one:
4987
+ routerRef.path = nodeOptions.router;
4988
+ if (!routerRef.path) {
4989
+ plugins.push({
4990
+ name: "@rangojs/router:auto-discover",
4991
+ config(userConfig) {
4992
+ if (routerRef.path) return;
4993
+ const root = userConfig.root ? resolve9(process.cwd(), userConfig.root) : process.cwd();
4994
+ const filter = createScanFilter(root, {
4995
+ include: resolvedOptions.include,
4996
+ exclude: resolvedOptions.exclude
4997
+ });
4998
+ const candidates = findRouterFiles(root, filter);
4999
+ if (candidates.length === 1) {
5000
+ const abs = candidates[0];
5001
+ routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
5002
+ } else if (candidates.length > 1) {
5003
+ const list = candidates.map(
5004
+ (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
5005
+ ).join("\n");
5006
+ throw new Error(
5007
+ `[rsc-router] Multiple routers found. Specify \`router\` to choose one:
4602
5008
  ${list}`
4603
- );
4604
- }
5009
+ );
5010
+ }
5011
+ }
5012
+ });
4605
5013
  }
4606
5014
  const rscOption = nodeOptions.rsc ?? true;
4607
5015
  if (rscOption !== false) {
@@ -4715,13 +5123,14 @@ ${list}`
4715
5123
  }
4716
5124
  }
4717
5125
  });
4718
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerPath));
5126
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
4719
5127
  plugins.push(
4720
5128
  rsc({
4721
5129
  entries: finalEntries
4722
5130
  })
4723
5131
  );
4724
5132
  }
5133
+ plugins.push(clientRefDedup());
4725
5134
  }
4726
5135
  plugins.push({
4727
5136
  name: "@rangojs/router:client-component-hmr",
@@ -4746,7 +5155,8 @@ ${list}`
4746
5155
  plugins.push(exposeInternalIds());
4747
5156
  plugins.push(exposeRouterId());
4748
5157
  plugins.push(createVersionPlugin());
4749
- const discoveryEntryPath = preset !== "cloudflare" ? routerPath : void 0;
5158
+ const discoveryEntryPath = preset !== "cloudflare" ? routerRef.path : void 0;
5159
+ const discoveryRouterRef = preset !== "cloudflare" ? routerRef : void 0;
4750
5160
  const injectorEntryPath = rscEntryPath ?? (preset === "cloudflare" ? void 0 : null);
4751
5161
  if (injectorEntryPath !== null) {
4752
5162
  plugins.push(createVersionInjectorPlugin(injectorEntryPath));
@@ -4754,6 +5164,7 @@ ${list}`
4754
5164
  plugins.push(createCjsToEsmPlugin());
4755
5165
  plugins.push(
4756
5166
  createRouterDiscoveryPlugin(discoveryEntryPath, {
5167
+ routerPathRef: discoveryRouterRef,
4757
5168
  enableBuildPrerender: prerenderEnabled,
4758
5169
  staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
4759
5170
  include: resolvedOptions.include,
@@ -4762,34 +5173,42 @@ ${list}`
4762
5173
  );
4763
5174
  return plugins;
4764
5175
  }
5176
+
5177
+ // src/vite/plugins/refresh-cmd.ts
5178
+ function poke() {
5179
+ return {
5180
+ name: "vite-plugin-poke",
5181
+ apply: "serve",
5182
+ configureServer(server) {
5183
+ const stdin = process.stdin;
5184
+ const previousRawMode = stdin.isTTY ? stdin.isRaw : null;
5185
+ if (stdin.isTTY) {
5186
+ stdin.setRawMode(true);
5187
+ }
5188
+ const onData = (data) => {
5189
+ if (data.length !== 1) return;
5190
+ if (data[0] === 3) {
5191
+ process.emit("SIGINT", "SIGINT");
5192
+ return;
5193
+ }
5194
+ if (data[0] === 18) {
5195
+ server.hot.send({ type: "full-reload", path: "*" });
5196
+ server.config.logger.info(" browser reload (ctrl+r)", {
5197
+ timestamp: true
5198
+ });
5199
+ }
5200
+ };
5201
+ stdin.on("data", onData);
5202
+ server.httpServer?.on("close", () => {
5203
+ stdin.off("data", onData);
5204
+ if (stdin.isTTY && previousRawMode !== null) {
5205
+ stdin.setRawMode(previousRawMode);
5206
+ }
5207
+ });
5208
+ }
5209
+ };
5210
+ }
4765
5211
  export {
4766
- VIRTUAL_ROUTES_MANIFEST_ID,
4767
- buildRouteToStaticPrefix,
4768
- computeProductionHash,
4769
- createCjsToEsmPlugin,
4770
- createRouterDiscoveryPlugin,
4771
- createVersionInjectorPlugin,
4772
- createVersionPlugin,
4773
- createVirtualEntriesPlugin,
4774
- createVirtualStubPlugin,
4775
- encodePathParam,
4776
- evictHandlerCode,
4777
- exposeActionId,
4778
- exposeInternalIds,
4779
- exposeRouterId,
4780
- extractHandlerExportsFromChunk,
4781
- findMatchingParenInBundle,
4782
- flattenLeafEntries,
4783
- getManualChunks,
4784
- groupByConcurrency,
4785
- hashClientRefs,
4786
- jsonParseExpression,
4787
- notifyOnError,
4788
- onwarn,
4789
- printBanner,
4790
- rango,
4791
- rangoVersion,
4792
- runWithConcurrency,
4793
- sharedEsbuildOptions,
4794
- transformClientRefs
5212
+ poke,
5213
+ rango
4795
5214
  };