@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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 (356) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +71 -21
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +29 -5
  19. package/skills/layout/SKILL.md +13 -9
  20. package/skills/links/SKILL.md +173 -17
  21. package/skills/loader/SKILL.md +170 -23
  22. package/skills/middleware/SKILL.md +16 -10
  23. package/skills/migrate-nextjs/SKILL.md +38 -16
  24. package/skills/mime-routes/SKILL.md +27 -0
  25. package/skills/observability/SKILL.md +137 -0
  26. package/skills/parallel/SKILL.md +11 -7
  27. package/skills/prerender/SKILL.md +14 -33
  28. package/skills/rango/SKILL.md +250 -25
  29. package/skills/react-compiler/SKILL.md +168 -0
  30. package/skills/response-routes/SKILL.md +114 -47
  31. package/skills/route/SKILL.md +42 -5
  32. package/skills/router-setup/SKILL.md +3 -3
  33. package/skills/server-actions/SKILL.md +78 -42
  34. package/skills/tailwind/SKILL.md +27 -3
  35. package/skills/testing/SKILL.md +129 -0
  36. package/skills/testing/bindings.md +89 -0
  37. package/skills/testing/cache-prerender.md +124 -0
  38. package/skills/testing/client-components.md +122 -0
  39. package/skills/testing/e2e-parity.md +125 -0
  40. package/skills/testing/flight.md +92 -0
  41. package/skills/testing/handles.md +129 -0
  42. package/skills/testing/loader.md +128 -0
  43. package/skills/testing/middleware.md +99 -0
  44. package/skills/testing/render-handler.md +121 -0
  45. package/skills/testing/response-routes.md +95 -0
  46. package/skills/testing/reverse-and-types.md +84 -0
  47. package/skills/testing/server-actions.md +107 -0
  48. package/skills/testing/server-tree.md +128 -0
  49. package/skills/testing/setup.md +120 -0
  50. package/skills/typesafety/SKILL.md +316 -26
  51. package/skills/use-cache/SKILL.md +36 -5
  52. package/skills/vercel/SKILL.md +107 -0
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/__internal.ts +0 -65
  57. package/src/browser/action-coordinator.ts +53 -36
  58. package/src/browser/action-fence.ts +47 -0
  59. package/src/browser/app-shell.ts +14 -27
  60. package/src/browser/cookie-name.ts +140 -0
  61. package/src/browser/event-controller.ts +37 -143
  62. package/src/browser/history-state.ts +21 -0
  63. package/src/browser/index.ts +3 -3
  64. package/src/browser/invalidate-client-cache.ts +52 -0
  65. package/src/browser/navigation-bridge.ts +30 -59
  66. package/src/browser/navigation-client.ts +96 -84
  67. package/src/browser/navigation-store-handle.ts +38 -0
  68. package/src/browser/navigation-store.ts +32 -82
  69. package/src/browser/navigation-transaction.ts +9 -59
  70. package/src/browser/partial-update.ts +60 -127
  71. package/src/browser/prefetch/cache.ts +82 -72
  72. package/src/browser/prefetch/fetch.ts +108 -33
  73. package/src/browser/prefetch/queue.ts +6 -3
  74. package/src/browser/rango-state.ts +157 -115
  75. package/src/browser/react/Link.tsx +0 -2
  76. package/src/browser/react/NavigationProvider.tsx +41 -48
  77. package/src/browser/react/ScrollRestoration.tsx +10 -6
  78. package/src/browser/react/filter-segment-order.ts +0 -2
  79. package/src/browser/react/index.ts +0 -48
  80. package/src/browser/react/location-state-shared.ts +166 -8
  81. package/src/browser/react/location-state.ts +39 -14
  82. package/src/browser/react/use-action.ts +6 -15
  83. package/src/browser/react/use-handle.ts +17 -14
  84. package/src/browser/react/use-link-status.ts +0 -4
  85. package/src/browser/react/use-navigation.ts +0 -3
  86. package/src/browser/react/use-params.ts +11 -11
  87. package/src/browser/react/use-reverse.ts +106 -0
  88. package/src/browser/react/use-router.ts +20 -5
  89. package/src/browser/react/use-search-params.ts +0 -5
  90. package/src/browser/react/use-segments.ts +0 -13
  91. package/src/browser/response-adapter.ts +52 -1
  92. package/src/browser/rsc-router.tsx +70 -34
  93. package/src/browser/scroll-restoration.ts +22 -14
  94. package/src/browser/segment-structure-assert.ts +2 -2
  95. package/src/browser/server-action-bridge.ts +168 -44
  96. package/src/browser/types.ts +36 -21
  97. package/src/browser/validate-redirect-origin.ts +43 -16
  98. package/src/build/collect-fallback-refs.ts +107 -0
  99. package/src/build/generate-manifest.ts +60 -35
  100. package/src/build/generate-route-types.ts +3 -0
  101. package/src/build/index.ts +8 -2
  102. package/src/build/prefix-tree-utils.ts +123 -0
  103. package/src/build/route-trie.ts +89 -10
  104. package/src/build/route-types/codegen.ts +4 -4
  105. package/src/build/route-types/include-resolution.ts +1 -1
  106. package/src/build/route-types/param-extraction.ts +6 -3
  107. package/src/build/route-types/per-module-writer.ts +7 -4
  108. package/src/build/route-types/router-processing.ts +122 -22
  109. package/src/build/route-types/scan-filter.ts +1 -1
  110. package/src/build/route-types/source-scan.ts +118 -0
  111. package/src/build/runtime-discovery.ts +9 -20
  112. package/src/cache/cache-error.ts +104 -0
  113. package/src/cache/cache-policy.ts +68 -28
  114. package/src/cache/cache-runtime.ts +134 -32
  115. package/src/cache/cache-scope.ts +100 -74
  116. package/src/cache/cache-tag.ts +98 -0
  117. package/src/cache/cf/cf-cache-store.ts +2255 -238
  118. package/src/cache/cf/index.ts +6 -16
  119. package/src/cache/document-cache.ts +61 -20
  120. package/src/cache/handle-snapshot.ts +63 -0
  121. package/src/cache/index.ts +22 -20
  122. package/src/cache/memory-segment-store.ts +136 -37
  123. package/src/cache/profile-registry.ts +6 -30
  124. package/src/cache/read-through-swr.ts +41 -11
  125. package/src/cache/segment-codec.ts +0 -16
  126. package/src/cache/tag-invalidation.ts +230 -0
  127. package/src/cache/types.ts +33 -100
  128. package/src/cache/vercel/index.ts +11 -0
  129. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  130. package/src/client.rsc.tsx +6 -21
  131. package/src/client.tsx +25 -61
  132. package/src/component-utils.ts +19 -0
  133. package/src/context-var.ts +17 -5
  134. package/src/decode-loader-results.ts +36 -0
  135. package/src/defer.ts +196 -0
  136. package/src/deps/ssr.ts +0 -1
  137. package/src/errors.ts +30 -4
  138. package/src/handle.ts +31 -23
  139. package/src/handles/MetaTags.tsx +0 -14
  140. package/src/handles/breadcrumbs.ts +16 -5
  141. package/src/handles/meta.ts +0 -39
  142. package/src/host/cookie-handler.ts +0 -36
  143. package/src/host/errors.ts +0 -24
  144. package/src/host/index.ts +8 -2
  145. package/src/host/pattern-matcher.ts +7 -50
  146. package/src/host/router.ts +107 -99
  147. package/src/host/testing.ts +40 -27
  148. package/src/host/types.ts +37 -4
  149. package/src/host/utils.ts +1 -1
  150. package/src/href-client.ts +137 -22
  151. package/src/index.rsc.ts +63 -9
  152. package/src/index.ts +64 -9
  153. package/src/internal-debug.ts +2 -4
  154. package/src/loader-store.ts +500 -0
  155. package/src/loader.rsc.ts +20 -13
  156. package/src/loader.ts +12 -11
  157. package/src/missing-id-error.ts +68 -0
  158. package/src/network-error-thrower.tsx +1 -6
  159. package/src/outlet-provider.tsx +1 -5
  160. package/src/prerender/param-hash.ts +10 -11
  161. package/src/prerender/store.ts +32 -37
  162. package/src/prerender.ts +61 -6
  163. package/src/redirect-origin.ts +100 -0
  164. package/src/response-utils.ts +9 -0
  165. package/src/reverse.ts +65 -40
  166. package/src/root-error-boundary.tsx +1 -19
  167. package/src/route-content-wrapper.tsx +7 -72
  168. package/src/route-definition/dsl-helpers.ts +244 -281
  169. package/src/route-definition/helper-factories.ts +29 -139
  170. package/src/route-definition/helpers-types.ts +40 -17
  171. package/src/route-definition/redirect.ts +43 -9
  172. package/src/route-definition/resolve-handler-use.ts +6 -0
  173. package/src/route-definition/use-item-types.ts +32 -0
  174. package/src/route-map-builder.ts +0 -16
  175. package/src/route-types.ts +19 -41
  176. package/src/router/basename.ts +14 -0
  177. package/src/router/content-negotiation.ts +15 -15
  178. package/src/router/error-handling.ts +13 -17
  179. package/src/router/find-match.ts +44 -23
  180. package/src/router/handler-context.ts +4 -41
  181. package/src/router/intercept-resolution.ts +14 -19
  182. package/src/router/lazy-includes.ts +9 -46
  183. package/src/router/loader-resolution.ts +91 -46
  184. package/src/router/logging.ts +0 -6
  185. package/src/router/manifest.ts +18 -29
  186. package/src/router/match-api.ts +0 -20
  187. package/src/router/match-context.ts +0 -22
  188. package/src/router/match-handlers.ts +57 -58
  189. package/src/router/match-middleware/background-revalidation.ts +0 -7
  190. package/src/router/match-middleware/cache-lookup.ts +150 -271
  191. package/src/router/match-middleware/cache-store.ts +3 -33
  192. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  193. package/src/router/match-middleware/segment-resolution.ts +0 -22
  194. package/src/router/match-pipelines.ts +1 -42
  195. package/src/router/match-result.ts +31 -80
  196. package/src/router/metrics.ts +0 -34
  197. package/src/router/middleware-types.ts +5 -112
  198. package/src/router/middleware.ts +118 -133
  199. package/src/router/navigation-snapshot.ts +0 -51
  200. package/src/router/params-util.ts +23 -0
  201. package/src/router/pattern-matching.ts +62 -67
  202. package/src/router/prerender-match.ts +99 -63
  203. package/src/router/preview-match.ts +3 -1
  204. package/src/router/request-classification.ts +28 -62
  205. package/src/router/revalidation.ts +50 -56
  206. package/src/router/route-snapshot.ts +0 -1
  207. package/src/router/router-context.ts +0 -27
  208. package/src/router/router-interfaces.ts +68 -35
  209. package/src/router/router-options.ts +55 -1
  210. package/src/router/router-registry.ts +2 -5
  211. package/src/router/segment-resolution/fresh.ts +44 -63
  212. package/src/router/segment-resolution/helpers.ts +34 -0
  213. package/src/router/segment-resolution/loader-cache.ts +40 -37
  214. package/src/router/segment-resolution/revalidation.ts +203 -285
  215. package/src/router/segment-resolution/static-store.ts +19 -5
  216. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  217. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  218. package/src/router/segment-resolution.ts +4 -1
  219. package/src/router/segment-wrappers.ts +0 -3
  220. package/src/router/state-cookie-name.ts +33 -0
  221. package/src/router/substitute-pattern-params.ts +56 -0
  222. package/src/router/telemetry-otel.ts +0 -20
  223. package/src/router/telemetry.ts +96 -19
  224. package/src/router/timeout.ts +0 -20
  225. package/src/router/trie-matching.ts +87 -48
  226. package/src/router/types.ts +9 -63
  227. package/src/router/url-params.ts +0 -5
  228. package/src/router.ts +80 -41
  229. package/src/rsc/handler-context.ts +3 -2
  230. package/src/rsc/handler.ts +83 -78
  231. package/src/rsc/helpers.ts +93 -5
  232. package/src/rsc/index.ts +1 -1
  233. package/src/rsc/json-route-result.ts +38 -0
  234. package/src/rsc/manifest-init.ts +28 -41
  235. package/src/rsc/origin-guard.ts +39 -25
  236. package/src/rsc/progressive-enhancement.ts +12 -1
  237. package/src/rsc/redirect-guard.ts +99 -0
  238. package/src/rsc/response-error.ts +79 -12
  239. package/src/rsc/response-route-handler.ts +76 -62
  240. package/src/rsc/rsc-rendering.ts +41 -60
  241. package/src/rsc/runtime-warnings.ts +23 -10
  242. package/src/rsc/server-action.ts +62 -67
  243. package/src/rsc/ssr-setup.ts +16 -0
  244. package/src/rsc/types.ts +10 -5
  245. package/src/runtime-env.ts +18 -0
  246. package/src/search-params.ts +4 -20
  247. package/src/segment-loader-promise.ts +14 -2
  248. package/src/segment-system.tsx +199 -142
  249. package/src/serialize.ts +243 -0
  250. package/src/server/context.ts +150 -51
  251. package/src/server/cookie-store.ts +80 -5
  252. package/src/server/handle-store.ts +7 -24
  253. package/src/server/loader-registry.ts +5 -24
  254. package/src/server/request-context.ts +165 -87
  255. package/src/ssr/index.tsx +14 -14
  256. package/src/static-handler.ts +10 -13
  257. package/src/testing/cache-status.ts +162 -0
  258. package/src/testing/collect-handle.ts +40 -0
  259. package/src/testing/dispatch.ts +618 -0
  260. package/src/testing/dom.entry.ts +22 -0
  261. package/src/testing/e2e/fixture.ts +188 -0
  262. package/src/testing/e2e/index.ts +128 -0
  263. package/src/testing/e2e/matchers.ts +35 -0
  264. package/src/testing/e2e/page-helpers.ts +272 -0
  265. package/src/testing/e2e/parity.ts +387 -0
  266. package/src/testing/e2e/server.ts +195 -0
  267. package/src/testing/flight-matchers.ts +97 -0
  268. package/src/testing/flight-normalize.ts +11 -0
  269. package/src/testing/flight-runtime.d.ts +57 -0
  270. package/src/testing/flight-tree.ts +682 -0
  271. package/src/testing/flight.entry.ts +52 -0
  272. package/src/testing/flight.ts +232 -0
  273. package/src/testing/generated-routes.ts +183 -0
  274. package/src/testing/index.ts +99 -0
  275. package/src/testing/internal/context.ts +348 -0
  276. package/src/testing/internal/flight-client-globals.ts +30 -0
  277. package/src/testing/internal/seed-vars.ts +54 -0
  278. package/src/testing/render-handler.ts +330 -0
  279. package/src/testing/render-route.tsx +566 -0
  280. package/src/testing/run-loader.ts +378 -0
  281. package/src/testing/run-middleware.ts +205 -0
  282. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  283. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  284. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  285. package/src/testing/vitest-stubs/version.ts +5 -0
  286. package/src/testing/vitest.ts +305 -0
  287. package/src/theme/ThemeProvider.tsx +0 -52
  288. package/src/theme/ThemeScript.tsx +0 -6
  289. package/src/theme/constants.ts +0 -12
  290. package/src/theme/index.ts +0 -7
  291. package/src/theme/theme-context.ts +1 -5
  292. package/src/theme/theme-script.ts +0 -14
  293. package/src/theme/use-theme.ts +0 -3
  294. package/src/types/boundaries.ts +0 -35
  295. package/src/types/cache-types.ts +13 -4
  296. package/src/types/error-types.ts +30 -90
  297. package/src/types/global-namespace.ts +54 -41
  298. package/src/types/handler-context.ts +97 -22
  299. package/src/types/index.ts +1 -10
  300. package/src/types/loader-types.ts +6 -3
  301. package/src/types/request-scope.ts +0 -19
  302. package/src/types/route-config.ts +6 -50
  303. package/src/types/route-entry.ts +0 -6
  304. package/src/types/segments.ts +18 -14
  305. package/src/urls/include-helper.ts +9 -56
  306. package/src/urls/index.ts +1 -11
  307. package/src/urls/path-helper-types.ts +19 -5
  308. package/src/urls/path-helper.ts +17 -106
  309. package/src/urls/pattern-types.ts +36 -19
  310. package/src/urls/response-types.ts +20 -19
  311. package/src/urls/type-extraction.ts +58 -139
  312. package/src/urls/urls-function.ts +1 -18
  313. package/src/use-loader.tsx +292 -107
  314. package/src/vite/debug.ts +1 -0
  315. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  316. package/src/vite/discovery/discover-routers.ts +95 -82
  317. package/src/vite/discovery/discovery-errors.ts +194 -0
  318. package/src/vite/discovery/prerender-collection.ts +26 -34
  319. package/src/vite/discovery/route-types-writer.ts +40 -84
  320. package/src/vite/discovery/state.ts +39 -1
  321. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  322. package/src/vite/index.ts +4 -0
  323. package/src/vite/plugin-types.ts +185 -10
  324. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  325. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  326. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  327. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  328. package/src/vite/plugins/expose-action-id.ts +4 -75
  329. package/src/vite/plugins/expose-id-utils.ts +3 -54
  330. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  331. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  332. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  333. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  334. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  335. package/src/vite/plugins/performance-tracks.ts +9 -16
  336. package/src/vite/plugins/refresh-cmd.ts +1 -1
  337. package/src/vite/plugins/use-cache-transform.ts +26 -49
  338. package/src/vite/plugins/vercel-output.ts +258 -0
  339. package/src/vite/plugins/version-injector.ts +2 -32
  340. package/src/vite/plugins/version-plugin.ts +32 -23
  341. package/src/vite/plugins/virtual-entries.ts +35 -17
  342. package/src/vite/rango.ts +148 -115
  343. package/src/vite/router-discovery.ts +220 -68
  344. package/src/vite/utils/ast-handler-extract.ts +15 -31
  345. package/src/vite/utils/bundle-analysis.ts +10 -15
  346. package/src/vite/utils/client-chunks.ts +184 -0
  347. package/src/vite/utils/forward-user-plugins.ts +171 -0
  348. package/src/vite/utils/manifest-utils.ts +4 -59
  349. package/src/vite/utils/package-resolution.ts +1 -73
  350. package/src/vite/utils/prerender-utils.ts +0 -34
  351. package/src/vite/utils/shared-utils.ts +95 -43
  352. package/src/browser/action-response-classifier.ts +0 -99
  353. package/src/browser/react/use-client-cache.ts +0 -58
  354. package/src/browser/shallow.ts +0 -40
  355. package/src/handles/index.ts +0 -7
  356. package/src/router/middleware-cookies.ts +0 -55
@@ -1,6 +1,6 @@
1
1
  // src/vite/rango.ts
2
2
  import { readFileSync as readFileSync7 } from "node:fs";
3
- import { resolve as resolve9 } from "node:path";
3
+ import { resolve as resolve10 } from "node:path";
4
4
 
5
5
  // src/vite/plugins/expose-action-id.ts
6
6
  import MagicString from "magic-string";
@@ -21,8 +21,8 @@ function hashId(filePath, exportName) {
21
21
  function makeStubId(filePath, exportName, isBuild) {
22
22
  return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
23
23
  }
24
- function hashInlineId(filePath, lineNumber, index) {
25
- const input = index !== void 0 && index > 0 ? `${filePath}:${lineNumber}:${index}` : `${filePath}:${lineNumber}`;
24
+ function hashInlineId(filePath, fnName, index) {
25
+ const input = `${filePath}:${fnName}:${index}`;
26
26
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
27
27
  }
28
28
  function buildExportMap(program) {
@@ -200,7 +200,8 @@ var NS = {
200
200
  prerender: "rango:prerender",
201
201
  build: "rango:build",
202
202
  dev: "rango:dev",
203
- transform: "rango:transform"
203
+ transform: "rango:transform",
204
+ chunks: "rango:chunks"
204
205
  };
205
206
  if (process.env.INTERNAL_RANGO_DEBUG) {
206
207
  const existing = debugFactory.disable();
@@ -292,7 +293,7 @@ function getRscPluginApi(config) {
292
293
  );
293
294
  if (plugin) {
294
295
  console.warn(
295
- `[rsc-router:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). Consider updating the name lookup if the plugin was renamed.`
296
+ `[rango:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). Consider updating the name lookup if the plugin was renamed.`
296
297
  );
297
298
  }
298
299
  }
@@ -393,7 +394,7 @@ function exposeActionId() {
393
394
  }
394
395
  if (!rscPluginApi) {
395
396
  throw new Error(
396
- "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
397
+ "[rango] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
397
398
  );
398
399
  }
399
400
  if (!isBuild) return;
@@ -430,7 +431,6 @@ function exposeActionId() {
430
431
  counterTransform?.record(id, performance.now() - start);
431
432
  }
432
433
  },
433
- // Build mode: renderChunk runs after all transforms and bundling complete
434
434
  renderChunk(code, chunk) {
435
435
  const start = counterRender ? performance.now() : 0;
436
436
  try {
@@ -465,7 +465,7 @@ function exposeActionId() {
465
465
 
466
466
  // src/vite/plugins/expose-internal-ids.ts
467
467
  import { parseAst as parseAst2 } from "vite";
468
- import MagicString4 from "magic-string";
468
+ import MagicString3 from "magic-string";
469
469
  import path4 from "node:path";
470
470
 
471
471
  // src/vite/utils/ast-handler-extract.ts
@@ -475,7 +475,7 @@ function isDirectivePrologueStatement(node) {
475
475
  function findImportInsertionPos(code, parseAst4) {
476
476
  let program;
477
477
  try {
478
- program = parseAst4(code, { jsx: true });
478
+ program = parseAst4(code, { lang: "tsx" });
479
479
  } catch {
480
480
  return 0;
481
481
  }
@@ -515,7 +515,7 @@ function walkNode(node, parent, ancestors, enter) {
515
515
  function findHandlerCalls(code, fnName, parseAst4) {
516
516
  let program;
517
517
  try {
518
- program = parseAst4(code, { jsx: true });
518
+ program = parseAst4(code, { lang: "tsx" });
519
519
  } catch {
520
520
  return [];
521
521
  }
@@ -589,7 +589,7 @@ function getImportedLocalNamesFromProgram(program, importedName) {
589
589
  }
590
590
  function getImportedLocalNames(code, importedName, parseAst4) {
591
591
  try {
592
- const program = parseAst4(code, { jsx: true });
592
+ const program = parseAst4(code, { lang: "tsx" });
593
593
  return getImportedLocalNamesFromProgram(program, importedName);
594
594
  } catch {
595
595
  return /* @__PURE__ */ new Set();
@@ -598,7 +598,7 @@ function getImportedLocalNames(code, importedName, parseAst4) {
598
598
  function extractImportDeclarations(code, parseAst4) {
599
599
  let program;
600
600
  try {
601
- program = parseAst4(code, { jsx: true });
601
+ program = parseAst4(code, { lang: "tsx" });
602
602
  } catch {
603
603
  return [];
604
604
  }
@@ -653,7 +653,7 @@ function isSafeVariableDeclaration(node, handlerNames) {
653
653
  function extractModuleLevelDeclarations(code, parseAst4, handlerNames) {
654
654
  let program;
655
655
  try {
656
- program = parseAst4(code, { jsx: true });
656
+ program = parseAst4(code, { lang: "tsx" });
657
657
  } catch {
658
658
  return [];
659
659
  }
@@ -697,14 +697,12 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
697
697
  parseAst4,
698
698
  handlerNames
699
699
  );
700
- const lineCounts = /* @__PURE__ */ new Map();
701
700
  const importStatements = [];
702
- for (const site of inlineSites) {
703
- const lineCount = lineCounts.get(site.lineNumber) ?? 0;
704
- lineCounts.set(site.lineNumber, lineCount + 1);
705
- const hash = hashInlineId(filePath, site.lineNumber, lineCount);
701
+ for (const [siteIndex, site] of inlineSites.entries()) {
702
+ const hash = hashInlineId(filePath, fnName, siteIndex);
706
703
  const exportName = `__sh_${hash}`;
707
- const virtualId = `\0${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
704
+ const idSuffix = `${filePath}:${fnName}:${siteIndex}`;
705
+ const virtualId = `\0${virtualPrefix}${idSuffix}`;
708
706
  const handlerCode = code.slice(site.callStart, site.callEnd);
709
707
  virtualRegistry.set(virtualId, {
710
708
  originalModuleId: moduleId,
@@ -714,7 +712,7 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
714
712
  exportName
715
713
  });
716
714
  s.overwrite(site.callStart, site.callEnd, exportName);
717
- const importId = `${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
715
+ const importId = `${virtualPrefix}${idSuffix}`;
718
716
  importStatements.push(`import { ${exportName} } from "${importId}";`);
719
717
  }
720
718
  if (importStatements.length > 0) {
@@ -746,6 +744,83 @@ var STRICT_CREATE_CONFIGS = [
746
744
 
747
745
  // src/vite/plugins/expose-ids/export-analysis.ts
748
746
  import { parseAst } from "vite";
747
+
748
+ // src/build/route-types/source-scan.ts
749
+ function isLineTerminator(ch) {
750
+ const c = ch.charCodeAt(0);
751
+ return c === 10 || c === 13 || c === 8232 || c === 8233;
752
+ }
753
+ function makeCodeClassifier(code) {
754
+ const n = code.length;
755
+ let i = 0;
756
+ let skipStart = -1;
757
+ let skipEnd = -1;
758
+ return (q) => {
759
+ if (q >= skipStart && q < skipEnd) return false;
760
+ while (i < n && i <= q) {
761
+ const c = code[i];
762
+ const d = i + 1 < n ? code[i + 1] : "";
763
+ let end = -1;
764
+ if (c === "/" && d === "/") {
765
+ let j = i + 2;
766
+ while (j < n && !isLineTerminator(code[j])) j++;
767
+ end = j;
768
+ } else if (c === "/" && d === "*") {
769
+ let j = i + 2;
770
+ while (j < n && !(code[j] === "*" && code[j + 1] === "/")) j++;
771
+ end = Math.min(n, j + 2);
772
+ } else if (c === '"' || c === "'" || c === "`") {
773
+ let j = i + 1;
774
+ while (j < n) {
775
+ if (code[j] === "\\") {
776
+ j += 2;
777
+ continue;
778
+ }
779
+ if (code[j] === c) {
780
+ j++;
781
+ break;
782
+ }
783
+ j++;
784
+ }
785
+ end = j;
786
+ }
787
+ if (end >= 0) {
788
+ if (q < end) {
789
+ skipStart = i;
790
+ skipEnd = end;
791
+ return false;
792
+ }
793
+ i = end;
794
+ } else {
795
+ i++;
796
+ }
797
+ }
798
+ return true;
799
+ };
800
+ }
801
+ function firstCodeMatchIndex(code, pattern) {
802
+ const inCode = makeCodeClassifier(code);
803
+ pattern.lastIndex = 0;
804
+ let m;
805
+ while ((m = pattern.exec(code)) !== null) {
806
+ if (inCode(m.index)) return m.index;
807
+ if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
808
+ }
809
+ return -1;
810
+ }
811
+ function codeMatchIndices(code, pattern) {
812
+ const inCode = makeCodeClassifier(code);
813
+ const indices = [];
814
+ pattern.lastIndex = 0;
815
+ let m;
816
+ while ((m = pattern.exec(code)) !== null) {
817
+ if (inCode(m.index)) indices.push(m.index);
818
+ if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
819
+ }
820
+ return indices;
821
+ }
822
+
823
+ // src/vite/plugins/expose-ids/export-analysis.ts
749
824
  function isExportOnlyFile(code, bindings) {
750
825
  if (bindings.length === 0) return false;
751
826
  const knownLocals = /* @__PURE__ */ new Set();
@@ -774,12 +849,30 @@ function isExportOnlyFile(code, bindings) {
774
849
  }
775
850
  return true;
776
851
  }
777
- function countCreateCallsForNames(code, fnNames) {
778
- const pattern = new RegExp(
852
+ function createCallPattern(fnNames) {
853
+ return new RegExp(
779
854
  `\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
780
855
  "g"
781
856
  );
782
- return (code.match(pattern) || []).length;
857
+ }
858
+ function countCreateCallsForNames(code, fnNames) {
859
+ return codeMatchIndices(code, createCallPattern(fnNames)).length;
860
+ }
861
+ function offsetToLineColumn(code, index) {
862
+ let line = 1;
863
+ let lineStart = 0;
864
+ const end = Math.min(index, code.length);
865
+ for (let i = 0; i < end; i++) {
866
+ if (code[i] === "\n") {
867
+ line++;
868
+ lineStart = i + 1;
869
+ }
870
+ }
871
+ return { line, column: index - lineStart + 1 };
872
+ }
873
+ function findUnsupportedCreateCallSites(code, fnNames, supportedBindings) {
874
+ const supported = new Set(supportedBindings.map((b) => b.callExprStart));
875
+ return codeMatchIndices(code, createCallPattern(fnNames)).filter((index) => !supported.has(index)).map((index) => offsetToLineColumn(code, index));
783
876
  }
784
877
  function getImportedFnNames(code, importedName) {
785
878
  const importPattern = /import\s*\{([^}]*)\}\s*from\s*["']@rangojs\/router(?:\/[^"']*)?["']/g;
@@ -810,6 +903,17 @@ function getCalledIdentifierFromCall(callExpr) {
810
903
  }
811
904
  return null;
812
905
  }
906
+ function unwrapSignatureWrappedCall(init, fnNameSet) {
907
+ if (init?.type !== "CallExpression") return init;
908
+ const directId = getCalledIdentifierFromCall(init);
909
+ if (directId && fnNameSet.has(directId)) return init;
910
+ const firstArg = init.arguments?.[0];
911
+ if (firstArg?.type === "CallExpression") {
912
+ const innerId = getCalledIdentifierFromCall(firstArg);
913
+ if (innerId && fnNameSet.has(innerId)) return firstArg;
914
+ }
915
+ return init;
916
+ }
813
917
  function collectCreateExportBindingsFallback(code, fnNames) {
814
918
  const alternation = fnNames.map(escapeRegExp).join("|");
815
919
  const exportConstPattern = new RegExp(
@@ -869,7 +973,7 @@ function collectCreateExportBindingsFallback(code, fnNames) {
869
973
  function collectCreateExportBindings(code, fnNames, program) {
870
974
  if (!program) {
871
975
  try {
872
- program = parseAst(code, { jsx: true });
976
+ program = parseAst(code, { lang: "tsx" });
873
977
  } catch {
874
978
  return collectCreateExportBindingsFallback(code, fnNames);
875
979
  }
@@ -882,16 +986,16 @@ function collectCreateExportBindings(code, fnNames, program) {
882
986
  return;
883
987
  }
884
988
  for (const decl of varDecl.declarations ?? []) {
885
- const calledIdentifier = getCalledIdentifierFromCall(decl?.init);
886
- if (decl?.id?.type !== "Identifier" || decl?.init?.type !== "CallExpression" || !calledIdentifier || !fnNameSet.has(calledIdentifier)) {
989
+ const callExpr = unwrapSignatureWrappedCall(decl?.init, fnNameSet);
990
+ const calledIdentifier = getCalledIdentifierFromCall(callExpr);
991
+ if (decl?.id?.type !== "Identifier" || callExpr?.type !== "CallExpression" || !calledIdentifier || !fnNameSet.has(calledIdentifier)) {
887
992
  continue;
888
993
  }
889
994
  const localName = decl.id.name;
890
995
  const exportNames = exportMap.get(localName) ?? [];
891
996
  if (exportNames.length === 0) continue;
892
- const callStart = decl.init.start;
893
- const callEnd = decl.init.end;
894
- const calleeEnd = decl.init.callee.end;
997
+ const callEnd = callExpr.end;
998
+ const calleeEnd = callExpr.callee.end;
895
999
  let openParenPos = -1;
896
1000
  for (let i = calleeEnd; i < callEnd; i++) {
897
1001
  if (code[i] === "(") {
@@ -905,10 +1009,10 @@ function collectCreateExportBindings(code, fnNames, program) {
905
1009
  bindings.push({
906
1010
  localName,
907
1011
  exportNames,
908
- callExprStart: decl.init.start,
1012
+ callExprStart: callExpr.start,
909
1013
  callOpenParenPos: openParenPos,
910
1014
  callCloseParenPos: closeParenPos,
911
- argCount: decl.init.arguments?.length ?? 0,
1015
+ argCount: callExpr.arguments?.length ?? 0,
912
1016
  statementEnd
913
1017
  });
914
1018
  }
@@ -927,9 +1031,20 @@ function collectCreateExportBindings(code, fnNames, program) {
927
1031
  }
928
1032
  return bindings;
929
1033
  }
930
- function buildUnsupportedShapeWarning(filePath, fnName) {
931
- return [
932
- `[rsc-router] Unsupported ${fnName} shape in "${filePath}".`,
1034
+ function buildUnsupportedShapeWarning(filePath, fnName, sites = []) {
1035
+ const lines = [`[rango] Unsupported ${fnName} shape in "${filePath}".`];
1036
+ if (sites.length === 1) {
1037
+ const s = sites[0];
1038
+ lines.push(
1039
+ `The ${fnName}(...) call at ${filePath}:${s.line}:${s.column} has no stable $$id injected \u2014 it is not in a supported shape.`
1040
+ );
1041
+ } else if (sites.length > 1) {
1042
+ lines.push(
1043
+ `These ${fnName}(...) calls have no stable $$id injected \u2014 they are not in a supported shape:`
1044
+ );
1045
+ for (const s of sites) lines.push(` - ${filePath}:${s.line}:${s.column}`);
1046
+ }
1047
+ lines.push(
933
1048
  `Supported shapes are:`,
934
1049
  ` - export const X = ${fnName}(...)`,
935
1050
  ` - const X = ${fnName}(...); export { X }`,
@@ -937,7 +1052,8 @@ function buildUnsupportedShapeWarning(filePath, fnName) {
937
1052
  `Potentially unsupported forms include:`,
938
1053
  ` - export let/var X = ${fnName}(...)`,
939
1054
  ` - inline ${fnName}(...) calls`
940
- ].join("\n");
1055
+ );
1056
+ return lines.join("\n");
941
1057
  }
942
1058
 
943
1059
  // src/vite/plugins/expose-ids/loader-transform.ts
@@ -951,7 +1067,7 @@ function generateClientLoaderStubs(bindings, code, filePath, isBuild) {
951
1067
  const lines = [];
952
1068
  for (const binding of bindings) {
953
1069
  for (const name of binding.exportNames) {
954
- const loaderId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1070
+ const loaderId = makeStubId(filePath, name, isBuild);
955
1071
  lines.push(
956
1072
  `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`
957
1073
  );
@@ -963,7 +1079,7 @@ function transformLoaders(bindings, s, filePath, isBuild) {
963
1079
  let hasChanges = false;
964
1080
  for (const binding of bindings) {
965
1081
  const exportName = binding.exportNames[0];
966
- const loaderId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1082
+ const loaderId = makeStubId(filePath, exportName, isBuild);
967
1083
  const paramInjection = binding.argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
968
1084
  s.appendLeft(binding.callCloseParenPos, paramInjection);
969
1085
  const propInjection = `
@@ -975,7 +1091,6 @@ ${binding.localName}.$$id = "${loaderId}";`;
975
1091
  }
976
1092
 
977
1093
  // src/vite/plugins/expose-ids/handler-transform.ts
978
- import MagicString2 from "magic-string";
979
1094
  function analyzeCreateHandleArgs(code, startPos, endPos) {
980
1095
  const content = code.slice(startPos, endPos).trim();
981
1096
  return { hasArgs: content.length > 0 };
@@ -989,7 +1104,7 @@ function transformHandles(bindings, s, code, filePath, isBuild) {
989
1104
  binding.callOpenParenPos + 1,
990
1105
  binding.callCloseParenPos
991
1106
  );
992
- const handleId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1107
+ const handleId = makeStubId(filePath, exportName, isBuild);
993
1108
  let paramInjection;
994
1109
  if (!args.hasArgs) {
995
1110
  paramInjection = `undefined, "${handleId}"`;
@@ -1008,7 +1123,7 @@ function transformLocationState(bindings, s, filePath, isBuild) {
1008
1123
  let hasChanges = false;
1009
1124
  for (const binding of bindings) {
1010
1125
  const exportName = binding.exportNames[0];
1011
- const stateKey = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1126
+ const stateKey = makeStubId(filePath, exportName, isBuild);
1012
1127
  const propInjection = `
1013
1128
  ${binding.localName}.__rsc_ls_key = "__rsc_ls_${stateKey}";`;
1014
1129
  s.appendRight(binding.statementEnd, propInjection);
@@ -1020,7 +1135,7 @@ function generateWholeFileStubs(cfg, bindings, code, filePath, isBuild) {
1020
1135
  if (!isExportOnlyFile(code, bindings)) return null;
1021
1136
  const exportNames = bindings.flatMap((b) => b.exportNames);
1022
1137
  const stubs = exportNames.map((name) => {
1023
- const handlerId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1138
+ const handlerId = makeStubId(filePath, name, isBuild);
1024
1139
  return `export const ${name} = { __brand: "${cfg.brand}", $$id: "${handlerId}" };`;
1025
1140
  });
1026
1141
  return { code: stubs.join("\n") + "\n", map: null };
@@ -1029,7 +1144,7 @@ function stubHandlerExprs(cfg, bindings, s, filePath, isBuild) {
1029
1144
  let hasChanges = false;
1030
1145
  for (const binding of bindings) {
1031
1146
  const exportName = binding.exportNames[0];
1032
- const handlerId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1147
+ const handlerId = makeStubId(filePath, exportName, isBuild);
1033
1148
  s.overwrite(
1034
1149
  binding.callExprStart,
1035
1150
  binding.callCloseParenPos + 1,
@@ -1043,7 +1158,7 @@ function transformHandlerIds(cfg, bindings, s, filePath, isBuild) {
1043
1158
  let hasChanges = false;
1044
1159
  for (const binding of bindings) {
1045
1160
  const exportName = binding.exportNames[0];
1046
- const handlerId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1161
+ const handlerId = makeStubId(filePath, exportName, isBuild);
1047
1162
  let paramInjection;
1048
1163
  if (binding.argCount === 0) {
1049
1164
  paramInjection = `undefined, "${handlerId}"`;
@@ -1062,7 +1177,7 @@ ${binding.localName}.$$id = "${handlerId}";`;
1062
1177
  }
1063
1178
 
1064
1179
  // src/vite/plugins/expose-ids/router-transform.ts
1065
- import MagicString3 from "magic-string";
1180
+ import MagicString2 from "magic-string";
1066
1181
  import path3 from "node:path";
1067
1182
  import { createHash } from "node:crypto";
1068
1183
  var debug2 = createRangoDebugger(NS.transform);
@@ -1072,10 +1187,10 @@ function transformRouter(code, filePath, routerFnNames, absolutePath) {
1072
1187
  "g"
1073
1188
  );
1074
1189
  let match;
1075
- const s = new MagicString3(code);
1190
+ const s = new MagicString2(code);
1076
1191
  let changed = false;
1077
- const basename3 = path3.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
1078
- const routeNamesImport = `./${basename3}.named-routes.gen.js`;
1192
+ const basename2 = path3.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
1193
+ const routeNamesImport = `./${basename2}.named-routes.gen.js`;
1079
1194
  const routeNamesVar = `__rsc_rn`;
1080
1195
  while ((match = pat.exec(code)) !== null) {
1081
1196
  const callStart = match.index;
@@ -1229,6 +1344,7 @@ ${lazyImports.join(",\n")}
1229
1344
  // --------------- Loader pre-scan (build mode) ---------------
1230
1345
  async buildStart() {
1231
1346
  if (!isBuild) return;
1347
+ if (this.environment && this.environment.name !== "rsc") return;
1232
1348
  const fs2 = await import("node:fs/promises");
1233
1349
  const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage"]);
1234
1350
  async function scanDir(dir) {
@@ -1335,7 +1451,7 @@ ${lazyImports.join(",\n")}
1335
1451
  }
1336
1452
  if (_cachedAst !== void 0 || _astParseFailed) return _cachedAst;
1337
1453
  try {
1338
- _cachedAst = parseAst2(code, { jsx: true });
1454
+ _cachedAst = parseAst2(code, { lang: "tsx" });
1339
1455
  } catch {
1340
1456
  _astParseFailed = true;
1341
1457
  }
@@ -1358,13 +1474,16 @@ ${lazyImports.join(",\n")}
1358
1474
  const hasCode = cfg.fnName === "createLoader" ? hasLoaderCode : cfg.fnName === "createHandle" ? hasHandleCode : hasLocationStateCode;
1359
1475
  if (!hasCode) continue;
1360
1476
  const fnNames = getFnNames(cfg.fnName);
1361
- const totalCalls = countCreateCallsForNames(code, fnNames);
1362
- const supportedBindings = getBindings(code, fnNames).length;
1363
- if (totalCalls <= supportedBindings) continue;
1477
+ const sites = findUnsupportedCreateCallSites(
1478
+ code,
1479
+ fnNames,
1480
+ getBindings(code, fnNames)
1481
+ );
1482
+ if (sites.length === 0) continue;
1364
1483
  const warnKey = `${id}::${cfg.fnName}`;
1365
1484
  if (unsupportedShapeWarnings.has(warnKey)) continue;
1366
1485
  unsupportedShapeWarnings.add(warnKey);
1367
- this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
1486
+ this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName, sites));
1368
1487
  }
1369
1488
  if (hasLoaderCode && isRscEnv) {
1370
1489
  const fnNames = getFnNames("createLoader");
@@ -1401,15 +1520,6 @@ ${lazyImports.join(",\n")}
1401
1520
  );
1402
1521
  if (wholeFile) return wholeFile;
1403
1522
  }
1404
- if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1405
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1406
- const exportNames = getBindings(code, fnNames).map(
1407
- (b) => b.exportNames[0]
1408
- );
1409
- if (exportNames.length > 0) {
1410
- prerenderHandlerModules.set(id, exportNames);
1411
- }
1412
- }
1413
1523
  let changed = false;
1414
1524
  const handlerConfigs = [
1415
1525
  hasStaticHandlerCode && STATIC_CONFIG,
@@ -1422,7 +1532,7 @@ ${lazyImports.join(",\n")}
1422
1532
  const totalCalls = countCreateCallsForNames(code, fnNames);
1423
1533
  const supportedBindings = getBindings(code, fnNames).length;
1424
1534
  if (totalCalls > supportedBindings) {
1425
- const iterS = new MagicString4(code);
1535
+ const iterS = new MagicString3(code);
1426
1536
  const result = transformInlineHandlers(
1427
1537
  cfg.fnName,
1428
1538
  VIRTUAL_HANDLER_PREFIX,
@@ -1587,16 +1697,24 @@ ${lazyImports.join(",\n")}
1587
1697
  return { code: lines.join("\n") + "\n", map: null };
1588
1698
  }
1589
1699
  }
1590
- if (hasStaticHandlerCode && isRscEnv && isBuild) {
1591
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1592
- const exportNames = getBindings(code, fnNames).map(
1593
- (b) => b.exportNames[0]
1594
- );
1595
- if (exportNames.length > 0) {
1596
- staticHandlerModules.set(id, exportNames);
1700
+ if (isRscEnv && isBuild) {
1701
+ const trackTypes = [
1702
+ [
1703
+ hasPrerenderHandlerCode,
1704
+ PRERENDER_CONFIG,
1705
+ prerenderHandlerModules
1706
+ ],
1707
+ [hasStaticHandlerCode, STATIC_CONFIG, staticHandlerModules]
1708
+ ];
1709
+ for (const [has2, cfg, trackMap] of trackTypes) {
1710
+ if (!has2) continue;
1711
+ const exportNames = getBindings(code, getFnNames(cfg.fnName)).map(
1712
+ (b) => b.exportNames[0]
1713
+ );
1714
+ if (exportNames.length > 0) trackMap.set(id, exportNames);
1597
1715
  }
1598
1716
  }
1599
- const s = new MagicString4(code);
1717
+ const s = new MagicString3(code);
1600
1718
  if (hasLoaderCode) {
1601
1719
  const fnNames = getFnNames("createLoader");
1602
1720
  changed = transformLoaders(
@@ -1625,41 +1743,13 @@ ${lazyImports.join(",\n")}
1625
1743
  isBuild
1626
1744
  ) || changed;
1627
1745
  }
1628
- if (hasPrerenderHandlerCode) {
1629
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1630
- const bindings = getBindings(code, fnNames);
1631
- if (isRscEnv) {
1632
- changed = transformHandlerIds(
1633
- PRERENDER_CONFIG,
1634
- bindings,
1635
- s,
1636
- filePath,
1637
- isBuild
1638
- ) || changed;
1639
- } else {
1640
- changed = stubHandlerExprs(
1641
- PRERENDER_CONFIG,
1642
- bindings,
1643
- s,
1644
- filePath,
1645
- isBuild
1646
- ) || changed;
1647
- }
1648
- }
1649
- if (hasStaticHandlerCode) {
1650
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1651
- const bindings = getBindings(code, fnNames);
1652
- if (isRscEnv) {
1653
- changed = transformHandlerIds(
1654
- STATIC_CONFIG,
1655
- bindings,
1656
- s,
1657
- filePath,
1658
- isBuild
1659
- ) || changed;
1660
- } else {
1661
- changed = stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) || changed;
1662
- }
1746
+ const finalHandlerConfigs = [
1747
+ hasPrerenderHandlerCode && PRERENDER_CONFIG,
1748
+ hasStaticHandlerCode && STATIC_CONFIG
1749
+ ].filter((c) => !!c);
1750
+ for (const cfg of finalHandlerConfigs) {
1751
+ const bindings = getBindings(code, getFnNames(cfg.fnName));
1752
+ changed = (isRscEnv ? transformHandlerIds(cfg, bindings, s, filePath, isBuild) : stubHandlerExprs(cfg, bindings, s, filePath, isBuild)) || changed;
1663
1753
  }
1664
1754
  if (!changed) return;
1665
1755
  return {
@@ -1675,10 +1765,11 @@ ${lazyImports.join(",\n")}
1675
1765
 
1676
1766
  // src/vite/plugins/use-cache-transform.ts
1677
1767
  import path5 from "node:path";
1678
- import MagicString5 from "magic-string";
1768
+ import MagicString4 from "magic-string";
1679
1769
  var debug4 = createRangoDebugger(NS.transform);
1680
1770
  var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
1681
1771
  var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
1772
+ var USE_CACHE_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
1682
1773
  function useCacheTransform() {
1683
1774
  let projectRoot = "";
1684
1775
  let isBuild = false;
@@ -1716,7 +1807,7 @@ function useCacheTransform() {
1716
1807
  let ast;
1717
1808
  try {
1718
1809
  const { parseAst: parseAst4 } = await import("vite");
1719
- ast = parseAst4(code);
1810
+ ast = parseAst4(code, { lang: "tsx" });
1720
1811
  } catch {
1721
1812
  return;
1722
1813
  }
@@ -1750,7 +1841,7 @@ function useCacheTransform() {
1750
1841
  };
1751
1842
  }
1752
1843
  function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLayoutOrTemplate, transformWrapExport) {
1753
- const nonFunctionExports = [];
1844
+ const unconfirmedExports = [];
1754
1845
  const { exportNames, output } = transformWrapExport(code, ast, {
1755
1846
  runtime: (value, name) => {
1756
1847
  const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
@@ -1759,20 +1850,21 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
1759
1850
  rejectNonAsyncFunction: false,
1760
1851
  filter: (name, meta) => {
1761
1852
  if (name === "default" && isLayoutOrTemplate) return false;
1762
- if (meta.isFunction === false) {
1763
- nonFunctionExports.push(name);
1853
+ if (meta.isFunction !== true) {
1854
+ unconfirmedExports.push(name);
1764
1855
  return false;
1765
1856
  }
1766
1857
  return true;
1767
1858
  }
1768
1859
  });
1769
- if (nonFunctionExports.length > 0) {
1860
+ if (unconfirmedExports.length > 0) {
1861
+ const plural = unconfirmedExports.length > 1;
1770
1862
  throw new Error(
1771
- `[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.`
1863
+ `[rango:use-cache] File-level "use cache" in ${sourceId} only wraps exports that are statically-confirmed functions. ${plural ? "These exports are" : "This export is"} not: ${unconfirmedExports.map((n) => `"${n}"`).join(", ")}. Declare them directly (export async function foo() {} or export const foo = async () => {}). A factory or otherwise statically-indeterminate initializer (export const foo = makeCached(fn)) is rejected even if it returns a function at runtime -- rewrite it as a direct async function, or move non-function exports to a separate module.`
1772
1864
  );
1773
1865
  }
1774
1866
  if (exportNames.length === 0) {
1775
- const s = new MagicString5(code);
1867
+ const s = new MagicString4(code);
1776
1868
  const directive2 = findFileLevelDirective(ast);
1777
1869
  if (directive2) {
1778
1870
  s.overwrite(
@@ -1807,7 +1899,7 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
1807
1899
  function transformFunctionLevelUseCache(code, ast, filePath, sourceId, isBuild, transformHoistInlineDirective) {
1808
1900
  try {
1809
1901
  const { output, names } = transformHoistInlineDirective(code, ast, {
1810
- directive: /^use cache(:\s*[\w-]+)?$/,
1902
+ directive: USE_CACHE_DIRECTIVE_RE,
1811
1903
  runtime: (value, name, meta) => {
1812
1904
  const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1813
1905
  const profileMatch = meta.directiveMatch[1];
@@ -1837,14 +1929,13 @@ function findFileLevelDirective(ast) {
1837
1929
  }
1838
1930
  return null;
1839
1931
  }
1840
- var VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
1841
1932
  var NEAR_MISS_RE = /^use cache:\s*.+$/;
1842
1933
  function warnOnNearMissDirectives(ast, fileId, warn) {
1843
1934
  const visit = (node) => {
1844
1935
  if (!node || typeof node !== "object") return;
1845
1936
  if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
1846
1937
  const value = node.expression.value;
1847
- if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !VALID_DIRECTIVE_RE.test(value)) {
1938
+ if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !USE_CACHE_DIRECTIVE_RE.test(value)) {
1848
1939
  const profilePart = value.slice("use cache:".length).trim();
1849
1940
  warn(
1850
1941
  `[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.`
@@ -1907,7 +1998,6 @@ function clientRefDedup() {
1907
1998
  if (this.environment?.name !== "client") return;
1908
1999
  if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
1909
2000
  if (!source.includes("/node_modules/")) return;
1910
- if (!importer) return;
1911
2001
  const packageName = extractPackageName(source);
1912
2002
  if (!packageName) return;
1913
2003
  if (clientExclude.includes(packageName)) return;
@@ -1938,7 +2028,7 @@ import {
1938
2028
  import { createElement, StrictMode } from "react";
1939
2029
  import { hydrateRoot } from "react-dom/client";
1940
2030
  import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
1941
- import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
2031
+ import { initBrowserApp, Rango } from "@rangojs/router/browser";
1942
2032
 
1943
2033
  async function initializeApp() {
1944
2034
  const deps = {
@@ -1953,7 +2043,7 @@ async function initializeApp() {
1953
2043
 
1954
2044
  hydrateRoot(
1955
2045
  document,
1956
- createElement(StrictMode, null, createElement(RSCRouter))
2046
+ createElement(StrictMode, null, createElement(Rango))
1957
2047
  );
1958
2048
  }
1959
2049
 
@@ -2022,6 +2112,38 @@ export default function handler(request, env) {
2022
2112
  }
2023
2113
  `.trim();
2024
2114
  }
2115
+ function getVirtualEntryRSCHost(hostEntryPath) {
2116
+ return `
2117
+ import * as __hostEntry from "${hostEntryPath}";
2118
+
2119
+ // Register every sub-app's fetchable loaders + route manifests at startup, same
2120
+ // as the single-router entry. Discovery's host fallback populates these for all
2121
+ // mounted sub-apps, so the aggregate manifests cover the whole host tree.
2122
+ import "virtual:rsc-router/loader-manifest";
2123
+ import "virtual:rsc-router/routes-manifest";
2124
+
2125
+ // The host entry module must export the HostRouter instance (createHostRouter()),
2126
+ // as a default export or a named \`hostRouter\`/\`router\` export. A Cloudflare-style
2127
+ // \`export default { fetch }\` object is not a HostRouter and is rejected here.
2128
+ const __defaultExport = __hostEntry.default;
2129
+ const hostRouter =
2130
+ __defaultExport && typeof __defaultExport.match === "function"
2131
+ ? __defaultExport
2132
+ : __hostEntry.hostRouter ?? __hostEntry.router;
2133
+
2134
+ if (!hostRouter || typeof hostRouter.match !== "function") {
2135
+ throw new Error(
2136
+ "[rango] The host entry (${hostEntryPath}) must export a HostRouter instance for the node/vercel preset: a default export, or a named 'hostRouter'/'router' export (e.g. export default createHostRouter()). A Cloudflare-style 'export default { fetch }' object is not supported on this preset."
2137
+ );
2138
+ }
2139
+
2140
+ // input = { env, ctx } from the launcher / node server. The host router threads
2141
+ // it unchanged to each matched sub-app's handler and cache factory.
2142
+ export default function handler(request, input) {
2143
+ return hostRouter.match(request, input);
2144
+ }
2145
+ `.trim();
2146
+ }
2025
2147
  var VIRTUAL_IDS = {
2026
2148
  browser: "virtual:rsc-router/entry.browser.js",
2027
2149
  ssr: "virtual:rsc-router/entry.ssr.js",
@@ -2040,7 +2162,7 @@ import { resolve } from "node:path";
2040
2162
  // package.json
2041
2163
  var package_default = {
2042
2164
  name: "@rangojs/router",
2043
- version: "0.0.0-experimental.97",
2165
+ version: "0.0.0-experimental.98914650",
2044
2166
  description: "Django-inspired RSC router with composable URL patterns",
2045
2167
  keywords: [
2046
2168
  "react",
@@ -2166,6 +2288,31 @@ var package_default = {
2166
2288
  "./host/testing": {
2167
2289
  types: "./src/host/testing.ts",
2168
2290
  default: "./src/host/testing.ts"
2291
+ },
2292
+ "./testing": {
2293
+ types: "./src/testing/index.ts",
2294
+ default: "./src/testing/index.ts"
2295
+ },
2296
+ "./testing/vitest": {
2297
+ types: "./src/testing/vitest.ts",
2298
+ default: "./dist/testing/vitest.js"
2299
+ },
2300
+ "./testing/dom": {
2301
+ types: "./src/testing/dom.entry.ts",
2302
+ default: "./src/testing/dom.entry.ts"
2303
+ },
2304
+ "./testing/e2e": {
2305
+ types: "./src/testing/e2e/index.ts",
2306
+ default: "./src/testing/e2e/index.ts"
2307
+ },
2308
+ "./testing/flight": {
2309
+ types: "./src/testing/flight.entry.ts",
2310
+ "react-server": "./src/testing/flight.entry.ts",
2311
+ default: "./src/testing/flight.entry.ts"
2312
+ },
2313
+ "./testing/flight-matchers": {
2314
+ types: "./src/testing/flight-matchers.ts",
2315
+ default: "./src/testing/flight-matchers.ts"
2169
2316
  }
2170
2317
  },
2171
2318
  publishConfig: {
@@ -2173,47 +2320,71 @@ var package_default = {
2173
2320
  tag: "experimental"
2174
2321
  },
2175
2322
  scripts: {
2176
- build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2323
+ build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2177
2324
  prepublishOnly: "pnpm build",
2178
- typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit",
2325
+ typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
2179
2326
  test: "playwright test",
2180
2327
  "test:ui": "playwright test --ui",
2328
+ "test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
2181
2329
  "test:unit": "vitest run",
2182
- "test:unit:watch": "vitest"
2330
+ "test:unit:watch": "vitest",
2331
+ "test:unit:rsc": "vitest run --config vitest.rsc.config.ts"
2183
2332
  },
2184
2333
  dependencies: {
2185
2334
  "@types/debug": "^4.1.12",
2186
- "@vitejs/plugin-rsc": "^0.5.23",
2335
+ "@vitejs/plugin-rsc": "^0.5.26",
2187
2336
  debug: "^4.4.1",
2188
2337
  "magic-string": "^0.30.17",
2189
2338
  picomatch: "^4.0.3",
2190
- "rsc-html-stream": "^0.0.7"
2339
+ "rsc-html-stream": "^0.0.7",
2340
+ srvx: "^0.11.15",
2341
+ tinyexec: "^0.3.2"
2191
2342
  },
2192
2343
  devDependencies: {
2193
2344
  "@playwright/test": "^1.49.1",
2345
+ "@shared/e2e": "workspace:*",
2346
+ "@testing-library/dom": "^10.4.1",
2347
+ "@testing-library/react": "^16.3.2",
2194
2348
  "@types/node": "^24.10.1",
2195
2349
  "@types/react": "catalog:",
2196
2350
  "@types/react-dom": "catalog:",
2197
2351
  esbuild: "^0.27.0",
2352
+ "happy-dom": "^20.10.1",
2198
2353
  jiti: "^2.6.1",
2199
2354
  react: "catalog:",
2200
2355
  "react-dom": "catalog:",
2201
- tinyexec: "^0.3.2",
2202
2356
  typescript: "^5.3.0",
2203
2357
  vitest: "^4.0.0"
2204
2358
  },
2205
2359
  peerDependencies: {
2206
- "@cloudflare/vite-plugin": "^1.25.0",
2207
- "@vitejs/plugin-rsc": "^0.5.23",
2208
- react: "^18.0.0 || ^19.0.0",
2209
- vite: "^7.3.0"
2360
+ "@cloudflare/vite-plugin": "^1.38.0",
2361
+ "@playwright/test": "^1.49.1",
2362
+ "@testing-library/react": ">=16",
2363
+ "@vercel/functions": "^3.0.0",
2364
+ "@vitejs/plugin-rsc": "^0.5.26",
2365
+ react: ">=19.2.6 <20",
2366
+ "react-dom": ">=19.2.6 <20",
2367
+ vite: "^8.0.0",
2368
+ vitest: ">=3"
2210
2369
  },
2211
2370
  peerDependenciesMeta: {
2212
2371
  "@cloudflare/vite-plugin": {
2213
2372
  optional: true
2214
2373
  },
2374
+ "@playwright/test": {
2375
+ optional: true
2376
+ },
2377
+ "@testing-library/react": {
2378
+ optional: true
2379
+ },
2380
+ "@vercel/functions": {
2381
+ optional: true
2382
+ },
2215
2383
  vite: {
2216
2384
  optional: true
2385
+ },
2386
+ vitest: {
2387
+ optional: true
2217
2388
  }
2218
2389
  }
2219
2390
  };
@@ -2288,10 +2459,10 @@ function extractParamsFromPattern(pattern) {
2288
2459
  function formatRouteEntry(key, pattern, _params, search) {
2289
2460
  const hasSearch = search && Object.keys(search).length > 0;
2290
2461
  if (!hasSearch) {
2291
- return ` ${key}: "${pattern}",`;
2462
+ return ` ${key}: ${JSON.stringify(pattern)},`;
2292
2463
  }
2293
- const searchBody = Object.entries(search).map(([k, v]) => `${k}: "${v}"`).join(", ");
2294
- return ` ${key}: { path: "${pattern}", search: { ${searchBody} } },`;
2464
+ const searchBody = Object.entries(search).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
2465
+ return ` ${key}: { path: ${JSON.stringify(pattern)}, search: { ${searchBody} } },`;
2295
2466
  }
2296
2467
 
2297
2468
  // src/build/route-types/ast-route-extraction.ts
@@ -2399,7 +2570,7 @@ ${objectBody}
2399
2570
  } as const;
2400
2571
 
2401
2572
  declare global {
2402
- namespace RSCRouter {
2573
+ namespace Rango {
2403
2574
  interface GeneratedRouteMap extends Readonly<typeof NamedRoutes> {}
2404
2575
  }
2405
2576
  }
@@ -2407,7 +2578,31 @@ declare global {
2407
2578
  }
2408
2579
 
2409
2580
  // src/build/route-types/scan-filter.ts
2581
+ import { join, relative } from "node:path";
2410
2582
  import picomatch from "picomatch";
2583
+ var DEFAULT_EXCLUDE_PATTERNS = [
2584
+ "**/__tests__/**",
2585
+ "**/__mocks__/**",
2586
+ "**/dist/**",
2587
+ "**/coverage/**",
2588
+ "**/*.test.{ts,tsx,js,jsx}",
2589
+ "**/*.spec.{ts,tsx,js,jsx}"
2590
+ ];
2591
+ function createScanFilter(root, opts) {
2592
+ const { include, exclude } = opts;
2593
+ const hasInclude = include && include.length > 0;
2594
+ const hasCustomExclude = exclude !== void 0;
2595
+ if (!hasInclude && !hasCustomExclude) return void 0;
2596
+ const effectiveExclude = exclude ?? DEFAULT_EXCLUDE_PATTERNS;
2597
+ const includeMatcher = hasInclude ? picomatch(include) : null;
2598
+ const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
2599
+ return (absolutePath) => {
2600
+ const rel = relative(root, absolutePath);
2601
+ if (excludeMatcher && excludeMatcher(rel)) return false;
2602
+ if (includeMatcher) return includeMatcher(rel);
2603
+ return true;
2604
+ };
2605
+ }
2411
2606
 
2412
2607
  // src/build/route-types/per-module-writer.ts
2413
2608
  import ts4 from "typescript";
@@ -2634,7 +2829,7 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2634
2829
  const realPath = resolve2(filePath);
2635
2830
  const key = variableName ? `${realPath}:${variableName}` : realPath;
2636
2831
  if (visited.has(key)) {
2637
- console.warn(`[rsc-router] Circular include detected, skipping: ${key}`);
2832
+ console.warn(`[rango] Circular include detected, skipping: ${key}`);
2638
2833
  return { routes: {}, searchSchemas: {} };
2639
2834
  }
2640
2835
  visited.add(key);
@@ -2676,7 +2871,7 @@ import {
2676
2871
  readdirSync
2677
2872
  } from "node:fs";
2678
2873
  import {
2679
- join,
2874
+ join as join2,
2680
2875
  dirname as dirname2,
2681
2876
  resolve as resolve3,
2682
2877
  sep,
@@ -2695,6 +2890,7 @@ function countPublicRouteEntries(source) {
2695
2890
  return count;
2696
2891
  }
2697
2892
  var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
2893
+ var ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
2698
2894
  function isRoutableSourceFile(name) {
2699
2895
  return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
2700
2896
  }
@@ -2704,14 +2900,14 @@ function findRouterFilesRecursive(dir, filter, results) {
2704
2900
  entries = readdirSync(dir, { withFileTypes: true });
2705
2901
  } catch (err) {
2706
2902
  console.warn(
2707
- `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
2903
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
2708
2904
  );
2709
2905
  return;
2710
2906
  }
2711
2907
  const childDirs = [];
2712
2908
  const routerFilesInDir = [];
2713
2909
  for (const entry of entries) {
2714
- const fullPath = join(dir, entry.name);
2910
+ const fullPath = join2(dir, entry.name);
2715
2911
  if (entry.isDirectory()) {
2716
2912
  if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === "__tests__" || entry.name === "__mocks__" || entry.name.startsWith("."))
2717
2913
  continue;
@@ -2722,7 +2918,7 @@ function findRouterFilesRecursive(dir, filter, results) {
2722
2918
  if (filter && !filter(fullPath)) continue;
2723
2919
  try {
2724
2920
  const source = readFileSync2(fullPath, "utf-8");
2725
- if (ROUTER_CALL_PATTERN.test(source)) {
2921
+ if (ROUTER_CALL_PATTERN.test(source) && firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0) {
2726
2922
  routerFilesInDir.push(fullPath);
2727
2923
  }
2728
2924
  } catch {
@@ -2760,7 +2956,7 @@ function findNestedRouterConflict(routerFiles) {
2760
2956
  }
2761
2957
  return null;
2762
2958
  }
2763
- function formatNestedRouterConflictError(conflict, prefix = "[rsc-router]") {
2959
+ function formatNestedRouterConflictError(conflict, prefix = "[rango]") {
2764
2960
  return `${prefix} Nested router roots are not supported.
2765
2961
  Router root: ${conflict.ancestor}
2766
2962
  Nested router: ${conflict.nested}
@@ -2856,19 +3052,38 @@ function extractBasenameFromRouter(code) {
2856
3052
  visit(sourceFile);
2857
3053
  return result;
2858
3054
  }
2859
- function applyBasenameToRoutes(result, basename3) {
3055
+ function applyBasenameToRoutes(result, basename2) {
2860
3056
  const prefixed = {};
2861
3057
  for (const [name, pattern] of Object.entries(result.routes)) {
2862
3058
  if (pattern === "/") {
2863
- prefixed[name] = basename3;
2864
- } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2865
- prefixed[name] = basename3 + pattern.slice(1);
3059
+ prefixed[name] = basename2;
3060
+ } else if (basename2.endsWith("/") && pattern.startsWith("/")) {
3061
+ prefixed[name] = basename2 + pattern.slice(1);
2866
3062
  } else {
2867
- prefixed[name] = basename3 + pattern;
3063
+ prefixed[name] = basename2 + pattern;
2868
3064
  }
2869
3065
  }
2870
3066
  return { routes: prefixed, searchSchemas: result.searchSchemas };
2871
3067
  }
3068
+ function genFileTsPath(sourceFile) {
3069
+ const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
3070
+ return join2(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
3071
+ }
3072
+ function resolveSearchSchemas(publicRouteNames, runtimeSchemas, sourceFile) {
3073
+ if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
3074
+ return runtimeSchemas;
3075
+ }
3076
+ const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
3077
+ if (Object.keys(staticParsed.searchSchemas).length === 0) {
3078
+ return runtimeSchemas;
3079
+ }
3080
+ const filtered = {};
3081
+ for (const name of publicRouteNames) {
3082
+ const schema = staticParsed.searchSchemas[name];
3083
+ if (schema) filtered[name] = schema;
3084
+ }
3085
+ return Object.keys(filtered).length > 0 ? filtered : runtimeSchemas;
3086
+ }
2872
3087
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2873
3088
  let routerSource;
2874
3089
  try {
@@ -2881,7 +3096,7 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2881
3096
  return { routes: {}, searchSchemas: {} };
2882
3097
  }
2883
3098
  const rawBasename = extractBasenameFromRouter(routerSource);
2884
- const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
3099
+ const basename2 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2885
3100
  let result;
2886
3101
  if (extraction.kind === "inline") {
2887
3102
  result = buildCombinedRouteMapWithSearch(
@@ -2906,8 +3121,8 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2906
3121
  result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2907
3122
  }
2908
3123
  }
2909
- if (basename3) {
2910
- result = applyBasenameToRoutes(result, basename3);
3124
+ if (basename2) {
3125
+ result = applyBasenameToRoutes(result, basename2);
2911
3126
  }
2912
3127
  return result;
2913
3128
  }
@@ -2916,13 +3131,50 @@ function findRouterFiles(root, filter) {
2916
3131
  findRouterFilesRecursive(root, filter, result);
2917
3132
  return result;
2918
3133
  }
3134
+ var HOST_ROUTER_CALL_PATTERN = /\bcreateHostRouter\s*[<(]/;
3135
+ var HOST_ROUTER_CALL_PATTERN_G = /\bcreateHostRouter\s*[<(]/g;
3136
+ function findHostRouterFilesRecursive(dir, filter, results) {
3137
+ let entries;
3138
+ try {
3139
+ entries = readdirSync(dir, { withFileTypes: true });
3140
+ } catch (err) {
3141
+ console.warn(
3142
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
3143
+ );
3144
+ return;
3145
+ }
3146
+ for (const entry of entries) {
3147
+ const fullPath = join2(dir, entry.name);
3148
+ if (entry.isDirectory()) {
3149
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === "__tests__" || entry.name === "__mocks__" || entry.name.startsWith("."))
3150
+ continue;
3151
+ findHostRouterFilesRecursive(fullPath, filter, results);
3152
+ continue;
3153
+ }
3154
+ if (!isRoutableSourceFile(entry.name)) continue;
3155
+ if (filter && !filter(fullPath)) continue;
3156
+ try {
3157
+ const source = readFileSync2(fullPath, "utf-8");
3158
+ if (HOST_ROUTER_CALL_PATTERN.test(source) && firstCodeMatchIndex(source, HOST_ROUTER_CALL_PATTERN_G) >= 0) {
3159
+ results.push(fullPath);
3160
+ }
3161
+ } catch {
3162
+ continue;
3163
+ }
3164
+ }
3165
+ }
3166
+ function findHostRouterFiles(root, filter) {
3167
+ const result = [];
3168
+ findHostRouterFilesRecursive(root, filter, result);
3169
+ return result;
3170
+ }
2919
3171
  function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2920
3172
  try {
2921
- const oldCombinedPath = join(root, "src", "named-routes.gen.ts");
3173
+ const oldCombinedPath = join2(root, "src", "named-routes.gen.ts");
2922
3174
  if (existsSync3(oldCombinedPath)) {
2923
3175
  unlinkSync(oldCombinedPath);
2924
3176
  console.log(
2925
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
3177
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
2926
3178
  );
2927
3179
  }
2928
3180
  } catch {
@@ -2944,18 +3196,12 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2944
3196
  }
2945
3197
  if (!extractUrlsFromRouter(routerSource)) continue;
2946
3198
  }
2947
- const routerBasename = pathBasename(routerFilePath).replace(
2948
- /\.(tsx?|jsx?)$/,
2949
- ""
2950
- );
2951
- const outPath = join(
2952
- dirname2(routerFilePath),
2953
- `${routerBasename}.named-routes.gen.ts`
2954
- );
3199
+ const outPath = genFileTsPath(routerFilePath);
2955
3200
  const existing = existsSync3(outPath) ? readFileSync2(outPath, "utf-8") : null;
2956
3201
  if (Object.keys(result.routes).length === 0) {
2957
3202
  if (!existing) {
2958
3203
  const emptySource = generateRouteTypesSource({});
3204
+ opts?.onWrite?.(outPath, emptySource);
2959
3205
  writeFileSync(outPath, emptySource);
2960
3206
  }
2961
3207
  continue;
@@ -2975,9 +3221,10 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2975
3221
  continue;
2976
3222
  }
2977
3223
  }
3224
+ opts?.onWrite?.(outPath, source);
2978
3225
  writeFileSync(outPath, source);
2979
3226
  console.log(
2980
- `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
3227
+ `[rango] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
2981
3228
  );
2982
3229
  }
2983
3230
  }
@@ -2994,7 +3241,7 @@ function normalizeModuleId(id) {
2994
3241
  function getClientModuleSignature(source) {
2995
3242
  let program;
2996
3243
  try {
2997
- program = parseAst3(source, { jsx: true });
3244
+ program = parseAst3(source, { lang: "tsx" });
2998
3245
  } catch {
2999
3246
  return void 0;
3000
3247
  }
@@ -3077,11 +3324,12 @@ function createVersionPlugin() {
3077
3324
  let currentVersion = buildVersion;
3078
3325
  let isDev = false;
3079
3326
  let server = null;
3327
+ let resolvedCacheDir;
3080
3328
  const clientModuleSignatures = /* @__PURE__ */ new Map();
3081
3329
  let versionCounter = 0;
3082
3330
  const bumpVersion = (reason) => {
3083
3331
  currentVersion = Date.now().toString(16) + String(++versionCounter);
3084
- console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
3332
+ console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
3085
3333
  const rscEnv = server?.environments?.rsc;
3086
3334
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
3087
3335
  "\0" + VIRTUAL_IDS.version
@@ -3095,6 +3343,7 @@ function createVersionPlugin() {
3095
3343
  enforce: "pre",
3096
3344
  configResolved(config) {
3097
3345
  isDev = config.command === "serve";
3346
+ resolvedCacheDir = config.cacheDir ? String(config.cacheDir).replace(/\\/g, "/") : void 0;
3098
3347
  },
3099
3348
  configureServer(devServer) {
3100
3349
  server = devServer;
@@ -3131,11 +3380,11 @@ function createVersionPlugin() {
3131
3380
  }
3132
3381
  return null;
3133
3382
  },
3134
- // Track RSC module changes and update version
3135
3383
  async hotUpdate(ctx) {
3136
3384
  if (!isDev) return;
3137
3385
  const isRscModule = this.environment?.name === "rsc";
3138
3386
  if (!isRscModule) return;
3387
+ if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
3139
3388
  if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
3140
3389
  return;
3141
3390
  }
@@ -3165,6 +3414,17 @@ function createVersionPlugin() {
3165
3414
  }
3166
3415
  };
3167
3416
  }
3417
+ function isViteDepCachePath(filePath, cacheDir) {
3418
+ if (!filePath) return false;
3419
+ const normalized = filePath.replace(/\\/g, "/");
3420
+ if (cacheDir) {
3421
+ const normalizedCacheDir = cacheDir.replace(/\\/g, "/").replace(/\/+$/, "");
3422
+ if (normalized === normalizedCacheDir || normalized.startsWith(normalizedCacheDir + "/")) {
3423
+ return true;
3424
+ }
3425
+ }
3426
+ return /\/node_modules\/\.vite[^/]*\//.test(normalized) || normalized.includes("/.vite-isolated/");
3427
+ }
3168
3428
 
3169
3429
  // src/vite/utils/shared-utils.ts
3170
3430
  import * as Vite from "vite";
@@ -3193,22 +3453,15 @@ function patchRsdwClientDebugInfoRecovery(code) {
3193
3453
  };
3194
3454
  }
3195
3455
  function performanceTracksOptimizeDepsPlugin() {
3456
+ const RSDW_CLIENT_RE = /react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
3196
3457
  return {
3197
3458
  name: "@rangojs/router:performance-tracks-optimize-deps",
3198
- setup(build) {
3199
- build.onLoad(
3200
- {
3201
- filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
3202
- },
3203
- async (args) => {
3204
- const code = await readFile(args.path, "utf8");
3205
- const patched = patchRsdwClientDebugInfoRecovery(code);
3206
- return {
3207
- contents: patched.code,
3208
- loader: "js"
3209
- };
3210
- }
3211
- );
3459
+ async load(id) {
3460
+ const cleanId = id.split("?")[0] ?? id;
3461
+ if (!RSDW_CLIENT_RE.test(cleanId)) return null;
3462
+ const code = await readFile(cleanId, "utf8");
3463
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3464
+ return { code: patched.code };
3212
3465
  }
3213
3466
  };
3214
3467
  }
@@ -3235,24 +3488,27 @@ function performanceTracksPlugin() {
3235
3488
  }
3236
3489
 
3237
3490
  // src/vite/utils/shared-utils.ts
3238
- var versionEsbuildPlugin = {
3491
+ function resolveRscEntryFromConfig(config) {
3492
+ const entries = config.environments?.["rsc"]?.optimizeDeps?.entries;
3493
+ if (typeof entries === "string") return entries;
3494
+ if (Array.isArray(entries) && entries.length > 0) return entries[0];
3495
+ return void 0;
3496
+ }
3497
+ var versionRolldownPlugin = {
3239
3498
  name: "@rangojs/router-version",
3240
- setup(build) {
3241
- build.onResolve({ filter: /^rsc-router:version$/ }, (args) => ({
3242
- path: args.path,
3243
- namespace: "@rangojs/router-virtual"
3244
- }));
3245
- build.onLoad(
3246
- { filter: /.*/, namespace: "@rangojs/router-virtual" },
3247
- () => ({
3248
- contents: `export const VERSION = "dev";`,
3249
- loader: "js"
3250
- })
3251
- );
3499
+ resolveId(id) {
3500
+ if (id === VIRTUAL_IDS.version) return "\0" + VIRTUAL_IDS.version;
3501
+ return void 0;
3502
+ },
3503
+ load(id) {
3504
+ if (id === "\0" + VIRTUAL_IDS.version) {
3505
+ return getVirtualVersionContent("dev");
3506
+ }
3507
+ return void 0;
3252
3508
  }
3253
3509
  };
3254
- var sharedEsbuildOptions = {
3255
- plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
3510
+ var sharedRolldownOptions = {
3511
+ plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()]
3256
3512
  };
3257
3513
  function createVirtualEntriesPlugin(entries, routerPathRef) {
3258
3514
  const virtualModules = {};
@@ -3287,15 +3543,36 @@ function createVirtualEntriesPlugin(entries, routerPathRef) {
3287
3543
  if (virtualId === VIRTUAL_IDS.rsc && routerPathRef?.path) {
3288
3544
  const raw = routerPathRef.path.startsWith(".") ? "/" + routerPathRef.path.slice(2) : routerPathRef.path;
3289
3545
  const absoluteRouterPath = raw.replaceAll("\\", "/");
3290
- return getVirtualEntryRSC(absoluteRouterPath);
3546
+ return routerPathRef.kind === "host" ? getVirtualEntryRSCHost(absoluteRouterPath) : getVirtualEntryRSC(absoluteRouterPath);
3291
3547
  }
3292
3548
  }
3293
3549
  return null;
3294
3550
  }
3295
3551
  };
3296
3552
  }
3553
+ function isContentHashedAssetConflict(message) {
3554
+ if (!message) return false;
3555
+ const match = /The emitted file "?([^"\s]+)"? overwrites a previously emitted file/.exec(
3556
+ message
3557
+ );
3558
+ if (!match) return false;
3559
+ const fileName = match[1];
3560
+ const base = fileName.slice(fileName.lastIndexOf("/") + 1);
3561
+ const dot = base.lastIndexOf(".");
3562
+ if (dot <= 0) return false;
3563
+ const stem = base.slice(0, dot);
3564
+ const HASH_LEN = 8;
3565
+ if (stem.length < HASH_LEN + 1 || stem[stem.length - HASH_LEN - 1] !== "-") {
3566
+ return false;
3567
+ }
3568
+ const hash = stem.slice(-HASH_LEN);
3569
+ return /^[A-Za-z0-9_-]+$/.test(hash) && /[A-Z0-9]/.test(hash);
3570
+ }
3297
3571
  function onwarn(warning, defaultHandler) {
3298
- if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.code === "SOURCEMAP_ERROR" || warning.code === "EMPTY_BUNDLE") {
3572
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.code === "SOURCEMAP_ERROR" || warning.code === "EMPTY_BUNDLE" || warning.code === "INEFFECTIVE_DYNAMIC_IMPORT") {
3573
+ return;
3574
+ }
3575
+ if (warning.code === "FILE_NAME_CONFLICT" && isContentHashedAssetConflict(warning.message)) {
3299
3576
  return;
3300
3577
  }
3301
3578
  if (warning.message?.includes("Sourcemap is likely to be incorrect")) {
@@ -3314,73 +3591,354 @@ function getManualChunks(id) {
3314
3591
  return "react";
3315
3592
  }
3316
3593
  const packageName = getPublishedPackageName();
3317
- if (normalized.includes(`node_modules/${packageName}/`) || normalized.includes("packages/rsc-router/") || normalized.includes("packages/rangojs-router/")) {
3594
+ if (normalized.includes(`node_modules/${packageName}/`) || /\/packages\/(rsc-router|rangojs-router)\/(src|dist)\//.test(normalized)) {
3318
3595
  return "router";
3319
3596
  }
3320
3597
  return void 0;
3321
3598
  }
3322
3599
 
3323
- // src/vite/utils/banner.ts
3324
- var rangoVersion = package_default.version;
3325
- var _bannerPrinted = false;
3326
- function printBanner(mode, preset, version) {
3327
- if (_bannerPrinted) return;
3328
- _bannerPrinted = true;
3329
- const dim = "\x1B[2m";
3330
- const bold = "\x1B[1m";
3331
- const reset = "\x1B[0m";
3332
- const banner = `
3333
- ${dim} \u2726 \u2726 \u2727. . .${reset}
3334
- ${dim} \u2571${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2571 \u2726 *${reset}
3335
- ${dim} ${reset}${bold}\u2551 \u2551${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2727. \u2571${reset}
3336
- ${dim} ${reset}${bold}\u2554\u2557 \u2551 \u2551 \u2551 \u2551${reset}${dim} * \u2571${reset}
3337
- ${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}
3338
- ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
3339
- ${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}
3340
- ${dim} ${reset}${bold}\u255A\u2550\u2550\u2557 \u2551${reset}${dim} * RSC Wrangler \u2727 \u2726${reset}
3341
- ${dim} * ${reset}${bold}\u2551 \u2551${reset}${dim} * \u2727. \u2571${reset}
3342
- ${dim} ${reset}${bold}\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
3343
-
3344
- v${version} \xB7 ${preset} \xB7 ${mode}
3345
- `;
3346
- console.log(banner);
3600
+ // src/vite/plugins/client-ref-hashing.ts
3601
+ import { relative as relative2 } from "node:path";
3602
+ import { createHash as createHash2 } from "node:crypto";
3603
+ var debug7 = createRangoDebugger(NS.transform);
3604
+ var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3605
+ var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3606
+ var FS_PREFIX = "/@fs/";
3607
+ function hashRefKey(relativeId) {
3608
+ return createHash2("sha256").update(relativeId).digest("hex").slice(0, 12);
3347
3609
  }
3348
-
3349
- // src/vite/plugins/version-injector.ts
3350
- import { resolve as resolve4 } from "node:path";
3351
- import * as Vite2 from "vite";
3352
- function createVersionInjectorPlugin(rscEntryPath) {
3353
- let resolvedEntryPath = "";
3610
+ function computeProductionHash(projectRoot, refKey) {
3611
+ let toHash;
3612
+ if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
3613
+ toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
3614
+ } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
3615
+ const absPath = decodeURIComponent(
3616
+ refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3617
+ );
3618
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3619
+ } else if (refKey.startsWith(FS_PREFIX)) {
3620
+ const absPath = refKey.slice(FS_PREFIX.length - 1);
3621
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3622
+ } else if (refKey.startsWith("/")) {
3623
+ toHash = refKey.slice(1);
3624
+ } else {
3625
+ return refKey;
3626
+ }
3627
+ return hashRefKey(toHash);
3628
+ }
3629
+ var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
3630
+ function transformClientRefs(code, projectRoot) {
3631
+ if (!code.includes("registerClientReference")) return null;
3632
+ let hasReplacement = false;
3633
+ const result = code.replace(
3634
+ REGISTER_CLIENT_REF_RE,
3635
+ (match, refKey) => {
3636
+ const hash = computeProductionHash(projectRoot, refKey);
3637
+ if (hash === refKey) return match;
3638
+ hasReplacement = true;
3639
+ return match.replace(`"${refKey}"`, `"${hash}"`);
3640
+ }
3641
+ );
3642
+ return hasReplacement ? result : null;
3643
+ }
3644
+ function hashClientRefs(projectRoot) {
3645
+ const counter = createCounter(debug7, "hash-client-refs");
3354
3646
  return {
3355
- name: "@rangojs/router:version-injector",
3356
- enforce: "pre",
3357
- configResolved(config) {
3358
- let entryPath = rscEntryPath;
3359
- if (!entryPath) {
3360
- const rscEnvConfig = config.environments?.["rsc"];
3361
- const entries = rscEnvConfig?.optimizeDeps?.entries;
3362
- if (typeof entries === "string") {
3363
- entryPath = entries;
3364
- } else if (Array.isArray(entries) && entries.length > 0) {
3365
- entryPath = entries[0];
3366
- }
3367
- }
3368
- if (entryPath) {
3369
- resolvedEntryPath = resolve4(config.root, entryPath);
3370
- }
3647
+ name: "@rangojs/router:hash-client-refs",
3648
+ enforce: "post",
3649
+ applyToEnvironment(env) {
3650
+ return env.name === "rsc";
3651
+ },
3652
+ buildEnd() {
3653
+ counter?.flush();
3371
3654
  },
3372
3655
  transform(code, id) {
3373
- if (!resolvedEntryPath) return null;
3374
- const normalizedId = Vite2.normalizePath(id);
3375
- const normalizedEntry = Vite2.normalizePath(resolvedEntryPath);
3376
- if (normalizedId !== normalizedEntry) {
3377
- return null;
3378
- }
3379
- const prepend = [
3380
- `import "virtual:rsc-router/routes-manifest";`
3381
- ];
3382
- let newCode = code;
3383
- const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
3656
+ const start = counter ? performance.now() : 0;
3657
+ try {
3658
+ const result = transformClientRefs(code, projectRoot);
3659
+ if (result === null) return;
3660
+ return { code: result, map: null };
3661
+ } finally {
3662
+ counter?.record(id, performance.now() - start);
3663
+ }
3664
+ }
3665
+ };
3666
+ }
3667
+
3668
+ // src/vite/utils/client-chunks.ts
3669
+ var debugChunks = createRangoDebugger(NS.chunks);
3670
+ function isSharedRuntime(meta) {
3671
+ return [meta.id, meta.normalizedId].some(
3672
+ (path6) => path6.includes("/node_modules/") || /\/@rangojs\/router\//.test(path6) || /\/packages\/(rangojs-router|rsc-router)\/(src|dist)\//.test(path6)
3673
+ );
3674
+ }
3675
+ function sanitizeGroup(name) {
3676
+ return name.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "app";
3677
+ }
3678
+ var ROUTE_ROOT_DIRS = /* @__PURE__ */ new Set([
3679
+ "routes",
3680
+ "route",
3681
+ "pages",
3682
+ "page",
3683
+ "app",
3684
+ "features",
3685
+ "feature",
3686
+ "views",
3687
+ "view",
3688
+ "handlers",
3689
+ "urls",
3690
+ "modules",
3691
+ "screens",
3692
+ "sections"
3693
+ ]);
3694
+ function directoryClientChunks(meta, ctx) {
3695
+ if (isSharedRuntime(meta)) {
3696
+ return void 0;
3697
+ }
3698
+ if (ctx?.fallbackRefs.size && ctx.fallbackRefs.has(hashRefKey(meta.normalizedId))) {
3699
+ debugChunks?.("fallback %s -> app-fallback", meta.normalizedId);
3700
+ return "app-fallback";
3701
+ }
3702
+ const segments = meta.normalizedId.split("/").filter(Boolean);
3703
+ const dirCount = segments.length - 1;
3704
+ if (dirCount >= 1) {
3705
+ for (let i = 0; i < dirCount - 1; i++) {
3706
+ if (ROUTE_ROOT_DIRS.has(segments[i].toLowerCase())) {
3707
+ const group = `app-${sanitizeGroup(segments[i + 1])}`;
3708
+ debugChunks?.("split %s -> %s", meta.normalizedId, group);
3709
+ return group;
3710
+ }
3711
+ }
3712
+ }
3713
+ debugChunks?.(
3714
+ "shared %s (no route-root marker; inherits default grouping)",
3715
+ meta.normalizedId
3716
+ );
3717
+ return void 0;
3718
+ }
3719
+ function resolveClientChunks(option, ctx) {
3720
+ if (!option) return void 0;
3721
+ if (option === true) return (meta) => directoryClientChunks(meta, ctx);
3722
+ return option;
3723
+ }
3724
+
3725
+ // src/vite/plugins/vercel-output.ts
3726
+ import { rm, mkdir, cp, writeFile } from "node:fs/promises";
3727
+ import { existsSync as existsSync4 } from "node:fs";
3728
+ import { resolve as resolve4, join as join3 } from "node:path";
3729
+ import { createRequire as createRequire2 } from "node:module";
3730
+ import { pathToFileURL } from "node:url";
3731
+ var LAUNCHER_SOURCE = `import { toNodeHandler } from "srvx/node";
3732
+ import { waitUntil } from "@vercel/functions";
3733
+ import rscHandler from "./rsc/index.js";
3734
+
3735
+ // The Vercel Node launcher invokes a Node (req, res) handler, not a Web fetch
3736
+ // handler. srvx's toNodeHandler bridges the Rango Web fetch handler and pipes
3737
+ // the streamed Response to the Node response (set supportsResponseStreaming).
3738
+ const onVercel = Boolean(process.env.VERCEL);
3739
+
3740
+ const fetchHandler = (request) =>
3741
+ rscHandler(request, {
3742
+ env: process.env,
3743
+ // Forward Vercel's waitUntil so cache writes / revalidation run off the
3744
+ // response path. Omitted off-platform so those writes settle inline.
3745
+ ctx: onVercel ? { waitUntil } : undefined,
3746
+ });
3747
+
3748
+ export default toNodeHandler(fetchHandler);
3749
+ `;
3750
+ async function assemble(root, options) {
3751
+ const dist = join3(root, "dist");
3752
+ for (const dir of ["client", "rsc", "ssr"]) {
3753
+ if (!existsSync4(join3(dist, dir))) {
3754
+ throw new Error(
3755
+ `[rango] preset "vercel": missing dist/${dir}. Run the production build first.`
3756
+ );
3757
+ }
3758
+ }
3759
+ const vercel = options.vercel ?? {};
3760
+ const functionName = vercel.functionName ?? "index";
3761
+ const out = join3(root, ".vercel", "output");
3762
+ const funcDir = join3(out, "functions", `${functionName}.func`);
3763
+ await rm(out, { recursive: true, force: true });
3764
+ await mkdir(funcDir, { recursive: true });
3765
+ await cp(join3(dist, "client"), join3(out, "static"), { recursive: true });
3766
+ await cp(join3(dist, "rsc"), join3(funcDir, "rsc"), { recursive: true });
3767
+ await cp(join3(dist, "ssr"), join3(funcDir, "ssr"), { recursive: true });
3768
+ if (existsSync4(join3(dist, "static"))) {
3769
+ await cp(join3(dist, "static"), join3(funcDir, "static"), {
3770
+ recursive: true
3771
+ });
3772
+ }
3773
+ const rangoRequire = createRequire2(import.meta.url);
3774
+ let srvxNodePath;
3775
+ try {
3776
+ srvxNodePath = rangoRequire.resolve("srvx/node");
3777
+ } catch {
3778
+ throw new Error(
3779
+ '[rango] preset "vercel" requires "srvx" (a dependency of @rangojs/router). Reinstall dependencies.'
3780
+ );
3781
+ }
3782
+ const appRequire = createRequire2(join3(root, "package.json"));
3783
+ let esbuildModule;
3784
+ try {
3785
+ esbuildModule = await import(pathToFileURL(appRequire.resolve("esbuild")).href);
3786
+ } catch {
3787
+ throw new Error(
3788
+ '[rango] preset "vercel" requires "esbuild" to bundle the function launcher. It ships with Vite; reinstall dependencies.'
3789
+ );
3790
+ }
3791
+ const esbuildBuild = esbuildModule.build ?? esbuildModule.default?.build;
3792
+ if (typeof esbuildBuild !== "function") {
3793
+ throw new Error('[rango] preset "vercel": could not load esbuild.build.');
3794
+ }
3795
+ try {
3796
+ await esbuildBuild({
3797
+ stdin: {
3798
+ contents: LAUNCHER_SOURCE,
3799
+ resolveDir: root,
3800
+ sourcefile: "func-entry.mjs",
3801
+ loader: "js"
3802
+ },
3803
+ outfile: join3(funcDir, "index.mjs"),
3804
+ bundle: true,
3805
+ format: "esm",
3806
+ platform: "node",
3807
+ target: "node18",
3808
+ alias: { "srvx/node": srvxNodePath },
3809
+ plugins: [
3810
+ {
3811
+ name: "external-rsc-entry",
3812
+ setup(b) {
3813
+ b.onResolve({ filter: /^\.\/rsc\/index\.js$/ }, () => ({
3814
+ path: "./rsc/index.js",
3815
+ external: true
3816
+ }));
3817
+ }
3818
+ }
3819
+ ]
3820
+ });
3821
+ } catch (error) {
3822
+ const message = error instanceof Error ? error.message : String(error);
3823
+ if (/@vercel\/functions/.test(message)) {
3824
+ throw new Error(
3825
+ '[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' + message
3826
+ );
3827
+ }
3828
+ throw error;
3829
+ }
3830
+ await writeFile(
3831
+ join3(funcDir, "package.json"),
3832
+ JSON.stringify({ type: "module" }, null, 2) + "\n"
3833
+ );
3834
+ const vcConfig = {
3835
+ runtime: vercel.runtime ?? "nodejs22.x",
3836
+ handler: "index.mjs",
3837
+ launcherType: "Nodejs",
3838
+ shouldAddHelpers: false,
3839
+ supportsResponseStreaming: true,
3840
+ maxDuration: vercel.maxDuration ?? 30
3841
+ };
3842
+ if (vercel.memory != null) vcConfig.memory = vercel.memory;
3843
+ if (vercel.regions != null) vcConfig.regions = vercel.regions;
3844
+ await writeFile(
3845
+ join3(funcDir, ".vc-config.json"),
3846
+ JSON.stringify(vcConfig, null, 2) + "\n"
3847
+ );
3848
+ await writeFile(
3849
+ join3(out, "config.json"),
3850
+ JSON.stringify(
3851
+ {
3852
+ version: 3,
3853
+ routes: [
3854
+ { handle: "filesystem" },
3855
+ { src: "/(.*)", dest: `/${functionName}` }
3856
+ ]
3857
+ },
3858
+ null,
3859
+ 2
3860
+ ) + "\n"
3861
+ );
3862
+ console.log(
3863
+ `[rango] assembled .vercel/output (function: ${functionName}.func)`
3864
+ );
3865
+ }
3866
+ function createVercelOutputPlugin(options) {
3867
+ let root = process.cwd();
3868
+ let isBuild = false;
3869
+ return {
3870
+ name: "@rangojs/router:vercel-output",
3871
+ configResolved(config) {
3872
+ root = resolve4(config.root);
3873
+ isBuild = config.command === "build";
3874
+ },
3875
+ // buildApp runs once after the whole multi-environment build (rsc, client,
3876
+ // ssr), so dist/ is complete here. closeBundle is unusable for this: it
3877
+ // fires per environment, and twice for ssr (the server-reference scan and
3878
+ // the real build), so it would run before dist/client exists.
3879
+ buildApp: {
3880
+ order: "post",
3881
+ async handler() {
3882
+ if (!isBuild) return;
3883
+ await assemble(root, options);
3884
+ }
3885
+ }
3886
+ };
3887
+ }
3888
+
3889
+ // src/vite/utils/banner.ts
3890
+ var rangoVersion = package_default.version;
3891
+ var _bannerPrinted = false;
3892
+ function printBanner(mode, preset, version) {
3893
+ if (_bannerPrinted) return;
3894
+ _bannerPrinted = true;
3895
+ const dim = "\x1B[2m";
3896
+ const bold = "\x1B[1m";
3897
+ const reset = "\x1B[0m";
3898
+ const banner = `
3899
+ ${dim} \u2726 \u2726 \u2727. . .${reset}
3900
+ ${dim} \u2571${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2571 \u2726 *${reset}
3901
+ ${dim} ${reset}${bold}\u2551 \u2551${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2727. \u2571${reset}
3902
+ ${dim} ${reset}${bold}\u2554\u2557 \u2551 \u2551 \u2551 \u2551${reset}${dim} * \u2571${reset}
3903
+ ${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}
3904
+ ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
3905
+ ${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}
3906
+ ${dim} ${reset}${bold}\u255A\u2550\u2550\u2557 \u2551${reset}${dim} * RSC Wrangler \u2727 \u2726${reset}
3907
+ ${dim} * ${reset}${bold}\u2551 \u2551${reset}${dim} * \u2727. \u2571${reset}
3908
+ ${dim} ${reset}${bold}\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
3909
+
3910
+ v${version} \xB7 ${preset} \xB7 ${mode}
3911
+ `;
3912
+ console.log(banner);
3913
+ }
3914
+
3915
+ // src/vite/plugins/version-injector.ts
3916
+ import { resolve as resolve5 } from "node:path";
3917
+ import * as Vite2 from "vite";
3918
+ function createVersionInjectorPlugin(rscEntryPath) {
3919
+ let resolvedEntryPath = "";
3920
+ return {
3921
+ name: "@rangojs/router:version-injector",
3922
+ enforce: "pre",
3923
+ configResolved(config) {
3924
+ let entryPath = rscEntryPath;
3925
+ if (!entryPath) entryPath = resolveRscEntryFromConfig(config);
3926
+ if (entryPath) {
3927
+ resolvedEntryPath = resolve5(config.root, entryPath);
3928
+ }
3929
+ },
3930
+ transform(code, id) {
3931
+ if (!resolvedEntryPath) return null;
3932
+ const normalizedId = Vite2.normalizePath(id);
3933
+ const normalizedEntry = Vite2.normalizePath(resolvedEntryPath);
3934
+ if (normalizedId !== normalizedEntry) {
3935
+ return null;
3936
+ }
3937
+ const prepend = [
3938
+ `import "virtual:rsc-router/routes-manifest";`
3939
+ ];
3940
+ let newCode = code;
3941
+ const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
3384
3942
  if (needsVersion) {
3385
3943
  prepend.push(`import { VERSION } from "@rangojs/router:version";`);
3386
3944
  newCode = newCode.replace(
@@ -3412,23 +3970,23 @@ function createVersionInjectorPlugin(rscEntryPath) {
3412
3970
  }
3413
3971
 
3414
3972
  // src/vite/plugins/cjs-to-esm.ts
3415
- var debug7 = createRangoDebugger(NS.transform);
3973
+ var debug8 = createRangoDebugger(NS.transform);
3416
3974
  function createCjsToEsmPlugin() {
3417
3975
  return {
3418
3976
  name: "@rangojs/router:cjs-to-esm",
3419
3977
  enforce: "pre",
3420
3978
  transform(code, id) {
3421
- const cleanId = id.split("?")[0];
3422
- if (cleanId.includes("vendor/react-server-dom/client.browser.js") || cleanId.includes("vendor\\react-server-dom\\client.browser.js")) {
3979
+ const cleanId = id.split("?")[0].replaceAll("\\", "/");
3980
+ if (cleanId.includes("vendor/react-server-dom/client.browser.js")) {
3423
3981
  const isProd = process.env.NODE_ENV === "production";
3424
3982
  const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
3425
- debug7?.("cjs-to-esm entry redirect %s", id);
3983
+ debug8?.("cjs-to-esm entry redirect %s", id);
3426
3984
  return {
3427
3985
  code: `export * from "${cjsFile}";`,
3428
3986
  map: null
3429
3987
  };
3430
3988
  }
3431
- if ((cleanId.includes("vendor/react-server-dom/cjs/") || cleanId.includes("vendor\\react-server-dom\\cjs\\")) && cleanId.includes("client.browser")) {
3989
+ if (cleanId.includes("vendor/react-server-dom/cjs/") && cleanId.includes("client.browser")) {
3432
3990
  let transformed = code;
3433
3991
  const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
3434
3992
  const license = licenseMatch ? licenseMatch[0] : "";
@@ -3458,7 +4016,7 @@ function createCjsToEsmPlugin() {
3458
4016
  "export const $1 ="
3459
4017
  );
3460
4018
  transformed = license + "\n" + transformed;
3461
- debug7?.("cjs-to-esm body rewrite %s", id);
4019
+ debug8?.("cjs-to-esm body rewrite %s", id);
3462
4020
  return {
3463
4021
  code: transformed,
3464
4022
  map: null
@@ -3471,10 +4029,10 @@ function createCjsToEsmPlugin() {
3471
4029
 
3472
4030
  // src/vite/router-discovery.ts
3473
4031
  import { createServer as createViteServer } from "vite";
3474
- import { resolve as resolve8 } from "node:path";
4032
+ import { resolve as resolve9 } from "node:path";
3475
4033
  import { readFileSync as readFileSync6 } from "node:fs";
3476
- import { createRequire as createRequire2, register } from "node:module";
3477
- import { pathToFileURL } from "node:url";
4034
+ import { createRequire as createRequire3, register } from "node:module";
4035
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
3478
4036
 
3479
4037
  // src/vite/plugins/virtual-stub-plugin.ts
3480
4038
  function createVirtualStubPlugin() {
@@ -3547,7 +4105,7 @@ function createCloudflareProtocolStubPlugin() {
3547
4105
  if (!code.includes(CF_PREFIX)) return null;
3548
4106
  let ast;
3549
4107
  try {
3550
- ast = this.parse(code);
4108
+ ast = this.parse(code, { lang: "tsx" });
3551
4109
  } catch {
3552
4110
  return null;
3553
4111
  }
@@ -3607,72 +4165,6 @@ function walk(node, visit) {
3607
4165
  }
3608
4166
  }
3609
4167
 
3610
- // src/vite/plugins/client-ref-hashing.ts
3611
- import { relative } from "node:path";
3612
- import { createHash as createHash2 } from "node:crypto";
3613
- var debug8 = createRangoDebugger(NS.transform);
3614
- var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3615
- var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3616
- var FS_PREFIX = "/@fs/";
3617
- function computeProductionHash(projectRoot, refKey) {
3618
- let toHash;
3619
- if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
3620
- toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
3621
- } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
3622
- const absPath = decodeURIComponent(
3623
- refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3624
- );
3625
- toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3626
- } else if (refKey.startsWith(FS_PREFIX)) {
3627
- const absPath = refKey.slice(FS_PREFIX.length - 1);
3628
- toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3629
- } else if (refKey.startsWith("/")) {
3630
- toHash = refKey.slice(1);
3631
- } else {
3632
- return refKey;
3633
- }
3634
- return createHash2("sha256").update(toHash).digest("hex").slice(0, 12);
3635
- }
3636
- var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
3637
- function transformClientRefs(code, projectRoot) {
3638
- if (!code.includes("registerClientReference")) return null;
3639
- let hasReplacement = false;
3640
- const result = code.replace(
3641
- REGISTER_CLIENT_REF_RE,
3642
- (match, refKey) => {
3643
- const hash = computeProductionHash(projectRoot, refKey);
3644
- if (hash === refKey) return match;
3645
- hasReplacement = true;
3646
- return match.replace(`"${refKey}"`, `"${hash}"`);
3647
- }
3648
- );
3649
- return hasReplacement ? result : null;
3650
- }
3651
- function hashClientRefs(projectRoot) {
3652
- const counter = createCounter(debug8, "hash-client-refs");
3653
- return {
3654
- name: "@rangojs/router:hash-client-refs",
3655
- // Run after the RSC plugin's transform (default enforce is normal)
3656
- enforce: "post",
3657
- applyToEnvironment(env) {
3658
- return env.name === "rsc";
3659
- },
3660
- buildEnd() {
3661
- counter?.flush();
3662
- },
3663
- transform(code, id) {
3664
- const start = counter ? performance.now() : 0;
3665
- try {
3666
- const result = transformClientRefs(code, projectRoot);
3667
- if (result === null) return;
3668
- return { code: result, map: null };
3669
- } finally {
3670
- counter?.record(id, performance.now() - start);
3671
- }
3672
- }
3673
- };
3674
- }
3675
-
3676
4168
  // src/vite/utils/bundle-analysis.ts
3677
4169
  function findMatchingParenInBundle(code, openParenPos) {
3678
4170
  let depth = 1;
@@ -3703,7 +4195,7 @@ function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detec
3703
4195
  if (detectPassthrough) {
3704
4196
  const eFnName = escapeRegExp(fnName);
3705
4197
  const callStartRe = new RegExp(
3706
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
4198
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3707
4199
  );
3708
4200
  const callStart = callStartRe.exec(chunkCode);
3709
4201
  if (callStart) {
@@ -3728,7 +4220,7 @@ function evictHandlerCode(code, exports, fnName, brand) {
3728
4220
  if (passthrough) continue;
3729
4221
  const eName = escapeRegExp(name);
3730
4222
  const callStartRe = new RegExp(
3731
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
4223
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3732
4224
  );
3733
4225
  const startMatch = callStartRe.exec(modified);
3734
4226
  if (!startMatch) continue;
@@ -3763,6 +4255,8 @@ function createDiscoveryState(entryPath, opts) {
3763
4255
  projectRoot: "",
3764
4256
  isBuildMode: false,
3765
4257
  userResolveAlias: void 0,
4258
+ userRunnerConfig: void 0,
4259
+ userResolvePlugins: [],
3766
4260
  scanFilter: void 0,
3767
4261
  cachedRouterFiles: void 0,
3768
4262
  opts,
@@ -3784,7 +4278,8 @@ function createDiscoveryState(entryPath, opts) {
3784
4278
  devServerOrigin: null,
3785
4279
  devServer: null,
3786
4280
  selfWrittenGenFiles: /* @__PURE__ */ new Map(),
3787
- SELF_WRITE_WINDOW_MS: 5e3
4281
+ SELF_WRITE_WINDOW_MS: 5e3,
4282
+ lastDiscoveryError: null
3788
4283
  };
3789
4284
  }
3790
4285
 
@@ -3822,11 +4317,57 @@ function checkSelfGenWrite(state, filePath, consume) {
3822
4317
  }
3823
4318
  }
3824
4319
 
3825
- // src/vite/utils/manifest-utils.ts
4320
+ // src/router/logging.ts
4321
+ import { AsyncLocalStorage } from "node:async_hooks";
4322
+
4323
+ // src/internal-debug.ts
4324
+ var INTERNAL_RANGO_DEBUG = typeof __RANGO_DEBUG__ !== "undefined" ? __RANGO_DEBUG__ : typeof process !== "undefined" && Boolean(process.env?.INTERNAL_RANGO_DEBUG);
4325
+
4326
+ // src/router/logging.ts
4327
+ var routerLogContext = new AsyncLocalStorage();
4328
+
4329
+ // src/router/pattern-matching.ts
4330
+ function parsePattern(pattern) {
4331
+ const segments = [];
4332
+ const segmentRegex = /\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([^/]*)|(\*)|([^/]+))/g;
4333
+ let match;
4334
+ while ((match = segmentRegex.exec(pattern)) !== null) {
4335
+ const [
4336
+ ,
4337
+ ,
4338
+ paramName,
4339
+ ,
4340
+ constraint,
4341
+ optional,
4342
+ suffix,
4343
+ wildcard,
4344
+ staticText
4345
+ ] = match;
4346
+ if (wildcard) {
4347
+ segments.push({ type: "wildcard", value: "*", optional: false });
4348
+ } else if (paramName) {
4349
+ segments.push({
4350
+ type: "param",
4351
+ value: paramName,
4352
+ optional: optional === "?",
4353
+ constraint: constraint ? constraint.split("|") : void 0,
4354
+ suffix: suffix || void 0
4355
+ });
4356
+ } else if (staticText) {
4357
+ segments.push({ type: "static", value: staticText, optional: false });
4358
+ }
4359
+ }
4360
+ return segments;
4361
+ }
4362
+
4363
+ // src/build/prefix-tree-utils.ts
3826
4364
  function flattenLeafEntries(prefixTree, routeManifest, result) {
3827
- function visit(node) {
4365
+ function visit(node, ancestorStaticPrefixes) {
3828
4366
  const children = node.children || {};
3829
4367
  if (Object.keys(children).length === 0 && node.routes && node.routes.length > 0) {
4368
+ if (ancestorStaticPrefixes.has(node.staticPrefix)) {
4369
+ return;
4370
+ }
3830
4371
  const routes = {};
3831
4372
  for (const name of node.routes) {
3832
4373
  if (name in routeManifest) {
@@ -3835,13 +4376,15 @@ function flattenLeafEntries(prefixTree, routeManifest, result) {
3835
4376
  }
3836
4377
  result.push({ staticPrefix: node.staticPrefix, routes });
3837
4378
  } else {
4379
+ const nextAncestors = new Set(ancestorStaticPrefixes);
4380
+ nextAncestors.add(node.staticPrefix);
3838
4381
  for (const child of Object.values(children)) {
3839
- visit(child);
4382
+ visit(child, nextAncestors);
3840
4383
  }
3841
4384
  }
3842
4385
  }
3843
4386
  for (const node of Object.values(prefixTree)) {
3844
- visit(node);
4387
+ visit(node, /* @__PURE__ */ new Set());
3845
4388
  }
3846
4389
  }
3847
4390
  function buildRouteToStaticPrefix(prefixTree, result) {
@@ -3858,6 +4401,218 @@ function buildRouteToStaticPrefix(prefixTree, result) {
3858
4401
  visit(node);
3859
4402
  }
3860
4403
  }
4404
+
4405
+ // src/build/route-trie.ts
4406
+ function buildRouteTrie(routeManifest, routeAncestry, routeToStaticPrefix, routeTrailingSlash, prerenderRouteNames, passthroughRouteNames, responseTypeRoutes) {
4407
+ const root = {};
4408
+ for (const [routeName, pattern] of Object.entries(routeManifest)) {
4409
+ const ancestry = routeAncestry[routeName] || [];
4410
+ const staticPrefix = routeToStaticPrefix[routeName] || "";
4411
+ const trailingSlash = routeTrailingSlash?.[routeName];
4412
+ const responseType = responseTypeRoutes?.[routeName];
4413
+ const hasTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
4414
+ const normalizedPattern = hasTrailingSlash ? pattern.slice(0, -1) : pattern;
4415
+ const segments = parsePattern(normalizedPattern);
4416
+ insertRoute(root, segments, 0, {
4417
+ n: routeName,
4418
+ sp: staticPrefix,
4419
+ a: ancestry,
4420
+ ...trailingSlash ? { ts: trailingSlash } : {},
4421
+ ...prerenderRouteNames?.has(routeName) ? { pr: true } : {},
4422
+ ...passthroughRouteNames?.has(routeName) ? { pt: true } : {},
4423
+ ...responseType ? { rt: responseType } : {}
4424
+ });
4425
+ }
4426
+ sortSuffixParams(root);
4427
+ return root;
4428
+ }
4429
+ function sortSuffixParams(node) {
4430
+ if (node.xp) {
4431
+ const sorted = {};
4432
+ for (const suffix of Object.keys(node.xp).sort(
4433
+ (a, b) => b.length - a.length
4434
+ )) {
4435
+ sorted[suffix] = node.xp[suffix];
4436
+ }
4437
+ node.xp = sorted;
4438
+ }
4439
+ if (node.s) {
4440
+ for (const child of Object.values(node.s)) {
4441
+ sortSuffixParams(child);
4442
+ }
4443
+ }
4444
+ if (node.p) {
4445
+ sortSuffixParams(node.p.c);
4446
+ }
4447
+ if (node.xp) {
4448
+ for (const child of Object.values(node.xp)) {
4449
+ sortSuffixParams(child.c);
4450
+ }
4451
+ }
4452
+ }
4453
+ function buildPerRouterTrie(manifest) {
4454
+ const ancestry = manifest._routeAncestry;
4455
+ if (!ancestry || Object.keys(ancestry).length === 0) {
4456
+ return null;
4457
+ }
4458
+ const routeToStaticPrefix = {};
4459
+ for (const name of Object.keys(manifest.routeManifest)) {
4460
+ routeToStaticPrefix[name] = "";
4461
+ }
4462
+ if (manifest.prefixTree) {
4463
+ buildRouteToStaticPrefix(manifest.prefixTree, routeToStaticPrefix);
4464
+ }
4465
+ return buildRouteTrie(
4466
+ manifest.routeManifest,
4467
+ ancestry,
4468
+ routeToStaticPrefix,
4469
+ manifest.routeTrailingSlash,
4470
+ manifest.prerenderRoutes ? new Set(manifest.prerenderRoutes) : void 0,
4471
+ manifest.passthroughRoutes ? new Set(manifest.passthroughRoutes) : void 0,
4472
+ manifest.responseTypeRoutes
4473
+ );
4474
+ }
4475
+ function insertRoute(node, segments, index, leaf) {
4476
+ const constraints = {};
4477
+ for (const seg of segments) {
4478
+ if (seg.type === "param") {
4479
+ if (seg.constraint) {
4480
+ constraints[seg.value] = seg.constraint;
4481
+ }
4482
+ }
4483
+ }
4484
+ const leafBase = {
4485
+ ...leaf,
4486
+ ...Object.keys(constraints).length > 0 ? { cv: constraints } : {}
4487
+ };
4488
+ insertSegments(node, segments, index, leafBase, []);
4489
+ }
4490
+ function mergeLeaves(existing, leaf) {
4491
+ if (!existing) return leaf;
4492
+ if (existing.rt && leaf.rt) {
4493
+ const merged = leaf;
4494
+ merged.nv = existing.nv || [];
4495
+ merged.nv.push({ routeKey: existing.n, responseType: existing.rt });
4496
+ return merged;
4497
+ }
4498
+ if (leaf.rt && !existing.rt) {
4499
+ if (!existing.nv) {
4500
+ existing.nv = [];
4501
+ existing.rf = true;
4502
+ }
4503
+ existing.nv.push({ routeKey: leaf.n, responseType: leaf.rt });
4504
+ return existing;
4505
+ }
4506
+ if (!leaf.rt && existing.rt) {
4507
+ if (!leaf.nv) leaf.nv = [];
4508
+ if (existing.nv) leaf.nv.push(...existing.nv);
4509
+ leaf.nv.push({ routeKey: existing.n, responseType: existing.rt });
4510
+ return leaf;
4511
+ }
4512
+ return leaf;
4513
+ }
4514
+ function mergeLeaf(node, leaf) {
4515
+ node.r = mergeLeaves(node.r, leaf);
4516
+ }
4517
+ function buildLeaf(leafBase, paramNames) {
4518
+ return paramNames.length > 0 ? { ...leafBase, pa: [...paramNames] } : { ...leafBase };
4519
+ }
4520
+ function insertSegments(node, segments, index, leafBase, paramNames) {
4521
+ if (index >= segments.length) {
4522
+ mergeLeaf(node, buildLeaf(leafBase, paramNames));
4523
+ return;
4524
+ }
4525
+ const segment = segments[index];
4526
+ if (segment.type === "static") {
4527
+ if (!node.s) node.s = {};
4528
+ if (!node.s[segment.value]) node.s[segment.value] = {};
4529
+ insertSegments(
4530
+ node.s[segment.value],
4531
+ segments,
4532
+ index + 1,
4533
+ leafBase,
4534
+ paramNames
4535
+ );
4536
+ } else if (segment.type === "param") {
4537
+ if (segment.optional) {
4538
+ insertSegments(node, segments, index + 1, leafBase, paramNames);
4539
+ }
4540
+ if (segment.suffix) {
4541
+ if (!node.xp) node.xp = {};
4542
+ if (!node.xp[segment.suffix]) {
4543
+ node.xp[segment.suffix] = { n: segment.value, c: {} };
4544
+ }
4545
+ insertSegments(node.xp[segment.suffix].c, segments, index + 1, leafBase, [
4546
+ ...paramNames,
4547
+ segment.value
4548
+ ]);
4549
+ } else {
4550
+ if (!node.p) {
4551
+ node.p = { n: segment.value, c: {} };
4552
+ }
4553
+ insertSegments(node.p.c, segments, index + 1, leafBase, [
4554
+ ...paramNames,
4555
+ segment.value
4556
+ ]);
4557
+ }
4558
+ } else if (segment.type === "wildcard") {
4559
+ const wildLeaf = {
4560
+ ...buildLeaf(leafBase, paramNames),
4561
+ pn: "*"
4562
+ };
4563
+ const existing = node.w ? { ...node.w } : void 0;
4564
+ const merged = mergeLeaves(existing, wildLeaf);
4565
+ node.w = merged;
4566
+ }
4567
+ }
4568
+
4569
+ // src/build/collect-fallback-refs.ts
4570
+ var CLIENT_REF = /* @__PURE__ */ Symbol.for("react.client.reference");
4571
+ var MAX_DEPTH = 40;
4572
+ var SYNTHETIC_PROPS = {
4573
+ error: new Error("rango: build-time fallback-chunk discovery"),
4574
+ reset: () => {
4575
+ },
4576
+ pathname: "/",
4577
+ info: { componentStack: "" }
4578
+ };
4579
+ function isReactNodeLike(v) {
4580
+ return Array.isArray(v) || typeof v === "object" && v !== null && "$$typeof" in v;
4581
+ }
4582
+ function walkElementTree(node, report, depth) {
4583
+ if (node == null || depth > MAX_DEPTH) return;
4584
+ if (Array.isArray(node)) {
4585
+ for (const child of node) walkElementTree(child, report, depth + 1);
4586
+ return;
4587
+ }
4588
+ if (typeof node !== "object") return;
4589
+ const el = node;
4590
+ const type = el.type;
4591
+ if (type?.$$typeof === CLIENT_REF && typeof type.$$id === "string") {
4592
+ report(type.$$id.split("#")[0]);
4593
+ }
4594
+ const props = el.props;
4595
+ if (props && typeof props === "object") {
4596
+ walkElementTree(props.children, report, depth + 1);
4597
+ for (const key in props) {
4598
+ if (key === "children") continue;
4599
+ const value = props[key];
4600
+ if (isReactNodeLike(value)) walkElementTree(value, report, depth + 1);
4601
+ }
4602
+ }
4603
+ }
4604
+ function collectFallbackClientRefs(boundary, report) {
4605
+ try {
4606
+ let node = boundary;
4607
+ if (typeof node === "function") {
4608
+ node = node(SYNTHETIC_PROPS);
4609
+ }
4610
+ walkElementTree(node, report, 0);
4611
+ } catch {
4612
+ }
4613
+ }
4614
+
4615
+ // src/vite/utils/manifest-utils.ts
3861
4616
  function jsonParseExpression(value) {
3862
4617
  const json = JSON.stringify(value);
3863
4618
  const escaped = json.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
@@ -3865,6 +4620,9 @@ function jsonParseExpression(value) {
3865
4620
  }
3866
4621
 
3867
4622
  // src/context-var.ts
4623
+ function hasContextVars(variables) {
4624
+ return Object.keys(variables).length > 0 || Object.getOwnPropertySymbols(variables).length > 0;
4625
+ }
3868
4626
  var NON_CACHEABLE_KEYS = /* @__PURE__ */ Symbol.for(
3869
4627
  "rango:non-cacheable-keys"
3870
4628
  );
@@ -3898,13 +4656,13 @@ function contextSet(variables, keyOrVar, value, options) {
3898
4656
  import { createHash as createHash4 } from "node:crypto";
3899
4657
  import {
3900
4658
  copyFileSync,
3901
- existsSync as existsSync4,
4659
+ existsSync as existsSync5,
3902
4660
  mkdirSync,
3903
4661
  rmSync,
3904
4662
  statSync,
3905
4663
  writeFileSync as writeFileSync2
3906
4664
  } from "node:fs";
3907
- import { resolve as resolve5 } from "node:path";
4665
+ import { resolve as resolve6 } from "node:path";
3908
4666
  function escapeRegExp2(str) {
3909
4667
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3910
4668
  }
@@ -4002,7 +4760,7 @@ function notifyOnError(registry, error, phase, routeKey, pathname, skipped) {
4002
4760
  }
4003
4761
  }
4004
4762
  function getStagedAssetDir(projectRoot) {
4005
- return resolve5(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
4763
+ return resolve6(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
4006
4764
  }
4007
4765
  function resetStagedBuildAssets(projectRoot) {
4008
4766
  rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
@@ -4012,8 +4770,8 @@ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
4012
4770
  mkdirSync(stagedDir, { recursive: true });
4013
4771
  const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
4014
4772
  const fileName = `${prefix}-${contentHash}.js`;
4015
- const filePath = resolve5(stagedDir, fileName);
4016
- if (!existsSync4(filePath)) {
4773
+ const filePath = resolve6(stagedDir, fileName);
4774
+ if (!existsSync5(filePath)) {
4017
4775
  writeFileSync2(filePath, `export default ${exportValue};
4018
4776
  `);
4019
4777
  }
@@ -4021,12 +4779,12 @@ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
4021
4779
  }
4022
4780
  function copyStagedBuildAssets(projectRoot, fileNames) {
4023
4781
  const stagedDir = getStagedAssetDir(projectRoot);
4024
- const distAssetsDir = resolve5(projectRoot, "dist/rsc/assets");
4782
+ const distAssetsDir = resolve6(projectRoot, "dist/rsc/assets");
4025
4783
  mkdirSync(distAssetsDir, { recursive: true });
4026
4784
  let totalBytes = 0;
4027
4785
  for (const fileName of new Set(fileNames)) {
4028
- const stagedPath = resolve5(stagedDir, fileName);
4029
- const distPath = resolve5(distAssetsDir, fileName);
4786
+ const stagedPath = resolve6(stagedDir, fileName);
4787
+ const distPath = resolve6(distAssetsDir, fileName);
4030
4788
  copyFileSync(stagedPath, distPath);
4031
4789
  totalBytes += statSync(stagedPath).size;
4032
4790
  }
@@ -4068,7 +4826,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4068
4826
  const progressInterval = totalDynamic > 0 ? setInterval(() => {
4069
4827
  const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4070
4828
  console.log(
4071
- `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4829
+ `[rango] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4072
4830
  );
4073
4831
  }, 5e3) : void 0;
4074
4832
  try {
@@ -4105,7 +4863,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4105
4863
  get env() {
4106
4864
  if (buildEnv !== void 0) return buildEnv;
4107
4865
  throw new Error(
4108
- "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
4866
+ "[rango] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
4109
4867
  );
4110
4868
  }
4111
4869
  };
@@ -4117,7 +4875,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4117
4875
  (performance.now() - getParamsStart).toFixed(1)
4118
4876
  );
4119
4877
  const concurrency = def.options?.concurrency ?? 1;
4120
- const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
4878
+ const hasBuildVars = hasContextVars(buildVars);
4121
4879
  for (const params of paramsList) {
4122
4880
  let url = substituteRouteParams(
4123
4881
  pattern,
@@ -4146,7 +4904,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4146
4904
  resolvedRoutes++;
4147
4905
  if (err.name === "Skip") {
4148
4906
  console.log(
4149
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
4907
+ `[rango] SKIP route "${routeName}" - ${err.message}`
4150
4908
  );
4151
4909
  notifyOnError(
4152
4910
  registry,
@@ -4159,14 +4917,14 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4159
4917
  continue;
4160
4918
  }
4161
4919
  console.error(
4162
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
4920
+ `[rango] Failed to get params for prerender route "${routeName}": ${err.message}`
4163
4921
  );
4164
4922
  notifyOnError(registry, err, "prerender", routeName);
4165
4923
  throw err;
4166
4924
  }
4167
4925
  } else {
4168
4926
  console.warn(
4169
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
4927
+ `[rango] Dynamic prerender route "${routeName}" has no getParams(), skipping`
4170
4928
  );
4171
4929
  }
4172
4930
  }
@@ -4177,7 +4935,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4177
4935
  clearInterval(progressInterval);
4178
4936
  const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4179
4937
  console.log(
4180
- `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4938
+ `[rango] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4181
4939
  );
4182
4940
  }
4183
4941
  }
@@ -4191,7 +4949,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4191
4949
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
4192
4950
  const concurrencyNote = maxConcurrency > 1 ? ` (concurrency: ${maxConcurrency})` : "";
4193
4951
  console.log(
4194
- `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
4952
+ `[rango] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
4195
4953
  );
4196
4954
  debug9?.(
4197
4955
  "prerender loop: %d entries, max concurrency %d",
@@ -4224,7 +4982,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4224
4982
  if (result.passthrough) {
4225
4983
  const elapsed2 = (performance.now() - startUrl).toFixed(0);
4226
4984
  console.log(
4227
- `[rsc-router] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
4985
+ `[rango] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
4228
4986
  );
4229
4987
  doneCount++;
4230
4988
  break;
@@ -4244,10 +5002,9 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4244
5002
  const interceptKey = `${result.routeName}/${paramHash}/i`;
4245
5003
  const interceptValue = JSON.stringify({
4246
5004
  segments: [...result.segments, ...result.interceptSegments],
4247
- handles: {
4248
- ...result.handles,
4249
- ...result.interceptHandles || {}
4250
- }
5005
+ // interceptHandles is the pre-encoded MERGED (main + intercept)
5006
+ // handle string (the producer merged before encoding).
5007
+ handles: result.interceptHandles ?? ""
4251
5008
  });
4252
5009
  manifestEntries[interceptKey] = stageBuildAssetModule(
4253
5010
  state.projectRoot,
@@ -4257,7 +5014,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4257
5014
  }
4258
5015
  const elapsed = (performance.now() - startUrl).toFixed(0);
4259
5016
  console.log(
4260
- `[rsc-router] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`
5017
+ `[rango] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`
4261
5018
  );
4262
5019
  doneCount++;
4263
5020
  break;
@@ -4265,7 +5022,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4265
5022
  if (err.name === "Skip") {
4266
5023
  const elapsed2 = (performance.now() - startUrl).toFixed(0);
4267
5024
  console.log(
4268
- `[rsc-router] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
5025
+ `[rango] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4269
5026
  );
4270
5027
  skipCount++;
4271
5028
  notifyOnError(
@@ -4280,7 +5037,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4280
5037
  }
4281
5038
  const elapsed = (performance.now() - startUrl).toFixed(0);
4282
5039
  console.error(
4283
- `[rsc-router] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
5040
+ `[rango] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
4284
5041
  );
4285
5042
  notifyOnError(
4286
5043
  registry,
@@ -4302,7 +5059,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4302
5059
  const parts = [`${doneCount} done`];
4303
5060
  if (skipCount > 0) parts.push(`${skipCount} skipped`);
4304
5061
  console.log(
4305
- `[rsc-router] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
5062
+ `[rango] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
4306
5063
  );
4307
5064
  debug9?.(
4308
5065
  "expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
@@ -4328,16 +5085,14 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4328
5085
  totalStaticCount += exportNames.length;
4329
5086
  }
4330
5087
  const startStatic = performance.now();
4331
- console.log(
4332
- `[rsc-router] Rendering ${totalStaticCount} static handler(s)...`
4333
- );
5088
+ console.log(`[rango] Rendering ${totalStaticCount} static handler(s)...`);
4334
5089
  for (const [moduleId, exportNames] of state.resolvedStaticModules) {
4335
5090
  let mod;
4336
5091
  try {
4337
5092
  mod = await rscEnv.runner.import(moduleId);
4338
5093
  } catch (err) {
4339
5094
  console.error(
4340
- `[rsc-router] Failed to import static module ${moduleId}: ${err.message}`
5095
+ `[rango] Failed to import static module ${moduleId}: ${err.message}`
4341
5096
  );
4342
5097
  notifyOnError(registry, err, "static");
4343
5098
  throw err;
@@ -4359,7 +5114,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4359
5114
  !state.isBuildMode
4360
5115
  );
4361
5116
  if (result) {
4362
- const hasHandles = Object.keys(result.handles).length > 0;
5117
+ const hasHandles = result.handles !== "";
4363
5118
  const exportValue = hasHandles ? JSON.stringify(result) : JSON.stringify(result.encoded);
4364
5119
  manifestEntries[def.$$id] = stageBuildAssetModule(
4365
5120
  state.projectRoot,
@@ -4367,9 +5122,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4367
5122
  exportValue
4368
5123
  );
4369
5124
  const elapsed = (performance.now() - startHandler).toFixed(0);
4370
- console.log(
4371
- `[rsc-router] OK ${name.padEnd(40)} (${elapsed}ms)`
4372
- );
5125
+ console.log(`[rango] OK ${name.padEnd(40)} (${elapsed}ms)`);
4373
5126
  staticDone++;
4374
5127
  handled = true;
4375
5128
  break;
@@ -4378,7 +5131,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4378
5131
  if (err.name === "Skip") {
4379
5132
  const elapsed2 = (performance.now() - startHandler).toFixed(0);
4380
5133
  console.log(
4381
- `[rsc-router] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
5134
+ `[rango] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4382
5135
  );
4383
5136
  staticSkip++;
4384
5137
  notifyOnError(registry, err, "static", void 0, void 0, true);
@@ -4387,16 +5140,14 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4387
5140
  }
4388
5141
  const elapsed = (performance.now() - startHandler).toFixed(0);
4389
5142
  console.error(
4390
- `[rsc-router] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
5143
+ `[rango] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
4391
5144
  );
4392
5145
  notifyOnError(registry, err, "static");
4393
5146
  throw err;
4394
5147
  }
4395
5148
  }
4396
5149
  if (!handled) {
4397
- console.warn(
4398
- `[rsc-router] No router could render static handler "${name}"`
4399
- );
5150
+ console.warn(`[rango] No router could render static handler "${name}"`);
4400
5151
  }
4401
5152
  }
4402
5153
  }
@@ -4407,7 +5158,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4407
5158
  const staticParts = [`${staticDone} done`];
4408
5159
  if (staticSkip > 0) staticParts.push(`${staticSkip} skipped`);
4409
5160
  console.log(
4410
- `[rsc-router] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
5161
+ `[rango] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
4411
5162
  );
4412
5163
  debug9?.(
4413
5164
  "renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
@@ -4418,6 +5169,80 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4418
5169
  );
4419
5170
  }
4420
5171
 
5172
+ // src/vite/discovery/discovery-errors.ts
5173
+ function indent(text, pad) {
5174
+ return text.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n");
5175
+ }
5176
+ async function invokeLazyMount(loader, context, errors) {
5177
+ try {
5178
+ await loader();
5179
+ } catch (error) {
5180
+ errors.push({ context, error });
5181
+ }
5182
+ }
5183
+ function isLazyMount(route) {
5184
+ return !!route && route.kind === "lazy" && typeof route.handler === "function";
5185
+ }
5186
+ async function resolveHostRouterHandlers(hostRegistry) {
5187
+ const errors = [];
5188
+ for (const [hostId, entry] of hostRegistry) {
5189
+ for (const route of entry.routes) {
5190
+ if (isLazyMount(route)) {
5191
+ await invokeLazyMount(
5192
+ route.handler,
5193
+ `host "${hostId}" route handler`,
5194
+ errors
5195
+ );
5196
+ }
5197
+ }
5198
+ if (isLazyMount(entry.fallback)) {
5199
+ await invokeLazyMount(
5200
+ entry.fallback.handler,
5201
+ `host "${hostId}" fallback handler`,
5202
+ errors
5203
+ );
5204
+ }
5205
+ }
5206
+ return errors;
5207
+ }
5208
+ function formatNoRoutersError(entryPath, errors) {
5209
+ const base = `[rango] No routers found in registry after importing ${entryPath}`;
5210
+ if (errors.length === 0) {
5211
+ return base;
5212
+ }
5213
+ const formatted = errors.map(({ context, error }) => {
5214
+ const err = error instanceof Error ? error : new Error(String(error));
5215
+ const detail = err.stack ?? err.message;
5216
+ return ` - while resolving ${context}:
5217
+ ${indent(detail, " ")}`;
5218
+ }).join("\n");
5219
+ return `${base}
5220
+
5221
+ ${errors.length} error(s) were caught during host-router discovery and likely explain why no routers were registered:
5222
+ ${formatted}`;
5223
+ }
5224
+ function toCause(errors) {
5225
+ if (errors.length === 0) return void 0;
5226
+ if (errors.length === 1) return errors[0].error;
5227
+ return new AggregateError(
5228
+ errors.map((e) => e.error),
5229
+ "Multiple host-router handlers failed during discovery"
5230
+ );
5231
+ }
5232
+ var DiscoveryError = class _DiscoveryError extends Error {
5233
+ constructor(entryPath, caught) {
5234
+ super(formatNoRoutersError(entryPath, caught));
5235
+ const cause = toCause(caught);
5236
+ if (cause !== void 0) {
5237
+ this.cause = cause;
5238
+ }
5239
+ this.name = "DiscoveryError";
5240
+ this.entryPath = entryPath;
5241
+ this.caught = caught;
5242
+ Object.setPrototypeOf(this, _DiscoveryError.prototype);
5243
+ }
5244
+ };
5245
+
4421
5246
  // src/vite/discovery/discover-routers.ts
4422
5247
  var debug10 = createRangoDebugger(NS.discovery);
4423
5248
  async function discoverRouters(state, rscEnv) {
@@ -4434,27 +5259,17 @@ async function discoverRouters(state, rscEnv) {
4434
5259
  );
4435
5260
  let registry = serverMod.RouterRegistry;
4436
5261
  if (!registry || registry.size === 0) {
5262
+ const discoveryErrors = [];
4437
5263
  try {
4438
5264
  const hostRegistry = serverMod.HostRouterRegistry;
4439
5265
  if (hostRegistry && hostRegistry.size > 0) {
4440
5266
  console.log(
4441
- `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
5267
+ `[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
4442
5268
  );
4443
- for (const [, entry] of hostRegistry) {
4444
- for (const route of entry.routes) {
4445
- if (typeof route.handler === "function") {
4446
- try {
4447
- await route.handler();
4448
- } catch {
4449
- }
4450
- }
4451
- }
4452
- if (entry.fallback && typeof entry.fallback.handler === "function") {
4453
- try {
4454
- await entry.fallback.handler();
4455
- } catch {
4456
- }
4457
- }
5269
+ const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
5270
+ discoveryErrors.push(...handlerErrors);
5271
+ for (const { context, error } of handlerErrors) {
5272
+ debug10?.("caught error while resolving %s: %O", context, error);
4458
5273
  }
4459
5274
  const freshServerMod = await rscEnv.runner.import(
4460
5275
  "@rangojs/router/server"
@@ -4465,12 +5280,11 @@ async function discoverRouters(state, rscEnv) {
4465
5280
  registry = freshRegistry;
4466
5281
  }
4467
5282
  }
4468
- } catch {
5283
+ } catch (error) {
5284
+ discoveryErrors.push({ context: "host-router discovery", error });
4469
5285
  }
4470
5286
  if (!registry || registry.size === 0) {
4471
- throw new Error(
4472
- `[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`
4473
- );
5287
+ throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
4474
5288
  }
4475
5289
  }
4476
5290
  const buildMod = await timed(
@@ -4498,6 +5312,15 @@ async function discoverRouters(state, rscEnv) {
4498
5312
  let mergedRouteTrailingSlash = {};
4499
5313
  let routerMountIndex = 0;
4500
5314
  const allManifests = [];
5315
+ const clientChunkCtx = state.opts?.clientChunkCtx;
5316
+ const collectClientFallbackRef = clientChunkCtx ? (refKey) => clientChunkCtx.fallbackRefs.add(
5317
+ computeProductionHash(state.projectRoot, refKey)
5318
+ ) : void 0;
5319
+ const collectFromBoundaryNode = (node) => {
5320
+ if (collectClientFallbackRef) {
5321
+ collectFallbackClientRefs(node, collectClientFallbackRef);
5322
+ }
5323
+ };
4501
5324
  const manifestGenStart = debug10 ? performance.now() : 0;
4502
5325
  for (const [id, router] of registry) {
4503
5326
  if (!router.urlpatterns || !generateManifestFull) {
@@ -4506,10 +5329,18 @@ async function discoverRouters(state, rscEnv) {
4506
5329
  const manifest = generateManifestFull(
4507
5330
  router.urlpatterns,
4508
5331
  routerMountIndex,
4509
- router.__basename ? { urlPrefix: router.__basename } : void 0
5332
+ {
5333
+ ...router.__basename ? { urlPrefix: router.__basename } : {},
5334
+ ...collectClientFallbackRef ? { collectClientFallbackRef } : {}
5335
+ }
4510
5336
  );
4511
5337
  routerMountIndex++;
4512
5338
  allManifests.push({ id, manifest });
5339
+ if (collectClientFallbackRef) {
5340
+ collectFromBoundaryNode(router.__defaultErrorBoundary);
5341
+ collectFromBoundaryNode(router.__defaultNotFoundBoundary);
5342
+ collectFromBoundaryNode(router.__notFound);
5343
+ }
4513
5344
  const routeCount = Object.keys(manifest.routeManifest).length;
4514
5345
  const staticRoutes = Object.values(manifest.routeManifest).filter(
4515
5346
  (p) => !p.includes(":") && !p.includes("*")
@@ -4546,21 +5377,17 @@ async function discoverRouters(state, rscEnv) {
4546
5377
  if (manifest.routeTrailingSlash) {
4547
5378
  Object.assign(mergedRouteTrailingSlash, manifest.routeTrailingSlash);
4548
5379
  }
4549
- flattenLeafEntries(
4550
- manifest.prefixTree,
4551
- manifest.routeManifest,
4552
- newMergedPrecomputedEntries
4553
- );
4554
- newPerRouterManifestDataMap.set(id, manifest.routeManifest);
4555
5380
  const routerPrecomputed = [];
4556
5381
  flattenLeafEntries(
4557
5382
  manifest.prefixTree,
4558
5383
  manifest.routeManifest,
4559
5384
  routerPrecomputed
4560
5385
  );
5386
+ newMergedPrecomputedEntries.push(...routerPrecomputed);
5387
+ newPerRouterManifestDataMap.set(id, manifest.routeManifest);
4561
5388
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
4562
5389
  console.log(
4563
- `[rsc-router] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
5390
+ `[rango] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
4564
5391
  );
4565
5392
  }
4566
5393
  if (registry.size > 1) {
@@ -4569,7 +5396,7 @@ async function discoverRouters(state, rscEnv) {
4569
5396
  );
4570
5397
  if (autoIds.length > 1) {
4571
5398
  console.warn(
4572
- `[rsc-router] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). In multi-router setups, each createRouter() must have an explicit \`id\` option to ensure per-router manifest data is matched correctly at runtime. Example: createRouter({ id: "site", ... })`
5399
+ `[rango] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). In multi-router setups, each createRouter() must have an explicit \`id\` option to ensure per-router manifest data is matched correctly at runtime. Example: createRouter({ id: "site", ... })`
4573
5400
  );
4574
5401
  }
4575
5402
  }
@@ -4581,8 +5408,7 @@ async function discoverRouters(state, rscEnv) {
4581
5408
  let newMergedRouteTrie = null;
4582
5409
  const trieStart = debug10 ? performance.now() : 0;
4583
5410
  if (Object.keys(newMergedRouteManifest).length > 0) {
4584
- const buildRouteTrie = buildMod.buildRouteTrie;
4585
- if (buildRouteTrie && mergedRouteAncestry) {
5411
+ if (mergedRouteAncestry) {
4586
5412
  const routeToStaticPrefix = {};
4587
5413
  for (const { manifest } of allManifests) {
4588
5414
  for (const name of Object.keys(manifest.routeManifest)) {
@@ -4614,31 +5440,16 @@ async function discoverRouters(state, rscEnv) {
4614
5440
  newMergedRouteManifest,
4615
5441
  mergedRouteAncestry,
4616
5442
  routeToStaticPrefix,
4617
- Object.keys(mergedRouteTrailingSlash).length > 0 ? mergedRouteTrailingSlash : void 0,
4618
- prerenderRouteNames.size > 0 ? prerenderRouteNames : void 0,
4619
- passthroughRouteNames.size > 0 ? passthroughRouteNames : void 0,
4620
- Object.keys(mergedResponseTypeRoutes).length > 0 ? mergedResponseTypeRoutes : void 0
5443
+ mergedRouteTrailingSlash,
5444
+ prerenderRouteNames,
5445
+ passthroughRouteNames,
5446
+ mergedResponseTypeRoutes
4621
5447
  );
4622
5448
  for (const { id, manifest } of allManifests) {
4623
- if (!manifest._routeAncestry || Object.keys(manifest._routeAncestry).length === 0)
4624
- continue;
4625
- const perRouterStaticPrefix = {};
4626
- for (const name of Object.keys(manifest.routeManifest)) {
4627
- perRouterStaticPrefix[name] = "";
5449
+ const perRouterTrie = buildPerRouterTrie(manifest);
5450
+ if (perRouterTrie) {
5451
+ newPerRouterTrieMap.set(id, perRouterTrie);
4628
5452
  }
4629
- buildRouteToStaticPrefix(manifest.prefixTree, perRouterStaticPrefix);
4630
- const perRouterPrerenderNames = manifest.prerenderRoutes ? new Set(manifest.prerenderRoutes) : void 0;
4631
- const perRouterPassthroughNames = manifest.passthroughRoutes ? new Set(manifest.passthroughRoutes) : void 0;
4632
- const perRouterTrie = buildRouteTrie(
4633
- manifest.routeManifest,
4634
- manifest._routeAncestry,
4635
- perRouterStaticPrefix,
4636
- manifest.routeTrailingSlash && Object.keys(manifest.routeTrailingSlash).length > 0 ? manifest.routeTrailingSlash : void 0,
4637
- perRouterPrerenderNames && perRouterPrerenderNames.size > 0 ? perRouterPrerenderNames : void 0,
4638
- perRouterPassthroughNames && perRouterPassthroughNames.size > 0 ? perRouterPassthroughNames : void 0,
4639
- manifest.responseTypeRoutes && Object.keys(manifest.responseTypeRoutes).length > 0 ? manifest.responseTypeRoutes : void 0
4640
- );
4641
- newPerRouterTrieMap.set(id, perRouterTrie);
4642
5453
  }
4643
5454
  }
4644
5455
  }
@@ -4659,8 +5470,8 @@ async function discoverRouters(state, rscEnv) {
4659
5470
  }
4660
5471
 
4661
5472
  // src/vite/discovery/route-types-writer.ts
4662
- import { dirname as dirname3, basename, join as join2, resolve as resolve6 } from "node:path";
4663
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
5473
+ import { dirname as dirname3, join as join4, resolve as resolve7 } from "node:path";
5474
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6, unlinkSync as unlinkSync2 } from "node:fs";
4664
5475
  function filterUserNamedRoutes(manifest) {
4665
5476
  const filtered = {};
4666
5477
  for (const [name, pattern] of Object.entries(manifest)) {
@@ -4670,51 +5481,32 @@ function filterUserNamedRoutes(manifest) {
4670
5481
  }
4671
5482
  return filtered;
4672
5483
  }
5484
+ function writeGenFileIfChanged(state, outPath, source, opts) {
5485
+ const existing = existsSync6(outPath) ? readFileSync4(outPath, "utf-8") : null;
5486
+ if (existing === source) return;
5487
+ markSelfGenWrite(state, outPath, source);
5488
+ writeFileSync3(outPath, source);
5489
+ if (opts?.log) console.log(`[rango] Generated route types -> ${outPath}`);
5490
+ }
4673
5491
  function writeCombinedRouteTypesWithTracking(state, opts) {
4674
5492
  const routerFiles = state.cachedRouterFiles ?? findRouterFiles(state.projectRoot, state.scanFilter);
4675
5493
  state.cachedRouterFiles = routerFiles;
4676
- const preContent = /* @__PURE__ */ new Map();
4677
- for (const routerFilePath of routerFiles) {
4678
- const routerDir = dirname3(routerFilePath);
4679
- const routerBasename = basename(routerFilePath).replace(
4680
- /\.(tsx?|jsx?)$/,
4681
- ""
4682
- );
4683
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4684
- try {
4685
- preContent.set(outPath, readFileSync4(outPath, "utf-8"));
4686
- } catch {
4687
- }
4688
- }
4689
- writeCombinedRouteTypes(state.projectRoot, routerFiles, opts);
4690
- for (const routerFilePath of routerFiles) {
4691
- const routerDir = dirname3(routerFilePath);
4692
- const routerBasename = basename(routerFilePath).replace(
4693
- /\.(tsx?|jsx?)$/,
4694
- ""
4695
- );
4696
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4697
- if (!existsSync5(outPath)) continue;
4698
- try {
4699
- const content = readFileSync4(outPath, "utf-8");
4700
- if (content !== preContent.get(outPath)) {
4701
- markSelfGenWrite(state, outPath, content);
4702
- }
4703
- } catch {
4704
- }
4705
- }
5494
+ writeCombinedRouteTypes(state.projectRoot, routerFiles, {
5495
+ ...opts,
5496
+ onWrite: (outPath, content) => markSelfGenWrite(state, outPath, content)
5497
+ });
4706
5498
  }
4707
5499
  function writeRouteTypesFiles(state) {
4708
5500
  if (state.perRouterManifests.length === 0) return;
4709
5501
  try {
4710
5502
  const entryDir = dirname3(
4711
- resolve6(state.projectRoot, state.resolvedEntryPath)
5503
+ resolve7(state.projectRoot, state.resolvedEntryPath)
4712
5504
  );
4713
- const oldCombinedPath = join2(entryDir, "named-routes.gen.ts");
4714
- if (existsSync5(oldCombinedPath)) {
5505
+ const oldCombinedPath = join4(entryDir, "named-routes.gen.ts");
5506
+ if (existsSync6(oldCombinedPath)) {
4715
5507
  unlinkSync2(oldCombinedPath);
4716
5508
  console.log(
4717
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
5509
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
4718
5510
  );
4719
5511
  }
4720
5512
  } catch {
@@ -4728,39 +5520,23 @@ function writeRouteTypesFiles(state) {
4728
5520
  if (!sourceFile) continue;
4729
5521
  if (sourceFile.includes("node_modules")) {
4730
5522
  throw new Error(
4731
- `[rsc-router] Router "${id}" has sourceFile inside node_modules: ${sourceFile}
5523
+ `[rango] Router "${id}" has sourceFile inside node_modules: ${sourceFile}
4732
5524
  This means createRouter() stack trace parsing matched a Vite internal frame.
4733
5525
  Set an explicit \`id\` on createRouter() or check the call site.`
4734
5526
  );
4735
5527
  }
4736
- const routerDir = dirname3(sourceFile);
4737
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4738
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
5528
+ const outPath = genFileTsPath(sourceFile);
4739
5529
  const userRoutes = filterUserNamedRoutes(routeManifest);
4740
- let effectiveSearchSchemas = routeSearchSchemas;
4741
- if ((!effectiveSearchSchemas || Object.keys(effectiveSearchSchemas).length === 0) && sourceFile) {
4742
- const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
4743
- if (Object.keys(staticParsed.searchSchemas).length > 0) {
4744
- const filtered = {};
4745
- for (const name of Object.keys(userRoutes)) {
4746
- const schema = staticParsed.searchSchemas[name];
4747
- if (schema) filtered[name] = schema;
4748
- }
4749
- if (Object.keys(filtered).length > 0) {
4750
- effectiveSearchSchemas = filtered;
4751
- }
4752
- }
4753
- }
5530
+ const effectiveSearchSchemas = resolveSearchSchemas(
5531
+ Object.keys(userRoutes),
5532
+ routeSearchSchemas,
5533
+ sourceFile
5534
+ );
4754
5535
  const source = generateRouteTypesSource(
4755
5536
  userRoutes,
4756
5537
  effectiveSearchSchemas && Object.keys(effectiveSearchSchemas).length > 0 ? effectiveSearchSchemas : void 0
4757
5538
  );
4758
- const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4759
- if (existing !== source) {
4760
- markSelfGenWrite(state, outPath, source);
4761
- writeFileSync3(outPath, source);
4762
- console.log(`[rsc-router] Generated route types -> ${outPath}`);
4763
- }
5539
+ writeGenFileIfChanged(state, outPath, source, { log: true });
4764
5540
  }
4765
5541
  }
4766
5542
  function supplementGenFilesWithRuntimeRoutes(state) {
@@ -4798,23 +5574,17 @@ function supplementGenFilesWithRuntimeRoutes(state) {
4798
5574
  }
4799
5575
  }
4800
5576
  }
4801
- const routerDir = dirname3(sourceFile);
4802
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4803
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
5577
+ const outPath = genFileTsPath(sourceFile);
4804
5578
  const source = generateRouteTypesSource(
4805
5579
  mergedRoutes,
4806
5580
  Object.keys(mergedSearchSchemas).length > 0 ? mergedSearchSchemas : void 0
4807
5581
  );
4808
- const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4809
- if (existing !== source) {
4810
- markSelfGenWrite(state, outPath, source);
4811
- writeFileSync3(outPath, source);
4812
- }
5582
+ writeGenFileIfChanged(state, outPath, source);
4813
5583
  }
4814
5584
  }
4815
5585
 
4816
5586
  // src/vite/discovery/virtual-module-codegen.ts
4817
- import { dirname as dirname4, basename as basename2, join as join3 } from "node:path";
5587
+ import { dirname as dirname4, basename, join as join5 } from "node:path";
4818
5588
  function generateRoutesManifestModule(state) {
4819
5589
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
4820
5590
  if (hasManifest) {
@@ -4825,11 +5595,11 @@ function generateRoutesManifestModule(state) {
4825
5595
  for (const entry of state.perRouterManifests) {
4826
5596
  if (entry.sourceFile) {
4827
5597
  const routerDir = dirname4(entry.sourceFile);
4828
- const routerBasename = basename2(entry.sourceFile).replace(
5598
+ const routerBasename = basename(entry.sourceFile).replace(
4829
5599
  /\.(tsx?|jsx?)$/,
4830
5600
  ""
4831
5601
  );
4832
- const genPath = join3(
5602
+ const genPath = join5(
4833
5603
  routerDir,
4834
5604
  `${routerBasename}.named-routes.gen.js`
4835
5605
  ).replaceAll("\\", "/");
@@ -4846,7 +5616,7 @@ function generateRoutesManifestModule(state) {
4846
5616
  }
4847
5617
  }
4848
5618
  const lines = [
4849
- `import { setCachedManifest, setPrecomputedEntries, setRouteTrie, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
5619
+ `import { setCachedManifest, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
4850
5620
  ...genFileImports,
4851
5621
  // Clear stale per-router cached data (manifest, trie, precomputed entries)
4852
5622
  // before re-populating. In Cloudflare dev mode, program reloads re-evaluate
@@ -4882,18 +5652,6 @@ function generateRoutesManifestModule(state) {
4882
5652
  );
4883
5653
  }
4884
5654
  }
4885
- if (state.isBuildMode) {
4886
- if (state.mergedPrecomputedEntries && state.mergedPrecomputedEntries.length > 0) {
4887
- lines.push(
4888
- `setPrecomputedEntries(${jsonParseExpression(state.mergedPrecomputedEntries)});`
4889
- );
4890
- }
4891
- if (state.mergedRouteTrie) {
4892
- lines.push(
4893
- `setRouteTrie(${jsonParseExpression(state.mergedRouteTrie)});`
4894
- );
4895
- }
4896
- }
4897
5655
  for (const routerId of state.perRouterManifestDataMap.keys()) {
4898
5656
  lines.push(
4899
5657
  `registerRouterManifestLoader(${JSON.stringify(routerId)}, () => import(${JSON.stringify(VIRTUAL_ROUTES_MANIFEST_ID + "/" + routerId)}));`
@@ -4922,11 +5680,11 @@ function generatePerRouterModule(state, routerId) {
4922
5680
  const lines = [];
4923
5681
  if (routerEntry?.sourceFile) {
4924
5682
  const routerDir = dirname4(routerEntry.sourceFile);
4925
- const routerBasename = basename2(routerEntry.sourceFile).replace(
5683
+ const routerBasename = basename(routerEntry.sourceFile).replace(
4926
5684
  /\.(tsx?|jsx?)$/,
4927
5685
  ""
4928
5686
  );
4929
- const genPath = join3(
5687
+ const genPath = join5(
4930
5688
  routerDir,
4931
5689
  `${routerBasename}.named-routes.gen.js`
4932
5690
  ).replaceAll("\\", "/");
@@ -4949,17 +5707,17 @@ function generatePerRouterModule(state, routerId) {
4949
5707
  `export const precomputedEntries = ${jsonParseExpression(entries)};`
4950
5708
  );
4951
5709
  }
4952
- return lines.join("\n") || "// empty router manifest";
5710
+ return lines.join("\n") || "";
4953
5711
  }
4954
5712
 
4955
5713
  // src/vite/discovery/bundle-postprocess.ts
4956
- import { resolve as resolve7 } from "node:path";
4957
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "node:fs";
5714
+ import { resolve as resolve8 } from "node:path";
5715
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "node:fs";
4958
5716
  function postprocessBundle(state) {
4959
5717
  const hasPrerenderData = state.prerenderManifestEntries && Object.keys(state.prerenderManifestEntries).length > 0;
4960
5718
  const hasStaticData = state.staticManifestEntries && Object.keys(state.staticManifestEntries).length > 0;
4961
5719
  if (!hasPrerenderData && !hasStaticData) return;
4962
- const rscEntryPath = resolve7(
5720
+ const rscEntryPath = resolve8(
4963
5721
  state.projectRoot,
4964
5722
  "dist/rsc",
4965
5723
  state.rscEntryFileName ?? "index.js"
@@ -4980,7 +5738,7 @@ function postprocessBundle(state) {
4980
5738
  ];
4981
5739
  for (const target of evictionTargets) {
4982
5740
  for (const info of target.infos) {
4983
- const chunkPath = resolve7(state.projectRoot, "dist/rsc", info.fileName);
5741
+ const chunkPath = resolve8(state.projectRoot, "dist/rsc", info.fileName);
4984
5742
  try {
4985
5743
  const code = readFileSync5(chunkPath, "utf-8");
4986
5744
  const result = evictHandlerCode(
@@ -4993,19 +5751,19 @@ function postprocessBundle(state) {
4993
5751
  writeFileSync4(chunkPath, result.code);
4994
5752
  const savedKB = (result.savedBytes / 1024).toFixed(1);
4995
5753
  console.log(
4996
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
5754
+ `[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4997
5755
  );
4998
5756
  }
4999
5757
  } catch (replaceErr) {
5000
5758
  console.warn(
5001
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
5759
+ `[rango] Failed to evict ${target.label}: ${replaceErr.message}`
5002
5760
  );
5003
5761
  }
5004
5762
  }
5005
5763
  }
5006
5764
  state.handlerChunkInfoMap.clear();
5007
5765
  state.staticHandlerChunkInfoMap.clear();
5008
- if (hasPrerenderData && existsSync6(rscEntryPath)) {
5766
+ if (hasPrerenderData && existsSync7(rscEntryPath)) {
5009
5767
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
5010
5768
  if (!rscCode.includes("__prerender-manifest.js")) {
5011
5769
  try {
@@ -5020,12 +5778,12 @@ function postprocessBundle(state) {
5020
5778
  manifestMap[key] = `./assets/${assetFileName}`;
5021
5779
  }
5022
5780
  const manifestCode = [
5023
- `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
5781
+ `const m=${jsonParseExpression(manifestMap)};`,
5024
5782
  `export function loadPrerenderAsset(s){return import(s)}`,
5025
5783
  `export default m;`,
5026
5784
  ""
5027
5785
  ].join("\n");
5028
- const manifestPath = resolve7(
5786
+ const manifestPath = resolve8(
5029
5787
  state.projectRoot,
5030
5788
  "dist/rsc/__prerender-manifest.js"
5031
5789
  );
@@ -5036,16 +5794,16 @@ function postprocessBundle(state) {
5036
5794
  writeFileSync4(rscEntryPath, injection + rscCode);
5037
5795
  const totalKB = (totalBytes / 1024).toFixed(1);
5038
5796
  console.log(
5039
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
5797
+ `[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
5040
5798
  );
5041
5799
  } catch (err) {
5042
5800
  throw new Error(
5043
- `[rsc-router] Failed to write prerender assets: ${err.message}`
5801
+ `[rango] Failed to write prerender assets: ${err.message}`
5044
5802
  );
5045
5803
  }
5046
5804
  }
5047
5805
  }
5048
- if (hasStaticData && existsSync6(rscEntryPath)) {
5806
+ if (hasStaticData && existsSync7(rscEntryPath)) {
5049
5807
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
5050
5808
  if (!rscCode.includes("__static-manifest.js")) {
5051
5809
  try {
@@ -5063,7 +5821,7 @@ function postprocessBundle(state) {
5063
5821
  }
5064
5822
  const manifestCode = `const m={${manifestEntries.join(",")}};globalThis.__STATIC_MANIFEST=m;export default m;
5065
5823
  `;
5066
- const manifestPath = resolve7(
5824
+ const manifestPath = resolve8(
5067
5825
  state.projectRoot,
5068
5826
  "dist/rsc/__static-manifest.js"
5069
5827
  );
@@ -5074,11 +5832,11 @@ function postprocessBundle(state) {
5074
5832
  writeFileSync4(rscEntryPath, injection + rscCode);
5075
5833
  const totalKB = (totalBytes / 1024).toFixed(1);
5076
5834
  console.log(
5077
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
5835
+ `[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
5078
5836
  );
5079
5837
  } catch (err) {
5080
5838
  throw new Error(
5081
- `[rsc-router] Failed to write static assets: ${err.message}`
5839
+ `[rango] Failed to write static assets: ${err.message}`
5082
5840
  );
5083
5841
  }
5084
5842
  }
@@ -5095,8 +5853,8 @@ function createDiscoveryGate(s, debug11) {
5095
5853
  let pendingEvents = false;
5096
5854
  const beginGate = () => {
5097
5855
  if (gatePending) return;
5098
- s.discoveryDone = new Promise((resolve10) => {
5099
- gateResolver = resolve10;
5856
+ s.discoveryDone = new Promise((resolve11) => {
5857
+ gateResolver = resolve11;
5100
5858
  });
5101
5859
  gatePending = true;
5102
5860
  };
@@ -5159,6 +5917,57 @@ function createDiscoveryGate(s, debug11) {
5159
5917
  };
5160
5918
  }
5161
5919
 
5920
+ // src/vite/utils/forward-user-plugins.ts
5921
+ function isDenied(name) {
5922
+ return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
5923
+ }
5924
+ function hasResolutionHooks(p) {
5925
+ return Boolean(p.resolveId || p.load);
5926
+ }
5927
+ function stripToResolutionHooks(p) {
5928
+ const stripped = { name: p.name };
5929
+ if (p.enforce) stripped.enforce = p.enforce;
5930
+ if (p.applyToEnvironment)
5931
+ stripped.applyToEnvironment = p.applyToEnvironment;
5932
+ if (p.resolveId) stripped.resolveId = p.resolveId;
5933
+ if (p.load) stripped.load = p.load;
5934
+ return stripped;
5935
+ }
5936
+ function selectForwardableResolvePlugins(plugins) {
5937
+ if (!plugins) return [];
5938
+ const forwarded = [];
5939
+ for (const p of plugins) {
5940
+ const name = p?.name;
5941
+ if (!name || isDenied(name)) continue;
5942
+ if (!hasResolutionHooks(p)) continue;
5943
+ forwarded.push(stripToResolutionHooks(p));
5944
+ }
5945
+ return forwarded;
5946
+ }
5947
+ function pickForwardedRunnerConfig(config) {
5948
+ const r = config.resolve ?? {};
5949
+ const resolve11 = {};
5950
+ if (r.alias !== void 0) resolve11.alias = r.alias;
5951
+ if (r.dedupe !== void 0) resolve11.dedupe = r.dedupe;
5952
+ if (r.conditions !== void 0) resolve11.conditions = r.conditions;
5953
+ if (r.mainFields !== void 0) resolve11.mainFields = r.mainFields;
5954
+ if (r.extensions !== void 0) resolve11.extensions = r.extensions;
5955
+ if (r.preserveSymlinks !== void 0)
5956
+ resolve11.preserveSymlinks = r.preserveSymlinks;
5957
+ if (r.tsconfigPaths !== void 0) resolve11.tsconfigPaths = r.tsconfigPaths;
5958
+ const userOxc = config.oxc;
5959
+ const userJsx = userOxc && typeof userOxc === "object" && typeof userOxc.jsx === "object" && userOxc.jsx !== null ? userOxc.jsx : {};
5960
+ const oxc = userOxc && typeof userOxc === "object" ? {
5961
+ ...userOxc,
5962
+ jsx: { ...userJsx, runtime: "automatic", importSource: "react" }
5963
+ } : { jsx: { runtime: "automatic", importSource: "react" } };
5964
+ return {
5965
+ resolve: resolve11,
5966
+ define: config.define,
5967
+ oxc
5968
+ };
5969
+ }
5970
+
5162
5971
  // src/vite/router-discovery.ts
5163
5972
  var debugDiscovery = createRangoDebugger(NS.discovery);
5164
5973
  var debugRoutes = createRangoDebugger(NS.routes);
@@ -5174,21 +5983,29 @@ function ensureCloudflareProtocolLoaderRegistered() {
5174
5983
  );
5175
5984
  } catch (err) {
5176
5985
  console.warn(
5177
- `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5986
+ `[rango] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5178
5987
  );
5179
5988
  }
5180
5989
  }
5181
5990
  async function createTempRscServer(state, options = {}) {
5182
5991
  ensureCloudflareProtocolLoaderRegistered();
5183
5992
  const { default: rsc } = await import("@vitejs/plugin-rsc");
5993
+ const runnerConfig = state.userRunnerConfig;
5994
+ const resolveConfig = runnerConfig?.resolve ?? {
5995
+ alias: state.userResolveAlias
5996
+ };
5997
+ const oxcConfig = runnerConfig?.oxc ?? {
5998
+ jsx: { runtime: "automatic", importSource: "react" }
5999
+ };
5184
6000
  return createViteServer({
5185
6001
  root: state.projectRoot,
5186
6002
  configFile: false,
5187
6003
  server: { middlewareMode: true },
5188
6004
  appType: "custom",
5189
6005
  logLevel: "silent",
5190
- resolve: { alias: state.userResolveAlias },
5191
- esbuild: { jsx: "automatic", jsxImportSource: "react" },
6006
+ resolve: resolveConfig,
6007
+ ...runnerConfig?.define ? { define: runnerConfig.define } : {},
6008
+ oxc: oxcConfig,
5192
6009
  ...options.cacheDir && { cacheDir: options.cacheDir },
5193
6010
  plugins: [
5194
6011
  rsc({
@@ -5206,7 +6023,11 @@ async function createTempRscServer(state, options = {}) {
5206
6023
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
5207
6024
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
5208
6025
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
5209
- exposeRouterId()
6026
+ exposeRouterId(),
6027
+ // Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
6028
+ // to resolveId/load and placed last so framework resolution runs first;
6029
+ // Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
6030
+ ...state.userResolvePlugins
5210
6031
  ]
5211
6032
  });
5212
6033
  }
@@ -5215,15 +6036,15 @@ async function resolveBuildEnv(option, factoryCtx) {
5215
6036
  if (option === "auto") {
5216
6037
  if (factoryCtx.preset !== "cloudflare") {
5217
6038
  throw new Error(
5218
- '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
6039
+ '[rango] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
5219
6040
  );
5220
6041
  }
5221
6042
  try {
5222
- const userRequire = createRequire2(
5223
- resolve8(factoryCtx.root, "package.json")
6043
+ const userRequire = createRequire3(
6044
+ resolve9(factoryCtx.root, "package.json")
5224
6045
  );
5225
6046
  const wranglerPath = userRequire.resolve("wrangler");
5226
- const { getPlatformProxy } = await import(pathToFileURL(wranglerPath).href);
6047
+ const { getPlatformProxy } = await import(pathToFileURL2(wranglerPath).href);
5227
6048
  const proxy = await getPlatformProxy();
5228
6049
  return {
5229
6050
  env: proxy.env,
@@ -5231,7 +6052,7 @@ async function resolveBuildEnv(option, factoryCtx) {
5231
6052
  };
5232
6053
  } catch (err) {
5233
6054
  throw new Error(
5234
- `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
6055
+ `[rango] buildEnv: "auto" requires wrangler to be installed.
5235
6056
  Install it with: pnpm add -D wrangler
5236
6057
  ${err.message}`
5237
6058
  );
@@ -5262,7 +6083,7 @@ async function releaseBuildEnv(s) {
5262
6083
  try {
5263
6084
  await s.buildEnvDispose();
5264
6085
  } catch (err) {
5265
- console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
6086
+ console.warn(`[rango] buildEnv dispose failed: ${err.message}`);
5266
6087
  }
5267
6088
  s.buildEnvDispose = null;
5268
6089
  }
@@ -5285,21 +6106,21 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5285
6106
  },
5286
6107
  configResolved(config) {
5287
6108
  s.projectRoot = config.root;
6109
+ s.scanFilter = opts?.discovery ? createScanFilter(s.projectRoot, opts.discovery) : void 0;
5288
6110
  s.isBuildMode = config.command === "build";
5289
6111
  viteCommand = config.command;
5290
6112
  viteMode = config.mode;
5291
6113
  s.userResolveAlias = config.resolve.alias;
6114
+ s.userRunnerConfig = pickForwardedRunnerConfig(config);
6115
+ s.userResolvePlugins = selectForwardableResolvePlugins(
6116
+ config.plugins
6117
+ );
5292
6118
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
5293
6119
  s.resolvedEntryPath = opts.routerPathRef.path;
5294
6120
  }
5295
6121
  if (!s.resolvedEntryPath) {
5296
- const rscEnvConfig = config.environments?.["rsc"];
5297
- const entries = rscEnvConfig?.optimizeDeps?.entries;
5298
- if (typeof entries === "string") {
5299
- s.resolvedEntryPath = entries;
5300
- } else if (Array.isArray(entries) && entries.length > 0) {
5301
- s.resolvedEntryPath = entries[0];
5302
- }
6122
+ const entry = resolveRscEntryFromConfig(config);
6123
+ if (entry) s.resolvedEntryPath = entry;
5303
6124
  }
5304
6125
  if (opts?.staticRouteTypesGeneration !== false) {
5305
6126
  s.cachedRouterFiles = findRouterFiles(s.projectRoot, s.scanFilter);
@@ -5320,8 +6141,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5320
6141
  if (globalThis.__rscRouterDiscoveryActive) return;
5321
6142
  s.devServer = server;
5322
6143
  let resolveDiscovery;
5323
- const discoveryPromise = new Promise((resolve10) => {
5324
- resolveDiscovery = resolve10;
6144
+ const discoveryPromise = new Promise((resolve11) => {
6145
+ resolveDiscovery = resolve11;
5325
6146
  });
5326
6147
  const gate = createDiscoveryGate(s, debugDiscovery);
5327
6148
  const beginDiscoveryGate = gate.beginGate;
@@ -5426,9 +6247,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5426
6247
  "getOrCreateTempServer: FAILED message=%s",
5427
6248
  err.message
5428
6249
  );
5429
- console.warn(
5430
- `[rsc-router] Failed to create temp runner: ${err.message}`
5431
- );
6250
+ console.warn(`[rango] Failed to create temp runner: ${err.message}`);
5432
6251
  }
5433
6252
  return null;
5434
6253
  }
@@ -5515,7 +6334,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5515
6334
  }
5516
6335
  } catch (err) {
5517
6336
  console.warn(
5518
- `[rsc-router] Cloudflare dev discovery failed: ${err.message}
6337
+ `[rango] Cloudflare dev discovery failed: ${err.message}
5519
6338
  ${err.stack}`
5520
6339
  );
5521
6340
  }
@@ -5559,7 +6378,7 @@ ${err.stack}`
5559
6378
  );
5560
6379
  } catch (err) {
5561
6380
  console.warn(
5562
- `[rsc-router] Router discovery failed: ${err.message}
6381
+ `[rango] Router discovery failed: ${err.message}
5563
6382
  ${err.stack}`
5564
6383
  );
5565
6384
  } finally {
@@ -5592,20 +6411,15 @@ ${err.stack}`
5592
6411
  if (s.mergedRouteTrie && serverMod.setRouteTrie) {
5593
6412
  serverMod.setRouteTrie(s.mergedRouteTrie);
5594
6413
  }
5595
- if (serverMod.setRouterManifest) {
5596
- for (const [routerId, manifest] of s.perRouterManifestDataMap) {
5597
- serverMod.setRouterManifest(routerId, manifest);
5598
- }
5599
- }
5600
- if (serverMod.setRouterTrie) {
5601
- for (const [routerId, trie] of s.perRouterTrieMap) {
5602
- serverMod.setRouterTrie(routerId, trie);
5603
- }
5604
- }
5605
- if (serverMod.setRouterPrecomputedEntries) {
5606
- for (const [routerId, entries] of s.perRouterPrecomputedMap) {
5607
- serverMod.setRouterPrecomputedEntries(routerId, entries);
5608
- }
6414
+ const perRouterSetters = [
6415
+ [s.perRouterManifestDataMap, "setRouterManifest"],
6416
+ [s.perRouterTrieMap, "setRouterTrie"],
6417
+ [s.perRouterPrecomputedMap, "setRouterPrecomputedEntries"]
6418
+ ];
6419
+ for (const [map, fn] of perRouterSetters) {
6420
+ const setter = serverMod[fn];
6421
+ if (typeof setter !== "function") continue;
6422
+ for (const [routerId, value] of map) setter(routerId, value);
5609
6423
  }
5610
6424
  };
5611
6425
  server.middlewares.use("/__rsc_prerender", async (req, res) => {
@@ -5639,7 +6453,7 @@ ${err.stack}`
5639
6453
  registry = serverMod.RouterRegistry ?? null;
5640
6454
  } catch (err) {
5641
6455
  console.warn(
5642
- `[rsc-router] Dev prerender module refresh failed: ${err.message}`
6456
+ `[rango] Dev prerender module refresh failed: ${err.message}`
5643
6457
  );
5644
6458
  res.statusCode = 500;
5645
6459
  res.end(`Prerender handler error: ${err.message}`);
@@ -5684,10 +6498,9 @@ ${err.stack}`
5684
6498
  if (wantIntercept && result.interceptSegments?.length) {
5685
6499
  payload = {
5686
6500
  segments: [...result.segments, ...result.interceptSegments],
5687
- handles: {
5688
- ...result.handles,
5689
- ...result.interceptHandles || {}
5690
- }
6501
+ // Pre-encoded MERGED handle string from the producer (handles are
6502
+ // Flight-encoded so Promise/ReactNode values survive the wire).
6503
+ handles: result.interceptHandles ?? ""
5691
6504
  };
5692
6505
  } else {
5693
6506
  payload = { segments: result.segments, handles: result.handles };
@@ -5697,7 +6510,7 @@ ${err.stack}`
5697
6510
  return;
5698
6511
  } catch (err) {
5699
6512
  console.warn(
5700
- `[rsc-router] Dev prerender failed for ${pathname}: ${err.message}`
6513
+ `[rango] Dev prerender failed for ${pathname}: ${err.message}`
5701
6514
  );
5702
6515
  }
5703
6516
  }
@@ -5768,9 +6581,25 @@ ${err.stack}`
5768
6581
  () => writeRouteTypesFiles(s)
5769
6582
  );
5770
6583
  }
6584
+ if (s.lastDiscoveryError) {
6585
+ debugDiscovery?.(
6586
+ "hmr: cleared lastDiscoveryError (%s) after successful rediscovery",
6587
+ s.lastDiscoveryError.message
6588
+ );
6589
+ s.lastDiscoveryError = null;
6590
+ }
6591
+ if (rscEnv && !rscEnv.runner) forceCloudflareWorkerReload(rscEnv);
5771
6592
  } catch (err) {
6593
+ s.lastDiscoveryError = {
6594
+ message: err?.message ?? String(err),
6595
+ at: Date.now()
6596
+ };
5772
6597
  console.warn(
5773
- `[rsc-router] Runtime re-discovery failed: ${err.message}`
6598
+ `[rango] Runtime re-discovery failed: ${err.message}`
6599
+ );
6600
+ debugDiscovery?.(
6601
+ "hmr: lastDiscoveryError set (%s) \u2014 manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
6602
+ err?.message
5774
6603
  );
5775
6604
  } finally {
5776
6605
  debugDiscovery?.(
@@ -5780,6 +6609,25 @@ ${err.stack}`
5780
6609
  }
5781
6610
  });
5782
6611
  };
6612
+ const forceCloudflareWorkerReload = (rscEnv) => {
6613
+ if (!rscEnv?.hot) return;
6614
+ try {
6615
+ const graph = rscEnv.moduleGraph;
6616
+ if (graph?.invalidateAll) {
6617
+ graph.invalidateAll();
6618
+ debugDiscovery?.("hmr: invalidated workerd rsc module graph");
6619
+ }
6620
+ rscEnv.hot.send({ type: "full-reload" });
6621
+ debugDiscovery?.(
6622
+ "hmr: forced workerd rsc env reload (full-reload)"
6623
+ );
6624
+ } catch (err) {
6625
+ debugDiscovery?.(
6626
+ "hmr: workerd reload failed: %s",
6627
+ err?.message ?? err
6628
+ );
6629
+ }
6630
+ };
5783
6631
  const scheduleRouteRegeneration = () => {
5784
6632
  clearTimeout(routeChangeTimer);
5785
6633
  routeChangeTimer = setTimeout(() => {
@@ -5799,9 +6647,7 @@ ${err.stack}`
5799
6647
  }
5800
6648
  }
5801
6649
  } catch (err) {
5802
- console.error(
5803
- `[rsc-router] Route regeneration error: ${err.message}`
5804
- );
6650
+ console.error(`[rango] Route regeneration error: ${err.message}`);
5805
6651
  }
5806
6652
  debugDiscovery?.(
5807
6653
  "watcher: regenerated gen files (%sms)",
@@ -5810,7 +6656,7 @@ ${err.stack}`
5810
6656
  if (s.perRouterManifests.length > 0) {
5811
6657
  refreshRuntimeDiscovery().catch((err) => {
5812
6658
  console.warn(
5813
- `[rsc-router] Runtime re-discovery error: ${err.message}`
6659
+ `[rango] Runtime re-discovery error: ${err.message}`
5814
6660
  );
5815
6661
  resolveDiscoveryGate();
5816
6662
  });
@@ -5819,27 +6665,60 @@ ${err.stack}`
5819
6665
  };
5820
6666
  const handleRouteFileChange = (filePath) => {
5821
6667
  if (maybeHandleGeneratedRouteFileMutation(filePath)) return;
5822
- if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx"))
6668
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx")) {
6669
+ if (s.lastDiscoveryError) {
6670
+ debugDiscovery?.(
6671
+ "watcher: skip non-source %s [LASTERR %s]",
6672
+ filePath,
6673
+ s.lastDiscoveryError.message
6674
+ );
6675
+ }
5823
6676
  return;
5824
- if (s.scanFilter && !s.scanFilter(filePath)) return;
6677
+ }
6678
+ if (s.scanFilter && !s.scanFilter(filePath)) {
6679
+ if (s.lastDiscoveryError) {
6680
+ debugDiscovery?.(
6681
+ "watcher: skip scan-filter %s [LASTERR %s]",
6682
+ filePath,
6683
+ s.lastDiscoveryError.message
6684
+ );
6685
+ }
6686
+ return;
6687
+ }
6688
+ const inRecoveryMode = !!s.lastDiscoveryError;
5825
6689
  try {
5826
6690
  const source = readFileSync6(filePath, "utf-8");
5827
6691
  const trimmed = source.trimStart();
5828
- if (trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'"))
5829
- return;
5830
- const hasUrls = source.includes("urls(");
5831
- const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
5832
- if (!hasUrls && !hasCreateRouter) return;
5833
- debugDiscovery?.(
5834
- "watcher: %s matches (urls=%s, router=%s)",
5835
- filePath,
5836
- hasUrls,
5837
- hasCreateRouter
5838
- );
6692
+ const isUseClient = trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'");
6693
+ if (!inRecoveryMode && isUseClient) return;
6694
+ let hasUrls = source.includes("urls(");
6695
+ let hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
6696
+ if (hasUrls) hasUrls = firstCodeMatchIndex(source, /urls\(/g) >= 0;
6697
+ if (hasCreateRouter) {
6698
+ hasCreateRouter = firstCodeMatchIndex(source, /\bcreateRouter\s*[<(]/g) >= 0;
6699
+ }
6700
+ if (!inRecoveryMode && !hasUrls && !hasCreateRouter) return;
6701
+ if (inRecoveryMode) {
6702
+ debugDiscovery?.(
6703
+ "watcher: recovery rediscovery for %s (urls=%s, router=%s, useClient=%s) [LASTERR %s]",
6704
+ filePath,
6705
+ hasUrls,
6706
+ hasCreateRouter,
6707
+ isUseClient,
6708
+ s.lastDiscoveryError.message
6709
+ );
6710
+ } else {
6711
+ debugDiscovery?.(
6712
+ "watcher: %s matches (urls=%s, router=%s)",
6713
+ filePath,
6714
+ hasUrls,
6715
+ hasCreateRouter
6716
+ );
6717
+ }
5839
6718
  if (hasCreateRouter) {
5840
6719
  const nestedRouterConflict = findNestedRouterConflict([
5841
6720
  ...s.cachedRouterFiles ?? [],
5842
- resolve8(filePath)
6721
+ resolve9(filePath)
5843
6722
  ]);
5844
6723
  if (nestedRouterConflict) {
5845
6724
  server.config.logger.error(
@@ -5853,7 +6732,15 @@ ${err.stack}`
5853
6732
  gate.noteRouteEvent();
5854
6733
  }
5855
6734
  scheduleRouteRegeneration();
5856
- } catch {
6735
+ } catch (readErr) {
6736
+ if (s.lastDiscoveryError) {
6737
+ debugDiscovery?.(
6738
+ "watcher: read error %s: %s [LASTERR %s]",
6739
+ filePath,
6740
+ readErr?.message,
6741
+ s.lastDiscoveryError.message
6742
+ );
6743
+ }
5857
6744
  }
5858
6745
  };
5859
6746
  server.watcher.on("add", handleRouteFileChange);
@@ -5899,7 +6786,7 @@ ${err.stack}`
5899
6786
  const rscEnv = tempServer.environments?.rsc;
5900
6787
  if (!rscEnv?.runner) {
5901
6788
  console.warn(
5902
- "[rsc-router] RSC environment runner not available during build, skipping manifest generation"
6789
+ "[rango] RSC environment runner not available during build, skipping manifest generation"
5903
6790
  );
5904
6791
  return;
5905
6792
  }
@@ -5931,8 +6818,9 @@ ${err.stack}`
5931
6818
  ${err.stack}` : null
5932
6819
  ].filter(Boolean).join("\n");
5933
6820
  throw new Error(
5934
- `[rsc-router] Build-time router discovery failed:
5935
- ${details}`
6821
+ `[rango] Build-time router discovery failed:
6822
+ ${details}`,
6823
+ { cause: err }
5936
6824
  );
5937
6825
  } finally {
5938
6826
  delete globalThis.__rscRouterDiscoveryActive;
@@ -5960,7 +6848,7 @@ ${details}`
5960
6848
  // `consumeSelfGenWrite` inside `maybeHandleGeneratedRouteFileMutation`),
5961
6849
  // AND vite's own HMR pipeline (which invalidates the gen file's
5962
6850
  // importers and triggers a second workerd full reload — visible to the
5963
- // user as a duplicate "[RSCRouter] HMR: version changed" on the client).
6851
+ // user as a duplicate "[Rango] HMR: version changed" on the client).
5964
6852
  //
5965
6853
  // `peekSelfGenWrite` is the authoritative filter: its map only contains
5966
6854
  // paths that `markSelfGenWrite` has registered, so it natively works
@@ -6120,6 +7008,10 @@ async function rango(options) {
6120
7008
  const resolvedOptions = options ?? { preset: "node" };
6121
7009
  const preset = resolvedOptions.preset ?? "node";
6122
7010
  const showBanner = resolvedOptions.banner ?? true;
7011
+ const clientChunksOption = resolvedOptions.clientChunks ?? true;
7012
+ const useBuiltInClientChunks = clientChunksOption === true;
7013
+ const clientChunkCtx = useBuiltInClientChunks ? { fallbackRefs: /* @__PURE__ */ new Set() } : void 0;
7014
+ const clientChunks = resolveClientChunks(clientChunksOption, clientChunkCtx);
6123
7015
  debugConfig?.("rango(%s) setup start", preset);
6124
7016
  const plugins = [];
6125
7017
  const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
@@ -6138,7 +7030,11 @@ async function rango(options) {
6138
7030
  ];
6139
7031
  const pkg = getPublishedPackageName();
6140
7032
  const nested = (spec) => `${pkg} > ${spec}`;
6141
- const routerRef = { path: void 0 };
7033
+ const routerRef = {
7034
+ path: void 0,
7035
+ kind: "router"
7036
+ };
7037
+ const explicitHost = preset !== "cloudflare" ? resolvedOptions.host : void 0;
6142
7038
  const prerenderEnabled = true;
6143
7039
  if (preset === "cloudflare") {
6144
7040
  const { default: rsc } = await import("@vitejs/plugin-rsc");
@@ -6151,14 +7047,20 @@ async function rango(options) {
6151
7047
  enforce: "pre",
6152
7048
  config() {
6153
7049
  return {
6154
- // Exclude rsc-router modules from optimization to prevent module duplication
6155
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
6156
7050
  optimizeDeps: {
6157
7051
  exclude: excludeDeps,
6158
- esbuildOptions: sharedEsbuildOptions
7052
+ rolldownOptions: sharedRolldownOptions
6159
7053
  },
6160
7054
  resolve: {
6161
- alias: rangoAliases
7055
+ alias: rangoAliases,
7056
+ // Force a single React/React-DOM copy across all three RSC
7057
+ // environments. RSC requires exactly one react/react-dom instance
7058
+ // per environment runtime; consumer install topologies (pnpm
7059
+ // strict layout, experimental React pins, third-party "use client"
7060
+ // packages) can otherwise resolve duplicate copies, causing
7061
+ // "Invalid hook call" / lost context. Child environments inherit
7062
+ // this root dedupe, and Vite merges it with any consumer dedupe.
7063
+ dedupe: ["react", "react-dom"]
6162
7064
  },
6163
7065
  build: {
6164
7066
  rollupOptions: { onwarn }
@@ -6167,30 +7069,22 @@ async function rango(options) {
6167
7069
  client: {
6168
7070
  build: {
6169
7071
  rollupOptions: {
7072
+ onwarn,
6170
7073
  output: {
6171
7074
  manualChunks: getManualChunks
6172
7075
  }
6173
7076
  }
6174
7077
  },
6175
- // Pre-bundle rsc-html-stream to prevent discovery during first request
6176
- // Exclude rsc-router modules to ensure same Context instance
6177
7078
  optimizeDeps: {
6178
7079
  include: [nested("rsc-html-stream/client")],
6179
7080
  exclude: excludeDeps,
6180
- esbuildOptions: sharedEsbuildOptions
7081
+ rolldownOptions: sharedRolldownOptions
6181
7082
  }
6182
7083
  },
6183
7084
  ssr: {
6184
- // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
6185
7085
  build: {
6186
7086
  outDir: "./dist/rsc/ssr"
6187
7087
  },
6188
- resolve: {
6189
- // Ensure single React instance in SSR child environment
6190
- dedupe: ["react", "react-dom"]
6191
- },
6192
- // Pre-bundle SSR entry and React for proper module linking with childEnvironments
6193
- // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
6194
7088
  optimizeDeps: {
6195
7089
  entries: [finalEntries.ssr],
6196
7090
  include: [
@@ -6206,14 +7100,11 @@ async function rango(options) {
6206
7100
  )
6207
7101
  ],
6208
7102
  exclude: excludeDeps,
6209
- esbuildOptions: sharedEsbuildOptions
7103
+ rolldownOptions: sharedRolldownOptions
6210
7104
  }
6211
7105
  },
6212
7106
  rsc: {
6213
- // RSC environment needs exclude list and esbuild options
6214
- // Exclude rsc-router modules to prevent createContext in RSC environment
6215
7107
  optimizeDeps: {
6216
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
6217
7108
  include: [
6218
7109
  "react",
6219
7110
  "react/jsx-runtime",
@@ -6223,7 +7114,7 @@ async function rango(options) {
6223
7114
  )
6224
7115
  ],
6225
7116
  exclude: excludeDeps,
6226
- esbuildOptions: sharedEsbuildOptions
7117
+ rolldownOptions: sharedRolldownOptions
6227
7118
  }
6228
7119
  }
6229
7120
  }
@@ -6241,7 +7132,8 @@ async function rango(options) {
6241
7132
  plugins.push(
6242
7133
  rsc({
6243
7134
  entries: finalEntries,
6244
- serverHandler: false
7135
+ serverHandler: false,
7136
+ clientChunks
6245
7137
  })
6246
7138
  );
6247
7139
  plugins.push(clientRefDedup());
@@ -6250,17 +7142,44 @@ async function rango(options) {
6250
7142
  name: "@rangojs/router:auto-discover",
6251
7143
  config(userConfig) {
6252
7144
  if (routerRef.path) return;
6253
- const root = userConfig.root ? resolve9(process.cwd(), userConfig.root) : process.cwd();
7145
+ const root = userConfig.root ? resolve10(process.cwd(), userConfig.root) : process.cwd();
7146
+ const toRootRelative = (abs) => (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
7147
+ if (explicitHost) {
7148
+ routerRef.path = explicitHost.replaceAll("\\", "/");
7149
+ routerRef.kind = "host";
7150
+ return;
7151
+ }
6254
7152
  const candidates = findRouterFiles(root);
6255
7153
  if (candidates.length === 1) {
6256
- const abs = candidates[0];
6257
- routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
6258
- } else if (candidates.length > 1) {
6259
- const list = candidates.map(
6260
- (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
6261
- ).join("\n");
6262
- throw new Error(`[rsc-router] Multiple routers found:
6263
- ${list}`);
7154
+ routerRef.path = toRootRelative(candidates[0]);
7155
+ routerRef.kind = "router";
7156
+ return;
7157
+ }
7158
+ if (candidates.length !== 1) {
7159
+ const hostCandidates = findHostRouterFiles(root);
7160
+ if (hostCandidates.length === 1) {
7161
+ routerRef.path = toRootRelative(hostCandidates[0]);
7162
+ routerRef.kind = "host";
7163
+ return;
7164
+ }
7165
+ if (hostCandidates.length > 1) {
7166
+ const list = hostCandidates.map((f) => " - " + toRootRelative(f)).join("\n");
7167
+ throw new Error(
7168
+ `[rango] Multiple host routers found:
7169
+ ${list}
7170
+
7171
+ Set the \`host\` option to the entry to serve, e.g. rango({ preset: "${preset}", host: "./src/worker.rsc.tsx" }).`
7172
+ );
7173
+ }
7174
+ }
7175
+ if (candidates.length > 1) {
7176
+ const list = candidates.map((f) => " - " + toRootRelative(f)).join("\n");
7177
+ throw new Error(
7178
+ `[rango] Multiple routers found:
7179
+ ${list}
7180
+
7181
+ If this is a multi-app host router, export a createHostRouter() instance and set the \`host\` option (e.g. rango({ preset: "${preset}", host: "./src/worker.rsc.tsx" })), or use preset: "cloudflare" where you own the worker entry.`
7182
+ );
6264
7183
  }
6265
7184
  }
6266
7185
  });
@@ -6274,22 +7193,34 @@ ${list}`);
6274
7193
  plugins.push({
6275
7194
  name: "@rangojs/router:rsc-integration",
6276
7195
  enforce: "pre",
6277
- config() {
7196
+ config(_userConfig, configEnv) {
7197
+ const vercelDefine = preset === "vercel" && configEnv.command === "build" ? { "process.env.NODE_ENV": JSON.stringify("production") } : void 0;
7198
+ const vercelServerEnv = preset === "vercel" ? { resolve: { noExternal: true } } : void 0;
6278
7199
  return {
7200
+ ...vercelDefine ? { define: vercelDefine } : {},
6279
7201
  optimizeDeps: {
6280
7202
  exclude: excludeDeps,
6281
- esbuildOptions: sharedEsbuildOptions
7203
+ rolldownOptions: sharedRolldownOptions
6282
7204
  },
6283
7205
  build: {
6284
7206
  rollupOptions: { onwarn }
6285
7207
  },
6286
7208
  resolve: {
6287
- alias: rangoAliases
7209
+ alias: rangoAliases,
7210
+ // Force a single React/React-DOM copy across all three RSC
7211
+ // environments. RSC requires exactly one react/react-dom instance
7212
+ // per environment runtime; consumer install topologies (pnpm
7213
+ // strict layout, experimental React pins, third-party "use client"
7214
+ // packages) can otherwise resolve duplicate copies, causing
7215
+ // "Invalid hook call" / lost context. Child environments inherit
7216
+ // this root dedupe, and Vite merges it with any consumer dedupe.
7217
+ dedupe: ["react", "react-dom"]
6288
7218
  },
6289
7219
  environments: {
6290
7220
  client: {
6291
7221
  build: {
6292
7222
  rollupOptions: {
7223
+ onwarn,
6293
7224
  output: {
6294
7225
  manualChunks: getManualChunks
6295
7226
  }
@@ -6304,11 +7235,12 @@ ${list}`);
6304
7235
  nested("rsc-html-stream/client")
6305
7236
  ],
6306
7237
  exclude: excludeDeps,
6307
- esbuildOptions: sharedEsbuildOptions,
7238
+ rolldownOptions: sharedRolldownOptions,
6308
7239
  entries: [VIRTUAL_IDS.browser]
6309
7240
  }
6310
7241
  },
6311
7242
  ssr: {
7243
+ ...vercelServerEnv ?? {},
6312
7244
  optimizeDeps: {
6313
7245
  entries: [VIRTUAL_IDS.ssr],
6314
7246
  include: [
@@ -6323,10 +7255,11 @@ ${list}`);
6323
7255
  )
6324
7256
  ],
6325
7257
  exclude: excludeDeps,
6326
- esbuildOptions: sharedEsbuildOptions
7258
+ rolldownOptions: sharedRolldownOptions
6327
7259
  }
6328
7260
  },
6329
7261
  rsc: {
7262
+ ...vercelServerEnv ?? {},
6330
7263
  optimizeDeps: {
6331
7264
  entries: [VIRTUAL_IDS.rsc],
6332
7265
  include: [
@@ -6337,7 +7270,7 @@ ${list}`);
6337
7270
  "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6338
7271
  )
6339
7272
  ],
6340
- esbuildOptions: sharedEsbuildOptions
7273
+ rolldownOptions: sharedRolldownOptions
6341
7274
  }
6342
7275
  }
6343
7276
  }
@@ -6346,7 +7279,11 @@ ${list}`);
6346
7279
  configResolved(config) {
6347
7280
  if (showBanner) {
6348
7281
  const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
6349
- printBanner(mode, "node", rangoVersion);
7282
+ printBanner(
7283
+ mode,
7284
+ preset === "vercel" ? "vercel" : "node",
7285
+ rangoVersion
7286
+ );
6350
7287
  }
6351
7288
  const rscMinimalCount = config.plugins.filter(
6352
7289
  (p) => p.name === "rsc:minimal"
@@ -6354,7 +7291,7 @@ ${list}`);
6354
7291
  if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
6355
7292
  hasWarnedDuplicate = true;
6356
7293
  console.warn(
6357
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
7294
+ "[rango] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
6358
7295
  );
6359
7296
  }
6360
7297
  }
@@ -6363,7 +7300,8 @@ ${list}`);
6363
7300
  plugins.push(performanceTracksPlugin());
6364
7301
  plugins.push(
6365
7302
  rsc({
6366
- entries: finalEntries
7303
+ entries: finalEntries,
7304
+ clientChunks
6367
7305
  })
6368
7306
  );
6369
7307
  plugins.push(clientRefDedup());
@@ -6402,9 +7340,16 @@ ${list}`);
6402
7340
  routerPathRef: discoveryRouterRef,
6403
7341
  enableBuildPrerender: prerenderEnabled,
6404
7342
  buildEnv: options?.buildEnv,
6405
- preset
7343
+ preset,
7344
+ discovery: options?.discovery,
7345
+ clientChunkCtx
6406
7346
  })
6407
7347
  );
7348
+ if (preset === "vercel") {
7349
+ plugins.push(
7350
+ createVercelOutputPlugin(resolvedOptions)
7351
+ );
7352
+ }
6408
7353
  debugConfig?.(
6409
7354
  "rango(%s) setup done: %d plugin(s) (%sms)",
6410
7355
  preset,
@@ -6417,7 +7362,7 @@ ${list}`);
6417
7362
  // src/vite/plugins/refresh-cmd.ts
6418
7363
  function poke() {
6419
7364
  return {
6420
- name: "vite-plugin-poke",
7365
+ name: "@rangojs/router:poke",
6421
7366
  apply: "serve",
6422
7367
  configureServer(server) {
6423
7368
  const stdin = process.stdin;