@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa

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 (411) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +296 -887
  3. package/dist/bin/rango.js +459 -91
  4. package/dist/testing/vitest.js +36 -2
  5. package/dist/vite/index.js +1708 -414
  6. package/package.json +35 -10
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +82 -5
  9. package/skills/bundle-analysis/SKILL.md +2 -2
  10. package/skills/cache-guide/SKILL.md +14 -9
  11. package/skills/caching/SKILL.md +221 -12
  12. package/skills/catalog.json +271 -0
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +83 -2
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +5 -3
  19. package/skills/defer-hydration/SKILL.md +235 -0
  20. package/skills/document-cache/SKILL.md +11 -3
  21. package/skills/fonts/SKILL.md +1 -1
  22. package/skills/handler-use/SKILL.md +9 -9
  23. package/skills/hooks/SKILL.md +73 -900
  24. package/skills/hooks/data.md +273 -0
  25. package/skills/hooks/handle-and-actions.md +103 -0
  26. package/skills/hooks/navigation.md +110 -0
  27. package/skills/hooks/outlets.md +41 -0
  28. package/skills/hooks/state.md +228 -0
  29. package/skills/hooks/urls.md +135 -0
  30. package/skills/host-router/SKILL.md +84 -7
  31. package/skills/i18n/SKILL.md +1 -1
  32. package/skills/intercept/SKILL.md +51 -17
  33. package/skills/layout/SKILL.md +38 -16
  34. package/skills/links/SKILL.md +1 -1
  35. package/skills/loader/SKILL.md +48 -20
  36. package/skills/middleware/SKILL.md +11 -5
  37. package/skills/migrate-nextjs/SKILL.md +203 -20
  38. package/skills/migrate-react-router/SKILL.md +59 -675
  39. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  40. package/skills/migrate-react-router/component-migration.md +196 -0
  41. package/skills/migrate-react-router/data-and-actions.md +225 -0
  42. package/skills/migrate-react-router/route-mapping.md +271 -0
  43. package/skills/mime-routes/SKILL.md +3 -3
  44. package/skills/observability/SKILL.md +70 -5
  45. package/skills/parallel/SKILL.md +32 -8
  46. package/skills/ppr/SKILL.md +622 -0
  47. package/skills/prerender/SKILL.md +59 -28
  48. package/skills/rango/SKILL.md +124 -50
  49. package/skills/response-routes/SKILL.md +78 -46
  50. package/skills/route/SKILL.md +85 -6
  51. package/skills/router-setup/SKILL.md +41 -6
  52. package/skills/scripts/SKILL.md +179 -0
  53. package/skills/server-actions/SKILL.md +28 -3
  54. package/skills/shell-manifest/SKILL.md +185 -0
  55. package/skills/streams-and-websockets/SKILL.md +1 -1
  56. package/skills/tailwind/SKILL.md +28 -4
  57. package/skills/testing/SKILL.md +68 -654
  58. package/skills/testing/bindings.md +103 -0
  59. package/skills/testing/cache-prerender.md +127 -0
  60. package/skills/testing/client-components.md +124 -0
  61. package/skills/testing/e2e-parity.md +125 -0
  62. package/skills/testing/flight.md +91 -0
  63. package/skills/testing/handles.md +131 -0
  64. package/skills/testing/loader.md +128 -0
  65. package/skills/testing/middleware.md +99 -0
  66. package/skills/testing/render-handler.md +122 -0
  67. package/skills/testing/response-routes.md +95 -0
  68. package/skills/testing/reverse-and-types.md +85 -0
  69. package/skills/testing/server-actions.md +107 -0
  70. package/skills/testing/server-tree.md +128 -0
  71. package/skills/testing/setup.md +123 -0
  72. package/skills/theme/SKILL.md +1 -1
  73. package/skills/typesafety/SKILL.md +45 -918
  74. package/skills/typesafety/env-and-bindings.md +254 -0
  75. package/skills/typesafety/generated-files-and-cli.md +335 -0
  76. package/skills/typesafety/params-and-search.md +153 -0
  77. package/skills/typesafety/route-types.md +209 -0
  78. package/skills/use-cache/SKILL.md +47 -17
  79. package/skills/vercel/SKILL.md +128 -0
  80. package/skills/view-transitions/SKILL.md +44 -1
  81. package/src/__augment-tests__/augmented.check.ts +2 -3
  82. package/src/__internal.ts +0 -65
  83. package/src/browser/action-coordinator.ts +1 -1
  84. package/src/browser/action-fence.ts +47 -0
  85. package/src/browser/app-shell.ts +14 -27
  86. package/src/browser/connection-warmup.ts +134 -0
  87. package/src/browser/cookie-name.ts +140 -0
  88. package/src/browser/event-controller.ts +178 -100
  89. package/src/browser/invalidate-client-cache.ts +52 -0
  90. package/src/browser/logging.ts +28 -0
  91. package/src/browser/merge-segment-loaders.ts +6 -4
  92. package/src/browser/navigation-bridge.ts +81 -68
  93. package/src/browser/navigation-client.ts +115 -70
  94. package/src/browser/navigation-store-handle.ts +38 -0
  95. package/src/browser/navigation-store.ts +153 -88
  96. package/src/browser/navigation-transaction.ts +0 -32
  97. package/src/browser/network-error-handler.ts +34 -7
  98. package/src/browser/partial-update.ts +157 -144
  99. package/src/browser/prefetch/cache.ts +148 -81
  100. package/src/browser/prefetch/fetch.ts +231 -51
  101. package/src/browser/prefetch/queue.ts +25 -7
  102. package/src/browser/rango-state.ts +157 -115
  103. package/src/browser/react/Link.tsx +40 -7
  104. package/src/browser/react/NavigationProvider.tsx +140 -99
  105. package/src/browser/react/ScrollRestoration.tsx +10 -6
  106. package/src/browser/react/filter-segment-order.ts +17 -2
  107. package/src/browser/react/index.ts +0 -51
  108. package/src/browser/react/location-state-shared.ts +14 -15
  109. package/src/browser/react/location-state.ts +0 -1
  110. package/src/browser/react/use-action.ts +6 -15
  111. package/src/browser/react/use-handle.ts +0 -5
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +33 -8
  114. package/src/browser/react/use-navigation.ts +10 -5
  115. package/src/browser/react/use-params.ts +0 -2
  116. package/src/browser/react/use-router.ts +6 -4
  117. package/src/browser/react/use-search-params.ts +0 -5
  118. package/src/browser/react/use-segments.ts +0 -13
  119. package/src/browser/response-adapter.ts +74 -8
  120. package/src/browser/rsc-router.tsx +97 -22
  121. package/src/browser/scroll-restoration.ts +15 -8
  122. package/src/browser/segment-reconciler.ts +31 -21
  123. package/src/browser/server-action-bridge.ts +216 -38
  124. package/src/browser/types.ts +94 -22
  125. package/src/browser/validate-redirect-origin.ts +43 -16
  126. package/src/build/generate-manifest.ts +155 -131
  127. package/src/build/generate-route-types.ts +1 -1
  128. package/src/build/index.ts +11 -5
  129. package/src/build/prefix-tree-utils.ts +123 -0
  130. package/src/build/route-trie.ts +152 -22
  131. package/src/build/route-types/ast-route-extraction.ts +15 -8
  132. package/src/build/route-types/codegen.ts +12 -1
  133. package/src/build/route-types/include-resolution.ts +455 -61
  134. package/src/build/route-types/param-extraction.ts +6 -3
  135. package/src/build/route-types/per-module-writer.ts +15 -2
  136. package/src/build/route-types/router-processing.ts +77 -41
  137. package/src/build/route-types/source-scan.ts +105 -7
  138. package/src/build/runtime-discovery.ts +4 -1
  139. package/src/cache/cache-error.ts +104 -0
  140. package/src/cache/cache-key-utils.ts +58 -13
  141. package/src/cache/cache-policy.ts +108 -34
  142. package/src/cache/cache-runtime.ts +454 -101
  143. package/src/cache/cache-scope.ts +159 -54
  144. package/src/cache/cache-tag.ts +149 -0
  145. package/src/cache/cf/cf-base64.ts +33 -0
  146. package/src/cache/cf/cf-cache-constants.ts +127 -0
  147. package/src/cache/cf/cf-cache-store.ts +2170 -377
  148. package/src/cache/cf/cf-cache-types.ts +349 -0
  149. package/src/cache/cf/cf-kv-utils.ts +46 -0
  150. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  151. package/src/cache/cf/index.ts +6 -16
  152. package/src/cache/document-cache.ts +126 -41
  153. package/src/cache/handle-snapshot.ts +70 -0
  154. package/src/cache/index.ts +23 -20
  155. package/src/cache/memory-segment-store.ts +243 -37
  156. package/src/cache/profile-registry.ts +46 -31
  157. package/src/cache/read-through-swr.ts +56 -12
  158. package/src/cache/segment-codec.ts +13 -21
  159. package/src/cache/shell-snapshot.ts +417 -0
  160. package/src/cache/tag-invalidation.ts +230 -0
  161. package/src/cache/types.ts +194 -99
  162. package/src/cache/vercel/index.ts +11 -0
  163. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  164. package/src/client.rsc.tsx +39 -22
  165. package/src/client.tsx +28 -58
  166. package/src/cloudflare/index.ts +11 -0
  167. package/src/cloudflare/tracing.ts +108 -0
  168. package/src/component-utils.ts +19 -0
  169. package/src/components/DefaultDocument.tsx +8 -2
  170. package/src/context-var.ts +13 -1
  171. package/src/decode-loader-results.ts +18 -2
  172. package/src/defer.ts +185 -0
  173. package/src/deps/ssr.ts +0 -1
  174. package/src/encode-kv.ts +49 -0
  175. package/src/errors.ts +0 -3
  176. package/src/escape-script.ts +52 -0
  177. package/src/handle.ts +57 -40
  178. package/src/handles/MetaTags.tsx +24 -53
  179. package/src/handles/Scripts.tsx +183 -0
  180. package/src/handles/breadcrumbs.ts +35 -8
  181. package/src/handles/deferred-resolution.ts +127 -0
  182. package/src/handles/is-thenable.ts +18 -0
  183. package/src/handles/meta.ts +14 -40
  184. package/src/handles/script.ts +244 -0
  185. package/src/host/cookie-handler.ts +9 -60
  186. package/src/host/errors.ts +13 -22
  187. package/src/host/index.ts +7 -0
  188. package/src/host/pattern-matcher.ts +23 -52
  189. package/src/host/router.ts +1 -65
  190. package/src/host/testing.ts +40 -27
  191. package/src/host/types.ts +6 -2
  192. package/src/href-client.ts +7 -12
  193. package/src/index.rsc.ts +88 -8
  194. package/src/index.ts +90 -16
  195. package/src/internal-debug.ts +11 -10
  196. package/src/loader.rsc.ts +19 -9
  197. package/src/loader.ts +12 -4
  198. package/src/outlet-provider.tsx +1 -5
  199. package/src/prerender/param-hash.ts +16 -16
  200. package/src/prerender/store.ts +32 -37
  201. package/src/prerender.ts +75 -7
  202. package/src/redirect-origin.ts +114 -0
  203. package/src/regex-escape.ts +8 -0
  204. package/src/render-error-thrower.tsx +20 -0
  205. package/src/response-utils.ts +25 -0
  206. package/src/root-error-boundary.tsx +1 -19
  207. package/src/route-content-wrapper.tsx +13 -49
  208. package/src/route-definition/dsl-helpers.ts +60 -53
  209. package/src/route-definition/helper-factories.ts +0 -2
  210. package/src/route-definition/helpers-types.ts +46 -46
  211. package/src/route-definition/index.ts +1 -2
  212. package/src/route-definition/redirect.ts +44 -11
  213. package/src/route-definition/resolve-handler-use.ts +6 -1
  214. package/src/route-definition/use-item-types.ts +3 -6
  215. package/src/route-map-builder.ts +41 -20
  216. package/src/route-types.ts +0 -5
  217. package/src/router/content-negotiation.ts +58 -23
  218. package/src/router/error-handling.ts +44 -17
  219. package/src/router/find-match.ts +129 -30
  220. package/src/router/handler-context.ts +6 -1
  221. package/src/router/instrument.ts +355 -0
  222. package/src/router/intercept-resolution.ts +35 -2
  223. package/src/router/lazy-includes.ts +79 -56
  224. package/src/router/loader-resolution.ts +151 -73
  225. package/src/router/logging.ts +0 -6
  226. package/src/router/manifest.ts +74 -40
  227. package/src/router/match-api.ts +76 -52
  228. package/src/router/match-context.ts +0 -22
  229. package/src/router/match-handlers.ts +181 -178
  230. package/src/router/match-middleware/background-revalidation.ts +40 -24
  231. package/src/router/match-middleware/cache-lookup.ts +115 -194
  232. package/src/router/match-middleware/cache-store.ts +61 -50
  233. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  234. package/src/router/match-middleware/segment-resolution.ts +0 -22
  235. package/src/router/match-pipelines.ts +1 -42
  236. package/src/router/match-result.ts +36 -67
  237. package/src/router/metrics.ts +0 -34
  238. package/src/router/middleware-types.ts +0 -116
  239. package/src/router/middleware.ts +231 -120
  240. package/src/router/navigation-snapshot.ts +7 -56
  241. package/src/router/params-util.ts +23 -0
  242. package/src/router/parse-pattern.ts +115 -0
  243. package/src/router/pattern-matching.ts +99 -152
  244. package/src/router/prefetch-cache-ttl.ts +51 -0
  245. package/src/router/prefetch-limits.ts +37 -0
  246. package/src/router/prerender-match.ts +111 -66
  247. package/src/router/preview-match.ts +3 -1
  248. package/src/router/request-classification.ts +47 -42
  249. package/src/router/revalidation.ts +75 -81
  250. package/src/router/route-snapshot.ts +14 -3
  251. package/src/router/router-context.ts +6 -29
  252. package/src/router/router-interfaces.ts +70 -8
  253. package/src/router/router-options.ts +126 -4
  254. package/src/router/segment-resolution/fresh.ts +104 -80
  255. package/src/router/segment-resolution/helpers.ts +86 -6
  256. package/src/router/segment-resolution/loader-cache.ts +155 -39
  257. package/src/router/segment-resolution/loader-mask.ts +60 -0
  258. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  259. package/src/router/segment-resolution/mask-nested.ts +83 -0
  260. package/src/router/segment-resolution/revalidation.ts +215 -304
  261. package/src/router/segment-resolution/static-store.ts +19 -5
  262. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  263. package/src/router/segment-resolution/view-transition-default.ts +35 -15
  264. package/src/router/segment-resolution.ts +5 -1
  265. package/src/router/segment-wrappers.ts +6 -5
  266. package/src/router/state-cookie-name.ts +33 -0
  267. package/src/router/substitute-pattern-params.ts +54 -35
  268. package/src/router/telemetry-otel.ts +160 -200
  269. package/src/router/telemetry.ts +9 -23
  270. package/src/router/timeout.ts +0 -20
  271. package/src/router/tracing.ts +215 -0
  272. package/src/router/trie-matching.ts +171 -64
  273. package/src/router/types.ts +1 -63
  274. package/src/router/url-params.ts +13 -5
  275. package/src/router.ts +119 -48
  276. package/src/rsc/full-payload.ts +70 -0
  277. package/src/rsc/handler-context.ts +1 -0
  278. package/src/rsc/handler.ts +267 -152
  279. package/src/rsc/helpers.ts +78 -4
  280. package/src/rsc/index.ts +1 -4
  281. package/src/rsc/json-route-result.ts +38 -0
  282. package/src/rsc/loader-fetch.ts +114 -38
  283. package/src/rsc/manifest-init.ts +29 -42
  284. package/src/rsc/nonce.ts +10 -1
  285. package/src/rsc/origin-guard.ts +11 -15
  286. package/src/rsc/progressive-enhancement.ts +120 -13
  287. package/src/rsc/redirect-guard.ts +100 -0
  288. package/src/rsc/response-cache-serve.ts +238 -0
  289. package/src/rsc/response-error.ts +79 -12
  290. package/src/rsc/response-route-handler.ts +58 -141
  291. package/src/rsc/rsc-rendering.ts +492 -49
  292. package/src/rsc/runtime-warnings.ts +14 -0
  293. package/src/rsc/server-action.ts +268 -82
  294. package/src/rsc/shell-capture.ts +1190 -0
  295. package/src/rsc/shell-serve.ts +181 -0
  296. package/src/rsc/transition-gate.ts +89 -0
  297. package/src/rsc/types.ts +45 -3
  298. package/src/runtime-env.ts +18 -0
  299. package/src/search-params.ts +31 -26
  300. package/src/segment-loader-promise.ts +49 -4
  301. package/src/segment-system.tsx +260 -95
  302. package/src/server/context.ts +99 -9
  303. package/src/server/cookie-parse.ts +32 -0
  304. package/src/server/cookie-store.ts +125 -2
  305. package/src/server/handle-store.ts +21 -38
  306. package/src/server/loader-registry.ts +33 -42
  307. package/src/server/request-context.ts +379 -138
  308. package/src/ssr/index.tsx +491 -182
  309. package/src/ssr/inject-rsc-eager.ts +167 -0
  310. package/src/ssr/ssr-root.tsx +228 -0
  311. package/src/static-handler.ts +10 -13
  312. package/src/testing/cache-status.ts +44 -48
  313. package/src/testing/collect-handle.ts +14 -31
  314. package/src/testing/dispatch.ts +533 -160
  315. package/src/testing/e2e/fixture.ts +45 -11
  316. package/src/testing/e2e/index.ts +1 -22
  317. package/src/testing/e2e/matchers.ts +0 -16
  318. package/src/testing/e2e/parity.ts +85 -4
  319. package/src/testing/e2e/server.ts +12 -0
  320. package/src/testing/flight-matchers.ts +7 -14
  321. package/src/testing/flight-normalize.ts +11 -0
  322. package/src/testing/flight-runtime.d.ts +36 -0
  323. package/src/testing/flight-tree.ts +682 -0
  324. package/src/testing/flight.entry.ts +30 -0
  325. package/src/testing/flight.ts +145 -70
  326. package/src/testing/generated-routes.ts +26 -50
  327. package/src/testing/index.ts +18 -19
  328. package/src/testing/internal/context.ts +184 -68
  329. package/src/testing/internal/flight-client-globals.ts +30 -0
  330. package/src/testing/internal/seed-vars.ts +54 -0
  331. package/src/testing/render-handler.ts +357 -0
  332. package/src/testing/render-route.tsx +134 -115
  333. package/src/testing/run-loader.ts +140 -51
  334. package/src/testing/run-middleware.ts +59 -33
  335. package/src/testing/run-transition-when.ts +164 -0
  336. package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
  337. package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
  338. package/src/testing/vitest.ts +138 -16
  339. package/src/theme/ThemeProvider.tsx +56 -84
  340. package/src/theme/ThemeScript.tsx +7 -9
  341. package/src/theme/constants.ts +52 -13
  342. package/src/theme/index.ts +0 -7
  343. package/src/theme/theme-context.ts +1 -5
  344. package/src/theme/theme-script.ts +22 -21
  345. package/src/theme/use-theme.ts +0 -3
  346. package/src/types/boundaries.ts +0 -35
  347. package/src/types/cache-types.ts +13 -4
  348. package/src/types/error-types.ts +30 -90
  349. package/src/types/global-namespace.ts +15 -15
  350. package/src/types/handler-context.ts +45 -15
  351. package/src/types/index.ts +2 -10
  352. package/src/types/loader-types.ts +6 -3
  353. package/src/types/request-scope.ts +8 -22
  354. package/src/types/route-config.ts +20 -52
  355. package/src/types/route-entry.ts +0 -6
  356. package/src/types/segments.ts +100 -13
  357. package/src/urls/include-helper.ts +10 -12
  358. package/src/urls/include-provider.ts +71 -0
  359. package/src/urls/index.ts +2 -8
  360. package/src/urls/path-helper-types.ts +52 -14
  361. package/src/urls/path-helper.ts +5 -54
  362. package/src/urls/pattern-types.ts +36 -0
  363. package/src/urls/type-extraction.ts +76 -42
  364. package/src/urls/urls-function.ts +0 -14
  365. package/src/use-loader.tsx +0 -186
  366. package/src/vercel/index.ts +11 -0
  367. package/src/vercel/tracing.ts +88 -0
  368. package/src/vite/discovery/bundle-postprocess.ts +2 -1
  369. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  370. package/src/vite/discovery/discover-routers.ts +34 -43
  371. package/src/vite/discovery/discovery-errors.ts +61 -0
  372. package/src/vite/discovery/prerender-collection.ts +33 -46
  373. package/src/vite/discovery/state.ts +12 -1
  374. package/src/vite/discovery/virtual-module-codegen.ts +1 -11
  375. package/src/vite/index.ts +9 -0
  376. package/src/vite/inject-client-debug.ts +88 -0
  377. package/src/vite/plugin-types.ts +143 -10
  378. package/src/vite/plugins/cjs-to-esm.ts +8 -12
  379. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  380. package/src/vite/plugins/client-ref-hashing.ts +0 -10
  381. package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
  382. package/src/vite/plugins/expose-action-id.ts +2 -73
  383. package/src/vite/plugins/expose-id-utils.ts +85 -56
  384. package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
  385. package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
  386. package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
  387. package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
  388. package/src/vite/plugins/expose-internal-ids.ts +10 -1
  389. package/src/vite/plugins/performance-tracks.ts +0 -3
  390. package/src/vite/plugins/refresh-cmd.ts +1 -1
  391. package/src/vite/plugins/use-cache-transform.ts +21 -46
  392. package/src/vite/plugins/vercel-output.ts +384 -0
  393. package/src/vite/plugins/version-injector.ts +22 -27
  394. package/src/vite/plugins/version-plugin.ts +6 -66
  395. package/src/vite/plugins/virtual-entries.ts +137 -26
  396. package/src/vite/rango.ts +146 -135
  397. package/src/vite/router-discovery.ts +189 -48
  398. package/src/vite/utils/ast-handler-extract.ts +11 -20
  399. package/src/vite/utils/bundle-analysis.ts +6 -13
  400. package/src/vite/utils/client-chunks.ts +0 -6
  401. package/src/vite/utils/directive-prologue.ts +40 -0
  402. package/src/vite/utils/forward-user-plugins.ts +0 -22
  403. package/src/vite/utils/manifest-utils.ts +4 -75
  404. package/src/vite/utils/package-resolution.ts +1 -73
  405. package/src/vite/utils/prerender-utils.ts +71 -44
  406. package/src/vite/utils/shared-utils.ts +55 -37
  407. package/src/browser/react/use-client-cache.ts +0 -58
  408. package/src/browser/shallow.ts +0 -40
  409. package/src/handles/index.ts +0 -7
  410. package/src/network-error-thrower.tsx +0 -23
  411. package/src/router/middleware-cookies.ts +0 -55
@@ -1,6 +1,6 @@
1
1
  // src/vite/rango.ts
2
- import { readFileSync as readFileSync7 } from "node:fs";
3
- import { resolve as resolve9 } from "node:path";
2
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "node:fs";
3
+ import { resolve as resolve11 } from "node:path";
4
4
 
5
5
  // src/vite/plugins/expose-action-id.ts
6
6
  import MagicString from "magic-string";
@@ -10,6 +10,13 @@ import fs from "node:fs";
10
10
  // src/vite/plugins/expose-id-utils.ts
11
11
  import path from "node:path";
12
12
  import crypto from "node:crypto";
13
+
14
+ // src/regex-escape.ts
15
+ function escapeRegExp(input) {
16
+ return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17
+ }
18
+
19
+ // src/vite/plugins/expose-id-utils.ts
13
20
  function normalizePath(p) {
14
21
  return p.split(path.sep).join("/");
15
22
  }
@@ -158,7 +165,9 @@ function countArgs(code, startPos, endPos) {
158
165
  while (i < endPos) {
159
166
  const skipped = skipStringOrComment(code, i);
160
167
  if (skipped > i) {
161
- hasContent = true;
168
+ const ch = code[i];
169
+ const isComment = ch === "/" && i + 1 < code.length && (code[i + 1] === "/" || code[i + 1] === "*");
170
+ if (!isComment) hasContent = true;
162
171
  i = skipped;
163
172
  continue;
164
173
  }
@@ -187,8 +196,59 @@ function findStatementEnd(code, pos) {
187
196
  }
188
197
  return i;
189
198
  }
190
- function escapeRegExp(input) {
191
- return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
199
+ function findCallParenAfterGenerics(code, afterCalleeIndex) {
200
+ let i = afterCalleeIndex;
201
+ while (i < code.length) {
202
+ const skipped = skipStringOrComment(code, i);
203
+ if (skipped > i) {
204
+ i = skipped;
205
+ continue;
206
+ }
207
+ if (/\s/.test(code[i])) {
208
+ i++;
209
+ continue;
210
+ }
211
+ break;
212
+ }
213
+ if (i >= code.length) return -1;
214
+ if (code[i] === "(") return i;
215
+ if (code[i] === "<") {
216
+ let depth = 0;
217
+ while (i < code.length) {
218
+ const skipped = skipStringOrComment(code, i);
219
+ if (skipped > i) {
220
+ i = skipped;
221
+ continue;
222
+ }
223
+ const ch = code[i];
224
+ if (ch === "<") {
225
+ depth++;
226
+ } else if (ch === ">") {
227
+ depth--;
228
+ if (depth === 0) {
229
+ i++;
230
+ break;
231
+ }
232
+ }
233
+ i++;
234
+ }
235
+ if (depth !== 0) return -1;
236
+ while (i < code.length) {
237
+ const skipped = skipStringOrComment(code, i);
238
+ if (skipped > i) {
239
+ i = skipped;
240
+ continue;
241
+ }
242
+ if (/\s/.test(code[i])) {
243
+ i++;
244
+ continue;
245
+ }
246
+ break;
247
+ }
248
+ if (i < code.length && code[i] === "(") return i;
249
+ return -1;
250
+ }
251
+ return -1;
192
252
  }
193
253
 
194
254
  // src/vite/debug.ts
@@ -431,7 +491,6 @@ function exposeActionId() {
431
491
  counterTransform?.record(id, performance.now() - start);
432
492
  }
433
493
  },
434
- // Build mode: renderChunk runs after all transforms and bundling complete
435
494
  renderChunk(code, chunk) {
436
495
  const start = counterRender ? performance.now() : 0;
437
496
  try {
@@ -473,10 +532,10 @@ import path4 from "node:path";
473
532
  function isDirectivePrologueStatement(node) {
474
533
  return node?.type === "ExpressionStatement" && typeof node.directive === "string";
475
534
  }
476
- function findImportInsertionPos(code, parseAst4) {
535
+ function findImportInsertionPos(code, parseAst5) {
477
536
  let program;
478
537
  try {
479
- program = parseAst4(code, { lang: "tsx" });
538
+ program = parseAst5(code, { lang: "tsx" });
480
539
  } catch {
481
540
  return 0;
482
541
  }
@@ -513,10 +572,10 @@ function walkNode(node, parent, ancestors, enter) {
513
572
  }
514
573
  ancestors.pop();
515
574
  }
516
- function findHandlerCalls(code, fnName, parseAst4) {
575
+ function findHandlerCalls(code, fnName, parseAst5) {
517
576
  let program;
518
577
  try {
519
- program = parseAst4(code, { lang: "tsx" });
578
+ program = parseAst5(code, { lang: "tsx" });
520
579
  } catch {
521
580
  return [];
522
581
  }
@@ -588,18 +647,18 @@ function getImportedLocalNamesFromProgram(program, importedName) {
588
647
  }
589
648
  return localNames;
590
649
  }
591
- function getImportedLocalNames(code, importedName, parseAst4) {
650
+ function getImportedLocalNames(code, importedName, parseAst5) {
592
651
  try {
593
- const program = parseAst4(code, { lang: "tsx" });
652
+ const program = parseAst5(code, { lang: "tsx" });
594
653
  return getImportedLocalNamesFromProgram(program, importedName);
595
654
  } catch {
596
655
  return /* @__PURE__ */ new Set();
597
656
  }
598
657
  }
599
- function extractImportDeclarations(code, parseAst4) {
658
+ function extractImportDeclarations(code, parseAst5) {
600
659
  let program;
601
660
  try {
602
- program = parseAst4(code, { lang: "tsx" });
661
+ program = parseAst5(code, { lang: "tsx" });
603
662
  } catch {
604
663
  return [];
605
664
  }
@@ -623,13 +682,18 @@ function isInertExpression(node) {
623
682
  (e) => e === null || isInertExpression(e)
624
683
  );
625
684
  case "ObjectExpression":
626
- return (node.properties ?? []).every(
627
- (p) => p.type === "Property" && (!p.computed || isInertExpression(p.key)) && isInertExpression(p.value)
628
- );
685
+ return (node.properties ?? []).every((p) => {
686
+ if (p.type === "SpreadElement" || p.type === "RestElement") {
687
+ return isInertExpression(p.argument);
688
+ }
689
+ return p.type === "Property" && (!p.computed || isInertExpression(p.key)) && isInertExpression(p.value);
690
+ });
629
691
  case "UnaryExpression":
630
692
  return isInertExpression(node.argument);
631
693
  case "BinaryExpression":
632
694
  return isInertExpression(node.left) && isInertExpression(node.right);
695
+ case "LogicalExpression":
696
+ return isInertExpression(node.left) && isInertExpression(node.right);
633
697
  case "ConditionalExpression":
634
698
  return isInertExpression(node.test) && isInertExpression(node.consequent) && isInertExpression(node.alternate);
635
699
  case "SpreadElement":
@@ -651,10 +715,10 @@ function isSafeVariableDeclaration(node, handlerNames) {
651
715
  (d) => isSafeDeclaratorInit(d.init) && !(d.init?.type === "CallExpression" && d.init.callee?.type === "Identifier" && handlerNames.has(d.init.callee.name))
652
716
  );
653
717
  }
654
- function extractModuleLevelDeclarations(code, parseAst4, handlerNames) {
718
+ function extractModuleLevelDeclarations(code, parseAst5, handlerNames) {
655
719
  let program;
656
720
  try {
657
- program = parseAst4(code, { lang: "tsx" });
721
+ program = parseAst5(code, { lang: "tsx" });
658
722
  } catch {
659
723
  return [];
660
724
  }
@@ -685,17 +749,17 @@ function extractModuleLevelDeclarations(code, parseAst4, handlerNames) {
685
749
  }
686
750
  return declarations;
687
751
  }
688
- function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtualRegistry, moduleId, parseAst4) {
689
- const sites = findHandlerCalls(code, fnName, parseAst4);
752
+ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtualRegistry, moduleId, parseAst5) {
753
+ const sites = findHandlerCalls(code, fnName, parseAst5);
690
754
  const inlineSites = sites.filter((site) => site.exportInfo === null);
691
755
  if (inlineSites.length === 0) return false;
692
- const imports = extractImportDeclarations(code, parseAst4);
693
- const staticNames = getImportedLocalNames(code, "Static", parseAst4);
694
- const prerenderNames = getImportedLocalNames(code, "Prerender", parseAst4);
756
+ const imports = extractImportDeclarations(code, parseAst5);
757
+ const staticNames = getImportedLocalNames(code, "Static", parseAst5);
758
+ const prerenderNames = getImportedLocalNames(code, "Prerender", parseAst5);
695
759
  const handlerNames = /* @__PURE__ */ new Set([...staticNames, ...prerenderNames]);
696
760
  const declarations = extractModuleLevelDeclarations(
697
761
  code,
698
- parseAst4,
762
+ parseAst5,
699
763
  handlerNames
700
764
  );
701
765
  const importStatements = [];
@@ -718,7 +782,7 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
718
782
  }
719
783
  if (importStatements.length > 0) {
720
784
  const importBlock = importStatements.join("\n") + "\n";
721
- const insertionPos = findImportInsertionPos(code, parseAst4);
785
+ const insertionPos = findImportInsertionPos(code, parseAst5);
722
786
  if (insertionPos === 0) {
723
787
  s.prepend(importBlock);
724
788
  } else {
@@ -751,25 +815,55 @@ function isLineTerminator(ch) {
751
815
  const c = ch.charCodeAt(0);
752
816
  return c === 10 || c === 13 || c === 8232 || c === 8233;
753
817
  }
818
+ var REGEX_PRECEDING_KEYWORDS = /* @__PURE__ */ new Set([
819
+ "return",
820
+ "typeof",
821
+ "instanceof",
822
+ "in",
823
+ "of",
824
+ "new",
825
+ "delete",
826
+ "void",
827
+ "do",
828
+ "else",
829
+ "yield",
830
+ "await",
831
+ "case",
832
+ "throw"
833
+ ]);
834
+ function isRegexPositionAt(code, slashPos, prevChar) {
835
+ if (prevChar === void 0) return true;
836
+ if (prevChar === ")" || prevChar === "]" || prevChar === "}") return false;
837
+ if (!/[\w$]/.test(prevChar)) return true;
838
+ let k = slashPos - 1;
839
+ while (k >= 0 && /\s/.test(code[k])) k--;
840
+ const wordEnd = k + 1;
841
+ while (k >= 0 && /[\w$]/.test(code[k])) k--;
842
+ return REGEX_PRECEDING_KEYWORDS.has(code.slice(k + 1, wordEnd));
843
+ }
754
844
  function makeCodeClassifier(code) {
755
845
  const n = code.length;
756
846
  let i = 0;
757
847
  let skipStart = -1;
758
848
  let skipEnd = -1;
849
+ let lastSig;
759
850
  return (q) => {
760
851
  if (q >= skipStart && q < skipEnd) return false;
761
852
  while (i < n && i <= q) {
762
853
  const c = code[i];
763
854
  const d = i + 1 < n ? code[i + 1] : "";
764
855
  let end = -1;
856
+ let transparent = false;
765
857
  if (c === "/" && d === "/") {
766
858
  let j = i + 2;
767
859
  while (j < n && !isLineTerminator(code[j])) j++;
768
860
  end = j;
861
+ transparent = true;
769
862
  } else if (c === "/" && d === "*") {
770
863
  let j = i + 2;
771
864
  while (j < n && !(code[j] === "*" && code[j + 1] === "/")) j++;
772
865
  end = Math.min(n, j + 2);
866
+ transparent = true;
773
867
  } else if (c === '"' || c === "'" || c === "`") {
774
868
  let j = i + 1;
775
869
  while (j < n) {
@@ -784,6 +878,29 @@ function makeCodeClassifier(code) {
784
878
  j++;
785
879
  }
786
880
  end = j;
881
+ } else if (c === "/" && d !== "/" && d !== "*" && isRegexPositionAt(code, i, lastSig)) {
882
+ let j = i + 1;
883
+ let inClass = false;
884
+ let closed = false;
885
+ while (j < n && !isLineTerminator(code[j])) {
886
+ const r = code[j];
887
+ if (r === "\\") {
888
+ j += 2;
889
+ continue;
890
+ }
891
+ if (r === "[") inClass = true;
892
+ else if (r === "]") inClass = false;
893
+ else if (r === "/" && !inClass) {
894
+ j++;
895
+ closed = true;
896
+ break;
897
+ }
898
+ j++;
899
+ }
900
+ if (closed) {
901
+ while (j < n && /[a-z]/.test(code[j])) j++;
902
+ end = j;
903
+ }
787
904
  }
788
905
  if (end >= 0) {
789
906
  if (q < end) {
@@ -792,7 +909,9 @@ function makeCodeClassifier(code) {
792
909
  return false;
793
910
  }
794
911
  i = end;
912
+ if (!transparent) lastSig = "x";
795
913
  } else {
914
+ if (!/\s/.test(c)) lastSig = c;
796
915
  i++;
797
916
  }
798
917
  }
@@ -851,13 +970,23 @@ function isExportOnlyFile(code, bindings) {
851
970
  return true;
852
971
  }
853
972
  function createCallPattern(fnNames) {
854
- return new RegExp(
855
- `\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
856
- "g"
973
+ return new RegExp(`\\b(?:${fnNames.map(escapeRegExp).join("|")})\\b`, "g");
974
+ }
975
+ function createCallStartIndices(code, fnNames) {
976
+ return codeMatchIndices(code, createCallPattern(fnNames)).filter(
977
+ (index) => findCallParenAfterGenerics(
978
+ code,
979
+ index + matchedNameLength(code, index)
980
+ ) !== -1
857
981
  );
858
982
  }
983
+ function matchedNameLength(code, index) {
984
+ let i = index;
985
+ while (i < code.length && /[A-Za-z0-9_$]/.test(code[i])) i++;
986
+ return i - index;
987
+ }
859
988
  function countCreateCallsForNames(code, fnNames) {
860
- return codeMatchIndices(code, createCallPattern(fnNames)).length;
989
+ return createCallStartIndices(code, fnNames).length;
861
990
  }
862
991
  function offsetToLineColumn(code, index) {
863
992
  let line = 1;
@@ -873,7 +1002,7 @@ function offsetToLineColumn(code, index) {
873
1002
  }
874
1003
  function findUnsupportedCreateCallSites(code, fnNames, supportedBindings) {
875
1004
  const supported = new Set(supportedBindings.map((b) => b.callExprStart));
876
- return codeMatchIndices(code, createCallPattern(fnNames)).filter((index) => !supported.has(index)).map((index) => offsetToLineColumn(code, index));
1005
+ return createCallStartIndices(code, fnNames).filter((index) => !supported.has(index)).map((index) => offsetToLineColumn(code, index));
877
1006
  }
878
1007
  function getImportedFnNames(code, importedName) {
879
1008
  const importPattern = /import\s*\{([^}]*)\}\s*from\s*["']@rangojs\/router(?:\/[^"']*)?["']/g;
@@ -1067,11 +1196,13 @@ function generateClientLoaderStubs(bindings, code, filePath, isBuild) {
1067
1196
  if (!isExportOnlyFile(code, bindings)) return null;
1068
1197
  const lines = [];
1069
1198
  for (const binding of bindings) {
1070
- for (const name of binding.exportNames) {
1071
- const loaderId = makeStubId(filePath, name, isBuild);
1072
- lines.push(
1073
- `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`
1074
- );
1199
+ const primaryName = binding.exportNames[0];
1200
+ const loaderId = makeStubId(filePath, primaryName, isBuild);
1201
+ lines.push(
1202
+ `export const ${primaryName} = { __brand: "loader", $$id: "${loaderId}" };`
1203
+ );
1204
+ for (const alias of binding.exportNames.slice(1)) {
1205
+ lines.push(`export const ${alias} = ${primaryName};`);
1075
1206
  }
1076
1207
  }
1077
1208
  return { code: lines.join("\n") + "\n" };
@@ -1092,22 +1223,13 @@ ${binding.localName}.$$id = "${loaderId}";`;
1092
1223
  }
1093
1224
 
1094
1225
  // src/vite/plugins/expose-ids/handler-transform.ts
1095
- function analyzeCreateHandleArgs(code, startPos, endPos) {
1096
- const content = code.slice(startPos, endPos).trim();
1097
- return { hasArgs: content.length > 0 };
1098
- }
1099
- function transformHandles(bindings, s, code, filePath, isBuild) {
1226
+ function transformHandles(bindings, s, filePath, isBuild) {
1100
1227
  let hasChanges = false;
1101
1228
  for (const binding of bindings) {
1102
1229
  const exportName = binding.exportNames[0];
1103
- const args = analyzeCreateHandleArgs(
1104
- code,
1105
- binding.callOpenParenPos + 1,
1106
- binding.callCloseParenPos
1107
- );
1108
1230
  const handleId = makeStubId(filePath, exportName, isBuild);
1109
1231
  let paramInjection;
1110
- if (!args.hasArgs) {
1232
+ if (binding.argCount === 0) {
1111
1233
  paramInjection = `undefined, "${handleId}"`;
1112
1234
  } else {
1113
1235
  paramInjection = `, "${handleId}"`;
@@ -1182,36 +1304,68 @@ import MagicString2 from "magic-string";
1182
1304
  import path3 from "node:path";
1183
1305
  import { createHash } from "node:crypto";
1184
1306
  var debug2 = createRangoDebugger(NS.transform);
1185
- function transformRouter(code, filePath, routerFnNames, absolutePath) {
1307
+ function skipLeadingTrivia(code, start, end) {
1308
+ let i = start;
1309
+ while (i < end) {
1310
+ const skipped = skipStringOrComment(code, i);
1311
+ if (skipped > i) {
1312
+ i = skipped;
1313
+ continue;
1314
+ }
1315
+ if (/\s/.test(code[i])) {
1316
+ i++;
1317
+ continue;
1318
+ }
1319
+ break;
1320
+ }
1321
+ return i;
1322
+ }
1323
+ function transformRouter(code, filePath, routerFnNames, absolutePath, warn) {
1186
1324
  const pat = new RegExp(
1187
- `\\b(?:${routerFnNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\s*(?:<[^>]*>)?\\s*\\(`,
1325
+ `\\b(?:${routerFnNames.map(escapeRegExp).join("|")})\\b`,
1188
1326
  "g"
1189
1327
  );
1190
1328
  let match;
1191
1329
  const s = new MagicString2(code);
1192
1330
  let changed = false;
1331
+ const unsupportedSites = [];
1193
1332
  const basename2 = path3.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
1194
1333
  const routeNamesImport = `./${basename2}.named-routes.gen.js`;
1195
1334
  const routeNamesVar = `__rsc_rn`;
1335
+ const codeOffsets = new Set(codeMatchIndices(code, pat));
1336
+ pat.lastIndex = 0;
1196
1337
  while ((match = pat.exec(code)) !== null) {
1338
+ if (!codeOffsets.has(match.index)) continue;
1197
1339
  const callStart = match.index;
1198
- const parenPos = match.index + match[0].length - 1;
1340
+ const calleeEnd = match.index + match[0].length;
1341
+ const parenPos = findCallParenAfterGenerics(code, calleeEnd);
1342
+ if (parenPos === -1) continue;
1199
1343
  const closeParen = findMatchingParen(code, parenPos + 1);
1200
1344
  const callArgs = code.slice(parenPos + 1, closeParen);
1201
- if (callArgs.includes("$$id")) continue;
1345
+ if (callArgs.includes(`$$routeNames: ${routeNamesVar}`)) continue;
1346
+ const sourceFilePath = absolutePath ?? filePath;
1202
1347
  const lineNumber = code.slice(0, callStart).split("\n").length;
1203
1348
  const hash = createHash("sha256").update(`${filePath}:${lineNumber}`).digest("hex").slice(0, 8);
1204
- changed = true;
1205
- const sourceFilePath = absolutePath ?? filePath;
1206
1349
  const injected = ` $$id: "${hash}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
1207
- const afterParen = callArgs.trimStart();
1208
- if (afterParen.startsWith("{")) {
1209
- const bracePos = code.indexOf("{", parenPos + 1);
1210
- s.appendRight(bracePos + 1, injected);
1211
- } else if (afterParen.startsWith(")")) {
1350
+ const argsContentStart = skipLeadingTrivia(code, parenPos + 1, closeParen);
1351
+ const firstArgChar = code[argsContentStart];
1352
+ if (firstArgChar === "{") {
1353
+ changed = true;
1354
+ s.appendRight(argsContentStart + 1, injected);
1355
+ } else if (argsContentStart >= closeParen - 1) {
1356
+ changed = true;
1212
1357
  s.appendRight(parenPos + 1, `{${injected} }`);
1358
+ } else {
1359
+ const lastNl = code.lastIndexOf("\n", callStart - 1);
1360
+ const column = callStart - (lastNl + 1) + 1;
1361
+ unsupportedSites.push({ line: lineNumber, column });
1213
1362
  }
1214
1363
  }
1364
+ if (unsupportedSites.length > 0 && warn) {
1365
+ warn(
1366
+ buildUnsupportedShapeWarning(filePath, "createRouter", unsupportedSites)
1367
+ );
1368
+ }
1215
1369
  if (!changed) return null;
1216
1370
  s.prepend(
1217
1371
  `import { NamedRoutes as ${routeNamesVar} } from "${routeNamesImport}";
@@ -1245,11 +1399,13 @@ function exposeRouterId() {
1245
1399
  try {
1246
1400
  const filePath = normalizePath(path3.relative(projectRoot, id));
1247
1401
  const routerFnNames = getImportedFnNames(code, "createRouter");
1402
+ const warn = typeof this.warn === "function" ? (message) => this.warn(message) : void 0;
1248
1403
  return transformRouter(
1249
1404
  code,
1250
1405
  filePath,
1251
1406
  routerFnNames,
1252
- normalizePath(id)
1407
+ normalizePath(id),
1408
+ warn
1253
1409
  );
1254
1410
  } finally {
1255
1411
  counter?.record(id, performance.now() - start);
@@ -1345,6 +1501,7 @@ ${lazyImports.join(",\n")}
1345
1501
  // --------------- Loader pre-scan (build mode) ---------------
1346
1502
  async buildStart() {
1347
1503
  if (!isBuild) return;
1504
+ if (this.environment && this.environment.name !== "rsc") return;
1348
1505
  const fs2 = await import("node:fs/promises");
1349
1506
  const SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage"]);
1350
1507
  async function scanDir(dir) {
@@ -1729,7 +1886,6 @@ ${lazyImports.join(",\n")}
1729
1886
  changed = transformHandles(
1730
1887
  getBindings(code, fnNames),
1731
1888
  s,
1732
- code,
1733
1889
  filePath,
1734
1890
  isBuild
1735
1891
  ) || changed;
@@ -1806,8 +1962,8 @@ function useCacheTransform() {
1806
1962
  } = rscTransforms;
1807
1963
  let ast;
1808
1964
  try {
1809
- const { parseAst: parseAst4 } = await import("vite");
1810
- ast = parseAst4(code, { lang: "tsx" });
1965
+ const { parseAst: parseAst5 } = await import("vite");
1966
+ ast = parseAst5(code, { lang: "tsx" });
1811
1967
  } catch {
1812
1968
  return;
1813
1969
  }
@@ -1841,7 +1997,7 @@ function useCacheTransform() {
1841
1997
  };
1842
1998
  }
1843
1999
  function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLayoutOrTemplate, transformWrapExport) {
1844
- const nonFunctionExports = [];
2000
+ const unconfirmedExports = [];
1845
2001
  const { exportNames, output } = transformWrapExport(code, ast, {
1846
2002
  runtime: (value, name) => {
1847
2003
  const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
@@ -1850,16 +2006,17 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
1850
2006
  rejectNonAsyncFunction: false,
1851
2007
  filter: (name, meta) => {
1852
2008
  if (name === "default" && isLayoutOrTemplate) return false;
1853
- if (meta.isFunction === false) {
1854
- nonFunctionExports.push(name);
2009
+ if (meta.isFunction !== true) {
2010
+ unconfirmedExports.push(name);
1855
2011
  return false;
1856
2012
  }
1857
2013
  return true;
1858
2014
  }
1859
2015
  });
1860
- if (nonFunctionExports.length > 0) {
2016
+ if (unconfirmedExports.length > 0) {
2017
+ const plural = unconfirmedExports.length > 1;
1861
2018
  throw new Error(
1862
- `[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.`
2019
+ `[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.`
1863
2020
  );
1864
2021
  }
1865
2022
  if (exportNames.length === 0) {
@@ -1997,7 +2154,6 @@ function clientRefDedup() {
1997
2154
  if (this.environment?.name !== "client") return;
1998
2155
  if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
1999
2156
  if (!source.includes("/node_modules/")) return;
2000
- if (!importer) return;
2001
2157
  const packageName = extractPackageName(source);
2002
2158
  if (!packageName) return;
2003
2159
  if (clientExclude.includes(packageName)) return;
@@ -2039,11 +2195,16 @@ async function initializeApp() {
2039
2195
  createTemporaryReferenceSet,
2040
2196
  };
2041
2197
 
2042
- await initBrowserApp({ rscStream, deps });
2198
+ // initBrowserApp resolves the initial payload and returns the browser app
2199
+ // context, including strictMode (default true) from createRouter. StrictMode
2200
+ // is the default; createRouter({ strictMode: false }) ships the opt-out in the
2201
+ // payload metadata. StrictMode emits no DOM, so toggling never changes markup.
2202
+ const { strictMode } = await initBrowserApp({ rscStream, deps });
2043
2203
 
2204
+ const app = createElement(Rango);
2044
2205
  hydrateRoot(
2045
2206
  document,
2046
- createElement(StrictMode, null, createElement(Rango))
2207
+ strictMode === false ? app : createElement(StrictMode, null, app)
2047
2208
  );
2048
2209
  }
2049
2210
 
@@ -2051,9 +2212,14 @@ initializeApp().catch(console.error);
2051
2212
  `.trim();
2052
2213
  var VIRTUAL_ENTRY_SSR = `
2053
2214
  import { createFromReadableStream } from "@rangojs/router/internal/deps/ssr";
2054
- import { renderToReadableStream } from "react-dom/server.edge";
2215
+ import { renderToReadableStream, resume } from "react-dom/server.edge";
2216
+ import { prerender } from "react-dom/static.edge";
2055
2217
  import { injectRSCPayload } from "@rangojs/router/internal/deps/html-stream-server";
2056
- import { createSSRHandler } from "@rangojs/router/ssr";
2218
+ import {
2219
+ createSSRHandler,
2220
+ createShellCaptureHandler,
2221
+ createShellResumeHandler,
2222
+ } from "@rangojs/router/ssr";
2057
2223
 
2058
2224
  export const renderHTML = createSSRHandler({
2059
2225
  createFromReadableStream,
@@ -2062,8 +2228,35 @@ export const renderHTML = createSSRHandler({
2062
2228
  loadBootstrapScriptContent: () =>
2063
2229
  import.meta.viteRsc.loadBootstrapScriptContent("index"),
2064
2230
  });
2231
+
2232
+ export const captureShellHTML = createShellCaptureHandler({
2233
+ createFromReadableStream,
2234
+ renderToReadableStream,
2235
+ injectRSCPayload,
2236
+ prerender,
2237
+ resume,
2238
+ loadBootstrapScriptContent: () =>
2239
+ import.meta.viteRsc.loadBootstrapScriptContent("index"),
2240
+ });
2241
+
2242
+ export const resumeShellHTML = createShellResumeHandler({
2243
+ createFromReadableStream,
2244
+ renderToReadableStream,
2245
+ injectRSCPayload,
2246
+ prerender,
2247
+ resume,
2248
+ loadBootstrapScriptContent: () =>
2249
+ import.meta.viteRsc.loadBootstrapScriptContent("index"),
2250
+ });
2065
2251
  `.trim();
2252
+ var RSC_ENTRY_BOOTSTRAP_IMPORTS = [
2253
+ "virtual:rsc-router/routes-manifest",
2254
+ "virtual:rsc-router/loader-manifest"
2255
+ ];
2066
2256
  function getVirtualEntryRSC(routerPath) {
2257
+ const bootstrapImports = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
2258
+ (id) => `import "${id}";`
2259
+ ).join("\n");
2067
2260
  return `
2068
2261
  import {
2069
2262
  renderToReadableStream,
@@ -2077,15 +2270,9 @@ import { router } from "${routerPath}";
2077
2270
  import { createRSCHandler } from "@rangojs/router/internal/rsc-handler";
2078
2271
  import { VERSION } from "@rangojs/router:version";
2079
2272
 
2080
- // Import loader manifest to ensure all fetchable loaders are registered at startup
2081
- // This is critical for serverless/multi-process deployments where the loader module
2082
- // might not be imported before a GET request arrives
2083
- import "virtual:rsc-router/loader-manifest";
2084
-
2085
- // Import pre-generated route manifest so href() works immediately on cold start.
2086
- // In build mode, this contains the full route map generated at build time.
2087
- // In dev mode, this is a no-op (manifest is populated in-memory by the discovery plugin).
2088
- import "virtual:rsc-router/routes-manifest";
2273
+ // Startup bootstrap imports (routes + loader manifests). See
2274
+ // RSC_ENTRY_BOOTSTRAP_IMPORTS \u2014 the same list the custom-entry injector uses.
2275
+ ${bootstrapImports}
2089
2276
 
2090
2277
  // Lazily create the handler on first request so that ESM live bindings
2091
2278
  // have resolved by the time we read \`router\`. During HMR the module may
@@ -2096,6 +2283,11 @@ export default function handler(request, env) {
2096
2283
  _handler = createRSCHandler({
2097
2284
  router,
2098
2285
  version: VERSION,
2286
+ // Forward the router's CSP nonce provider. createRSCHandler reads the
2287
+ // provider only from options.nonce; without this, createRouter({ nonce })
2288
+ // is silently dropped on the Node preset (the Cloudflare path wires it via
2289
+ // router.fetch). router.nonce is undefined when unconfigured, a safe no-op.
2290
+ nonce: router.nonce,
2099
2291
  deps: {
2100
2292
  renderToReadableStream,
2101
2293
  decodeReply,
@@ -2112,6 +2304,77 @@ export default function handler(request, env) {
2112
2304
  }
2113
2305
  `.trim();
2114
2306
  }
2307
+ function getVirtualEntryRSCHost(hostEntryPath) {
2308
+ return `
2309
+ import * as __hostEntry from "${hostEntryPath}";
2310
+ import { isNoRouteMatchError } from "@rangojs/router/host";
2311
+
2312
+ // Register every sub-app's fetchable loaders + route manifests at startup, same
2313
+ // as the single-router entry. Discovery's host fallback populates these for all
2314
+ // mounted sub-apps, so the aggregate manifests cover the whole host tree.
2315
+ import "virtual:rsc-router/loader-manifest";
2316
+ import "virtual:rsc-router/routes-manifest";
2317
+
2318
+ // The host entry module must export the HostRouter instance (createHostRouter()),
2319
+ // as a default export or a named \`hostRouter\`/\`router\` export. A Cloudflare-style
2320
+ // \`export default { fetch }\` object is not a HostRouter and is rejected (on first
2321
+ // request; see the lazy resolution below).
2322
+ // We require BOTH .match() and .host(): a regular createRouter() also exposes
2323
+ // .match(), so matching on .match() alone would accept an ordinary router and then
2324
+ // return its MatchResult (not a Response) at runtime. .host() is unique to a
2325
+ // HostRouter, so it disambiguates a mistaken \`hostRouter\` path.
2326
+ // Exports are read dynamically (m[name]) so Rollup does not emit IMPORT_IS_UNDEFINED
2327
+ // warnings for the named exports a default-only host module legitimately omits.
2328
+ const __resolveHostRouter = (m) => {
2329
+ for (const name of ["default", "hostRouter", "router"]) {
2330
+ const candidate = m[name];
2331
+ if (
2332
+ candidate &&
2333
+ typeof candidate.match === "function" &&
2334
+ typeof candidate.host === "function"
2335
+ )
2336
+ return candidate;
2337
+ }
2338
+ return undefined;
2339
+ };
2340
+
2341
+ // Resolve + validate the HostRouter lazily on first request, mirroring the
2342
+ // single-router entry's \`_handler\`. During HMR the host module can re-evaluate
2343
+ // before its createHostRouter() export has resolved; validating at module-
2344
+ // evaluation time would then throw a spurious "must export a HostRouter" error
2345
+ // overlay for a perfectly valid app. Resolving on first request lets the live
2346
+ // binding settle first.
2347
+ let _hostRouter;
2348
+
2349
+ // input = { env, ctx } from the launcher / node server. The host router threads
2350
+ // it unchanged to each matched sub-app's handler and cache factory.
2351
+ // On node/vercel rango owns this entry, so there is no user worker to translate
2352
+ // an unmatched host into a response: catch NoRouteMatchError and return 404
2353
+ // (parity with the documented Cloudflare catch). Other errors propagate.
2354
+ export default async function handler(request, input) {
2355
+ if (!_hostRouter) {
2356
+ _hostRouter = __resolveHostRouter(__hostEntry);
2357
+ if (!_hostRouter) {
2358
+ throw new Error(
2359
+ "[rango] The host entry (${hostEntryPath}) must export a HostRouter instance (createHostRouter()) for the node/vercel preset: a default export, or a named 'hostRouter'/'router' export. An ordinary createRouter() is not a host router (it has no .host()), and a Cloudflare-style 'export default { fetch }' object is not supported on this preset."
2360
+ );
2361
+ }
2362
+ }
2363
+ try {
2364
+ return await _hostRouter.match(request, input);
2365
+ } catch (err) {
2366
+ // isNoRouteMatchError also matches by name: a workspace with a duplicated
2367
+ // @rangojs/router copy can throw a NoRouteMatchError whose prototype differs
2368
+ // from this module's import, so a bare instanceof would turn an
2369
+ // unmatched-host 404 into a 500.
2370
+ if (isNoRouteMatchError(err)) {
2371
+ return new Response("Not Found", { status: 404 });
2372
+ }
2373
+ throw err;
2374
+ }
2375
+ }
2376
+ `.trim();
2377
+ }
2115
2378
  var VIRTUAL_IDS = {
2116
2379
  browser: "virtual:rsc-router/entry.browser.js",
2117
2380
  ssr: "virtual:rsc-router/entry.ssr.js",
@@ -2130,7 +2393,7 @@ import { resolve } from "node:path";
2130
2393
  // package.json
2131
2394
  var package_default = {
2132
2395
  name: "@rangojs/router",
2133
- version: "0.0.0-experimental.bd6e11bc",
2396
+ version: "0.0.0-experimental.bdaf10aa",
2134
2397
  description: "Django-inspired RSC router with composable URL patterns",
2135
2398
  keywords: [
2136
2399
  "react",
@@ -2240,6 +2503,16 @@ var package_default = {
2240
2503
  "react-server": "./src/cache/cache-runtime.ts",
2241
2504
  default: "./src/cache/cache-runtime.ts"
2242
2505
  },
2506
+ "./cloudflare": {
2507
+ types: "./src/cloudflare/index.ts",
2508
+ "react-server": "./src/cloudflare/index.ts",
2509
+ default: "./src/cloudflare/index.ts"
2510
+ },
2511
+ "./vercel": {
2512
+ types: "./src/vercel/index.ts",
2513
+ "react-server": "./src/vercel/index.ts",
2514
+ default: "./src/vercel/index.ts"
2515
+ },
2243
2516
  "./theme": {
2244
2517
  types: "./src/theme/index.ts",
2245
2518
  default: "./src/theme/index.ts"
@@ -2288,7 +2561,7 @@ var package_default = {
2288
2561
  tag: "experimental"
2289
2562
  },
2290
2563
  scripts: {
2291
- build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2564
+ build: "pnpm exec 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 exec esbuild src/testing/vitest.ts --bundle --format=esm --outfile=dist/testing/vitest.js --platform=node --packages=external && pnpm exec 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",
2292
2565
  prepublishOnly: "pnpm build",
2293
2566
  typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
2294
2567
  test: "playwright test",
@@ -2300,14 +2573,18 @@ var package_default = {
2300
2573
  },
2301
2574
  dependencies: {
2302
2575
  "@types/debug": "^4.1.12",
2303
- "@vitejs/plugin-rsc": "^0.5.26",
2576
+ "@vitejs/plugin-rsc": "^0.5.27",
2304
2577
  debug: "^4.4.1",
2305
2578
  "magic-string": "^0.30.17",
2306
- picomatch: "^4.0.3",
2579
+ picomatch: "^4.0.4",
2307
2580
  "rsc-html-stream": "^0.0.7",
2581
+ srvx: "^0.11.15",
2308
2582
  tinyexec: "^0.3.2"
2309
2583
  },
2310
2584
  devDependencies: {
2585
+ "@opentelemetry/api": "^1.9.0",
2586
+ "@opentelemetry/context-async-hooks": "^2.9.0",
2587
+ "@opentelemetry/sdk-trace-base": "^2.9.0",
2311
2588
  "@playwright/test": "^1.49.1",
2312
2589
  "@shared/e2e": "workspace:*",
2313
2590
  "@testing-library/dom": "^10.4.1",
@@ -2315,40 +2592,51 @@ var package_default = {
2315
2592
  "@types/node": "^24.10.1",
2316
2593
  "@types/react": "catalog:",
2317
2594
  "@types/react-dom": "catalog:",
2318
- esbuild: "^0.27.0",
2595
+ esbuild: "^0.28.1",
2319
2596
  "happy-dom": "^20.10.1",
2320
- jiti: "^2.6.1",
2597
+ jiti: "^2.7.0",
2321
2598
  react: "catalog:",
2322
2599
  "react-dom": "catalog:",
2323
2600
  typescript: "^5.3.0",
2324
- vitest: "^4.0.0"
2601
+ vitest: "^4.1.9"
2325
2602
  },
2326
2603
  peerDependencies: {
2327
- "@cloudflare/vite-plugin": "^1.38.0",
2604
+ "@cloudflare/vite-plugin": "^1.42.1",
2605
+ "@opentelemetry/api": "^1.9.0",
2328
2606
  "@playwright/test": "^1.49.1",
2329
2607
  "@testing-library/react": ">=16",
2330
- "@vitejs/plugin-rsc": "^0.5.26",
2608
+ "@vercel/functions": "^3.0.0",
2609
+ "@vitejs/plugin-rsc": "^0.5.27",
2331
2610
  react: ">=19.2.6 <20",
2332
2611
  "react-dom": ">=19.2.6 <20",
2333
- vite: "^8.0.0",
2612
+ vite: "^8.0.16",
2334
2613
  vitest: ">=3"
2335
2614
  },
2336
2615
  peerDependenciesMeta: {
2337
2616
  "@cloudflare/vite-plugin": {
2338
2617
  optional: true
2339
2618
  },
2619
+ "@opentelemetry/api": {
2620
+ optional: true
2621
+ },
2340
2622
  "@playwright/test": {
2341
2623
  optional: true
2342
2624
  },
2343
2625
  "@testing-library/react": {
2344
2626
  optional: true
2345
2627
  },
2628
+ "@vercel/functions": {
2629
+ optional: true
2630
+ },
2346
2631
  vite: {
2347
2632
  optional: true
2348
2633
  },
2349
2634
  vitest: {
2350
2635
  optional: true
2351
2636
  }
2637
+ },
2638
+ engines: {
2639
+ node: "^20.19.0 || >=22.12.0"
2352
2640
  }
2353
2641
  };
2354
2642
 
@@ -2422,10 +2710,10 @@ function extractParamsFromPattern(pattern) {
2422
2710
  function formatRouteEntry(key, pattern, _params, search) {
2423
2711
  const hasSearch = search && Object.keys(search).length > 0;
2424
2712
  if (!hasSearch) {
2425
- return ` ${key}: "${pattern}",`;
2713
+ return ` ${key}: ${JSON.stringify(pattern)},`;
2426
2714
  }
2427
- const searchBody = Object.entries(search).map(([k, v]) => `${k}: "${v}"`).join(", ");
2428
- return ` ${key}: { path: "${pattern}", search: { ${searchBody} } },`;
2715
+ const searchBody = Object.entries(search).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
2716
+ return ` ${key}: { path: ${JSON.stringify(pattern)}, search: { ${searchBody} } },`;
2429
2717
  }
2430
2718
 
2431
2719
  // src/build/route-types/ast-route-extraction.ts
@@ -2451,8 +2739,8 @@ function extractObjectStringProperties(node) {
2451
2739
  }
2452
2740
 
2453
2741
  // src/build/route-types/ast-route-extraction.ts
2454
- function extractRoutesFromSource(code) {
2455
- const sourceFile = ts2.createSourceFile(
2742
+ function extractRoutesFromSource(code, sourceFileArg) {
2743
+ const sourceFile = sourceFileArg ?? ts2.createSourceFile(
2456
2744
  "input.tsx",
2457
2745
  code,
2458
2746
  ts2.ScriptTarget.Latest,
@@ -2532,16 +2820,44 @@ export const NamedRoutes = {
2532
2820
  ${objectBody}
2533
2821
  } as const;
2534
2822
 
2823
+ // Aliased so the augmentation below does not pay a homomorphic mapped-type
2824
+ // instantiation per route; \`as const\` already makes the members readonly.
2825
+ type NamedRoutesShape = typeof NamedRoutes;
2826
+
2535
2827
  declare global {
2536
2828
  namespace Rango {
2537
- interface GeneratedRouteMap extends Readonly<typeof NamedRoutes> {}
2829
+ interface GeneratedRouteMap extends NamedRoutesShape {}
2538
2830
  }
2539
2831
  }
2540
2832
  `;
2541
2833
  }
2542
2834
 
2543
2835
  // src/build/route-types/scan-filter.ts
2836
+ import { join, relative } from "node:path";
2544
2837
  import picomatch from "picomatch";
2838
+ var DEFAULT_EXCLUDE_PATTERNS = [
2839
+ "**/__tests__/**",
2840
+ "**/__mocks__/**",
2841
+ "**/dist/**",
2842
+ "**/coverage/**",
2843
+ "**/*.test.{ts,tsx,js,jsx}",
2844
+ "**/*.spec.{ts,tsx,js,jsx}"
2845
+ ];
2846
+ function createScanFilter(root, opts) {
2847
+ const { include, exclude } = opts;
2848
+ const hasInclude = include && include.length > 0;
2849
+ const hasCustomExclude = exclude !== void 0;
2850
+ if (!hasInclude && !hasCustomExclude) return void 0;
2851
+ const effectiveExclude = exclude ?? DEFAULT_EXCLUDE_PATTERNS;
2852
+ const includeMatcher = hasInclude ? picomatch(include) : null;
2853
+ const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
2854
+ return (absolutePath) => {
2855
+ const rel = relative(root, absolutePath);
2856
+ if (excludeMatcher && excludeMatcher(rel)) return false;
2857
+ if (includeMatcher) return includeMatcher(rel);
2858
+ return true;
2859
+ };
2860
+ }
2545
2861
 
2546
2862
  // src/build/route-types/per-module-writer.ts
2547
2863
  import ts4 from "typescript";
@@ -2550,6 +2866,33 @@ import ts4 from "typescript";
2550
2866
  import { readFileSync, existsSync as existsSync2 } from "node:fs";
2551
2867
  import { dirname, resolve as resolve2 } from "node:path";
2552
2868
  import ts3 from "typescript";
2869
+ function createScanMemo() {
2870
+ return { files: /* @__PURE__ */ new Map(), blockSourceFiles: /* @__PURE__ */ new Map() };
2871
+ }
2872
+ function parseBlock(memo, block) {
2873
+ if (memo) {
2874
+ const cached = memo.blockSourceFiles.get(block);
2875
+ if (cached) return cached;
2876
+ }
2877
+ const sf = ts3.createSourceFile(
2878
+ "input.tsx",
2879
+ block,
2880
+ ts3.ScriptTarget.Latest,
2881
+ true,
2882
+ ts3.ScriptKind.TSX
2883
+ );
2884
+ if (memo) memo.blockSourceFiles.set(block, sf);
2885
+ return sf;
2886
+ }
2887
+ function readSourceMemoized(memo, realPath) {
2888
+ if (memo) {
2889
+ const cached = memo.files.get(realPath);
2890
+ if (cached !== void 0) return cached;
2891
+ }
2892
+ const source = readFileSync(realPath, "utf-8");
2893
+ if (memo) memo.files.set(realPath, source);
2894
+ return source;
2895
+ }
2553
2896
  function extractNamePrefixFromInclude(node) {
2554
2897
  if (node.arguments.length >= 3) {
2555
2898
  const thirdArg = node.arguments[2];
@@ -2565,8 +2908,98 @@ function extractNamePrefixFromInclude(node) {
2565
2908
  }
2566
2909
  return null;
2567
2910
  }
2568
- function extractIncludesWithDiagnostics(code) {
2569
- const sourceFile = ts3.createSourceFile(
2911
+ function thenSelectsNonDefaultMember(expr) {
2912
+ const findThenCall = (e) => {
2913
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression) && e.expression.name.text === "then") {
2914
+ return e;
2915
+ }
2916
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression)) {
2917
+ return findThenCall(e.expression.expression);
2918
+ }
2919
+ if (ts3.isPropertyAccessExpression(e)) return findThenCall(e.expression);
2920
+ if (ts3.isParenthesizedExpression(e)) return findThenCall(e.expression);
2921
+ if (ts3.isAwaitExpression(e)) return findThenCall(e.expression);
2922
+ return null;
2923
+ };
2924
+ const thenCall = findThenCall(expr);
2925
+ if (!thenCall || thenCall.arguments.length === 0) return false;
2926
+ const cb = thenCall.arguments[0];
2927
+ if (!ts3.isArrowFunction(cb) && !ts3.isFunctionExpression(cb)) return false;
2928
+ let ret;
2929
+ if (ts3.isBlock(cb.body)) {
2930
+ for (const stmt of cb.body.statements) {
2931
+ if (ts3.isReturnStatement(stmt) && stmt.expression) {
2932
+ ret = stmt.expression;
2933
+ break;
2934
+ }
2935
+ }
2936
+ } else {
2937
+ ret = cb.body;
2938
+ }
2939
+ if (!ret) return false;
2940
+ if (ts3.isPropertyAccessExpression(ret)) {
2941
+ return ret.name.text !== "default";
2942
+ }
2943
+ return false;
2944
+ }
2945
+ function extractDynamicImportSpecifier(node) {
2946
+ let body;
2947
+ if (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node)) {
2948
+ body = node.body;
2949
+ }
2950
+ if (!body) return null;
2951
+ let expr;
2952
+ if (ts3.isBlock(body)) {
2953
+ for (const stmt of body.statements) {
2954
+ if (ts3.isReturnStatement(stmt) && stmt.expression) {
2955
+ expr = stmt.expression;
2956
+ break;
2957
+ }
2958
+ }
2959
+ } else {
2960
+ expr = body;
2961
+ }
2962
+ if (!expr) return null;
2963
+ const findImportCall = (e) => {
2964
+ if (ts3.isCallExpression(e) && e.expression.kind === ts3.SyntaxKind.ImportKeyword) {
2965
+ return e;
2966
+ }
2967
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression)) {
2968
+ return findImportCall(e.expression.expression);
2969
+ }
2970
+ if (ts3.isPropertyAccessExpression(e)) return findImportCall(e.expression);
2971
+ if (ts3.isParenthesizedExpression(e)) return findImportCall(e.expression);
2972
+ if (ts3.isAwaitExpression(e)) return findImportCall(e.expression);
2973
+ return null;
2974
+ };
2975
+ const importCall = findImportCall(expr);
2976
+ if (!importCall || importCall.arguments.length === 0) return null;
2977
+ if (thenSelectsNonDefaultMember(expr)) return null;
2978
+ return getStringValue(importCall.arguments[0]);
2979
+ }
2980
+ function resolveDefaultExportTarget(code, sourceFile) {
2981
+ let result = null;
2982
+ function visit(node) {
2983
+ if (result) return;
2984
+ if (ts3.isExportAssignment(node) && !node.isExportEquals) {
2985
+ const expr = node.expression;
2986
+ if (ts3.isIdentifier(expr)) {
2987
+ result = { variableName: expr.text };
2988
+ } else if (ts3.isCallExpression(expr)) {
2989
+ const callee = expr.expression;
2990
+ if (ts3.isIdentifier(callee) && callee.text === "urls") {
2991
+ result = { inlineBlock: expr.getText(sourceFile) };
2992
+ }
2993
+ }
2994
+ return;
2995
+ }
2996
+ ts3.forEachChild(node, visit);
2997
+ }
2998
+ visit(sourceFile);
2999
+ return result;
3000
+ }
3001
+ function extractIncludesWithDiagnostics(code, sourceFileArg) {
3002
+ const sourceFile = sourceFileArg ?? ts3.createSourceFile(
2570
3003
  "input.tsx",
2571
3004
  code,
2572
3005
  ts3.ScriptTarget.Latest,
@@ -2590,12 +3023,19 @@ function extractIncludesWithDiagnostics(code) {
2590
3023
  }
2591
3024
  const secondArg = node.arguments[1];
2592
3025
  const namePrefix = extractNamePrefixFromInclude(node);
3026
+ const dynamicImportSpec = extractDynamicImportSpecifier(secondArg);
2593
3027
  if (ts3.isIdentifier(secondArg)) {
2594
3028
  resolved.push({
2595
3029
  pathPrefix,
2596
3030
  variableName: secondArg.text,
2597
3031
  namePrefix
2598
3032
  });
3033
+ } else if (dynamicImportSpec !== null) {
3034
+ resolved.push({
3035
+ pathPrefix,
3036
+ moduleSpecifier: dynamicImportSpec,
3037
+ namePrefix
3038
+ });
2599
3039
  } else if (ts3.isCallExpression(secondArg)) {
2600
3040
  const callText = secondArg.expression.getText(sourceFile);
2601
3041
  unresolvable.push({
@@ -2620,7 +3060,7 @@ function extractIncludesWithDiagnostics(code) {
2620
3060
  return { resolved, unresolvable };
2621
3061
  }
2622
3062
  function resolveImportedVariable(code, localName) {
2623
- const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
3063
+ const importRegex = /import\s*(?:[\w$]+\s*,\s*)?\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
2624
3064
  let match;
2625
3065
  while ((match = importRegex.exec(code)) !== null) {
2626
3066
  const imports = match[1];
@@ -2657,8 +3097,8 @@ function resolveImportPath(importSpec, fromFile) {
2657
3097
  }
2658
3098
  return null;
2659
3099
  }
2660
- function extractUrlsBlockForVariable(code, varName) {
2661
- const sourceFile = ts3.createSourceFile(
3100
+ function extractUrlsBlockForVariable(code, varName, sourceFileArg) {
3101
+ const sourceFile = sourceFileArg ?? ts3.createSourceFile(
2662
3102
  "input.tsx",
2663
3103
  code,
2664
3104
  ts3.ScriptTarget.Latest,
@@ -2680,67 +3120,113 @@ function extractUrlsBlockForVariable(code, varName) {
2680
3120
  visit(sourceFile);
2681
3121
  return result;
2682
3122
  }
2683
- function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSchemasOut, diagnosticsOut) {
3123
+ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSchemasOut, diagnosticsOut, memo) {
2684
3124
  const routeMap = {};
2685
- const localRoutes = extractRoutesFromSource(block);
3125
+ const blockSourceFile = parseBlock(memo, block);
3126
+ const localRoutes = extractRoutesFromSource(block, blockSourceFile);
2686
3127
  for (const { name, pattern, search } of localRoutes) {
2687
3128
  routeMap[name] = pattern;
2688
3129
  if (search && searchSchemasOut) {
2689
3130
  searchSchemasOut[name] = search;
2690
3131
  }
2691
3132
  }
2692
- const { resolved: includes, unresolvable } = extractIncludesWithDiagnostics(block);
3133
+ const { resolved: includes, unresolvable } = extractIncludesWithDiagnostics(
3134
+ block,
3135
+ blockSourceFile
3136
+ );
2693
3137
  if (diagnosticsOut) {
2694
3138
  for (const entry of unresolvable) {
2695
3139
  diagnosticsOut.push({ ...entry, sourceFile: filePath });
2696
3140
  }
2697
3141
  }
2698
- for (const { pathPrefix, variableName, namePrefix } of includes) {
3142
+ for (const inc of includes) {
3143
+ const { pathPrefix, namePrefix } = inc;
2699
3144
  let childResult;
2700
- const imported = resolveImportedVariable(fullSource, variableName);
2701
- if (imported) {
2702
- const targetFile = resolveImportPath(imported.specifier, filePath);
3145
+ if (inc.moduleSpecifier) {
3146
+ const targetFile = resolveImportPath(inc.moduleSpecifier, filePath);
2703
3147
  if (!targetFile) {
2704
- if (diagnosticsOut) {
2705
- diagnosticsOut.push({
2706
- pathPrefix,
2707
- namePrefix,
2708
- reason: "file-not-found",
2709
- sourceFile: filePath,
2710
- detail: `import "${imported.specifier}" resolved to no file`
2711
- });
2712
- }
3148
+ diagnosticsOut?.push({
3149
+ pathPrefix,
3150
+ namePrefix,
3151
+ reason: "file-not-found",
3152
+ sourceFile: filePath,
3153
+ detail: `import("${inc.moduleSpecifier}") resolved to no file`
3154
+ });
2713
3155
  continue;
2714
3156
  }
2715
- childResult = buildCombinedRouteMapWithSearch(
2716
- targetFile,
2717
- imported.exportedName,
2718
- visited,
2719
- diagnosticsOut
2720
- );
2721
- } else {
2722
- const sameFileBlock = extractUrlsBlockForVariable(
2723
- fullSource,
2724
- variableName
3157
+ let targetSource;
3158
+ try {
3159
+ targetSource = readSourceMemoized(memo, resolve2(targetFile));
3160
+ } catch (err) {
3161
+ diagnosticsOut?.push({
3162
+ pathPrefix,
3163
+ namePrefix,
3164
+ reason: "file-not-found",
3165
+ sourceFile: filePath,
3166
+ detail: `import("${inc.moduleSpecifier}") resolved to "${targetFile}" but reading it failed: ${err?.message ?? String(err)}`
3167
+ });
3168
+ continue;
3169
+ }
3170
+ const def = resolveDefaultExportTarget(
3171
+ targetSource,
3172
+ parseBlock(memo, targetSource)
2725
3173
  );
2726
- if (!sameFileBlock) {
2727
- if (diagnosticsOut) {
2728
- diagnosticsOut.push({
2729
- pathPrefix,
2730
- namePrefix,
2731
- reason: "unresolvable-import",
2732
- sourceFile: filePath,
2733
- detail: `variable "${variableName}" not found in imports or same-file scope`
2734
- });
2735
- }
3174
+ if (!def) {
3175
+ diagnosticsOut?.push({
3176
+ pathPrefix,
3177
+ namePrefix,
3178
+ reason: "unresolvable-import",
3179
+ sourceFile: filePath,
3180
+ detail: `import("${inc.moduleSpecifier}") has no resolvable \`export default urls(...)\``
3181
+ });
2736
3182
  continue;
2737
3183
  }
2738
- childResult = buildCombinedRouteMapWithSearch(
2739
- filePath,
3184
+ if (def.inlineBlock) {
3185
+ childResult = buildCombinedRouteMapWithSearch(
3186
+ targetFile,
3187
+ void 0,
3188
+ visited,
3189
+ diagnosticsOut,
3190
+ def.inlineBlock,
3191
+ memo
3192
+ );
3193
+ } else {
3194
+ const variableName = def.variableName;
3195
+ const resolved = resolveIncludedVariable({
3196
+ variableName,
3197
+ resolutionFile: targetFile,
3198
+ resolutionSource: targetSource,
3199
+ reportFile: filePath,
3200
+ memo,
3201
+ visited,
3202
+ diagnosticsOut,
3203
+ pathPrefix,
3204
+ namePrefix,
3205
+ fileNotFoundDetail: (specifier) => `import("${inc.moduleSpecifier}") default re-exports "${variableName}" from "${specifier}", which resolved to no file`,
3206
+ notFoundDetail: () => `import("${inc.moduleSpecifier}") default "${variableName}" not found in its imports or same-file scope`
3207
+ });
3208
+ if (!resolved) continue;
3209
+ childResult = resolved;
3210
+ }
3211
+ } else if (inc.variableName) {
3212
+ const variableName = inc.variableName;
3213
+ const resolved = resolveIncludedVariable({
2740
3214
  variableName,
3215
+ resolutionFile: filePath,
3216
+ resolutionSource: fullSource,
3217
+ reportFile: filePath,
3218
+ memo,
2741
3219
  visited,
2742
- diagnosticsOut
2743
- );
3220
+ diagnosticsOut,
3221
+ pathPrefix,
3222
+ namePrefix,
3223
+ fileNotFoundDetail: (specifier) => `import "${specifier}" resolved to no file`,
3224
+ notFoundDetail: () => `variable "${variableName}" not found in imports or same-file scope`
3225
+ });
3226
+ if (!resolved) continue;
3227
+ childResult = resolved;
3228
+ } else {
3229
+ continue;
2744
3230
  }
2745
3231
  if (namePrefix === null) {
2746
3232
  continue;
@@ -2763,8 +3249,67 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2763
3249
  }
2764
3250
  return routeMap;
2765
3251
  }
2766
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
3252
+ function resolveIncludedVariable(opts) {
3253
+ const {
3254
+ variableName,
3255
+ resolutionFile,
3256
+ resolutionSource,
3257
+ reportFile,
3258
+ memo,
3259
+ visited,
3260
+ diagnosticsOut,
3261
+ pathPrefix,
3262
+ namePrefix
3263
+ } = opts;
3264
+ const imported = resolveImportedVariable(resolutionSource, variableName);
3265
+ if (imported) {
3266
+ const targetFile = resolveImportPath(imported.specifier, resolutionFile);
3267
+ if (!targetFile) {
3268
+ diagnosticsOut?.push({
3269
+ pathPrefix,
3270
+ namePrefix,
3271
+ reason: "file-not-found",
3272
+ sourceFile: reportFile,
3273
+ detail: opts.fileNotFoundDetail(imported.specifier)
3274
+ });
3275
+ return null;
3276
+ }
3277
+ return buildCombinedRouteMapWithSearch(
3278
+ targetFile,
3279
+ imported.exportedName,
3280
+ visited,
3281
+ diagnosticsOut,
3282
+ void 0,
3283
+ memo
3284
+ );
3285
+ }
3286
+ const sameFileBlock = extractUrlsBlockForVariable(
3287
+ resolutionSource,
3288
+ variableName,
3289
+ parseBlock(memo, resolutionSource)
3290
+ );
3291
+ if (!sameFileBlock) {
3292
+ diagnosticsOut?.push({
3293
+ pathPrefix,
3294
+ namePrefix,
3295
+ reason: "unresolvable-import",
3296
+ sourceFile: reportFile,
3297
+ detail: opts.notFoundDetail()
3298
+ });
3299
+ return null;
3300
+ }
3301
+ return buildCombinedRouteMapWithSearch(
3302
+ resolutionFile,
3303
+ variableName,
3304
+ visited,
3305
+ diagnosticsOut,
3306
+ void 0,
3307
+ memo
3308
+ );
3309
+ }
3310
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock, memo) {
2767
3311
  visited = visited ?? /* @__PURE__ */ new Set();
3312
+ memo = memo ?? createScanMemo();
2768
3313
  const realPath = resolve2(filePath);
2769
3314
  const key = variableName ? `${realPath}:${variableName}` : realPath;
2770
3315
  if (visited.has(key)) {
@@ -2774,7 +3319,7 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2774
3319
  visited.add(key);
2775
3320
  let source;
2776
3321
  try {
2777
- source = readFileSync(realPath, "utf-8");
3322
+ source = readSourceMemoized(memo, realPath);
2778
3323
  } catch {
2779
3324
  return { routes: {}, searchSchemas: {} };
2780
3325
  }
@@ -2782,7 +3327,11 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2782
3327
  if (inlineBlock) {
2783
3328
  block = inlineBlock;
2784
3329
  } else if (variableName) {
2785
- const extracted = extractUrlsBlockForVariable(source, variableName);
3330
+ const extracted = extractUrlsBlockForVariable(
3331
+ source,
3332
+ variableName,
3333
+ parseBlock(memo, source)
3334
+ );
2786
3335
  if (!extracted) return { routes: {}, searchSchemas: {} };
2787
3336
  block = extracted;
2788
3337
  } else {
@@ -2795,7 +3344,8 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2795
3344
  realPath,
2796
3345
  visited,
2797
3346
  searchSchemas,
2798
- diagnosticsOut
3347
+ diagnosticsOut,
3348
+ memo
2799
3349
  );
2800
3350
  visited.delete(key);
2801
3351
  return { routes, searchSchemas };
@@ -2810,7 +3360,7 @@ import {
2810
3360
  readdirSync
2811
3361
  } from "node:fs";
2812
3362
  import {
2813
- join,
3363
+ join as join2,
2814
3364
  dirname as dirname2,
2815
3365
  resolve as resolve3,
2816
3366
  sep,
@@ -2833,7 +3383,10 @@ var ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
2833
3383
  function isRoutableSourceFile(name) {
2834
3384
  return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
2835
3385
  }
2836
- function findRouterFilesRecursive(dir, filter, results) {
3386
+ function isExcludedScanDir(name) {
3387
+ return name === "node_modules" || name === "dist" || name === "coverage" || name === "__tests__" || name === "__mocks__" || name.startsWith(".");
3388
+ }
3389
+ function findCallSiteFilesRecursive(dir, pattern, patternG, stopAtMatchDir, filter, results) {
2837
3390
  let entries;
2838
3391
  try {
2839
3392
  entries = readdirSync(dir, { withFileTypes: true });
@@ -2844,32 +3397,36 @@ function findRouterFilesRecursive(dir, filter, results) {
2844
3397
  return;
2845
3398
  }
2846
3399
  const childDirs = [];
2847
- const routerFilesInDir = [];
3400
+ const matchesInDir = [];
2848
3401
  for (const entry of entries) {
2849
- const fullPath = join(dir, entry.name);
3402
+ const fullPath = join2(dir, entry.name);
2850
3403
  if (entry.isDirectory()) {
2851
- if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === "__tests__" || entry.name === "__mocks__" || entry.name.startsWith("."))
2852
- continue;
2853
- childDirs.push(fullPath);
3404
+ if (!isExcludedScanDir(entry.name)) childDirs.push(fullPath);
2854
3405
  continue;
2855
3406
  }
2856
3407
  if (!isRoutableSourceFile(entry.name)) continue;
2857
3408
  if (filter && !filter(fullPath)) continue;
2858
3409
  try {
2859
3410
  const source = readFileSync2(fullPath, "utf-8");
2860
- if (ROUTER_CALL_PATTERN.test(source) && firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0) {
2861
- routerFilesInDir.push(fullPath);
3411
+ if (pattern.test(source) && firstCodeMatchIndex(source, patternG) >= 0) {
3412
+ matchesInDir.push(fullPath);
2862
3413
  }
2863
3414
  } catch {
2864
3415
  continue;
2865
3416
  }
2866
3417
  }
2867
- if (routerFilesInDir.length > 0) {
2868
- results.push(...routerFilesInDir);
2869
- return;
2870
- }
2871
- for (const childDir of childDirs) {
2872
- findRouterFilesRecursive(childDir, filter, results);
3418
+ results.push(...matchesInDir);
3419
+ if (!stopAtMatchDir || matchesInDir.length === 0) {
3420
+ for (const childDir of childDirs) {
3421
+ findCallSiteFilesRecursive(
3422
+ childDir,
3423
+ pattern,
3424
+ patternG,
3425
+ stopAtMatchDir,
3426
+ filter,
3427
+ results
3428
+ );
3429
+ }
2873
3430
  }
2874
3431
  }
2875
3432
  function findNestedRouterConflict(routerFiles) {
@@ -3006,7 +3563,7 @@ function applyBasenameToRoutes(result, basename2) {
3006
3563
  }
3007
3564
  function genFileTsPath(sourceFile) {
3008
3565
  const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
3009
- return join(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
3566
+ return join2(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
3010
3567
  }
3011
3568
  function resolveSearchSchemas(publicRouteNames, runtimeSchemas, sourceFile) {
3012
3569
  if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
@@ -3067,12 +3624,33 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
3067
3624
  }
3068
3625
  function findRouterFiles(root, filter) {
3069
3626
  const result = [];
3070
- findRouterFilesRecursive(root, filter, result);
3627
+ findCallSiteFilesRecursive(
3628
+ root,
3629
+ ROUTER_CALL_PATTERN,
3630
+ ROUTER_CALL_PATTERN_G,
3631
+ true,
3632
+ filter,
3633
+ result
3634
+ );
3635
+ return result;
3636
+ }
3637
+ var HOST_ROUTER_CALL_PATTERN = /\bcreateHostRouter\s*[<(]/;
3638
+ var HOST_ROUTER_CALL_PATTERN_G = /\bcreateHostRouter\s*[<(]/g;
3639
+ function findHostRouterFiles(root, filter) {
3640
+ const result = [];
3641
+ findCallSiteFilesRecursive(
3642
+ root,
3643
+ HOST_ROUTER_CALL_PATTERN,
3644
+ HOST_ROUTER_CALL_PATTERN_G,
3645
+ false,
3646
+ filter,
3647
+ result
3648
+ );
3071
3649
  return result;
3072
3650
  }
3073
3651
  function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
3074
3652
  try {
3075
- const oldCombinedPath = join(root, "src", "named-routes.gen.ts");
3653
+ const oldCombinedPath = join2(root, "src", "named-routes.gen.ts");
3076
3654
  if (existsSync3(oldCombinedPath)) {
3077
3655
  unlinkSync(oldCombinedPath);
3078
3656
  console.log(
@@ -3133,7 +3711,28 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
3133
3711
  }
3134
3712
 
3135
3713
  // src/vite/plugins/version-plugin.ts
3714
+ import { parseAst as parseAst4 } from "vite";
3715
+
3716
+ // src/vite/utils/directive-prologue.ts
3136
3717
  import { parseAst as parseAst3 } from "vite";
3718
+ function hasUseClientDirective(source) {
3719
+ let program;
3720
+ try {
3721
+ program = parseAst3(source, { lang: "tsx" });
3722
+ } catch {
3723
+ return false;
3724
+ }
3725
+ for (const node of program.body ?? []) {
3726
+ if (node?.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
3727
+ if (node.expression.value === "use client") return true;
3728
+ continue;
3729
+ }
3730
+ break;
3731
+ }
3732
+ return false;
3733
+ }
3734
+
3735
+ // src/vite/plugins/version-plugin.ts
3137
3736
  function isCodeModule(id) {
3138
3737
  return /\.(tsx?|jsx?)($|\?)/.test(id);
3139
3738
  }
@@ -3141,23 +3740,13 @@ function normalizeModuleId(id) {
3141
3740
  return id.split("?", 1)[0];
3142
3741
  }
3143
3742
  function getClientModuleSignature(source) {
3743
+ if (!hasUseClientDirective(source)) return void 0;
3144
3744
  let program;
3145
3745
  try {
3146
- program = parseAst3(source, { lang: "tsx" });
3746
+ program = parseAst4(source, { lang: "tsx" });
3147
3747
  } catch {
3148
3748
  return void 0;
3149
3749
  }
3150
- let isUseClient = false;
3151
- for (const node of program.body ?? []) {
3152
- if (node?.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
3153
- if (node.expression.value === "use client") {
3154
- isUseClient = true;
3155
- }
3156
- continue;
3157
- }
3158
- break;
3159
- }
3160
- if (!isUseClient) return void 0;
3161
3750
  const exports = /* @__PURE__ */ new Set();
3162
3751
  let hasDefault = false;
3163
3752
  let hasExportAll = false;
@@ -3282,7 +3871,6 @@ function createVersionPlugin() {
3282
3871
  }
3283
3872
  return null;
3284
3873
  },
3285
- // Track RSC module changes and update version
3286
3874
  async hotUpdate(ctx) {
3287
3875
  if (!isDev) return;
3288
3876
  const isRscModule = this.environment?.name === "rsc";
@@ -3331,6 +3919,7 @@ function isViteDepCachePath(filePath, cacheDir) {
3331
3919
 
3332
3920
  // src/vite/utils/shared-utils.ts
3333
3921
  import * as Vite from "vite";
3922
+ import { isAbsolute, resolve as resolve4 } from "node:path";
3334
3923
 
3335
3924
  // src/vite/plugins/performance-tracks.ts
3336
3925
  import { readFile } from "node:fs/promises";
@@ -3359,9 +3948,6 @@ function performanceTracksOptimizeDepsPlugin() {
3359
3948
  const RSDW_CLIENT_RE = /react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
3360
3949
  return {
3361
3950
  name: "@rangojs/router:performance-tracks-optimize-deps",
3362
- // Vite 8 optimizes deps with Rolldown (Rollup-style plugin pipeline), so the
3363
- // pre-bundled RSDW client is patched via load() rather than esbuild's onLoad.
3364
- // Returning code overrides Rolldown's default filesystem read for the module.
3365
3951
  async load(id) {
3366
3952
  const cleanId = id.split("?")[0] ?? id;
3367
3953
  if (!RSDW_CLIENT_RE.test(cleanId)) return null;
@@ -3416,6 +4002,21 @@ var versionRolldownPlugin = {
3416
4002
  var sharedRolldownOptions = {
3417
4003
  plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()]
3418
4004
  };
4005
+ function normalizeHostRouterEntry(rawInput, root, exists) {
4006
+ const raw = rawInput.replaceAll("\\", "/");
4007
+ const isRelative = raw.startsWith("./") || raw.startsWith("../");
4008
+ const isRooted = raw.startsWith("/") || isAbsolute(rawInput);
4009
+ if (isRooted) return raw;
4010
+ if (isRelative) {
4011
+ if (!exists(resolve4(root, raw))) {
4012
+ throw new Error(
4013
+ `[rango] hostRouter entry not found: "${rawInput}" (resolved under ${root}). Point it at your createHostRouter() module, e.g. rango({ hostRouter: "./src/worker.rsc.tsx" }).`
4014
+ );
4015
+ }
4016
+ return raw;
4017
+ }
4018
+ return exists(resolve4(root, raw)) ? "./" + raw : raw;
4019
+ }
3419
4020
  function createVirtualEntriesPlugin(entries, routerPathRef) {
3420
4021
  const virtualModules = {};
3421
4022
  if (entries.client === VIRTUAL_IDS.browser) {
@@ -3449,7 +4050,7 @@ function createVirtualEntriesPlugin(entries, routerPathRef) {
3449
4050
  if (virtualId === VIRTUAL_IDS.rsc && routerPathRef?.path) {
3450
4051
  const raw = routerPathRef.path.startsWith(".") ? "/" + routerPathRef.path.slice(2) : routerPathRef.path;
3451
4052
  const absoluteRouterPath = raw.replaceAll("\\", "/");
3452
- return getVirtualEntryRSC(absoluteRouterPath);
4053
+ return routerPathRef.kind === "host" ? getVirtualEntryRSCHost(absoluteRouterPath) : getVirtualEntryRSC(absoluteRouterPath);
3453
4054
  }
3454
4055
  }
3455
4056
  return null;
@@ -3504,7 +4105,7 @@ function getManualChunks(id) {
3504
4105
  }
3505
4106
 
3506
4107
  // src/vite/plugins/client-ref-hashing.ts
3507
- import { relative } from "node:path";
4108
+ import { relative as relative2 } from "node:path";
3508
4109
  import { createHash as createHash2 } from "node:crypto";
3509
4110
  var debug7 = createRangoDebugger(NS.transform);
3510
4111
  var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
@@ -3521,10 +4122,10 @@ function computeProductionHash(projectRoot, refKey) {
3521
4122
  const absPath = decodeURIComponent(
3522
4123
  refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3523
4124
  );
3524
- toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
4125
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3525
4126
  } else if (refKey.startsWith(FS_PREFIX)) {
3526
4127
  const absPath = refKey.slice(FS_PREFIX.length - 1);
3527
- toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
4128
+ toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3528
4129
  } else if (refKey.startsWith("/")) {
3529
4130
  toHash = refKey.slice(1);
3530
4131
  } else {
@@ -3551,7 +4152,6 @@ function hashClientRefs(projectRoot) {
3551
4152
  const counter = createCounter(debug7, "hash-client-refs");
3552
4153
  return {
3553
4154
  name: "@rangojs/router:hash-client-refs",
3554
- // Run after the RSC plugin's transform (default enforce is normal)
3555
4155
  enforce: "post",
3556
4156
  applyToEnvironment(env) {
3557
4157
  return env.name === "rsc";
@@ -3602,31 +4202,246 @@ function directoryClientChunks(meta, ctx) {
3602
4202
  if (isSharedRuntime(meta)) {
3603
4203
  return void 0;
3604
4204
  }
3605
- if (ctx?.fallbackRefs.size && ctx.fallbackRefs.has(hashRefKey(meta.normalizedId))) {
3606
- debugChunks?.("fallback %s -> app-fallback", meta.normalizedId);
3607
- return "app-fallback";
4205
+ if (ctx?.fallbackRefs.size && ctx.fallbackRefs.has(hashRefKey(meta.normalizedId))) {
4206
+ debugChunks?.("fallback %s -> app-fallback", meta.normalizedId);
4207
+ return "app-fallback";
4208
+ }
4209
+ const segments = meta.normalizedId.split("/").filter(Boolean);
4210
+ const dirCount = segments.length - 1;
4211
+ if (dirCount >= 1) {
4212
+ for (let i = 0; i < dirCount - 1; i++) {
4213
+ if (ROUTE_ROOT_DIRS.has(segments[i].toLowerCase())) {
4214
+ const group = `app-${sanitizeGroup(segments[i + 1])}`;
4215
+ debugChunks?.("split %s -> %s", meta.normalizedId, group);
4216
+ return group;
4217
+ }
4218
+ }
4219
+ }
4220
+ debugChunks?.(
4221
+ "shared %s (no route-root marker; inherits default grouping)",
4222
+ meta.normalizedId
4223
+ );
4224
+ return void 0;
4225
+ }
4226
+ function resolveClientChunks(option, ctx) {
4227
+ if (!option) return void 0;
4228
+ if (option === true) return (meta) => directoryClientChunks(meta, ctx);
4229
+ return option;
4230
+ }
4231
+
4232
+ // src/vite/plugins/vercel-output.ts
4233
+ import { rm, mkdir, cp, writeFile } from "node:fs/promises";
4234
+ import { existsSync as existsSync4 } from "node:fs";
4235
+ import { resolve as resolve5, join as join3 } from "node:path";
4236
+ import { createRequire as createRequire2 } from "node:module";
4237
+ import { pathToFileURL } from "node:url";
4238
+ var LAUNCHER_SOURCE = `import { toNodeHandler } from "srvx/node";
4239
+ import { waitUntil } from "@vercel/functions";
4240
+ import rscHandler from "./rsc/index.js";
4241
+
4242
+ // The Vercel Node launcher invokes a Node (req, res) handler, not a Web fetch
4243
+ // handler. srvx's toNodeHandler bridges the Rango Web fetch handler and pipes
4244
+ // the streamed Response to the Node response (set supportsResponseStreaming).
4245
+ const onVercel = Boolean(process.env.VERCEL);
4246
+
4247
+ const fetchHandler = (request) =>
4248
+ rscHandler(request, {
4249
+ env: process.env,
4250
+ // Forward Vercel's waitUntil so cache writes / revalidation run off the
4251
+ // response path. Omitted off-platform so those writes settle inline.
4252
+ ctx: onVercel ? { waitUntil } : undefined,
4253
+ });
4254
+
4255
+ export default toNodeHandler(fetchHandler);
4256
+ `;
4257
+ function assertVercelNodeRuntime(runtime) {
4258
+ if (runtime != null && !runtime.startsWith("nodejs")) {
4259
+ throw new Error(
4260
+ `[rango] preset "vercel": runtime "${runtime}" is not supported. This preset emits a Node serverless function; use a "nodejs*" runtime (default "nodejs22.x"). The Edge runtime is not supported.`
4261
+ );
4262
+ }
4263
+ }
4264
+ function assertValidVercelFunctionName(functionName) {
4265
+ if (!/^[A-Za-z0-9._-]+$/.test(functionName)) {
4266
+ throw new Error(
4267
+ `[rango] preset "vercel": invalid functionName ${JSON.stringify(
4268
+ functionName
4269
+ )}. Use letters, digits, ".", "_" or "-" only (it becomes the .func directory and the routing dest).`
4270
+ );
4271
+ }
4272
+ }
4273
+ function buildVercelVcConfig(vercel) {
4274
+ const vcConfig = {
4275
+ runtime: vercel.runtime ?? "nodejs22.x",
4276
+ handler: "index.mjs",
4277
+ launcherType: "Nodejs",
4278
+ shouldAddHelpers: false,
4279
+ supportsResponseStreaming: true,
4280
+ maxDuration: vercel.maxDuration ?? 30
4281
+ };
4282
+ if (vercel.memory != null) vcConfig.memory = vercel.memory;
4283
+ if (vercel.regions != null) vcConfig.regions = vercel.regions;
4284
+ return vcConfig;
4285
+ }
4286
+ function buildVercelOutputConfig(functionName, assetsDir) {
4287
+ const assetsPrefix = assetsDir.replace(/^\/+|\/+$/g, "");
4288
+ const assetHeaderRoute = assetsPrefix ? [
4289
+ {
4290
+ src: `/${escapeRegExp(assetsPrefix)}/(.*)`,
4291
+ headers: { "cache-control": "public, max-age=31536000, immutable" },
4292
+ continue: true
4293
+ }
4294
+ ] : [];
4295
+ return {
4296
+ version: 3,
4297
+ routes: [
4298
+ ...assetHeaderRoute,
4299
+ { handle: "filesystem" },
4300
+ { src: "/(.*)", dest: `/${functionName}` }
4301
+ ]
4302
+ };
4303
+ }
4304
+ async function assemble(root, options, assetsDir, publicDir) {
4305
+ const vercel = options.vercel ?? {};
4306
+ assertVercelNodeRuntime(vercel.runtime);
4307
+ const assetsPrefix = assetsDir.replace(/^\/+|\/+$/g, "");
4308
+ if (publicDir && assetsPrefix && existsSync4(join3(publicDir, assetsPrefix))) {
4309
+ console.warn(
4310
+ `[rango] preset "vercel": ${join3(publicDir, assetsPrefix)} exists. Files under public/${assetsPrefix}/ share the /${assetsPrefix}/ URL prefix with hashed build assets and will be served with a one-year immutable cache-control header. Move un-hashed public files out of "${assetsPrefix}/".`
4311
+ );
4312
+ }
4313
+ const dist = join3(root, "dist");
4314
+ for (const dir of ["client", "rsc", "ssr"]) {
4315
+ if (!existsSync4(join3(dist, dir))) {
4316
+ throw new Error(
4317
+ `[rango] preset "vercel": missing dist/${dir}. Run the production build first.`
4318
+ );
4319
+ }
4320
+ }
4321
+ const functionName = vercel.functionName ?? "index";
4322
+ assertValidVercelFunctionName(functionName);
4323
+ const out = join3(root, ".vercel", "output");
4324
+ const funcDir = join3(out, "functions", `${functionName}.func`);
4325
+ await rm(out, { recursive: true, force: true });
4326
+ await mkdir(funcDir, { recursive: true });
4327
+ await cp(join3(dist, "client"), join3(out, "static"), { recursive: true });
4328
+ await cp(join3(dist, "rsc"), join3(funcDir, "rsc"), { recursive: true });
4329
+ await cp(join3(dist, "ssr"), join3(funcDir, "ssr"), { recursive: true });
4330
+ if (existsSync4(join3(dist, "static"))) {
4331
+ await cp(join3(dist, "static"), join3(funcDir, "static"), {
4332
+ recursive: true
4333
+ });
4334
+ }
4335
+ const rangoRequire = createRequire2(import.meta.url);
4336
+ let srvxNodePath;
4337
+ try {
4338
+ srvxNodePath = rangoRequire.resolve("srvx/node");
4339
+ } catch {
4340
+ throw new Error(
4341
+ '[rango] preset "vercel" requires "srvx" (a dependency of @rangojs/router). Reinstall dependencies.'
4342
+ );
4343
+ }
4344
+ const appRequire = createRequire2(join3(root, "package.json"));
4345
+ const resolveEsbuildPath = () => {
4346
+ try {
4347
+ const viteRequire = createRequire2(appRequire.resolve("vite"));
4348
+ return viteRequire.resolve("esbuild");
4349
+ } catch {
4350
+ }
4351
+ try {
4352
+ return appRequire.resolve("esbuild");
4353
+ } catch {
4354
+ }
4355
+ return rangoRequire.resolve("esbuild");
4356
+ };
4357
+ let esbuildModule;
4358
+ try {
4359
+ esbuildModule = await import(pathToFileURL(resolveEsbuildPath()).href);
4360
+ } catch {
4361
+ throw new Error(
4362
+ '[rango] preset "vercel" requires "esbuild" to bundle the function launcher. It ships with Vite; reinstall dependencies (or add esbuild to your app dependencies).'
4363
+ );
4364
+ }
4365
+ const esbuildBuild = esbuildModule.build ?? esbuildModule.default?.build;
4366
+ if (typeof esbuildBuild !== "function") {
4367
+ throw new Error('[rango] preset "vercel": could not load esbuild.build.');
3608
4368
  }
3609
- const segments = meta.normalizedId.split("/").filter(Boolean);
3610
- const dirCount = segments.length - 1;
3611
- if (dirCount >= 1) {
3612
- for (let i = 0; i < dirCount - 1; i++) {
3613
- if (ROUTE_ROOT_DIRS.has(segments[i].toLowerCase())) {
3614
- const group = `app-${sanitizeGroup(segments[i + 1])}`;
3615
- debugChunks?.("split %s -> %s", meta.normalizedId, group);
3616
- return group;
3617
- }
4369
+ try {
4370
+ await esbuildBuild({
4371
+ stdin: {
4372
+ contents: LAUNCHER_SOURCE,
4373
+ resolveDir: root,
4374
+ sourcefile: "func-entry.mjs",
4375
+ loader: "js"
4376
+ },
4377
+ outfile: join3(funcDir, "index.mjs"),
4378
+ bundle: true,
4379
+ format: "esm",
4380
+ platform: "node",
4381
+ target: "node18",
4382
+ alias: { "srvx/node": srvxNodePath },
4383
+ plugins: [
4384
+ {
4385
+ name: "external-rsc-entry",
4386
+ setup(b) {
4387
+ b.onResolve({ filter: /^\.\/rsc\/index\.js$/ }, () => ({
4388
+ path: "./rsc/index.js",
4389
+ external: true
4390
+ }));
4391
+ }
4392
+ }
4393
+ ]
4394
+ });
4395
+ } catch (error) {
4396
+ const message = error instanceof Error ? error.message : String(error);
4397
+ if (/@vercel\/functions/.test(message)) {
4398
+ throw new Error(
4399
+ '[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' + message
4400
+ );
3618
4401
  }
4402
+ throw error;
3619
4403
  }
3620
- debugChunks?.(
3621
- "shared %s (no route-root marker; inherits default grouping)",
3622
- meta.normalizedId
4404
+ await writeFile(
4405
+ join3(funcDir, "package.json"),
4406
+ JSON.stringify({ type: "module" }, null, 2) + "\n"
4407
+ );
4408
+ await writeFile(
4409
+ join3(funcDir, ".vc-config.json"),
4410
+ JSON.stringify(buildVercelVcConfig(vercel), null, 2) + "\n"
4411
+ );
4412
+ await writeFile(
4413
+ join3(out, "config.json"),
4414
+ JSON.stringify(buildVercelOutputConfig(functionName, assetsDir), null, 2) + "\n"
4415
+ );
4416
+ console.log(
4417
+ `[rango] assembled .vercel/output (function: ${functionName}.func)`
3623
4418
  );
3624
- return void 0;
3625
4419
  }
3626
- function resolveClientChunks(option, ctx) {
3627
- if (!option) return void 0;
3628
- if (option === true) return (meta) => directoryClientChunks(meta, ctx);
3629
- return option;
4420
+ function createVercelOutputPlugin(options) {
4421
+ let root = process.cwd();
4422
+ let isBuild = false;
4423
+ let assetsDir = "assets";
4424
+ let publicDir = "";
4425
+ return {
4426
+ name: "@rangojs/router:vercel-output",
4427
+ configResolved(config) {
4428
+ root = resolve5(config.root);
4429
+ isBuild = config.command === "build";
4430
+ assetsDir = config.environments?.client?.build?.assetsDir ?? config.build?.assetsDir ?? "assets";
4431
+ publicDir = config.publicDir || "";
4432
+ },
4433
+ // buildApp runs once after the whole multi-environment build (rsc, client,
4434
+ // ssr), so dist/ is complete here. closeBundle is unusable for this: it
4435
+ // fires per environment, and twice for ssr (the server-reference scan and
4436
+ // the real build), so it would run before dist/client exists.
4437
+ buildApp: {
4438
+ order: "post",
4439
+ async handler() {
4440
+ if (!isBuild) return;
4441
+ await assemble(root, options, assetsDir, publicDir);
4442
+ }
4443
+ }
4444
+ };
3630
4445
  }
3631
4446
 
3632
4447
  // src/vite/utils/banner.ts
@@ -3656,7 +4471,7 @@ ${dim} ${reset}${bold}\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550$
3656
4471
  }
3657
4472
 
3658
4473
  // src/vite/plugins/version-injector.ts
3659
- import { resolve as resolve4 } from "node:path";
4474
+ import { resolve as resolve6 } from "node:path";
3660
4475
  import * as Vite2 from "vite";
3661
4476
  function createVersionInjectorPlugin(rscEntryPath) {
3662
4477
  let resolvedEntryPath = "";
@@ -3667,7 +4482,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
3667
4482
  let entryPath = rscEntryPath;
3668
4483
  if (!entryPath) entryPath = resolveRscEntryFromConfig(config);
3669
4484
  if (entryPath) {
3670
- resolvedEntryPath = resolve4(config.root, entryPath);
4485
+ resolvedEntryPath = resolve6(config.root, entryPath);
3671
4486
  }
3672
4487
  },
3673
4488
  transform(code, id) {
@@ -3677,9 +4492,9 @@ function createVersionInjectorPlugin(rscEntryPath) {
3677
4492
  if (normalizedId !== normalizedEntry) {
3678
4493
  return null;
3679
4494
  }
3680
- const prepend = [
3681
- `import "virtual:rsc-router/routes-manifest";`
3682
- ];
4495
+ const prepend = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
4496
+ (id2) => `import "${id2}";`
4497
+ );
3683
4498
  let newCode = code;
3684
4499
  const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
3685
4500
  if (needsVersion) {
@@ -3715,13 +4530,17 @@ function createVersionInjectorPlugin(rscEntryPath) {
3715
4530
  // src/vite/plugins/cjs-to-esm.ts
3716
4531
  var debug8 = createRangoDebugger(NS.transform);
3717
4532
  function createCjsToEsmPlugin() {
4533
+ let isProduction = false;
3718
4534
  return {
3719
4535
  name: "@rangojs/router:cjs-to-esm",
3720
4536
  enforce: "pre",
4537
+ configResolved(config) {
4538
+ isProduction = config.isProduction;
4539
+ },
3721
4540
  transform(code, id) {
3722
4541
  const cleanId = id.split("?")[0].replaceAll("\\", "/");
3723
4542
  if (cleanId.includes("vendor/react-server-dom/client.browser.js")) {
3724
- const isProd = process.env.NODE_ENV === "production";
4543
+ const isProd = isProduction;
3725
4544
  const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
3726
4545
  debug8?.("cjs-to-esm entry redirect %s", id);
3727
4546
  return {
@@ -3772,10 +4591,39 @@ function createCjsToEsmPlugin() {
3772
4591
 
3773
4592
  // src/vite/router-discovery.ts
3774
4593
  import { createServer as createViteServer } from "vite";
3775
- import { resolve as resolve8 } from "node:path";
4594
+ import { resolve as resolve10 } from "node:path";
3776
4595
  import { readFileSync as readFileSync6 } from "node:fs";
3777
- import { createRequire as createRequire2, register } from "node:module";
3778
- import { pathToFileURL } from "node:url";
4596
+ import { createRequire as createRequire3, register } from "node:module";
4597
+ import { pathToFileURL as pathToFileURL2 } from "node:url";
4598
+
4599
+ // src/vite/inject-client-debug.ts
4600
+ function isRouterInternalDebugId(id) {
4601
+ if (!id.includes("internal-debug")) return false;
4602
+ const norm = id.replace(/\\/g, "/");
4603
+ return /\/internal-debug\.[cm]?[jt]sx?(\?|$)/.test(norm) && (norm.includes("/@rangojs/router/") || norm.includes("/packages/rangojs-router/"));
4604
+ }
4605
+ function injectClientDebugFlag(id) {
4606
+ if (!isRouterInternalDebugId(id)) return null;
4607
+ return {
4608
+ code: `export const INTERNAL_RANGO_DEBUG = ${!!process.env.INTERNAL_RANGO_DEBUG};
4609
+ `,
4610
+ map: null
4611
+ };
4612
+ }
4613
+ function internalDebugNoCacheMiddleware() {
4614
+ return function rangoInternalDebugNoCache(req, res, next) {
4615
+ if (req.url && isRouterInternalDebugId(req.url)) {
4616
+ const setHeader = res.setHeader.bind(res);
4617
+ res.setHeader = (name, value) => {
4618
+ return setHeader(
4619
+ name,
4620
+ name.toLowerCase() === "cache-control" ? "no-cache" : value
4621
+ );
4622
+ };
4623
+ }
4624
+ next();
4625
+ };
4626
+ }
3779
4627
 
3780
4628
  // src/vite/plugins/virtual-stub-plugin.ts
3781
4629
  function createVirtualStubPlugin() {
@@ -4060,7 +4908,93 @@ function checkSelfGenWrite(state, filePath, consume) {
4060
4908
  }
4061
4909
  }
4062
4910
 
4063
- // src/vite/utils/manifest-utils.ts
4911
+ // src/router/logging.ts
4912
+ import { AsyncLocalStorage } from "node:async_hooks";
4913
+
4914
+ // src/internal-debug.ts
4915
+ var INTERNAL_RANGO_DEBUG = typeof process !== "undefined" && Boolean(process.env?.INTERNAL_RANGO_DEBUG);
4916
+
4917
+ // src/router/logging.ts
4918
+ var routerLogContext = new AsyncLocalStorage();
4919
+
4920
+ // src/router/url-params.ts
4921
+ var PATH_SAFE_ESCAPES = {
4922
+ "%3A": ":",
4923
+ "%40": "@",
4924
+ "%24": "$",
4925
+ "%26": "&",
4926
+ "%2B": "+",
4927
+ "%2C": ",",
4928
+ "%3B": ";",
4929
+ "%3D": "="
4930
+ };
4931
+ function encodePathSegment(value) {
4932
+ return encodeURIComponent(value).replace(
4933
+ /%(?:3A|40|24|26|2B|2C|3B|3D)/gi,
4934
+ (match) => PATH_SAFE_ESCAPES[match.toUpperCase()] ?? match
4935
+ );
4936
+ }
4937
+ function encodePathRemainder(value, encode = encodePathSegment) {
4938
+ return value.split("/").map(encode).join("/");
4939
+ }
4940
+
4941
+ // src/router/parse-pattern.ts
4942
+ function parsePattern(pattern) {
4943
+ const segments = [];
4944
+ const segmentRegex = /\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([+*])?([^/]*)|(\*)|([^/]+))/g;
4945
+ let match;
4946
+ while ((match = segmentRegex.exec(pattern)) !== null) {
4947
+ const [
4948
+ ,
4949
+ ,
4950
+ paramName,
4951
+ ,
4952
+ constraint,
4953
+ optional,
4954
+ repeat,
4955
+ suffix,
4956
+ wildcard,
4957
+ staticText
4958
+ ] = match;
4959
+ if (wildcard) {
4960
+ segments.push({ type: "wildcard", value: "*", optional: false });
4961
+ } else if (paramName) {
4962
+ if (repeat && !suffix && optional !== "?" && !constraint) {
4963
+ segments.push({
4964
+ type: "wildcard",
4965
+ value: paramName,
4966
+ optional: false,
4967
+ oneOrMore: repeat === "+"
4968
+ });
4969
+ } else {
4970
+ segments.push({
4971
+ type: "param",
4972
+ value: paramName,
4973
+ optional: optional === "?",
4974
+ constraint: constraint ? constraint.split("|") : void 0,
4975
+ // Fold a non-modifier `+`/`*` back into the literal suffix.
4976
+ suffix: (repeat ?? "") + (suffix ?? "") || void 0
4977
+ });
4978
+ }
4979
+ } else if (staticText) {
4980
+ segments.push({ type: "static", value: staticText, optional: false });
4981
+ }
4982
+ }
4983
+ for (let i = 0; i < segments.length - 1; i++) {
4984
+ const s = segments[i];
4985
+ if (s.type === "wildcard" && s.value !== "*") {
4986
+ segments[i] = {
4987
+ type: "param",
4988
+ value: s.value,
4989
+ optional: false,
4990
+ suffix: s.oneOrMore ? "+" : "*"
4991
+ };
4992
+ }
4993
+ }
4994
+ return segments;
4995
+ }
4996
+
4997
+ // src/build/prefix-tree-utils.ts
4064
4998
  function flattenLeafEntries(prefixTree, routeManifest, result) {
4065
4999
  function visit(node, ancestorStaticPrefixes) {
4066
5000
  const children = node.children || {};
@@ -4101,6 +5035,226 @@ function buildRouteToStaticPrefix(prefixTree, result) {
4101
5035
  visit(node);
4102
5036
  }
4103
5037
  }
5038
+
5039
+ // src/build/route-trie.ts
5040
+ function buildRouteTrie(routeManifest, routeAncestry, routeToStaticPrefix, routeTrailingSlash, prerenderRouteNames, passthroughRouteNames, responseTypeRoutes) {
5041
+ const root = {};
5042
+ for (const [routeName, pattern] of Object.entries(routeManifest)) {
5043
+ const ancestry = routeAncestry[routeName] || [];
5044
+ const staticPrefix = routeToStaticPrefix[routeName] || "";
5045
+ const trailingSlash = routeTrailingSlash?.[routeName];
5046
+ const responseType = responseTypeRoutes?.[routeName];
5047
+ const hasTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
5048
+ const normalizedPattern = hasTrailingSlash ? pattern.slice(0, -1) : pattern;
5049
+ const segments = parsePattern(normalizedPattern);
5050
+ insertRoute(root, segments, 0, {
5051
+ n: routeName,
5052
+ sp: staticPrefix,
5053
+ a: ancestry,
5054
+ ...trailingSlash ? { ts: trailingSlash } : {},
5055
+ ...prerenderRouteNames?.has(routeName) ? { pr: true } : {},
5056
+ ...passthroughRouteNames?.has(routeName) ? { pt: true } : {},
5057
+ ...responseType ? { rt: responseType } : {}
5058
+ });
5059
+ }
5060
+ sortSuffixParams(root);
5061
+ return root;
5062
+ }
5063
+ function sortSuffixParams(node) {
5064
+ if (node.xp) {
5065
+ const sorted = {};
5066
+ for (const suffix of Object.keys(node.xp).sort(
5067
+ (a, b) => b.length - a.length
5068
+ )) {
5069
+ sorted[suffix] = node.xp[suffix];
5070
+ }
5071
+ node.xp = sorted;
5072
+ for (const child of Object.values(node.xp)) {
5073
+ sortSuffixParams(child.c);
5074
+ }
5075
+ }
5076
+ if (node.s) {
5077
+ for (const child of Object.values(node.s)) {
5078
+ sortSuffixParams(child);
5079
+ }
5080
+ }
5081
+ if (node.p) {
5082
+ sortSuffixParams(node.p.c);
5083
+ }
5084
+ }
5085
+ function buildPerRouterTrie(manifest) {
5086
+ const ancestry = manifest._routeAncestry;
5087
+ if (!ancestry || Object.keys(ancestry).length === 0) {
5088
+ return null;
5089
+ }
5090
+ const routeToStaticPrefix = {};
5091
+ for (const name of Object.keys(manifest.routeManifest)) {
5092
+ routeToStaticPrefix[name] = "";
5093
+ }
5094
+ if (manifest.prefixTree) {
5095
+ buildRouteToStaticPrefix(manifest.prefixTree, routeToStaticPrefix);
5096
+ }
5097
+ return buildRouteTrie(
5098
+ manifest.routeManifest,
5099
+ ancestry,
5100
+ routeToStaticPrefix,
5101
+ manifest.routeTrailingSlash,
5102
+ manifest.prerenderRoutes ? new Set(manifest.prerenderRoutes) : void 0,
5103
+ manifest.passthroughRoutes ? new Set(manifest.passthroughRoutes) : void 0,
5104
+ manifest.responseTypeRoutes
5105
+ );
5106
+ }
5107
+ function insertRoute(node, segments, index, leaf) {
5108
+ const constraints = {};
5109
+ for (const seg of segments) {
5110
+ if (seg.type === "param") {
5111
+ if (seg.constraint) {
5112
+ constraints[seg.value] = seg.constraint;
5113
+ }
5114
+ }
5115
+ }
5116
+ const leafBase = {
5117
+ ...leaf,
5118
+ ...Object.keys(constraints).length > 0 ? { cv: constraints } : {}
5119
+ };
5120
+ insertSegments(node, segments, index, leafBase, []);
5121
+ }
5122
+ function toVariant(leaf, responseType) {
5123
+ return leaf.pa ? { routeKey: leaf.n, responseType, pa: leaf.pa } : { routeKey: leaf.n, responseType };
5124
+ }
5125
+ function mergeLeaves(existing, leaf) {
5126
+ if (!existing) return leaf;
5127
+ if (existing.rt && leaf.rt) {
5128
+ const merged = leaf;
5129
+ merged.nv = existing.nv || [];
5130
+ merged.nv.push(toVariant(existing, existing.rt));
5131
+ return merged;
5132
+ }
5133
+ if (leaf.rt && !existing.rt) {
5134
+ if (!existing.nv) {
5135
+ existing.nv = [];
5136
+ existing.rf = true;
5137
+ }
5138
+ existing.nv.push(toVariant(leaf, leaf.rt));
5139
+ return existing;
5140
+ }
5141
+ if (!leaf.rt && existing.rt) {
5142
+ if (!leaf.nv) leaf.nv = [];
5143
+ if (existing.nv) leaf.nv.push(...existing.nv);
5144
+ leaf.nv.push(toVariant(existing, existing.rt));
5145
+ return leaf;
5146
+ }
5147
+ return leaf;
5148
+ }
5149
+ function mergeLeaf(node, leaf) {
5150
+ node.r = mergeLeaves(node.r, leaf);
5151
+ }
5152
+ function buildLeaf(leafBase, paramNames) {
5153
+ return paramNames.length > 0 ? { ...leafBase, pa: [...paramNames] } : { ...leafBase };
5154
+ }
5155
+ function insertSegments(node, segments, index, leafBase, paramNames) {
5156
+ if (index >= segments.length) {
5157
+ mergeLeaf(node, buildLeaf(leafBase, paramNames));
5158
+ return;
5159
+ }
5160
+ const segment = segments[index];
5161
+ if (segment.type === "static") {
5162
+ if (!node.s) node.s = {};
5163
+ if (!node.s[segment.value]) node.s[segment.value] = {};
5164
+ insertSegments(
5165
+ node.s[segment.value],
5166
+ segments,
5167
+ index + 1,
5168
+ leafBase,
5169
+ paramNames
5170
+ );
5171
+ } else if (segment.type === "param") {
5172
+ if (segment.optional) {
5173
+ insertSegments(node, segments, index + 1, leafBase, paramNames);
5174
+ }
5175
+ if (segment.suffix) {
5176
+ if (!node.xp) node.xp = {};
5177
+ if (!node.xp[segment.suffix]) {
5178
+ node.xp[segment.suffix] = { n: segment.value, c: {} };
5179
+ }
5180
+ insertSegments(node.xp[segment.suffix].c, segments, index + 1, leafBase, [
5181
+ ...paramNames,
5182
+ segment.value
5183
+ ]);
5184
+ } else {
5185
+ if (!node.p) {
5186
+ node.p = { n: segment.value, c: {} };
5187
+ }
5188
+ insertSegments(node.p.c, segments, index + 1, leafBase, [
5189
+ ...paramNames,
5190
+ segment.value
5191
+ ]);
5192
+ }
5193
+ } else if (segment.type === "wildcard") {
5194
+ const wildLeaf = {
5195
+ ...buildLeaf(leafBase, paramNames),
5196
+ pn: segment.value,
5197
+ ...segment.oneOrMore ? { w1: true } : {}
5198
+ };
5199
+ const existing = node.w;
5200
+ const canMerge = existing === void 0 || Boolean(existing.rt) || Boolean(wildLeaf.rt) || existing.pn === wildLeaf.pn && Boolean(existing.w1) === Boolean(wildLeaf.w1);
5201
+ if (canMerge) {
5202
+ const merged = mergeLeaves(
5203
+ existing ? { ...existing } : void 0,
5204
+ wildLeaf
5205
+ );
5206
+ node.w = merged;
5207
+ }
5208
+ }
5209
+ }
5210
+
5211
+ // src/build/collect-fallback-refs.ts
5212
+ var CLIENT_REF = /* @__PURE__ */ Symbol.for("react.client.reference");
5213
+ var MAX_DEPTH = 40;
5214
+ var SYNTHETIC_PROPS = {
5215
+ error: new Error("rango: build-time fallback-chunk discovery"),
5216
+ reset: () => {
5217
+ },
5218
+ pathname: "/",
5219
+ info: { componentStack: "" }
5220
+ };
5221
+ function isReactNodeLike(v) {
5222
+ return Array.isArray(v) || typeof v === "object" && v !== null && "$$typeof" in v;
5223
+ }
5224
+ function walkElementTree(node, report, depth) {
5225
+ if (node == null || depth > MAX_DEPTH) return;
5226
+ if (Array.isArray(node)) {
5227
+ for (const child of node) walkElementTree(child, report, depth + 1);
5228
+ return;
5229
+ }
5230
+ if (typeof node !== "object") return;
5231
+ const el = node;
5232
+ const type = el.type;
5233
+ if (type?.$$typeof === CLIENT_REF && typeof type.$$id === "string") {
5234
+ report(type.$$id.split("#")[0]);
5235
+ }
5236
+ const props = el.props;
5237
+ if (props && typeof props === "object") {
5238
+ walkElementTree(props.children, report, depth + 1);
5239
+ for (const key in props) {
5240
+ if (key === "children") continue;
5241
+ const value = props[key];
5242
+ if (isReactNodeLike(value)) walkElementTree(value, report, depth + 1);
5243
+ }
5244
+ }
5245
+ }
5246
+ function collectFallbackClientRefs(boundary, report) {
5247
+ try {
5248
+ let node = boundary;
5249
+ if (typeof node === "function") {
5250
+ node = node(SYNTHETIC_PROPS);
5251
+ }
5252
+ walkElementTree(node, report, 0);
5253
+ } catch {
5254
+ }
5255
+ }
5256
+
5257
+ // src/vite/utils/manifest-utils.ts
4104
5258
  function jsonParseExpression(value) {
4105
5259
  const json = JSON.stringify(value);
4106
5260
  const escaped = json.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
@@ -4108,6 +5262,9 @@ function jsonParseExpression(value) {
4108
5262
  }
4109
5263
 
4110
5264
  // src/context-var.ts
5265
+ function hasContextVars(variables) {
5266
+ return Object.keys(variables).length > 0 || Object.getOwnPropertySymbols(variables).length > 0;
5267
+ }
4111
5268
  var NON_CACHEABLE_KEYS = /* @__PURE__ */ Symbol.for(
4112
5269
  "rango:non-cacheable-keys"
4113
5270
  );
@@ -4141,24 +5298,31 @@ function contextSet(variables, keyOrVar, value, options) {
4141
5298
  import { createHash as createHash4 } from "node:crypto";
4142
5299
  import {
4143
5300
  copyFileSync,
4144
- existsSync as existsSync4,
5301
+ existsSync as existsSync5,
4145
5302
  mkdirSync,
4146
5303
  rmSync,
4147
5304
  statSync,
4148
5305
  writeFileSync as writeFileSync2
4149
5306
  } from "node:fs";
4150
- import { resolve as resolve5 } from "node:path";
4151
- function escapeRegExp2(str) {
4152
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4153
- }
5307
+ import { resolve as resolve7 } from "node:path";
4154
5308
  function encodePathParam(value) {
4155
- return String(value).split("/").map((segment) => encodeURIComponent(segment)).join("/");
5309
+ return encodePathRemainder(String(value), encodeURIComponent);
4156
5310
  }
4157
5311
  function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
4158
5312
  let result = pattern;
4159
5313
  let hadOmittedOptional = false;
4160
5314
  for (const [key, value] of Object.entries(params)) {
4161
- const escaped = escapeRegExp2(key);
5315
+ const escaped = escapeRegExp(key);
5316
+ const catchAllRe = new RegExp(`:${escaped}[+*]`);
5317
+ if (catchAllRe.test(result)) {
5318
+ if (value === "") {
5319
+ result = result.replace(catchAllRe, "");
5320
+ hadOmittedOptional = true;
5321
+ } else {
5322
+ result = result.replace(catchAllRe, encodePathRemainder(value, encode));
5323
+ }
5324
+ continue;
5325
+ }
4162
5326
  if (value === "") {
4163
5327
  result = result.replace(
4164
5328
  new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
@@ -4191,13 +5355,24 @@ async function runWithConcurrency(items, concurrency, fn) {
4191
5355
  return;
4192
5356
  }
4193
5357
  let nextIndex = 0;
5358
+ let firstError;
5359
+ let failed = false;
4194
5360
  async function worker() {
4195
- while (nextIndex < items.length) {
5361
+ while (nextIndex < items.length && !failed) {
4196
5362
  const idx = nextIndex++;
4197
- await fn(items[idx]);
5363
+ try {
5364
+ await fn(items[idx]);
5365
+ } catch (err) {
5366
+ if (!failed) {
5367
+ failed = true;
5368
+ firstError = err;
5369
+ }
5370
+ return;
5371
+ }
4198
5372
  }
4199
5373
  }
4200
5374
  await Promise.all(Array.from({ length: limit }, () => worker()));
5375
+ if (failed) throw firstError;
4201
5376
  }
4202
5377
  function groupByConcurrency(entries) {
4203
5378
  const map = /* @__PURE__ */ new Map();
@@ -4244,8 +5419,25 @@ function notifyOnError(registry, error, phase, routeKey, pathname, skipped) {
4244
5419
  break;
4245
5420
  }
4246
5421
  }
5422
+ function resolvePrerenderError(registry, error, onError, label, elapsed, phase, routeKey, pathname) {
5423
+ const isSkip = error?.name === "Skip";
5424
+ if (isSkip || onError === "warn") {
5425
+ if (isSkip) {
5426
+ console.log(`[rango] SKIP ${label} (${elapsed}ms) - ${error.message}`);
5427
+ } else {
5428
+ console.warn(
5429
+ `[rango] WARN ${label} (${elapsed}ms) - render error, not pre-rendered (prerender.onError: "warn"): ${error.message}`
5430
+ );
5431
+ }
5432
+ notifyOnError(registry, error, phase, routeKey, pathname, true);
5433
+ return;
5434
+ }
5435
+ console.error(`[rango] FAIL ${label} (${elapsed}ms) - ${error.message}`);
5436
+ notifyOnError(registry, error, phase, routeKey, pathname);
5437
+ throw error;
5438
+ }
4247
5439
  function getStagedAssetDir(projectRoot) {
4248
- return resolve5(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
5440
+ return resolve7(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
4249
5441
  }
4250
5442
  function resetStagedBuildAssets(projectRoot) {
4251
5443
  rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
@@ -4255,8 +5447,8 @@ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
4255
5447
  mkdirSync(stagedDir, { recursive: true });
4256
5448
  const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
4257
5449
  const fileName = `${prefix}-${contentHash}.js`;
4258
- const filePath = resolve5(stagedDir, fileName);
4259
- if (!existsSync4(filePath)) {
5450
+ const filePath = resolve7(stagedDir, fileName);
5451
+ if (!existsSync5(filePath)) {
4260
5452
  writeFileSync2(filePath, `export default ${exportValue};
4261
5453
  `);
4262
5454
  }
@@ -4264,12 +5456,12 @@ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
4264
5456
  }
4265
5457
  function copyStagedBuildAssets(projectRoot, fileNames) {
4266
5458
  const stagedDir = getStagedAssetDir(projectRoot);
4267
- const distAssetsDir = resolve5(projectRoot, "dist/rsc/assets");
5459
+ const distAssetsDir = resolve7(projectRoot, "dist/rsc/assets");
4268
5460
  mkdirSync(distAssetsDir, { recursive: true });
4269
5461
  let totalBytes = 0;
4270
5462
  for (const fileName of new Set(fileNames)) {
4271
- const stagedPath = resolve5(stagedDir, fileName);
4272
- const distPath = resolve5(distAssetsDir, fileName);
5463
+ const stagedPath = resolve7(stagedDir, fileName);
5464
+ const distPath = resolve7(distAssetsDir, fileName);
4273
5465
  copyFileSync(stagedPath, distPath);
4274
5466
  totalBytes += statSync(stagedPath).size;
4275
5467
  }
@@ -4360,7 +5552,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4360
5552
  (performance.now() - getParamsStart).toFixed(1)
4361
5553
  );
4362
5554
  const concurrency = def.options?.concurrency ?? 1;
4363
- const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
5555
+ const hasBuildVars = hasContextVars(buildVars);
4364
5556
  for (const params of paramsList) {
4365
5557
  let url = substituteRouteParams(
4366
5558
  pattern,
@@ -4445,6 +5637,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4445
5637
  const manifestEntries = {};
4446
5638
  let doneCount = 0;
4447
5639
  let skipCount = 0;
5640
+ const prerenderOnError = state.opts?.prerenderOnError ?? "fail";
4448
5641
  const startTotal = performance.now();
4449
5642
  const groups = groupByConcurrency(entries);
4450
5643
  for (const group of groups) {
@@ -4487,10 +5680,9 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4487
5680
  const interceptKey = `${result.routeName}/${paramHash}/i`;
4488
5681
  const interceptValue = JSON.stringify({
4489
5682
  segments: [...result.segments, ...result.interceptSegments],
4490
- handles: {
4491
- ...result.handles,
4492
- ...result.interceptHandles || {}
4493
- }
5683
+ // interceptHandles is the pre-encoded MERGED (main + intercept)
5684
+ // handle string (the producer merged before encoding).
5685
+ handles: result.interceptHandles ?? ""
4494
5686
  });
4495
5687
  manifestEntries[interceptKey] = stageBuildAssetModule(
4496
5688
  state.projectRoot,
@@ -4505,34 +5697,19 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
4505
5697
  doneCount++;
4506
5698
  break;
4507
5699
  } catch (err) {
4508
- if (err.name === "Skip") {
4509
- const elapsed2 = (performance.now() - startUrl).toFixed(0);
4510
- console.log(
4511
- `[rango] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4512
- );
4513
- skipCount++;
4514
- notifyOnError(
4515
- registry,
4516
- err,
4517
- "prerender",
4518
- entry.routeName,
4519
- entry.urlPath,
4520
- true
4521
- );
4522
- break;
4523
- }
4524
5700
  const elapsed = (performance.now() - startUrl).toFixed(0);
4525
- console.error(
4526
- `[rango] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
4527
- );
4528
- notifyOnError(
5701
+ resolvePrerenderError(
4529
5702
  registry,
4530
5703
  err,
5704
+ prerenderOnError,
5705
+ entry.urlPath.padEnd(40),
5706
+ elapsed,
4531
5707
  "prerender",
4532
5708
  entry.routeName,
4533
5709
  entry.urlPath
4534
5710
  );
4535
- throw err;
5711
+ skipCount++;
5712
+ break;
4536
5713
  }
4537
5714
  }
4538
5715
  }
@@ -4567,6 +5744,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4567
5744
  let staticDone = 0;
4568
5745
  let staticSkip = 0;
4569
5746
  let totalStaticCount = 0;
5747
+ const prerenderOnError = state.opts?.prerenderOnError ?? "fail";
4570
5748
  for (const [, exportNames] of state.resolvedStaticModules) {
4571
5749
  totalStaticCount += exportNames.length;
4572
5750
  }
@@ -4600,7 +5778,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4600
5778
  !state.isBuildMode
4601
5779
  );
4602
5780
  if (result) {
4603
- const hasHandles = Object.keys(result.handles).length > 0;
5781
+ const hasHandles = result.handles !== "";
4604
5782
  const exportValue = hasHandles ? JSON.stringify(result) : JSON.stringify(result.encoded);
4605
5783
  manifestEntries[def.$$id] = stageBuildAssetModule(
4606
5784
  state.projectRoot,
@@ -4614,22 +5792,18 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4614
5792
  break;
4615
5793
  }
4616
5794
  } catch (err) {
4617
- if (err.name === "Skip") {
4618
- const elapsed2 = (performance.now() - startHandler).toFixed(0);
4619
- console.log(
4620
- `[rango] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4621
- );
4622
- staticSkip++;
4623
- notifyOnError(registry, err, "static", void 0, void 0, true);
4624
- handled = true;
4625
- break;
4626
- }
4627
5795
  const elapsed = (performance.now() - startHandler).toFixed(0);
4628
- console.error(
4629
- `[rango] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
5796
+ resolvePrerenderError(
5797
+ registry,
5798
+ err,
5799
+ prerenderOnError,
5800
+ name.padEnd(40),
5801
+ elapsed,
5802
+ "static"
4630
5803
  );
4631
- notifyOnError(registry, err, "static");
4632
- throw err;
5804
+ staticSkip++;
5805
+ handled = true;
5806
+ break;
4633
5807
  }
4634
5808
  }
4635
5809
  if (!handled) {
@@ -4728,6 +5902,28 @@ var DiscoveryError = class _DiscoveryError extends Error {
4728
5902
  Object.setPrototypeOf(this, _DiscoveryError.prototype);
4729
5903
  }
4730
5904
  };
5905
+ function describeDiscoveryFailure(err, opts = {}) {
5906
+ if (err instanceof DiscoveryError && err.caught.length === 0) {
5907
+ const entry = err.entryPath ?? "the router entry";
5908
+ if (opts.reoptimizeObserved) {
5909
+ return {
5910
+ level: "warn",
5911
+ message: `[rango] No routers found while Vite was re-optimizing dependencies on dev boot. This is transient: routes are served per-request and discovery re-runs automatically, so it clears on the next boot. If routes still 404, confirm ${entry} calls createRouter().`
5912
+ };
5913
+ }
5914
+ return {
5915
+ level: "error",
5916
+ message: `${err.message}
5917
+ Ensure ${entry} calls createRouter() at module top level and that the configured router entry path is correct.`
5918
+ };
5919
+ }
5920
+ const e = err;
5921
+ const detail = e?.stack ?? e?.message ?? String(err);
5922
+ return {
5923
+ level: "error",
5924
+ message: `[rango] Router discovery failed: ${detail}`
5925
+ };
5926
+ }
4731
5927
 
4732
5928
  // src/vite/discovery/discover-routers.ts
4733
5929
  var debug10 = createRangoDebugger(NS.discovery);
@@ -4803,8 +5999,8 @@ async function discoverRouters(state, rscEnv) {
4803
5999
  computeProductionHash(state.projectRoot, refKey)
4804
6000
  ) : void 0;
4805
6001
  const collectFromBoundaryNode = (node) => {
4806
- if (collectClientFallbackRef && buildMod.collectFallbackClientRefs) {
4807
- buildMod.collectFallbackClientRefs(node, collectClientFallbackRef);
6002
+ if (collectClientFallbackRef) {
6003
+ collectFallbackClientRefs(node, collectClientFallbackRef);
4808
6004
  }
4809
6005
  };
4810
6006
  const manifestGenStart = debug10 ? performance.now() : 0;
@@ -4812,7 +6008,7 @@ async function discoverRouters(state, rscEnv) {
4812
6008
  if (!router.urlpatterns || !generateManifestFull) {
4813
6009
  continue;
4814
6010
  }
4815
- const manifest = generateManifestFull(
6011
+ const manifest = await generateManifestFull(
4816
6012
  router.urlpatterns,
4817
6013
  routerMountIndex,
4818
6014
  {
@@ -4863,18 +6059,14 @@ async function discoverRouters(state, rscEnv) {
4863
6059
  if (manifest.routeTrailingSlash) {
4864
6060
  Object.assign(mergedRouteTrailingSlash, manifest.routeTrailingSlash);
4865
6061
  }
4866
- flattenLeafEntries(
4867
- manifest.prefixTree,
4868
- manifest.routeManifest,
4869
- newMergedPrecomputedEntries
4870
- );
4871
- newPerRouterManifestDataMap.set(id, manifest.routeManifest);
4872
6062
  const routerPrecomputed = [];
4873
6063
  flattenLeafEntries(
4874
6064
  manifest.prefixTree,
4875
6065
  manifest.routeManifest,
4876
6066
  routerPrecomputed
4877
6067
  );
6068
+ newMergedPrecomputedEntries.push(...routerPrecomputed);
6069
+ newPerRouterManifestDataMap.set(id, manifest.routeManifest);
4878
6070
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
4879
6071
  console.log(
4880
6072
  `[rango] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
@@ -4898,8 +6090,7 @@ async function discoverRouters(state, rscEnv) {
4898
6090
  let newMergedRouteTrie = null;
4899
6091
  const trieStart = debug10 ? performance.now() : 0;
4900
6092
  if (Object.keys(newMergedRouteManifest).length > 0) {
4901
- const buildRouteTrie = buildMod.buildRouteTrie;
4902
- if (buildRouteTrie && mergedRouteAncestry) {
6093
+ if (mergedRouteAncestry) {
4903
6094
  const routeToStaticPrefix = {};
4904
6095
  for (const { manifest } of allManifests) {
4905
6096
  for (const name of Object.keys(manifest.routeManifest)) {
@@ -4937,25 +6128,10 @@ async function discoverRouters(state, rscEnv) {
4937
6128
  mergedResponseTypeRoutes
4938
6129
  );
4939
6130
  for (const { id, manifest } of allManifests) {
4940
- if (!manifest._routeAncestry || Object.keys(manifest._routeAncestry).length === 0)
4941
- continue;
4942
- const perRouterStaticPrefix = {};
4943
- for (const name of Object.keys(manifest.routeManifest)) {
4944
- perRouterStaticPrefix[name] = "";
6131
+ const perRouterTrie = buildPerRouterTrie(manifest);
6132
+ if (perRouterTrie) {
6133
+ newPerRouterTrieMap.set(id, perRouterTrie);
4945
6134
  }
4946
- buildRouteToStaticPrefix(manifest.prefixTree, perRouterStaticPrefix);
4947
- const perRouterPrerenderNames = manifest.prerenderRoutes ? new Set(manifest.prerenderRoutes) : void 0;
4948
- const perRouterPassthroughNames = manifest.passthroughRoutes ? new Set(manifest.passthroughRoutes) : void 0;
4949
- const perRouterTrie = buildRouteTrie(
4950
- manifest.routeManifest,
4951
- manifest._routeAncestry,
4952
- perRouterStaticPrefix,
4953
- manifest.routeTrailingSlash,
4954
- perRouterPrerenderNames,
4955
- perRouterPassthroughNames,
4956
- manifest.responseTypeRoutes
4957
- );
4958
- newPerRouterTrieMap.set(id, perRouterTrie);
4959
6135
  }
4960
6136
  }
4961
6137
  }
@@ -4975,9 +6151,46 @@ async function discoverRouters(state, rscEnv) {
4975
6151
  return serverMod;
4976
6152
  }
4977
6153
 
6154
+ // src/vite/discovery/dev-prerender-cache.ts
6155
+ function payloadBodiesFromResult(result) {
6156
+ const main = JSON.stringify({
6157
+ segments: result.segments,
6158
+ handles: result.handles
6159
+ });
6160
+ const intercept = result.interceptSegments?.length ? JSON.stringify({
6161
+ segments: [...result.segments, ...result.interceptSegments],
6162
+ handles: result.interceptHandles ?? ""
6163
+ }) : main;
6164
+ return { main, intercept };
6165
+ }
6166
+ function devPrerenderCacheKey(pathname, opts) {
6167
+ return `${pathname}|i=${opts.intercept ? 1 : 0}|p=${opts.passthrough ? 1 : 0}|r=${opts.routeName ?? ""}`;
6168
+ }
6169
+ var MAX_ENTRIES_PER_ROUTER = 500;
6170
+ function createDevPrerenderCache(maxEntriesPerRouter = MAX_ENTRIES_PER_ROUTER) {
6171
+ const buckets = /* @__PURE__ */ new WeakMap();
6172
+ return {
6173
+ get(router, key) {
6174
+ return buckets.get(router)?.get(key);
6175
+ },
6176
+ set(router, key, body) {
6177
+ let bucket = buckets.get(router);
6178
+ if (!bucket) {
6179
+ bucket = /* @__PURE__ */ new Map();
6180
+ buckets.set(router, bucket);
6181
+ }
6182
+ if (!bucket.has(key) && bucket.size >= maxEntriesPerRouter) {
6183
+ const oldest = bucket.keys().next().value;
6184
+ if (oldest !== void 0) bucket.delete(oldest);
6185
+ }
6186
+ bucket.set(key, body);
6187
+ }
6188
+ };
6189
+ }
6190
+
4978
6191
  // src/vite/discovery/route-types-writer.ts
4979
- import { dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
4980
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
6192
+ import { dirname as dirname3, join as join4, resolve as resolve8 } from "node:path";
6193
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6, unlinkSync as unlinkSync2 } from "node:fs";
4981
6194
  function filterUserNamedRoutes(manifest) {
4982
6195
  const filtered = {};
4983
6196
  for (const [name, pattern] of Object.entries(manifest)) {
@@ -4988,7 +6201,7 @@ function filterUserNamedRoutes(manifest) {
4988
6201
  return filtered;
4989
6202
  }
4990
6203
  function writeGenFileIfChanged(state, outPath, source, opts) {
4991
- const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
6204
+ const existing = existsSync6(outPath) ? readFileSync4(outPath, "utf-8") : null;
4992
6205
  if (existing === source) return;
4993
6206
  markSelfGenWrite(state, outPath, source);
4994
6207
  writeFileSync3(outPath, source);
@@ -5006,10 +6219,10 @@ function writeRouteTypesFiles(state) {
5006
6219
  if (state.perRouterManifests.length === 0) return;
5007
6220
  try {
5008
6221
  const entryDir = dirname3(
5009
- resolve6(state.projectRoot, state.resolvedEntryPath)
6222
+ resolve8(state.projectRoot, state.resolvedEntryPath)
5010
6223
  );
5011
- const oldCombinedPath = join2(entryDir, "named-routes.gen.ts");
5012
- if (existsSync5(oldCombinedPath)) {
6224
+ const oldCombinedPath = join4(entryDir, "named-routes.gen.ts");
6225
+ if (existsSync6(oldCombinedPath)) {
5013
6226
  unlinkSync2(oldCombinedPath);
5014
6227
  console.log(
5015
6228
  `[rango] Removed stale combined route types: ${oldCombinedPath}`
@@ -5090,7 +6303,7 @@ function supplementGenFilesWithRuntimeRoutes(state) {
5090
6303
  }
5091
6304
 
5092
6305
  // src/vite/discovery/virtual-module-codegen.ts
5093
- import { dirname as dirname4, basename, join as join3 } from "node:path";
6306
+ import { dirname as dirname4, basename, join as join5 } from "node:path";
5094
6307
  function generateRoutesManifestModule(state) {
5095
6308
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
5096
6309
  if (hasManifest) {
@@ -5105,7 +6318,7 @@ function generateRoutesManifestModule(state) {
5105
6318
  /\.(tsx?|jsx?)$/,
5106
6319
  ""
5107
6320
  );
5108
- const genPath = join3(
6321
+ const genPath = join5(
5109
6322
  routerDir,
5110
6323
  `${routerBasename}.named-routes.gen.js`
5111
6324
  ).replaceAll("\\", "/");
@@ -5190,7 +6403,7 @@ function generatePerRouterModule(state, routerId) {
5190
6403
  /\.(tsx?|jsx?)$/,
5191
6404
  ""
5192
6405
  );
5193
- const genPath = join3(
6406
+ const genPath = join5(
5194
6407
  routerDir,
5195
6408
  `${routerBasename}.named-routes.gen.js`
5196
6409
  ).replaceAll("\\", "/");
@@ -5213,17 +6426,17 @@ function generatePerRouterModule(state, routerId) {
5213
6426
  `export const precomputedEntries = ${jsonParseExpression(entries)};`
5214
6427
  );
5215
6428
  }
5216
- return lines.join("\n") || "// empty router manifest";
6429
+ return lines.join("\n") || "";
5217
6430
  }
5218
6431
 
5219
6432
  // src/vite/discovery/bundle-postprocess.ts
5220
- import { resolve as resolve7 } from "node:path";
5221
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "node:fs";
6433
+ import { resolve as resolve9 } from "node:path";
6434
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "node:fs";
5222
6435
  function postprocessBundle(state) {
5223
6436
  const hasPrerenderData = state.prerenderManifestEntries && Object.keys(state.prerenderManifestEntries).length > 0;
5224
6437
  const hasStaticData = state.staticManifestEntries && Object.keys(state.staticManifestEntries).length > 0;
5225
6438
  if (!hasPrerenderData && !hasStaticData) return;
5226
- const rscEntryPath = resolve7(
6439
+ const rscEntryPath = resolve9(
5227
6440
  state.projectRoot,
5228
6441
  "dist/rsc",
5229
6442
  state.rscEntryFileName ?? "index.js"
@@ -5244,7 +6457,7 @@ function postprocessBundle(state) {
5244
6457
  ];
5245
6458
  for (const target of evictionTargets) {
5246
6459
  for (const info of target.infos) {
5247
- const chunkPath = resolve7(state.projectRoot, "dist/rsc", info.fileName);
6460
+ const chunkPath = resolve9(state.projectRoot, "dist/rsc", info.fileName);
5248
6461
  try {
5249
6462
  const code = readFileSync5(chunkPath, "utf-8");
5250
6463
  const result = evictHandlerCode(
@@ -5269,7 +6482,7 @@ function postprocessBundle(state) {
5269
6482
  }
5270
6483
  state.handlerChunkInfoMap.clear();
5271
6484
  state.staticHandlerChunkInfoMap.clear();
5272
- if (hasPrerenderData && existsSync6(rscEntryPath)) {
6485
+ if (hasPrerenderData && existsSync7(rscEntryPath)) {
5273
6486
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
5274
6487
  if (!rscCode.includes("__prerender-manifest.js")) {
5275
6488
  try {
@@ -5284,12 +6497,12 @@ function postprocessBundle(state) {
5284
6497
  manifestMap[key] = `./assets/${assetFileName}`;
5285
6498
  }
5286
6499
  const manifestCode = [
5287
- `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
6500
+ `const m=${jsonParseExpression(manifestMap)};`,
5288
6501
  `export function loadPrerenderAsset(s){return import(s)}`,
5289
6502
  `export default m;`,
5290
6503
  ""
5291
6504
  ].join("\n");
5292
- const manifestPath = resolve7(
6505
+ const manifestPath = resolve9(
5293
6506
  state.projectRoot,
5294
6507
  "dist/rsc/__prerender-manifest.js"
5295
6508
  );
@@ -5309,7 +6522,7 @@ function postprocessBundle(state) {
5309
6522
  }
5310
6523
  }
5311
6524
  }
5312
- if (hasStaticData && existsSync6(rscEntryPath)) {
6525
+ if (hasStaticData && existsSync7(rscEntryPath)) {
5313
6526
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
5314
6527
  if (!rscCode.includes("__static-manifest.js")) {
5315
6528
  try {
@@ -5327,7 +6540,7 @@ function postprocessBundle(state) {
5327
6540
  }
5328
6541
  const manifestCode = `const m={${manifestEntries.join(",")}};globalThis.__STATIC_MANIFEST=m;export default m;
5329
6542
  `;
5330
- const manifestPath = resolve7(
6543
+ const manifestPath = resolve9(
5331
6544
  state.projectRoot,
5332
6545
  "dist/rsc/__static-manifest.js"
5333
6546
  );
@@ -5359,8 +6572,8 @@ function createDiscoveryGate(s, debug11) {
5359
6572
  let pendingEvents = false;
5360
6573
  const beginGate = () => {
5361
6574
  if (gatePending) return;
5362
- s.discoveryDone = new Promise((resolve10) => {
5363
- gateResolver = resolve10;
6575
+ s.discoveryDone = new Promise((resolve12) => {
6576
+ gateResolver = resolve12;
5364
6577
  });
5365
6578
  gatePending = true;
5366
6579
  };
@@ -5452,15 +6665,15 @@ function selectForwardableResolvePlugins(plugins) {
5452
6665
  }
5453
6666
  function pickForwardedRunnerConfig(config) {
5454
6667
  const r = config.resolve ?? {};
5455
- const resolve10 = {};
5456
- if (r.alias !== void 0) resolve10.alias = r.alias;
5457
- if (r.dedupe !== void 0) resolve10.dedupe = r.dedupe;
5458
- if (r.conditions !== void 0) resolve10.conditions = r.conditions;
5459
- if (r.mainFields !== void 0) resolve10.mainFields = r.mainFields;
5460
- if (r.extensions !== void 0) resolve10.extensions = r.extensions;
6668
+ const resolve12 = {};
6669
+ if (r.alias !== void 0) resolve12.alias = r.alias;
6670
+ if (r.dedupe !== void 0) resolve12.dedupe = r.dedupe;
6671
+ if (r.conditions !== void 0) resolve12.conditions = r.conditions;
6672
+ if (r.mainFields !== void 0) resolve12.mainFields = r.mainFields;
6673
+ if (r.extensions !== void 0) resolve12.extensions = r.extensions;
5461
6674
  if (r.preserveSymlinks !== void 0)
5462
- resolve10.preserveSymlinks = r.preserveSymlinks;
5463
- if (r.tsconfigPaths !== void 0) resolve10.tsconfigPaths = r.tsconfigPaths;
6675
+ resolve12.preserveSymlinks = r.preserveSymlinks;
6676
+ if (r.tsconfigPaths !== void 0) resolve12.tsconfigPaths = r.tsconfigPaths;
5464
6677
  const userOxc = config.oxc;
5465
6678
  const userJsx = userOxc && typeof userOxc === "object" && typeof userOxc.jsx === "object" && userOxc.jsx !== null ? userOxc.jsx : {};
5466
6679
  const oxc = userOxc && typeof userOxc === "object" ? {
@@ -5468,7 +6681,7 @@ function pickForwardedRunnerConfig(config) {
5468
6681
  jsx: { ...userJsx, runtime: "automatic", importSource: "react" }
5469
6682
  } : { jsx: { runtime: "automatic", importSource: "react" } };
5470
6683
  return {
5471
- resolve: resolve10,
6684
+ resolve: resolve12,
5472
6685
  define: config.define,
5473
6686
  oxc
5474
6687
  };
@@ -5546,11 +6759,11 @@ async function resolveBuildEnv(option, factoryCtx) {
5546
6759
  );
5547
6760
  }
5548
6761
  try {
5549
- const userRequire = createRequire2(
5550
- resolve8(factoryCtx.root, "package.json")
6762
+ const userRequire = createRequire3(
6763
+ resolve10(factoryCtx.root, "package.json")
5551
6764
  );
5552
6765
  const wranglerPath = userRequire.resolve("wrangler");
5553
- const { getPlatformProxy } = await import(pathToFileURL(wranglerPath).href);
6766
+ const { getPlatformProxy } = await import(pathToFileURL2(wranglerPath).href);
5554
6767
  const proxy = await getPlatformProxy();
5555
6768
  return {
5556
6769
  env: proxy.env,
@@ -5602,16 +6815,17 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5602
6815
  let viteMode = "production";
5603
6816
  return {
5604
6817
  name: "@rangojs/router:discovery",
5605
- config() {
5606
- const config = {
5607
- define: {
5608
- __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG)
5609
- }
5610
- };
5611
- return config;
6818
+ // Make INTERNAL_RANGO_DEBUG reach the CLIENT debug logs by just setting the
6819
+ // env var. See injectClientDebugFlag: bakes the resolved flag into the
6820
+ // internal-debug module so FE debug no longer depends on Vite delivering the
6821
+ // `__RANGO_DEBUG__` define to the client (which it does only as an injected
6822
+ // global whose presence varies across consumer setups). Runs in dev and build.
6823
+ transform(_code, id) {
6824
+ return injectClientDebugFlag(id);
5612
6825
  },
5613
6826
  configResolved(config) {
5614
6827
  s.projectRoot = config.root;
6828
+ s.scanFilter = opts?.discovery ? createScanFilter(s.projectRoot, opts.discovery) : void 0;
5615
6829
  s.isBuildMode = config.command === "build";
5616
6830
  viteCommand = config.command;
5617
6831
  viteMode = config.mode;
@@ -5645,9 +6859,10 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5645
6859
  if (s.isBuildMode) return;
5646
6860
  if (globalThis.__rscRouterDiscoveryActive) return;
5647
6861
  s.devServer = server;
6862
+ server.middlewares.use(internalDebugNoCacheMiddleware());
5648
6863
  let resolveDiscovery;
5649
- const discoveryPromise = new Promise((resolve10) => {
5650
- resolveDiscovery = resolve10;
6864
+ const discoveryPromise = new Promise((resolve12) => {
6865
+ resolveDiscovery = resolve12;
5651
6866
  });
5652
6867
  const gate = createDiscoveryGate(s, debugDiscovery);
5653
6868
  const beginDiscoveryGate = gate.beginGate;
@@ -5754,6 +6969,10 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5754
6969
  );
5755
6970
  console.warn(`[rango] Failed to create temp runner: ${err.message}`);
5756
6971
  }
6972
+ await prerenderTempServer?.close().catch(() => {
6973
+ });
6974
+ prerenderTempServer = null;
6975
+ prerenderNodeRegistry = null;
5757
6976
  return null;
5758
6977
  }
5759
6978
  async function clearTempRegistries(tempRscEnv) {
@@ -5805,6 +7024,15 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5805
7024
  await importEntryAndRegistry(tempRscEnv);
5806
7025
  return tempRscEnv;
5807
7026
  }
7027
+ const emitDiscoveryFailure = (err, hashBefore, hashAfter) => {
7028
+ const reoptimizeObserved = hashBefore !== void 0 && hashAfter !== void 0 && hashBefore !== hashAfter;
7029
+ const report = describeDiscoveryFailure(err, { reoptimizeObserved });
7030
+ if (report.level === "warn") {
7031
+ console.warn(report.message);
7032
+ } else {
7033
+ console.error(report.message);
7034
+ }
7035
+ };
5808
7036
  const discover = async () => {
5809
7037
  const discoverStart = performance.now();
5810
7038
  const rscEnv = server.environments?.rsc;
@@ -5814,18 +7042,21 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5814
7042
  globalThis.__rscRouterDiscoveryActive ?? false
5815
7043
  );
5816
7044
  s.devServerOrigin = getDevServerOrigin();
7045
+ let tempRscEnv;
7046
+ let optimizerHashBefore2;
5817
7047
  try {
5818
7048
  await timed(
5819
7049
  debugDiscovery,
5820
7050
  "acquireBuildEnv",
5821
7051
  () => acquireBuildEnv(s, viteCommand, viteMode)
5822
7052
  );
5823
- const tempRscEnv = await timed(
7053
+ tempRscEnv = await timed(
5824
7054
  debugDiscovery,
5825
7055
  "getOrCreateTempServer",
5826
7056
  () => getOrCreateTempServer()
5827
7057
  );
5828
7058
  if (tempRscEnv) {
7059
+ optimizerHashBefore2 = tempRscEnv.depsOptimizer?.metadata?.browserHash;
5829
7060
  await timed(
5830
7061
  debugDiscovery,
5831
7062
  "discoverRouters (cloudflare)",
@@ -5838,9 +7069,10 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
5838
7069
  );
5839
7070
  }
5840
7071
  } catch (err) {
5841
- console.warn(
5842
- `[rango] Cloudflare dev discovery failed: ${err.message}
5843
- ${err.stack}`
7072
+ emitDiscoveryFailure(
7073
+ err,
7074
+ optimizerHashBefore2,
7075
+ tempRscEnv?.depsOptimizer?.metadata?.browserHash
5844
7076
  );
5845
7077
  }
5846
7078
  debugDiscovery?.(
@@ -5850,6 +7082,7 @@ ${err.stack}`
5850
7082
  resolveDiscovery();
5851
7083
  return;
5852
7084
  }
7085
+ const optimizerHashBefore = rscEnv.depsOptimizer?.metadata?.browserHash;
5853
7086
  try {
5854
7087
  debugDiscovery?.("dev: node path start");
5855
7088
  await timed(
@@ -5859,17 +7092,12 @@ ${err.stack}`
5859
7092
  );
5860
7093
  const serverMod = await timed(
5861
7094
  debugDiscovery,
5862
- "import @rangojs/router/server",
5863
- () => rscEnv.runner.import("@rangojs/router/server")
7095
+ "discoverRouters",
7096
+ () => discoverRouters(s, rscEnv)
5864
7097
  );
5865
7098
  if (serverMod?.setManifestReadyPromise) {
5866
7099
  serverMod.setManifestReadyPromise(discoveryPromise);
5867
7100
  }
5868
- await timed(
5869
- debugDiscovery,
5870
- "discoverRouters",
5871
- () => discoverRouters(s, rscEnv)
5872
- );
5873
7101
  s.devServerOrigin = getDevServerOrigin();
5874
7102
  timedSync(
5875
7103
  debugDiscovery,
@@ -5882,9 +7110,10 @@ ${err.stack}`
5882
7110
  () => propagateDiscoveryState(rscEnv)
5883
7111
  );
5884
7112
  } catch (err) {
5885
- console.warn(
5886
- `[rango] Router discovery failed: ${err.message}
5887
- ${err.stack}`
7113
+ emitDiscoveryFailure(
7114
+ err,
7115
+ optimizerHashBefore,
7116
+ rscEnv.depsOptimizer?.metadata?.browserHash
5888
7117
  );
5889
7118
  } finally {
5890
7119
  debugDiscovery?.(
@@ -5900,6 +7129,7 @@ ${err.stack}`
5900
7129
  0
5901
7130
  );
5902
7131
  let mainRegistry = null;
7132
+ const devPrerenderCache = createDevPrerenderCache();
5903
7133
  const propagateDiscoveryState = async (rscEnv) => {
5904
7134
  const serverMod = await rscEnv.runner.import("@rangojs/router/server");
5905
7135
  if (!serverMod) return;
@@ -5969,8 +7199,19 @@ ${err.stack}`
5969
7199
  registry = mainRegistry;
5970
7200
  }
5971
7201
  if (!registry) {
5972
- if (!prerenderNodeRegistry) {
5973
- await getOrCreateTempServer();
7202
+ const tempRscEnv = await getOrCreateTempServer();
7203
+ if (tempRscEnv) {
7204
+ try {
7205
+ await importEntryAndRegistry(tempRscEnv);
7206
+ } catch (err) {
7207
+ console.warn(
7208
+ `[rango] Dev prerender module refresh failed: ${err.message}`
7209
+ );
7210
+ res.statusCode = 500;
7211
+ res.end(`Prerender handler error: ${err.message}`);
7212
+ logResult(500, "temp module refresh failed");
7213
+ return;
7214
+ }
5974
7215
  }
5975
7216
  registry = prerenderNodeRegistry;
5976
7217
  }
@@ -5983,8 +7224,29 @@ ${err.stack}`
5983
7224
  const wantIntercept = url.searchParams.get("intercept") === "1";
5984
7225
  const wantRouteName = url.searchParams.get("routeName");
5985
7226
  const wantPassthrough = url.searchParams.get("passthrough") === "1";
7227
+ const variantDims = {
7228
+ passthrough: wantPassthrough,
7229
+ routeName: wantRouteName
7230
+ };
7231
+ const keyMain = devPrerenderCacheKey(pathname, {
7232
+ intercept: false,
7233
+ ...variantDims
7234
+ });
7235
+ const keyIntercept = devPrerenderCacheKey(pathname, {
7236
+ intercept: true,
7237
+ ...variantDims
7238
+ });
7239
+ const requestedKey = wantIntercept ? keyIntercept : keyMain;
5986
7240
  for (const [, routerInstance] of registry) {
5987
7241
  if (!routerInstance.matchForPrerender) continue;
7242
+ const cached = devPrerenderCache.get(routerInstance, requestedKey);
7243
+ if (cached !== void 0) {
7244
+ res.setHeader("content-type", "application/json");
7245
+ res.setHeader("x-rango-prerender-cache", "HIT");
7246
+ res.end(cached);
7247
+ logResult(200, "cache hit");
7248
+ return;
7249
+ }
5988
7250
  try {
5989
7251
  const result = await routerInstance.matchForPrerender(
5990
7252
  pathname,
@@ -5998,26 +7260,24 @@ ${err.stack}`
5998
7260
  if (!result) continue;
5999
7261
  if (result.passthrough) continue;
6000
7262
  if (wantRouteName && result.routeName !== wantRouteName) continue;
7263
+ const bodies = payloadBodiesFromResult(result);
7264
+ devPrerenderCache.set(routerInstance, keyMain, bodies.main);
7265
+ devPrerenderCache.set(
7266
+ routerInstance,
7267
+ keyIntercept,
7268
+ bodies.intercept
7269
+ );
6001
7270
  res.setHeader("content-type", "application/json");
6002
- let payload;
6003
- if (wantIntercept && result.interceptSegments?.length) {
6004
- payload = {
6005
- segments: [...result.segments, ...result.interceptSegments],
6006
- handles: {
6007
- ...result.handles,
6008
- ...result.interceptHandles || {}
6009
- }
6010
- };
6011
- } else {
6012
- payload = { segments: result.segments, handles: result.handles };
6013
- }
6014
- res.end(JSON.stringify(payload));
7271
+ res.setHeader("x-rango-prerender-cache", "MISS");
7272
+ res.end(wantIntercept ? bodies.intercept : bodies.main);
6015
7273
  logResult(200, `match ${result.routeName}`);
6016
7274
  return;
6017
7275
  } catch (err) {
6018
- console.warn(
6019
- `[rango] Dev prerender failed for ${pathname}: ${err.message}`
6020
- );
7276
+ if (err?.name !== "Skip") {
7277
+ console.warn(
7278
+ `[rango] Dev prerender error for ${pathname} (serving live instead): ${err.message}`
7279
+ );
7280
+ }
6021
7281
  }
6022
7282
  }
6023
7283
  res.statusCode = 404;
@@ -6224,7 +7484,7 @@ ${err.stack}`
6224
7484
  if (hasCreateRouter) {
6225
7485
  const nestedRouterConflict = findNestedRouterConflict([
6226
7486
  ...s.cachedRouterFiles ?? [],
6227
- resolve8(filePath)
7487
+ resolve10(filePath)
6228
7488
  ]);
6229
7489
  if (nestedRouterConflict) {
6230
7490
  server.config.logger.error(
@@ -6536,7 +7796,11 @@ async function rango(options) {
6536
7796
  ];
6537
7797
  const pkg = getPublishedPackageName();
6538
7798
  const nested = (spec) => `${pkg} > ${spec}`;
6539
- const routerRef = { path: void 0 };
7799
+ const routerRef = {
7800
+ path: void 0,
7801
+ kind: "router"
7802
+ };
7803
+ const explicitHostRouter = preset !== "cloudflare" ? resolvedOptions.hostRouter : void 0;
6540
7804
  const prerenderEnabled = true;
6541
7805
  if (preset === "cloudflare") {
6542
7806
  const { default: rsc } = await import("@vitejs/plugin-rsc");
@@ -6549,8 +7813,6 @@ async function rango(options) {
6549
7813
  enforce: "pre",
6550
7814
  config() {
6551
7815
  return {
6552
- // Exclude rsc-router modules from optimization to prevent module duplication
6553
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
6554
7816
  optimizeDeps: {
6555
7817
  exclude: excludeDeps,
6556
7818
  rolldownOptions: sharedRolldownOptions
@@ -6573,21 +7835,12 @@ async function rango(options) {
6573
7835
  client: {
6574
7836
  build: {
6575
7837
  rollupOptions: {
6576
- // FILE_NAME_CONFLICT (and any other client-build warning) is
6577
- // emitted by the CLIENT environment build, which consults THIS
6578
- // env's onwarn -- Vite 8's environment builds do NOT propagate
6579
- // the top-level build.rollupOptions.onwarn into the client env.
6580
- // Wire it here so the suppression runs where the conflicts
6581
- // originate (the top-level handler is invoked 0x for these; the
6582
- // client-env handler is invoked for all of them).
6583
7838
  onwarn,
6584
7839
  output: {
6585
7840
  manualChunks: getManualChunks
6586
7841
  }
6587
7842
  }
6588
7843
  },
6589
- // Pre-bundle rsc-html-stream to prevent discovery during first request
6590
- // Exclude rsc-router modules to ensure same Context instance
6591
7844
  optimizeDeps: {
6592
7845
  include: [nested("rsc-html-stream/client")],
6593
7846
  exclude: excludeDeps,
@@ -6595,12 +7848,9 @@ async function rango(options) {
6595
7848
  }
6596
7849
  },
6597
7850
  ssr: {
6598
- // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
6599
7851
  build: {
6600
7852
  outDir: "./dist/rsc/ssr"
6601
7853
  },
6602
- // Pre-bundle SSR entry and React for proper module linking with childEnvironments
6603
- // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
6604
7854
  optimizeDeps: {
6605
7855
  entries: [finalEntries.ssr],
6606
7856
  include: [
@@ -6620,10 +7870,7 @@ async function rango(options) {
6620
7870
  }
6621
7871
  },
6622
7872
  rsc: {
6623
- // RSC environment needs exclude list and esbuild options
6624
- // Exclude rsc-router modules to prevent createContext in RSC environment
6625
7873
  optimizeDeps: {
6626
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
6627
7874
  include: [
6628
7875
  "react",
6629
7876
  "react/jsx-runtime",
@@ -6661,17 +7908,49 @@ async function rango(options) {
6661
7908
  name: "@rangojs/router:auto-discover",
6662
7909
  config(userConfig) {
6663
7910
  if (routerRef.path) return;
6664
- const root = userConfig.root ? resolve9(process.cwd(), userConfig.root) : process.cwd();
7911
+ const root = userConfig.root ? resolve11(process.cwd(), userConfig.root) : process.cwd();
7912
+ const toRootRelative = (abs) => (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
7913
+ const bulletList = (files) => files.map((f) => " - " + toRootRelative(f)).join("\n");
7914
+ if (explicitHostRouter) {
7915
+ routerRef.path = normalizeHostRouterEntry(
7916
+ explicitHostRouter,
7917
+ root,
7918
+ existsSync8
7919
+ );
7920
+ routerRef.kind = "host";
7921
+ return;
7922
+ }
7923
+ const hostCandidates = findHostRouterFiles(root);
7924
+ if (hostCandidates.length === 1) {
7925
+ const hostPath = toRootRelative(hostCandidates[0]);
7926
+ console.info(
7927
+ `[rango] Serving host router entry ${hostPath} (auto-detected). Set the \`hostRouter\` option to override.`
7928
+ );
7929
+ routerRef.path = hostPath;
7930
+ routerRef.kind = "host";
7931
+ return;
7932
+ }
7933
+ if (hostCandidates.length > 1) {
7934
+ throw new Error(
7935
+ `[rango] Multiple host routers found:
7936
+ ${bulletList(hostCandidates)}
7937
+
7938
+ Set the \`hostRouter\` option to the entry to serve, e.g. rango({ preset: "${preset}", hostRouter: "./src/worker.rsc.tsx" }).`
7939
+ );
7940
+ }
6665
7941
  const candidates = findRouterFiles(root);
6666
7942
  if (candidates.length === 1) {
6667
- const abs = candidates[0];
6668
- routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
6669
- } else if (candidates.length > 1) {
6670
- const list = candidates.map(
6671
- (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
6672
- ).join("\n");
6673
- throw new Error(`[rango] Multiple routers found:
6674
- ${list}`);
7943
+ routerRef.path = toRootRelative(candidates[0]);
7944
+ routerRef.kind = "router";
7945
+ return;
7946
+ }
7947
+ if (candidates.length > 1) {
7948
+ throw new Error(
7949
+ `[rango] Multiple routers found:
7950
+ ${bulletList(candidates)}
7951
+
7952
+ If this is a multi-app host router, export a createHostRouter() instance and set the \`hostRouter\` option (e.g. rango({ preset: "${preset}", hostRouter: "./src/worker.rsc.tsx" })), or use preset: "cloudflare" where you own the worker entry.`
7953
+ );
6675
7954
  }
6676
7955
  }
6677
7956
  });
@@ -6685,8 +7964,11 @@ ${list}`);
6685
7964
  plugins.push({
6686
7965
  name: "@rangojs/router:rsc-integration",
6687
7966
  enforce: "pre",
6688
- config() {
7967
+ config(_userConfig, configEnv) {
7968
+ const vercelDefine = preset === "vercel" && configEnv.command === "build" ? { "process.env.NODE_ENV": JSON.stringify("production") } : void 0;
7969
+ const vercelServerEnv = preset === "vercel" && configEnv.command === "build" ? { resolve: { noExternal: true } } : void 0;
6689
7970
  return {
7971
+ ...vercelDefine ? { define: vercelDefine } : {},
6690
7972
  optimizeDeps: {
6691
7973
  exclude: excludeDeps,
6692
7974
  rolldownOptions: sharedRolldownOptions
@@ -6709,13 +7991,6 @@ ${list}`);
6709
7991
  client: {
6710
7992
  build: {
6711
7993
  rollupOptions: {
6712
- // FILE_NAME_CONFLICT (and any other client-build warning) is
6713
- // emitted by the CLIENT environment build, which consults THIS
6714
- // env's onwarn -- Vite 8's environment builds do NOT propagate
6715
- // the top-level build.rollupOptions.onwarn into the client env.
6716
- // Wire it here so the suppression runs where the conflicts
6717
- // originate (the top-level handler is invoked 0x for these; the
6718
- // client-env handler is invoked for all of them).
6719
7994
  onwarn,
6720
7995
  output: {
6721
7996
  manualChunks: getManualChunks
@@ -6736,6 +8011,7 @@ ${list}`);
6736
8011
  }
6737
8012
  },
6738
8013
  ssr: {
8014
+ ...vercelServerEnv ?? {},
6739
8015
  optimizeDeps: {
6740
8016
  entries: [VIRTUAL_IDS.ssr],
6741
8017
  include: [
@@ -6754,6 +8030,7 @@ ${list}`);
6754
8030
  }
6755
8031
  },
6756
8032
  rsc: {
8033
+ ...vercelServerEnv ?? {},
6757
8034
  optimizeDeps: {
6758
8035
  entries: [VIRTUAL_IDS.rsc],
6759
8036
  include: [
@@ -6764,6 +8041,12 @@ ${list}`);
6764
8041
  "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6765
8042
  )
6766
8043
  ],
8044
+ // Vite 8 does not propagate the top-level optimizeDeps.exclude
8045
+ // (set in config()) to non-client envs, so the rsc env must set
8046
+ // it explicitly — mirroring the node ssr env and the cloudflare
8047
+ // rsc env. Without it a strict-pnpm npm-installed app can try to
8048
+ // pre-bundle the router's own subpath entries and fail.
8049
+ exclude: excludeDeps,
6767
8050
  rolldownOptions: sharedRolldownOptions
6768
8051
  }
6769
8052
  }
@@ -6773,7 +8056,11 @@ ${list}`);
6773
8056
  configResolved(config) {
6774
8057
  if (showBanner) {
6775
8058
  const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
6776
- printBanner(mode, "node", rangoVersion);
8059
+ printBanner(
8060
+ mode,
8061
+ preset === "vercel" ? "vercel" : "node",
8062
+ rangoVersion
8063
+ );
6777
8064
  }
6778
8065
  const rscMinimalCount = config.plugins.filter(
6779
8066
  (p) => p.name === "rsc:minimal"
@@ -6806,8 +8093,7 @@ ${list}`);
6806
8093
  return;
6807
8094
  try {
6808
8095
  const source = readFileSync7(file, "utf-8");
6809
- const trimmed = source.trimStart();
6810
- if (trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'")) {
8096
+ if (hasUseClientDirective(source)) {
6811
8097
  return [];
6812
8098
  }
6813
8099
  } catch {
@@ -6831,9 +8117,16 @@ ${list}`);
6831
8117
  enableBuildPrerender: prerenderEnabled,
6832
8118
  buildEnv: options?.buildEnv,
6833
8119
  preset,
8120
+ prerenderOnError: options?.prerender?.onError,
8121
+ discovery: options?.discovery,
6834
8122
  clientChunkCtx
6835
8123
  })
6836
8124
  );
8125
+ if (preset === "vercel") {
8126
+ plugins.push(
8127
+ createVercelOutputPlugin(resolvedOptions)
8128
+ );
8129
+ }
6837
8130
  debugConfig?.(
6838
8131
  "rango(%s) setup done: %d plugin(s) (%sms)",
6839
8132
  preset,
@@ -6846,7 +8139,7 @@ ${list}`);
6846
8139
  // src/vite/plugins/refresh-cmd.ts
6847
8140
  function poke() {
6848
8141
  return {
6849
- name: "vite-plugin-poke",
8142
+ name: "@rangojs/router:poke",
6850
8143
  apply: "serve",
6851
8144
  configureServer(server) {
6852
8145
  const stdin = process.stdin;
@@ -6924,6 +8217,7 @@ function poke() {
6924
8217
  };
6925
8218
  }
6926
8219
  export {
8220
+ directoryClientChunks,
6927
8221
  poke,
6928
8222
  rango
6929
8223
  };