@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
@@ -16,8 +16,13 @@ import {
16
16
  formatNestedRouterConflictError,
17
17
  findNestedRouterConflict,
18
18
  findRouterFiles,
19
+ createScanFilter,
19
20
  } from "../build/generate-route-types.js";
20
21
  import { firstCodeMatchIndex } from "../build/route-types/source-scan.js";
22
+ import {
23
+ injectClientDebugFlag,
24
+ internalDebugNoCacheMiddleware,
25
+ } from "./inject-client-debug.js";
21
26
  import { createVersionPlugin } from "./plugins/version-plugin.js";
22
27
  import { createVirtualStubPlugin } from "./plugins/virtual-stub-plugin.js";
23
28
  import {
@@ -41,6 +46,12 @@ import {
41
46
  peekSelfGenWrite,
42
47
  } from "./discovery/self-gen-tracking.js";
43
48
  import { discoverRouters } from "./discovery/discover-routers.js";
49
+ import { describeDiscoveryFailure } from "./discovery/discovery-errors.js";
50
+ import {
51
+ createDevPrerenderCache,
52
+ devPrerenderCacheKey,
53
+ payloadBodiesFromResult,
54
+ } from "./discovery/dev-prerender-cache.js";
44
55
  import {
45
56
  writeCombinedRouteTypesWithTracking,
46
57
  writeRouteTypesFiles,
@@ -309,23 +320,23 @@ export function createRouterDiscoveryPlugin(
309
320
  return {
310
321
  name: "@rangojs/router:discovery",
311
322
 
312
- config() {
313
- const config: any = {
314
- define: {
315
- __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG),
316
- },
317
- };
318
- // Prerender/static handler modules are bundled naturally with the
319
- // rest of the RSC entry. A previous design forced them into dedicated
320
- // __prerender-handlers / __static-handlers chunks via manualChunks,
321
- // but Rollup hoisted all shared dependencies into those chunks,
322
- // inflating them to ~1 MB with active runtime code. Handler code is
323
- // evicted in closeBundle regardless of which chunk it lands in.
324
- return config;
323
+ // Make INTERNAL_RANGO_DEBUG reach the CLIENT debug logs by just setting the
324
+ // env var. See injectClientDebugFlag: bakes the resolved flag into the
325
+ // internal-debug module so FE debug no longer depends on Vite delivering the
326
+ // `__RANGO_DEBUG__` define to the client (which it does only as an injected
327
+ // global whose presence varies across consumer setups). Runs in dev and build.
328
+ transform(_code, id) {
329
+ return injectClientDebugFlag(id);
325
330
  },
326
331
 
327
332
  configResolved(config) {
328
333
  s.projectRoot = config.root;
334
+ // Compile the optional discovery scan filter (glob include/exclude) now
335
+ // that the project root is known. findRouterFiles() below — and the
336
+ // build/HMR rediscovery paths — honor s.scanFilter.
337
+ s.scanFilter = opts?.discovery
338
+ ? createScanFilter(s.projectRoot, opts.discovery)
339
+ : undefined;
329
340
  s.isBuildMode = config.command === "build";
330
341
  viteCommand = config.command as "serve" | "build";
331
342
  viteMode = config.mode;
@@ -384,6 +395,11 @@ export function createRouterDiscoveryPlugin(
384
395
  if ((globalThis as any).__rscRouterDiscoveryActive) return;
385
396
  s.devServer = server;
386
397
 
398
+ // Serve the internal-debug module no-cache: consumers resolve it into
399
+ // node_modules, where dev's immutable `?v=` caching pinned browsers to a
400
+ // stale baked INTERNAL_RANGO_DEBUG. See internalDebugNoCacheMiddleware.
401
+ server.middlewares.use(internalDebugNoCacheMiddleware());
402
+
387
403
  // Discovery promise that the handler can await if requests arrive
388
404
  // before discovery completes
389
405
  let resolveDiscovery: () => void;
@@ -424,7 +440,8 @@ export function createRouterDiscoveryPlugin(
424
440
  releaseBuildEnv(s).catch(() => {});
425
441
  });
426
442
 
427
- // Mirror the build-path contract (router-discovery.ts ~line 878):
443
+ // Mirror the build-path contract (the buildStart hook below, which sets
444
+ // __rscRouterDiscoveryActive before running user modules):
428
445
  // set __rscRouterDiscoveryActive before running user modules so any
429
446
  // module-level router.reverse() calls return a placeholder instead
430
447
  // of throwing. The temp Vite server's module runner has its own
@@ -541,6 +558,14 @@ export function createRouterDiscoveryPlugin(
541
558
  );
542
559
  console.warn(`[rango] Failed to create temp runner: ${err.message}`);
543
560
  }
561
+ // Reached only on failure (runner unavailable, or create/import threw
562
+ // AFTER the server was created). Close the just-created server so a
563
+ // failed discovery does not leak it until the next call or dev shutdown,
564
+ // and null the refs so the reuse path above starts clean. Mirrors the
565
+ // close pattern used when an existing server is discarded (above).
566
+ await prerenderTempServer?.close().catch(() => {});
567
+ prerenderTempServer = null;
568
+ prerenderNodeRegistry = null;
544
569
  return null;
545
570
  }
546
571
 
@@ -642,6 +667,30 @@ export function createRouterDiscoveryPlugin(
642
667
  return tempRscEnv;
643
668
  }
644
669
 
670
+ // Surface a discovery failure on either dev path (Node RSC runner or the
671
+ // Cloudflare temp Node server). `hashBefore`/`hashAfter` are the discovering
672
+ // environment's dep-optimizer browserHash snapshots: a change across the
673
+ // attempt means a reload-causing re-optimization landed mid-flight, so an
674
+ // empty registry was the transient race (downgraded to a warning) rather
675
+ // than a genuine misconfig (loud, actionable error). Shared so both catch
676
+ // sites frame the same failure identically.
677
+ const emitDiscoveryFailure = (
678
+ err: unknown,
679
+ hashBefore: string | undefined,
680
+ hashAfter: string | undefined,
681
+ ): void => {
682
+ const reoptimizeObserved =
683
+ hashBefore !== undefined &&
684
+ hashAfter !== undefined &&
685
+ hashBefore !== hashAfter;
686
+ const report = describeDiscoveryFailure(err, { reoptimizeObserved });
687
+ if (report.level === "warn") {
688
+ console.warn(report.message);
689
+ } else {
690
+ console.error(report.message);
691
+ }
692
+ };
693
+
645
694
  const discover = async () => {
646
695
  const discoverStart = performance.now();
647
696
  const rscEnv = (server.environments as any)?.rsc;
@@ -657,18 +706,26 @@ export function createRouterDiscoveryPlugin(
657
706
 
658
707
  // Create a temp Node.js server to run runtime discovery and generate
659
708
  // named route types (static parser can't resolve factory calls).
709
+ // The temp server is a separate Vite instance with its own dep
710
+ // optimizer; snapshot ITS browserHash (hoisted so the catch can tell a
711
+ // transient re-optimization apart from a genuine empty registry, the
712
+ // same way the Node path below does).
713
+ let tempRscEnv: any;
714
+ let optimizerHashBefore: string | undefined;
660
715
  try {
661
716
  // Acquire build-time env bindings for dev prerender
662
717
  await timed(debugDiscovery, "acquireBuildEnv", () =>
663
718
  acquireBuildEnv(s, viteCommand, viteMode),
664
719
  );
665
720
 
666
- const tempRscEnv = await timed(
721
+ tempRscEnv = await timed(
667
722
  debugDiscovery,
668
723
  "getOrCreateTempServer",
669
724
  () => getOrCreateTempServer(),
670
725
  );
671
726
  if (tempRscEnv) {
727
+ optimizerHashBefore =
728
+ tempRscEnv.depsOptimizer?.metadata?.browserHash;
672
729
  await timed(debugDiscovery, "discoverRouters (cloudflare)", () =>
673
730
  discoverRouters(s, tempRscEnv),
674
731
  );
@@ -677,8 +734,10 @@ export function createRouterDiscoveryPlugin(
677
734
  );
678
735
  }
679
736
  } catch (err: any) {
680
- console.warn(
681
- `[rango] Cloudflare dev discovery failed: ${err.message}\n${err.stack}`,
737
+ emitDiscoveryFailure(
738
+ err,
739
+ optimizerHashBefore,
740
+ tempRscEnv?.depsOptimizer?.metadata?.browserHash,
682
741
  );
683
742
  }
684
743
 
@@ -690,6 +749,13 @@ export function createRouterDiscoveryPlugin(
690
749
  return;
691
750
  }
692
751
 
752
+ // Snapshot the dep-optimizer hash before discovery so the catch can tell
753
+ // a transient re-optimization race apart from a genuine empty registry.
754
+ // A reload-causing re-optimization regenerates browserHash; if it changed
755
+ // across the attempt, an empty read was almost certainly the race below.
756
+ const optimizerHashBefore: string | undefined =
757
+ rscEnv.depsOptimizer?.metadata?.browserHash;
758
+
693
759
  try {
694
760
  // Acquire build-time env bindings for dev prerender (Node.js path)
695
761
  debugDiscovery?.("dev: node path start");
@@ -697,21 +763,31 @@ export function createRouterDiscoveryPlugin(
697
763
  acquireBuildEnv(s, viteCommand, viteMode),
698
764
  );
699
765
 
700
- // Set the readiness gate BEFORE discovery so early requests
701
- // block until manifest is populated
702
- const serverMod = await timed(
703
- debugDiscovery,
704
- "import @rangojs/router/server",
705
- () => rscEnv.runner.import("@rangojs/router/server"),
766
+ // Discover routers FIRST, then arm the manifest-readiness gate on the
767
+ // server module discovery actually read the registry from.
768
+ //
769
+ // We deliberately do NOT pre-import "@rangojs/router/server" before the
770
+ // entry to arm the gate early. During a Vite dependency re-optimization
771
+ // (dev boot after a lockfile change, or `vite dev --force`), a module
772
+ // imported here before the entry resolves to the pre-optimize copy of
773
+ // the runner's module graph, while discoverRouters' entry import — which
774
+ // awaits the in-flight re-optimization — resolves to the post-optimize
775
+ // copy. createRouter() then populates RouterRegistry on the fresh copy,
776
+ // but a stale pre-imported "@rangojs/router/server" reads the other
777
+ // copy's empty Map and discovery throws a spurious "No routers found"
778
+ // even though the app is configured correctly. discoverRouters imports
779
+ // the entry first and reads the registry off the same instance, keeping
780
+ // read and write on one copy. The virtual manifest module's own gate
781
+ // (s.discoveryDone, armed by beginDiscoveryGate) already blocks early
782
+ // requests during discovery on the Node path, so arming
783
+ // manifestReadyPromise after discovery is sufficient here.
784
+ const serverMod = await timed(debugDiscovery, "discoverRouters", () =>
785
+ discoverRouters(s, rscEnv),
706
786
  );
707
787
  if (serverMod?.setManifestReadyPromise) {
708
788
  serverMod.setManifestReadyPromise(discoveryPromise);
709
789
  }
710
790
 
711
- await timed(debugDiscovery, "discoverRouters", () =>
712
- discoverRouters(s, rscEnv),
713
- );
714
-
715
791
  // Store server origin for dev prerender endpoint (virtual module injection)
716
792
  s.devServerOrigin = getDevServerOrigin();
717
793
 
@@ -729,8 +805,10 @@ export function createRouterDiscoveryPlugin(
729
805
  propagateDiscoveryState(rscEnv),
730
806
  );
731
807
  } catch (err: any) {
732
- console.warn(
733
- `[rango] Router discovery failed: ${err.message}\n${err.stack}`,
808
+ emitDiscoveryFailure(
809
+ err,
810
+ optimizerHashBefore,
811
+ rscEnv.depsOptimizer?.metadata?.browserHash,
734
812
  );
735
813
  } finally {
736
814
  debugDiscovery?.(
@@ -763,6 +841,15 @@ export function createRouterDiscoveryPlugin(
763
841
  // Registry from the main server's RSC environment (populated by discoverRouters)
764
842
  let mainRegistry: Map<string, any> | null = null;
765
843
 
844
+ // Memoized /__rsc_prerender render results, keyed by router-instance
845
+ // identity (#654). The per-request entry re-import below is what makes
846
+ // identity a valid freshness key: an HMR-invalidated chain re-runs
847
+ // createRouter() and replaces the registry instance, so cached bodies
848
+ // for the old instance become unreachable; an untouched chain returns
849
+ // the same instance and the cached body is byte-identical to a fresh
850
+ // render. See dev-prerender-cache.ts for the full invariant.
851
+ const devPrerenderCache = createDevPrerenderCache();
852
+
766
853
  // Push discovery state (manifest, trie, precomputed entries) to the
767
854
  // server module so runtime request handling uses the current routes.
768
855
  // Shared by initial discovery and HMR-triggered re-discovery.
@@ -853,9 +940,28 @@ export function createRouterDiscoveryPlugin(
853
940
 
854
941
  if (!registry) {
855
942
  // No main registry: the RSC env has no module runner (Cloudflare dev).
856
- // Lazily create a Node.js temp server for prerender evaluation.
857
- if (!prerenderNodeRegistry) {
858
- await getOrCreateTempServer();
943
+ // Lazily create a Node.js temp server for prerender evaluation, and
944
+ // re-import the entry through it on EVERY request — the temp server
945
+ // has its own file watcher, so a handler-only edit (a file without
946
+ // urls()/createRouter() that the main watcher's route-file sniff
947
+ // ignores) invalidates its module graph; the re-import re-evaluates
948
+ // exactly the dirty subgraph and re-registers fresh router
949
+ // instances. Before #654 the cached registry was only refreshed on
950
+ // route-file edits, so handler-only edits served stale prerender
951
+ // content on this path. Warm-cache re-imports are module-cache hits.
952
+ const tempRscEnv = await getOrCreateTempServer();
953
+ if (tempRscEnv) {
954
+ try {
955
+ await importEntryAndRegistry(tempRscEnv);
956
+ } catch (err: any) {
957
+ console.warn(
958
+ `[rango] Dev prerender module refresh failed: ${err.message}`,
959
+ );
960
+ res.statusCode = 500;
961
+ res.end(`Prerender handler error: ${err.message}`);
962
+ logResult(500, "temp module refresh failed");
963
+ return;
964
+ }
859
965
  }
860
966
  registry = prerenderNodeRegistry;
861
967
  }
@@ -871,8 +977,38 @@ export function createRouterDiscoveryPlugin(
871
977
  const wantRouteName = url.searchParams.get("routeName");
872
978
  const wantPassthrough = url.searchParams.get("passthrough") === "1";
873
979
 
980
+ // One render warms BOTH variant keys (matchForPrerender computes the
981
+ // intercept segments unconditionally), so a route's main and modal
982
+ // variants cost a single render per HMR generation.
983
+ const variantDims = {
984
+ passthrough: wantPassthrough,
985
+ routeName: wantRouteName,
986
+ };
987
+ const keyMain = devPrerenderCacheKey(pathname, {
988
+ intercept: false,
989
+ ...variantDims,
990
+ });
991
+ const keyIntercept = devPrerenderCacheKey(pathname, {
992
+ intercept: true,
993
+ ...variantDims,
994
+ });
995
+ const requestedKey = wantIntercept ? keyIntercept : keyMain;
996
+
874
997
  for (const [, routerInstance] of registry) {
875
998
  if (!routerInstance.matchForPrerender) continue;
999
+ // Cache is consulted per router IN LOOP ORDER so multi-router
1000
+ // fall-through semantics are identical to the uncached path: a
1001
+ // router that never produced a payload for this key still runs
1002
+ // its matchForPrerender (cheap trie miss / intentionally-uncached
1003
+ // error retry) before the next router is considered.
1004
+ const cached = devPrerenderCache.get(routerInstance, requestedKey);
1005
+ if (cached !== undefined) {
1006
+ res.setHeader("content-type", "application/json");
1007
+ res.setHeader("x-rango-prerender-cache", "HIT");
1008
+ res.end(cached);
1009
+ logResult(200, "cache hit");
1010
+ return;
1011
+ }
876
1012
  try {
877
1013
  const result = await routerInstance.matchForPrerender(
878
1014
  pathname,
@@ -888,26 +1024,31 @@ export function createRouterDiscoveryPlugin(
888
1024
  // This prevents returning the wrong entry when multiple routers
889
1025
  // have prerenderable routes sharing the same pathname.
890
1026
  if (wantRouteName && result.routeName !== wantRouteName) continue;
1027
+ // Pre-encoded MERGED handle string in the intercept body comes
1028
+ // from the producer (handles are Flight-encoded so
1029
+ // Promise/ReactNode values survive the wire).
1030
+ const bodies = payloadBodiesFromResult(result);
1031
+ devPrerenderCache.set(routerInstance, keyMain, bodies.main);
1032
+ devPrerenderCache.set(
1033
+ routerInstance,
1034
+ keyIntercept,
1035
+ bodies.intercept,
1036
+ );
891
1037
  res.setHeader("content-type", "application/json");
892
- let payload: Record<string, unknown>;
893
- if (wantIntercept && result.interceptSegments?.length) {
894
- payload = {
895
- segments: [...result.segments, ...result.interceptSegments],
896
- handles: {
897
- ...result.handles,
898
- ...(result.interceptHandles || {}),
899
- },
900
- };
901
- } else {
902
- payload = { segments: result.segments, handles: result.handles };
903
- }
904
- res.end(JSON.stringify(payload));
1038
+ res.setHeader("x-rango-prerender-cache", "MISS");
1039
+ res.end(wantIntercept ? bodies.intercept : bodies.main);
905
1040
  logResult(200, `match ${result.routeName}`);
906
1041
  return;
907
1042
  } catch (err: any) {
908
- console.warn(
909
- `[rango] Dev prerender failed for ${pathname}: ${err.message}`,
910
- );
1043
+ // matchForPrerender now re-throws render failures instead of baking
1044
+ // an error page (issue #587). In dev there is no frozen artifact, so
1045
+ // we fall through (404 -> live handler). A `throw new Skip()` is the
1046
+ // expected "skip this URL" signal, not a failure, so it stays quiet.
1047
+ if (err?.name !== "Skip") {
1048
+ console.warn(
1049
+ `[rango] Dev prerender error for ${pathname} (serving live instead): ${err.message}`,
1050
+ );
1051
+ }
911
1052
  }
912
1053
  }
913
1054
 
@@ -1,10 +1,6 @@
1
1
  import type MagicString from "magic-string";
2
2
  import { hashInlineId, buildExportMap } from "../plugins/expose-id-utils.js";
3
3
 
4
- // ---------------------------------------------------------------------------
5
- // Types
6
- // ---------------------------------------------------------------------------
7
-
8
4
  /** Minimal ESTree Program node — avoids importing from `rollup` (not a direct dep). */
9
5
  interface ProgramNode {
10
6
  type: "Program";
@@ -70,10 +66,6 @@ function findImportInsertionPos(
70
66
  return insertionPos;
71
67
  }
72
68
 
73
- // ---------------------------------------------------------------------------
74
- // AST walking helper
75
- // ---------------------------------------------------------------------------
76
-
77
69
  /**
78
70
  * Recursively walk an ESTree AST node, calling `enter` on each node.
79
71
  * Parent is passed for context.
@@ -111,10 +103,6 @@ function walkNode(
111
103
  ancestors.pop();
112
104
  }
113
105
 
114
- // ---------------------------------------------------------------------------
115
- // AST analysis
116
- // ---------------------------------------------------------------------------
117
-
118
106
  /**
119
107
  * Parse the file with Vite's parseAst and find all calls to `fnName`.
120
108
  * Distinguishes between `export const X = fnName(...)` (exportInfo set)
@@ -294,16 +282,23 @@ function isInertExpression(node: any): boolean {
294
282
  (e: any) => e === null || isInertExpression(e),
295
283
  );
296
284
  case "ObjectExpression":
297
- return (node.properties ?? []).every(
298
- (p: any) =>
285
+ return (node.properties ?? []).every((p: any) => {
286
+ // A spread inside an object (`{ ...x }`) is inert iff `x` is inert.
287
+ if (p.type === "SpreadElement" || p.type === "RestElement") {
288
+ return isInertExpression(p.argument);
289
+ }
290
+ return (
299
291
  p.type === "Property" &&
300
292
  (!p.computed || isInertExpression(p.key)) &&
301
- isInertExpression(p.value),
302
- );
293
+ isInertExpression(p.value)
294
+ );
295
+ });
303
296
  case "UnaryExpression":
304
297
  return isInertExpression(node.argument);
305
298
  case "BinaryExpression":
306
299
  return isInertExpression(node.left) && isInertExpression(node.right);
300
+ case "LogicalExpression":
301
+ return isInertExpression(node.left) && isInertExpression(node.right);
307
302
  case "ConditionalExpression":
308
303
  return (
309
304
  isInertExpression(node.test) &&
@@ -426,10 +421,6 @@ export function extractModuleLevelDeclarations(
426
421
  return declarations;
427
422
  }
428
423
 
429
- // ---------------------------------------------------------------------------
430
- // Transform
431
- // ---------------------------------------------------------------------------
432
-
433
424
  /**
434
425
  * Transform inline handler calls by extracting them into virtual modules.
435
426
  * Only processes inline calls (exportInfo === null); export const calls are
@@ -5,9 +5,7 @@ import {
5
5
 
6
6
  /**
7
7
  * Find matching close paren in bundled code using depth counting.
8
- * Uses skipStringOrComment from expose-id-utils to correctly handle
9
- * template literal ${...} expressions, comments, and nested strings.
10
- * Returns the position after the closing paren, or -1 if unmatched.
8
+ * Uses skipStringOrComment to correctly handle template literals, comments, and nested strings.
11
9
  * @internal Exported for testing only.
12
10
  */
13
11
  export function findMatchingParenInBundle(
@@ -30,9 +28,8 @@ export function findMatchingParenInBundle(
30
28
  }
31
29
 
32
30
  /**
33
- * Scan a bundled chunk for handler exports of a given type and extract
34
- * their names + $$id values. Optionally detects passthrough flag.
35
- * @internal Exported for testing only.
31
+ * Scan a bundled chunk for handler exports and extract their names + $$id values.
32
+ * Optionally detects passthrough flag. @internal Exported for testing only.
36
33
  */
37
34
  export function extractHandlerExportsFromChunk(
38
35
  chunkCode: string,
@@ -67,7 +64,7 @@ export function extractHandlerExportsFromChunk(
67
64
  const closePos = findMatchingParenInBundle(chunkCode, afterOpen);
68
65
  if (closePos !== -1) {
69
66
  const callBody = chunkCode.slice(callStart.index, closePos);
70
- isPassthrough = /passthrough\s*:\s*(!0|true)/.test(callBody);
67
+ isPassthrough = /passthrough\s*:\s*(!0|true)/.test(callBody); // !0 is minified true
71
68
  }
72
69
  }
73
70
  }
@@ -79,9 +76,8 @@ export function extractHandlerExportsFromChunk(
79
76
  }
80
77
 
81
78
  /**
82
- * Evict handler code from a bundled chunk, replacing full handler call
83
- * expressions with lightweight stub objects. Returns the modified code
84
- * and bytes saved, or null if no changes were made.
79
+ * Evict handler code from a bundled chunk, replacing full call expressions with stubs.
80
+ * Returns the modified code and bytes saved, or null if no changes were made.
85
81
  * @internal Exported for testing only.
86
82
  */
87
83
  export function evictHandlerCode(
@@ -110,13 +106,11 @@ export function evictHandlerCode(
110
106
  const closePos = findMatchingParenInBundle(modified, afterOpen);
111
107
  if (closePos === -1) continue;
112
108
 
113
- // Skip trailing whitespace and optional semicolon
114
109
  let rangeEnd = closePos;
115
110
  while (rangeEnd < modified.length && /\s/.test(modified[rangeEnd]))
116
111
  rangeEnd++;
117
112
  if (modified[rangeEnd] === ";") rangeEnd++;
118
113
 
119
- // Validate: matched range must contain the expected handlerId
120
114
  const matched = modified.slice(startMatch.index, rangeEnd);
121
115
  if (!matched.includes(handlerId)) continue;
122
116
 
@@ -124,7 +118,6 @@ export function evictHandlerCode(
124
118
  modified =
125
119
  modified.slice(0, startMatch.index) + stub + modified.slice(rangeEnd);
126
120
 
127
- // Remove the now-redundant $$id assignment line.
128
121
  modified = modified.replace(
129
122
  new RegExp(`\\n${eName}\\.\\$\\$id\\s*=\\s*"[^"]+";`),
130
123
  "",
@@ -1,9 +1,3 @@
1
- // Resolution of the public `clientChunks` option into the callback shape that
2
- // @vitejs/plugin-rsc expects. See plugin-types.ts (ClientChunks) and
3
- // docs/client-chunking.md for the contract. The mechanism: a distinct returned
4
- // name yields a distinct, dynamically-imported client chunk, independent of how
5
- // the RSC/server build chunked the importing modules.
6
-
7
1
  import type { ClientChunkMeta, ClientChunks } from "../plugin-types.js";
8
2
  import { createRangoDebugger, NS } from "../debug.js";
9
3
  import { hashRefKey } from "../plugins/client-ref-hashing.js";
@@ -0,0 +1,40 @@
1
+ import { parseAst } from "vite";
2
+
3
+ /**
4
+ * Detect a leading `"use client"` (or `'use client'`) directive in a module's
5
+ * directive prologue, tolerating leading comments/whitespace before it.
6
+ *
7
+ * A bare `source.trimStart().startsWith` check only strips whitespace, so a
8
+ * license banner / `// @ts-nocheck` before the directive would be missed. This
9
+ * walks the leading ExpressionStatement string-literal directives via the AST
10
+ * (a leading comment is not a `program.body` node), the single source of truth
11
+ * shared by both the rango plugin's HMR client-module sniff and the version
12
+ * plugin's getClientModuleSignature — so the two can never drift on what counts
13
+ * as a client module.
14
+ *
15
+ * Returns false when the source cannot be parsed (a syntactically broken file is
16
+ * not treated as a client module).
17
+ */
18
+ export function hasUseClientDirective(source: string): boolean {
19
+ let program: { body?: any[] };
20
+ try {
21
+ program = parseAst(source, { lang: "tsx" }) as { body?: any[] };
22
+ } catch {
23
+ return false;
24
+ }
25
+ for (const node of program.body ?? []) {
26
+ if (
27
+ node?.type === "ExpressionStatement" &&
28
+ node.expression?.type === "Literal" &&
29
+ typeof node.expression.value === "string"
30
+ ) {
31
+ if (node.expression.value === "use client") return true;
32
+ // Another leading string-literal directive (e.g. "use strict"): keep
33
+ // scanning the prologue.
34
+ continue;
35
+ }
36
+ // First non-directive statement ends the prologue.
37
+ break;
38
+ }
39
+ return false;
40
+ }
@@ -1,25 +1,3 @@
1
- /**
2
- * Discovery Runner Config Parity
3
- *
4
- * The discovery temp server (createTempRscServer) runs the user's handler
5
- * graph through a throwaway Node Vite server built with `configFile: false`.
6
- * Without help, that server only sees a fixed Rango-owned plugin set, so any
7
- * user resolution is absent during discovery, prerender, and static handler
8
- * rendering — even though it applies at request time. Two flavors of user
9
- * resolution must be carried across:
10
- *
11
- * - Third-party resolveId plugins (e.g. vite-tsconfig-paths) — forwarded as
12
- * plugin instances, see selectForwardableResolvePlugins.
13
- * - Native config-driven resolution, including Vite 8's built-in
14
- * `resolve.tsconfigPaths` (which supersedes vite-tsconfig-paths) — forwarded
15
- * as the data slice, see pickForwardedRunnerConfig.
16
- *
17
- * These helpers extract the resolution-relevant slice of the user's resolved
18
- * config (resolve.*, define, oxc) and forward the user's resolution plugins
19
- * into the temp server so discovery resolves modules the same way the real
20
- * environment does.
21
- */
22
-
23
1
  import type { Plugin, ResolvedConfig, UserConfig } from "vite";
24
2
 
25
3
  /**
@@ -1,78 +1,7 @@
1
- /**
2
- * Flatten prefix tree leaf nodes into precomputed route entries.
3
- * Leaf nodes have no children (no nested includes), so their routes can be
4
- * used directly by evaluateLazyEntry() without running the handler.
5
- * Non-leaf nodes are skipped because they have nested lazy includes that
6
- * require the handler to run for discovery.
7
- *
8
- * A leaf is also skipped when its staticPrefix collides with an ancestor
9
- * include node's staticPrefix. That happens when a dynamic param collapses the
10
- * staticPrefix of nested includes onto the parent's (e.g. `/m/:id/edit` -> sp
11
- * `/m`): precomputing such a leaf under the collapsed prefix would let the
12
- * ancestor's lazy entry claim a route it cannot register (the route is behind
13
- * further nested lazy includes), producing a RouteNotFoundError at request time
14
- * (issue #506). Those routes are resolved via the handler chain instead.
15
- */
16
- export function flattenLeafEntries(
17
- prefixTree: Record<string, any>,
18
- routeManifest: Record<string, string>,
19
- result: Array<{ staticPrefix: string; routes: Record<string, string> }>,
20
- ): void {
21
- function visit(node: any, ancestorStaticPrefixes: Set<string>): void {
22
- const children = node.children || {};
23
- if (
24
- Object.keys(children).length === 0 &&
25
- node.routes &&
26
- node.routes.length > 0
27
- ) {
28
- // Leaf node. Skip if its staticPrefix collides with an ancestor include
29
- // node's staticPrefix (dynamic-param collapse) — see doc comment above.
30
- if (ancestorStaticPrefixes.has(node.staticPrefix)) {
31
- return;
32
- }
33
- // Collect its routes from the manifest
34
- const routes: Record<string, string> = {};
35
- for (const name of node.routes) {
36
- if (name in routeManifest) {
37
- routes[name] = routeManifest[name];
38
- }
39
- }
40
- result.push({ staticPrefix: node.staticPrefix, routes });
41
- } else {
42
- // Non-leaf: recurse into children, tracking this node's staticPrefix as
43
- // an ancestor so a collapsed nested leaf below it is not over-claimed.
44
- const nextAncestors = new Set(ancestorStaticPrefixes);
45
- nextAncestors.add(node.staticPrefix);
46
- for (const child of Object.values(children)) {
47
- visit(child, nextAncestors);
48
- }
49
- }
50
- }
51
- for (const node of Object.values(prefixTree)) {
52
- visit(node, new Set());
53
- }
54
- }
55
-
56
- /**
57
- * Walk prefix tree to map each route name to its scope's staticPrefix.
58
- */
59
- export function buildRouteToStaticPrefix(
60
- prefixTree: Record<string, any>,
61
- result: Record<string, string>,
62
- ): void {
63
- function visit(node: any): void {
64
- const sp = node.staticPrefix || "";
65
- for (const name of node.routes || []) {
66
- result[name] = sp;
67
- }
68
- for (const child of Object.values(node.children || {})) {
69
- visit(child);
70
- }
71
- }
72
- for (const node of Object.values(prefixTree)) {
73
- visit(node);
74
- }
75
- }
1
+ export {
2
+ flattenLeafEntries,
3
+ buildRouteToStaticPrefix,
4
+ } from "../../build/prefix-tree-utils.js";
76
5
 
77
6
  /**
78
7
  * Wrap a value as `JSON.parse('...')` instead of a JS object literal.