@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17

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 (376) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +198 -44
  3. package/dist/bin/rango.js +287 -105
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3248 -1117
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +73 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +107 -1
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +245 -21
  12. package/skills/caching/SKILL.md +302 -6
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +364 -0
  17. package/skills/hooks/SKILL.md +270 -30
  18. package/skills/host-router/SKILL.md +82 -22
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +49 -5
  21. package/skills/layout/SKILL.md +35 -9
  22. package/skills/links/SKILL.md +249 -17
  23. package/skills/loader/SKILL.md +294 -30
  24. package/skills/middleware/SKILL.md +52 -13
  25. package/skills/migrate-nextjs/SKILL.md +584 -0
  26. package/skills/migrate-react-router/SKILL.md +769 -0
  27. package/skills/mime-routes/SKILL.md +27 -0
  28. package/skills/observability/SKILL.md +137 -0
  29. package/skills/parallel/SKILL.md +203 -7
  30. package/skills/prerender/SKILL.md +123 -100
  31. package/skills/rango/SKILL.md +250 -22
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +122 -47
  34. package/skills/route/SKILL.md +97 -5
  35. package/skills/router-setup/SKILL.md +90 -5
  36. package/skills/server-actions/SKILL.md +775 -0
  37. package/skills/streams-and-websockets/SKILL.md +283 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +129 -0
  40. package/skills/testing/bindings.md +89 -0
  41. package/skills/testing/cache-prerender.md +124 -0
  42. package/skills/testing/client-components.md +122 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +92 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +121 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +120 -0
  54. package/skills/typesafety/SKILL.md +329 -27
  55. package/skills/use-cache/SKILL.md +36 -5
  56. package/skills/view-transitions/SKILL.md +294 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +67 -40
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/app-version.ts +14 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +86 -147
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/link-interceptor.ts +4 -0
  70. package/src/browser/navigation-bridge.ts +148 -19
  71. package/src/browser/navigation-client.ts +187 -67
  72. package/src/browser/navigation-store-handle.ts +38 -0
  73. package/src/browser/navigation-store.ts +76 -67
  74. package/src/browser/navigation-transaction.ts +18 -66
  75. package/src/browser/partial-update.ts +123 -94
  76. package/src/browser/prefetch/cache.ts +214 -36
  77. package/src/browser/prefetch/fetch.ts +260 -38
  78. package/src/browser/prefetch/policy.ts +6 -0
  79. package/src/browser/prefetch/queue.ts +126 -20
  80. package/src/browser/prefetch/resource-ready.ts +77 -0
  81. package/src/browser/rango-state.ts +158 -76
  82. package/src/browser/react/Link.tsx +93 -11
  83. package/src/browser/react/NavigationProvider.tsx +115 -34
  84. package/src/browser/react/ScrollRestoration.tsx +10 -6
  85. package/src/browser/react/context.ts +7 -2
  86. package/src/browser/react/filter-segment-order.ts +49 -7
  87. package/src/browser/react/index.ts +0 -48
  88. package/src/browser/react/location-state-shared.ts +166 -8
  89. package/src/browser/react/location-state.ts +39 -14
  90. package/src/browser/react/use-action.ts +6 -15
  91. package/src/browser/react/use-handle.ts +23 -69
  92. package/src/browser/react/use-link-status.ts +0 -4
  93. package/src/browser/react/use-navigation.ts +22 -5
  94. package/src/browser/react/use-params.ts +20 -10
  95. package/src/browser/react/use-reverse.ts +106 -0
  96. package/src/browser/react/use-router.ts +46 -11
  97. package/src/browser/react/use-search-params.ts +0 -5
  98. package/src/browser/react/use-segments.ts +11 -21
  99. package/src/browser/response-adapter.ts +52 -1
  100. package/src/browser/rsc-router.tsx +215 -76
  101. package/src/browser/scroll-restoration.ts +46 -39
  102. package/src/browser/segment-reconciler.ts +36 -9
  103. package/src/browser/segment-structure-assert.ts +2 -2
  104. package/src/browser/server-action-bridge.ts +176 -50
  105. package/src/browser/types.ts +95 -11
  106. package/src/browser/validate-redirect-origin.ts +43 -16
  107. package/src/build/collect-fallback-refs.ts +107 -0
  108. package/src/build/generate-manifest.ts +65 -40
  109. package/src/build/generate-route-types.ts +5 -0
  110. package/src/build/index.ts +8 -2
  111. package/src/build/prefix-tree-utils.ts +123 -0
  112. package/src/build/route-trie.ts +137 -32
  113. package/src/build/route-types/codegen.ts +4 -4
  114. package/src/build/route-types/include-resolution.ts +9 -2
  115. package/src/build/route-types/param-extraction.ts +6 -3
  116. package/src/build/route-types/per-module-writer.ts +7 -4
  117. package/src/build/route-types/router-processing.ts +278 -96
  118. package/src/build/route-types/scan-filter.ts +9 -2
  119. package/src/build/route-types/source-scan.ts +118 -0
  120. package/src/build/runtime-discovery.ts +9 -20
  121. package/src/cache/cache-error.ts +104 -0
  122. package/src/cache/cache-policy.ts +68 -28
  123. package/src/cache/cache-runtime.ts +149 -43
  124. package/src/cache/cache-scope.ts +148 -81
  125. package/src/cache/cache-tag.ts +98 -0
  126. package/src/cache/cf/cf-cache-store.ts +2550 -93
  127. package/src/cache/cf/index.ts +11 -17
  128. package/src/cache/document-cache.ts +78 -27
  129. package/src/cache/handle-snapshot.ts +63 -0
  130. package/src/cache/index.ts +23 -20
  131. package/src/cache/memory-segment-store.ts +136 -37
  132. package/src/cache/profile-registry.ts +6 -30
  133. package/src/cache/read-through-swr.ts +41 -11
  134. package/src/cache/segment-codec.ts +0 -16
  135. package/src/cache/tag-invalidation.ts +230 -0
  136. package/src/cache/taint.ts +55 -0
  137. package/src/cache/types.ts +33 -100
  138. package/src/cache/vercel/index.ts +11 -0
  139. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  140. package/src/client.rsc.tsx +6 -21
  141. package/src/client.tsx +108 -290
  142. package/src/component-utils.ts +19 -0
  143. package/src/context-var.ts +84 -2
  144. package/src/debug.ts +2 -2
  145. package/src/decode-loader-results.ts +36 -0
  146. package/src/defer.ts +196 -0
  147. package/src/deps/ssr.ts +0 -1
  148. package/src/errors.ts +30 -4
  149. package/src/handle.ts +70 -22
  150. package/src/handles/MetaTags.tsx +0 -14
  151. package/src/handles/breadcrumbs.ts +16 -5
  152. package/src/handles/meta.ts +0 -39
  153. package/src/host/cookie-handler.ts +0 -36
  154. package/src/host/errors.ts +0 -24
  155. package/src/host/index.ts +8 -2
  156. package/src/host/pattern-matcher.ts +7 -50
  157. package/src/host/router.ts +107 -99
  158. package/src/host/testing.ts +40 -27
  159. package/src/host/types.ts +37 -4
  160. package/src/host/utils.ts +1 -1
  161. package/src/href-client.ts +137 -22
  162. package/src/index.rsc.ts +52 -26
  163. package/src/index.ts +100 -38
  164. package/src/internal-debug.ts +2 -4
  165. package/src/loader-store.ts +500 -0
  166. package/src/loader.rsc.ts +20 -13
  167. package/src/loader.ts +12 -11
  168. package/src/missing-id-error.ts +68 -0
  169. package/src/network-error-thrower.tsx +1 -6
  170. package/src/outlet-context.ts +1 -1
  171. package/src/outlet-provider.tsx +1 -5
  172. package/src/prerender/param-hash.ts +10 -11
  173. package/src/prerender/store.ts +37 -41
  174. package/src/prerender.ts +198 -82
  175. package/src/redirect-origin.ts +100 -0
  176. package/src/response-utils.ts +37 -0
  177. package/src/reverse.ts +65 -15
  178. package/src/root-error-boundary.tsx +1 -19
  179. package/src/route-content-wrapper.tsx +7 -72
  180. package/src/route-definition/dsl-helpers.ts +437 -274
  181. package/src/route-definition/helper-factories.ts +29 -139
  182. package/src/route-definition/helpers-types.ts +113 -37
  183. package/src/route-definition/index.ts +3 -0
  184. package/src/route-definition/redirect.ts +52 -10
  185. package/src/route-definition/resolve-handler-use.ts +161 -0
  186. package/src/route-definition/use-item-types.ts +32 -0
  187. package/src/route-map-builder.ts +7 -17
  188. package/src/route-types.ts +37 -41
  189. package/src/router/basename.ts +14 -0
  190. package/src/router/content-negotiation.ts +108 -9
  191. package/src/router/error-handling.ts +13 -17
  192. package/src/router/find-match.ts +45 -22
  193. package/src/router/handler-context.ts +83 -41
  194. package/src/router/intercept-resolution.ts +25 -23
  195. package/src/router/lazy-includes.ts +19 -53
  196. package/src/router/loader-resolution.ts +213 -30
  197. package/src/router/logging.ts +5 -8
  198. package/src/router/manifest.ts +49 -45
  199. package/src/router/match-api.ts +121 -205
  200. package/src/router/match-context.ts +0 -22
  201. package/src/router/match-handlers.ts +58 -58
  202. package/src/router/match-middleware/background-revalidation.ts +27 -6
  203. package/src/router/match-middleware/cache-lookup.ts +205 -249
  204. package/src/router/match-middleware/cache-store.ts +45 -32
  205. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  206. package/src/router/match-middleware/segment-resolution.ts +52 -18
  207. package/src/router/match-pipelines.ts +1 -42
  208. package/src/router/match-result.ts +104 -40
  209. package/src/router/metrics.ts +5 -34
  210. package/src/router/middleware-types.ts +13 -142
  211. package/src/router/middleware.ts +173 -143
  212. package/src/router/navigation-snapshot.ts +131 -0
  213. package/src/router/params-util.ts +23 -0
  214. package/src/router/pattern-matching.ts +109 -63
  215. package/src/router/prerender-match.ts +192 -54
  216. package/src/router/preview-match.ts +32 -102
  217. package/src/router/request-classification.ts +276 -0
  218. package/src/router/revalidation.ts +63 -55
  219. package/src/router/route-snapshot.ts +244 -0
  220. package/src/router/router-context.ts +6 -28
  221. package/src/router/router-interfaces.ts +100 -35
  222. package/src/router/router-options.ts +91 -11
  223. package/src/router/router-registry.ts +2 -5
  224. package/src/router/segment-resolution/fresh.ts +242 -75
  225. package/src/router/segment-resolution/helpers.ts +64 -25
  226. package/src/router/segment-resolution/loader-cache.ts +41 -37
  227. package/src/router/segment-resolution/revalidation.ts +456 -372
  228. package/src/router/segment-resolution/static-store.ts +19 -5
  229. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  230. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  231. package/src/router/segment-resolution.ts +4 -1
  232. package/src/router/segment-wrappers.ts +2 -3
  233. package/src/router/state-cookie-name.ts +33 -0
  234. package/src/router/substitute-pattern-params.ts +56 -0
  235. package/src/router/telemetry-otel.ts +0 -20
  236. package/src/router/telemetry.ts +96 -19
  237. package/src/router/timeout.ts +0 -20
  238. package/src/router/trie-matching.ts +91 -46
  239. package/src/router/types.ts +10 -63
  240. package/src/router/url-params.ts +44 -0
  241. package/src/router.ts +134 -43
  242. package/src/rsc/handler-context.ts +3 -2
  243. package/src/rsc/handler.ts +492 -383
  244. package/src/rsc/helpers.ts +162 -46
  245. package/src/rsc/index.ts +1 -1
  246. package/src/rsc/json-route-result.ts +38 -0
  247. package/src/rsc/loader-fetch.ts +23 -3
  248. package/src/rsc/manifest-init.ts +33 -42
  249. package/src/rsc/origin-guard.ts +39 -25
  250. package/src/rsc/progressive-enhancement.ts +30 -3
  251. package/src/rsc/redirect-guard.ts +99 -0
  252. package/src/rsc/response-error.ts +79 -12
  253. package/src/rsc/response-route-handler.ts +90 -63
  254. package/src/rsc/rsc-rendering.ts +56 -54
  255. package/src/rsc/runtime-warnings.ts +23 -10
  256. package/src/rsc/server-action.ts +74 -67
  257. package/src/rsc/ssr-setup.ts +18 -2
  258. package/src/rsc/types.ts +25 -6
  259. package/src/runtime-env.ts +18 -0
  260. package/src/search-params.ts +4 -20
  261. package/src/segment-content-promise.ts +67 -0
  262. package/src/segment-loader-promise.ts +134 -0
  263. package/src/segment-system.tsx +272 -129
  264. package/src/serialize.ts +243 -0
  265. package/src/server/context.ts +309 -61
  266. package/src/server/cookie-store.ts +80 -5
  267. package/src/server/handle-store.ts +26 -24
  268. package/src/server/loader-registry.ts +10 -28
  269. package/src/server/request-context.ts +348 -128
  270. package/src/ssr/index.tsx +23 -15
  271. package/src/static-handler.ts +27 -18
  272. package/src/testing/cache-status.ts +162 -0
  273. package/src/testing/collect-handle.ts +40 -0
  274. package/src/testing/dispatch.ts +618 -0
  275. package/src/testing/dom.entry.ts +22 -0
  276. package/src/testing/e2e/fixture.ts +188 -0
  277. package/src/testing/e2e/index.ts +128 -0
  278. package/src/testing/e2e/matchers.ts +35 -0
  279. package/src/testing/e2e/page-helpers.ts +272 -0
  280. package/src/testing/e2e/parity.ts +387 -0
  281. package/src/testing/e2e/server.ts +195 -0
  282. package/src/testing/flight-matchers.ts +97 -0
  283. package/src/testing/flight-normalize.ts +11 -0
  284. package/src/testing/flight-runtime.d.ts +57 -0
  285. package/src/testing/flight-tree.ts +682 -0
  286. package/src/testing/flight.entry.ts +52 -0
  287. package/src/testing/flight.ts +232 -0
  288. package/src/testing/generated-routes.ts +183 -0
  289. package/src/testing/index.ts +99 -0
  290. package/src/testing/internal/context.ts +348 -0
  291. package/src/testing/internal/flight-client-globals.ts +30 -0
  292. package/src/testing/internal/seed-vars.ts +54 -0
  293. package/src/testing/render-handler.ts +330 -0
  294. package/src/testing/render-route.tsx +566 -0
  295. package/src/testing/run-loader.ts +378 -0
  296. package/src/testing/run-middleware.ts +205 -0
  297. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  298. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  299. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  300. package/src/testing/vitest-stubs/version.ts +5 -0
  301. package/src/testing/vitest.ts +305 -0
  302. package/src/theme/ThemeProvider.tsx +0 -52
  303. package/src/theme/ThemeScript.tsx +0 -6
  304. package/src/theme/constants.ts +0 -12
  305. package/src/theme/index.ts +0 -7
  306. package/src/theme/theme-context.ts +1 -5
  307. package/src/theme/theme-script.ts +0 -14
  308. package/src/theme/use-theme.ts +0 -3
  309. package/src/types/boundaries.ts +0 -35
  310. package/src/types/cache-types.ts +17 -8
  311. package/src/types/error-types.ts +30 -90
  312. package/src/types/global-namespace.ts +54 -41
  313. package/src/types/handler-context.ts +233 -81
  314. package/src/types/index.ts +1 -10
  315. package/src/types/loader-types.ts +44 -15
  316. package/src/types/request-scope.ts +107 -0
  317. package/src/types/route-config.ts +6 -50
  318. package/src/types/route-entry.ts +19 -7
  319. package/src/types/segments.ts +37 -14
  320. package/src/urls/include-helper.ts +33 -70
  321. package/src/urls/index.ts +1 -11
  322. package/src/urls/path-helper-types.ts +58 -11
  323. package/src/urls/path-helper.ts +57 -111
  324. package/src/urls/pattern-types.ts +48 -19
  325. package/src/urls/response-types.ts +25 -22
  326. package/src/urls/type-extraction.ts +58 -139
  327. package/src/urls/urls-function.ts +1 -18
  328. package/src/use-loader.tsx +346 -89
  329. package/src/vite/debug.ts +185 -0
  330. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  331. package/src/vite/discovery/discover-routers.ts +130 -85
  332. package/src/vite/discovery/discovery-errors.ts +194 -0
  333. package/src/vite/discovery/gate-state.ts +171 -0
  334. package/src/vite/discovery/prerender-collection.ts +192 -99
  335. package/src/vite/discovery/route-types-writer.ts +40 -84
  336. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  337. package/src/vite/discovery/state.ts +51 -6
  338. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  339. package/src/vite/index.ts +8 -0
  340. package/src/vite/plugin-types.ts +187 -69
  341. package/src/vite/plugins/cjs-to-esm.ts +8 -18
  342. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  343. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  344. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  345. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  346. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  347. package/src/vite/plugins/expose-action-id.ts +49 -98
  348. package/src/vite/plugins/expose-id-utils.ts +11 -50
  349. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  350. package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
  351. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  352. package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
  353. package/src/vite/plugins/expose-internal-ids.ts +554 -317
  354. package/src/vite/plugins/performance-tracks.ts +89 -0
  355. package/src/vite/plugins/refresh-cmd.ts +89 -27
  356. package/src/vite/plugins/use-cache-transform.ts +73 -83
  357. package/src/vite/plugins/vercel-output.ts +258 -0
  358. package/src/vite/plugins/version-injector.ts +21 -25
  359. package/src/vite/plugins/version-plugin.ts +41 -20
  360. package/src/vite/plugins/virtual-entries.ts +2 -17
  361. package/src/vite/rango.ts +257 -289
  362. package/src/vite/router-discovery.ts +930 -140
  363. package/src/vite/utils/ast-handler-extract.ts +15 -31
  364. package/src/vite/utils/banner.ts +4 -4
  365. package/src/vite/utils/bundle-analysis.ts +10 -15
  366. package/src/vite/utils/client-chunks.ts +184 -0
  367. package/src/vite/utils/forward-user-plugins.ts +171 -0
  368. package/src/vite/utils/manifest-utils.ts +4 -59
  369. package/src/vite/utils/package-resolution.ts +20 -52
  370. package/src/vite/utils/prerender-utils.ts +27 -29
  371. package/src/vite/utils/shared-utils.ts +92 -42
  372. package/src/browser/action-response-classifier.ts +0 -99
  373. package/src/browser/react/use-client-cache.ts +0 -58
  374. package/src/browser/shallow.ts +0 -40
  375. package/src/handles/index.ts +0 -7
  376. package/src/router/middleware-cookies.ts +0 -55
@@ -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";
@@ -48,7 +44,7 @@ function findImportInsertionPos(
48
44
  ): number {
49
45
  let program: ProgramNode;
50
46
  try {
51
- program = parseAst(code, { jsx: true });
47
+ program = parseAst(code, { lang: "tsx" });
52
48
  } catch {
53
49
  return 0;
54
50
  }
@@ -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)
@@ -127,7 +115,7 @@ export function findHandlerCalls(
127
115
  ): HandlerCallSite[] {
128
116
  let program: ProgramNode;
129
117
  try {
130
- program = parseAst(code, { jsx: true });
118
+ program = parseAst(code, { lang: "tsx" });
131
119
  } catch {
132
120
  return [];
133
121
  }
@@ -239,7 +227,7 @@ export function getImportedLocalNames(
239
227
  parseAst: (code: string, options?: any) => ProgramNode,
240
228
  ): Set<string> {
241
229
  try {
242
- const program = parseAst(code, { jsx: true });
230
+ const program = parseAst(code, { lang: "tsx" });
243
231
  return getImportedLocalNamesFromProgram(program, importedName);
244
232
  } catch {
245
233
  return new Set<string>();
@@ -256,7 +244,7 @@ export function extractImportDeclarations(
256
244
  ): string[] {
257
245
  let program: ProgramNode;
258
246
  try {
259
- program = parseAst(code, { jsx: true });
247
+ program = parseAst(code, { lang: "tsx" });
260
248
  } catch {
261
249
  return [];
262
250
  }
@@ -380,7 +368,7 @@ export function extractModuleLevelDeclarations(
380
368
  ): string[] {
381
369
  let program: ProgramNode;
382
370
  try {
383
- program = parseAst(code, { jsx: true });
371
+ program = parseAst(code, { lang: "tsx" });
384
372
  } catch {
385
373
  return [];
386
374
  }
@@ -426,10 +414,6 @@ export function extractModuleLevelDeclarations(
426
414
  return declarations;
427
415
  }
428
416
 
429
- // ---------------------------------------------------------------------------
430
- // Transform
431
- // ---------------------------------------------------------------------------
432
-
433
417
  /**
434
418
  * Transform inline handler calls by extracting them into virtual modules.
435
419
  * Only processes inline calls (exportInfo === null); export const calls are
@@ -468,19 +452,19 @@ export function transformInlineHandlers(
468
452
  handlerNames,
469
453
  );
470
454
 
471
- // Track line occurrences for same-line collision handling
472
- const lineCounts = new Map<number, number>();
473
-
474
455
  // Collect all import statements to prepend
475
456
  const importStatements: string[] = [];
476
457
 
477
- for (const site of inlineSites) {
478
- const lineCount = lineCounts.get(site.lineNumber) ?? 0;
479
- lineCounts.set(site.lineNumber, lineCount + 1);
480
-
481
- const hash = hashInlineId(filePath, site.lineNumber, lineCount);
458
+ for (const [siteIndex, site] of inlineSites.entries()) {
459
+ // Key the extracted handler on its source-order index (per fnName), NOT its
460
+ // line number. The id flows into BOTH the export name and the virtual module
461
+ // path (which hashId hashes for the runtime $$id), and line numbers shift
462
+ // between the prerender and production build contexts. The index is invariant
463
+ // to those shifts, keeping the prerender manifest key == the runtime id.
464
+ const hash = hashInlineId(filePath, fnName, siteIndex);
482
465
  const exportName = `__sh_${hash}`;
483
- const virtualId = `\0${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
466
+ const idSuffix = `${filePath}:${fnName}:${siteIndex}`;
467
+ const virtualId = `\0${virtualPrefix}${idSuffix}`;
484
468
 
485
469
  // Extract the full handler call expression text
486
470
  const handlerCode = code.slice(site.callStart, site.callEnd);
@@ -498,7 +482,7 @@ export function transformInlineHandlers(
498
482
  s.overwrite(site.callStart, site.callEnd, exportName);
499
483
 
500
484
  // Build the import specifier for this virtual module
501
- const importId = `${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
485
+ const importId = `${virtualPrefix}${idSuffix}`;
502
486
  importStatements.push(`import { ${exportName} } from "${importId}";`);
503
487
  }
504
488
 
@@ -1,4 +1,4 @@
1
- import packageJson from "../../../package.json" with { type: "json" };
1
+ import packageJson from "../../../package.json";
2
2
 
3
3
  export const rangoVersion: string = packageJson.version;
4
4
 
@@ -23,11 +23,11 @@ ${dim} ╱${reset} ${bold}╔═╗${reset}${dim} * ╱
23
23
  ${dim} ${reset}${bold}║ ║${reset} ${bold}╔═╗${reset}${dim} * ✧. ╱${reset}
24
24
  ${dim} ${reset}${bold}╔╗ ║ ║ ║ ║${reset}${dim} * ╱${reset}
25
25
  ${dim} ${reset}${bold}║║ ║ ║ ║ ║ ╦═╗╔═╗╔╗╔╔═╗╔═╗${reset}${dim} ✧ ✦${reset}
26
- ${dim} ${reset}${bold}═╣║ ║ ╠═╝ ║ ╠╦╝╠═╣║║║║ ╦║ ║${reset}${dim} * ✧${reset}
26
+ ${dim} ${reset}${bold}║║ ║ ╠═╝ ║ ╠╦╝╠═╣║║║║ ╦║ ║${reset}${dim} * ✧${reset}
27
27
  ${dim} ${reset}${bold}║╚═╝ ╔═══╝ ╩╚═╩ ╩╝╚╝╚═╝╚═╝${reset}${dim} ✦ . *${reset}
28
28
  ${dim} ${reset}${bold}╚══╗ ║${reset}${dim} * RSC Wrangler ✧ ✦${reset}
29
- ${dim} * ${reset}${bold}║ ╠═${reset}${dim} * ✧. ╱${reset}
30
- ${bold}══════╝ ╚═════════╩═══${reset}${dim} ✦ *${reset}
29
+ ${dim} * ${reset}${bold}║ ║${reset}${dim} * ✧. ╱${reset}
30
+ ${dim} ${reset}${bold}═══╝ ╚════${reset}${dim} ✦ *${reset}
31
31
 
32
32
  v${version} · ${preset} · ${mode}
33
33
  `;
@@ -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,
@@ -59,7 +56,7 @@ export function extractHandlerExportsFromChunk(
59
56
  if (detectPassthrough) {
60
57
  const eFnName = escapeRegExp(fnName);
61
58
  const callStartRe = new RegExp(
62
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
59
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
63
60
  );
64
61
  const callStart = callStartRe.exec(chunkCode);
65
62
  if (callStart) {
@@ -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(
@@ -98,8 +94,10 @@ export function evictHandlerCode(
98
94
  if (passthrough) continue;
99
95
 
100
96
  const eName = escapeRegExp(name);
97
+ // Match const/let/var: Rolldown (Vite 8) emits top-level bindings in the
98
+ // non-minified RSC bundle as `var`, whereas Rollup used `const`.
101
99
  const callStartRe = new RegExp(
102
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
100
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`,
103
101
  );
104
102
  const startMatch = callStartRe.exec(modified);
105
103
  if (!startMatch) continue;
@@ -108,13 +106,11 @@ export function evictHandlerCode(
108
106
  const closePos = findMatchingParenInBundle(modified, afterOpen);
109
107
  if (closePos === -1) continue;
110
108
 
111
- // Skip trailing whitespace and optional semicolon
112
109
  let rangeEnd = closePos;
113
110
  while (rangeEnd < modified.length && /\s/.test(modified[rangeEnd]))
114
111
  rangeEnd++;
115
112
  if (modified[rangeEnd] === ";") rangeEnd++;
116
113
 
117
- // Validate: matched range must contain the expected handlerId
118
114
  const matched = modified.slice(startMatch.index, rangeEnd);
119
115
  if (!matched.includes(handlerId)) continue;
120
116
 
@@ -122,7 +118,6 @@ export function evictHandlerCode(
122
118
  modified =
123
119
  modified.slice(0, startMatch.index) + stub + modified.slice(rangeEnd);
124
120
 
125
- // Remove the now-redundant $$id assignment line.
126
121
  modified = modified.replace(
127
122
  new RegExp(`\\n${eName}\\.\\$\\$id\\s*=\\s*"[^"]+";`),
128
123
  "",
@@ -0,0 +1,184 @@
1
+ import type { ClientChunkMeta, ClientChunks } from "../plugin-types.js";
2
+ import { createRangoDebugger, NS } from "../debug.js";
3
+ import { hashRefKey } from "../plugins/client-ref-hashing.js";
4
+
5
+ /** The callback shape @vitejs/plugin-rsc's `clientChunks` option accepts. */
6
+ export type RscClientChunksFn = (meta: ClientChunkMeta) => string | undefined;
7
+
8
+ /**
9
+ * Build-time context the discovery pass populates and the built-in strategy
10
+ * reads. It refines how the catch-all (no route-root marker) modules are grouped
11
+ * without touching marker splits or the shared runtime:
12
+ *
13
+ * - `fallbackRefs`: production hashes of the `"use client"` modules a consumer
14
+ * registered as `errorBoundary`/`notFoundBoundary` fallbacks. Pulled into a
15
+ * dedicated `app-fallback` chunk so the error UI is not co-bundled with the
16
+ * very route code it exists to catch failures for (resilience), and so the
17
+ * chunk it would otherwise sit in gets named after a real module rather than
18
+ * the boundary. Populated by reading each fallback element's client-reference
19
+ * `$$id` during discovery (see discover-routers).
20
+ */
21
+ export interface ClientChunkContext {
22
+ fallbackRefs: Set<string>;
23
+ }
24
+
25
+ /**
26
+ * Opt-in observability for the built-in strategy. The route-root marker list is
27
+ * intentionally finite (see {@link ROUTE_ROOT_DIRS}); a consumer whose layout
28
+ * has no recognized marker (e.g. `src/parts/<feature>/…`) silently inherits the
29
+ * default grouping (no per-route split). That silence is the only real downside
30
+ * of a convention-based default, so we make the decision observable: run a build
31
+ * with `DEBUG=rango:chunks` to see, per client module, which route group it was
32
+ * assigned to or why it fell back to the shared grouping. Zero cost when off
33
+ * (the debugger is `undefined` unless the namespace is enabled). For full control
34
+ * over any layout, pass a `clientChunks` function instead of relying on the
35
+ * convention — that is the supported configurability path, not widening the list.
36
+ */
37
+ const debugChunks = createRangoDebugger(NS.chunks);
38
+
39
+ /**
40
+ * Modules that must stay on the default (shared) grouping regardless of strategy:
41
+ * React, the router client runtime, and anything in node_modules. Splitting these
42
+ * out per route would fragment the shared baseline and regress cache reuse — they
43
+ * are loaded on every route, so they belong in shared chunks.
44
+ *
45
+ * The Rango runtime is matched by package root only: `@rangojs/router` (the
46
+ * installed/aliased name) and the workspace `packages/(rangojs-router|rsc-router)/(src|dist)/`.
47
+ * The `(src|dist)` anchor matches the package's own source/build output but NOT
48
+ * consumer apps that merely live under a `packages/rangojs-router/` ancestor (the
49
+ * in-repo e2e apps), so their app components remain splittable. We deliberately do
50
+ * NOT match a bare `/src/browser/`: that is a consumer-owned path (a consumer's own
51
+ * `src/browser/Foo.tsx` must still split).
52
+ *
53
+ * We test BOTH `meta.id` (absolute) and `meta.normalizedId`. `normalizedId` is the
54
+ * project-root-relative form plugin-rsc derives (e.g. `../../src/browser/react/Link.tsx`
55
+ * for the in-repo runtime), which the package-root patterns miss; the absolute `id`
56
+ * always contains the package's real location, so it reliably catches the runtime.
57
+ */
58
+ function isSharedRuntime(meta: ClientChunkMeta): boolean {
59
+ return [meta.id, meta.normalizedId].some(
60
+ (path) =>
61
+ path.includes("/node_modules/") ||
62
+ /\/@rangojs\/router\//.test(path) ||
63
+ /\/packages\/(rangojs-router|rsc-router)\/(src|dist)\//.test(path),
64
+ );
65
+ }
66
+
67
+ /** Sanitize a raw group name into a filesystem/Rollup-safe chunk name fragment. */
68
+ function sanitizeGroup(name: string): string {
69
+ return name.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "app";
70
+ }
71
+
72
+ /**
73
+ * Directory names that conventionally hold one sub-directory per route/feature.
74
+ * When a `"use client"` module lives under one of these, the built-in strategy
75
+ * keys the chunk on the segment IMMEDIATELY AFTER the marker (the route id),
76
+ * rather than the module's immediate parent directory. This is what keeps
77
+ * `routes/foo/components/Button.tsx` and `routes/bar/components/Button.tsx` in
78
+ * `app-foo` / `app-bar` instead of colliding in a single `app-components`.
79
+ *
80
+ * Route identity lives in the path PREFIX; the immediate parent (a suffix) is
81
+ * only a reliable proxy for the un-nested `routes/<route>/Widget.tsx` layout.
82
+ */
83
+ const ROUTE_ROOT_DIRS = new Set([
84
+ "routes",
85
+ "route",
86
+ "pages",
87
+ "page",
88
+ "app",
89
+ "features",
90
+ "feature",
91
+ "views",
92
+ "view",
93
+ "handlers",
94
+ "urls",
95
+ "modules",
96
+ "screens",
97
+ "sections",
98
+ ]);
99
+
100
+ /**
101
+ * Built-in strategy used when `clientChunks: true` (also the default). Splits app
102
+ * client components by route/feature identity ONLY where it can recognize a route
103
+ * structure; everywhere else it inherits the default grouping (returns undefined).
104
+ * This conservatism is what makes it safe as a default:
105
+ *
106
+ * - A recognized route structure (`routes/<id>/…`, `app/<id>/…`, `handlers/<id>/…`
107
+ * etc.) splits into a per-route chunk `app-<id>`, at any nesting depth.
108
+ * - A flat `src/components/Button.tsx`, or host sub-apps already split by a dynamic
109
+ * `import()` boundary (each app's `serverChunk` differs), get `undefined` and so
110
+ * keep `@vitejs/plugin-rsc`'s default `serverChunk` grouping — i.e. NO change
111
+ * versus not enabling the option. Returning a parent-dir name here would instead
112
+ * merge unrelated modules (e.g. every host app's `components/Layout.tsx` into one
113
+ * `app-components`), re-introducing cross-app leakage.
114
+ *
115
+ * Resolution order:
116
+ * 1. Shared runtime (React / router / node_modules) -> `undefined` (never split).
117
+ * 2. A registered error/notFound fallback (`ctx.fallbackRefs`) -> `app-fallback`,
118
+ * regardless of location, so the error UI is decoupled from the happy path.
119
+ * 3. A {@link ROUTE_ROOT_DIRS} marker with a directory after it -> key on that
120
+ * next segment (the route id), robust to any nesting depth.
121
+ * 4. Otherwise `undefined` (inherit the default `serverChunk` grouping).
122
+ */
123
+ export function directoryClientChunks(
124
+ meta: ClientChunkMeta,
125
+ ctx?: ClientChunkContext,
126
+ ): string | undefined {
127
+ if (isSharedRuntime(meta)) {
128
+ // React / router runtime / node_modules: always shared, expected, uninteresting.
129
+ return undefined;
130
+ }
131
+ // Registered error/notFound fallbacks -> a dedicated chunk. The error UI must
132
+ // not co-bundle with the code it catches failures for, and removing it lets the
133
+ // chunk it would otherwise anchor be named after a real module, not the boundary.
134
+ if (
135
+ ctx?.fallbackRefs.size &&
136
+ ctx.fallbackRefs.has(hashRefKey(meta.normalizedId))
137
+ ) {
138
+ debugChunks?.("fallback %s -> app-fallback", meta.normalizedId);
139
+ return "app-fallback";
140
+ }
141
+ const segments = meta.normalizedId.split("/").filter(Boolean);
142
+ const dirCount = segments.length - 1; // exclude the filename
143
+ if (dirCount >= 1) {
144
+ // Route-root marker -> the segment after it is the route id. First marker
145
+ // wins, so a top-level route owns its whole subtree. The `< dirCount - 1`
146
+ // bound guarantees the segment after the marker is a directory, not the file.
147
+ for (let i = 0; i < dirCount - 1; i++) {
148
+ if (ROUTE_ROOT_DIRS.has(segments[i].toLowerCase())) {
149
+ const group = `app-${sanitizeGroup(segments[i + 1])}`;
150
+ debugChunks?.("split %s -> %s", meta.normalizedId, group);
151
+ return group;
152
+ }
153
+ }
154
+ }
155
+ // No recognized route structure -> inherit the default serverChunk grouping.
156
+ // This is the actionable "silent" case: app code that did NOT split by route.
157
+ // Surface it (under DEBUG=rango:chunks) so a consumer can see their layout
158
+ // missed the convention and either colocate under a marker dir or pass a fn.
159
+ debugChunks?.(
160
+ "shared %s (no route-root marker; inherits default grouping)",
161
+ meta.normalizedId,
162
+ );
163
+ return undefined;
164
+ }
165
+
166
+ /**
167
+ * Resolve a Rango `clientChunks` option into a @vitejs/plugin-rsc `clientChunks`
168
+ * callback, or `undefined` to leave plugin-rsc on its default (serverChunk)
169
+ * grouping.
170
+ *
171
+ * - `false` / `undefined` -> `undefined` (no override).
172
+ * - `true` -> the built-in {@link directoryClientChunks} strategy,
173
+ * bound to the discovery-populated {@link ClientChunkContext} (fallback chunk).
174
+ * - function -> the user's function, used verbatim (full control; the
175
+ * fallback refinement does not apply — the consumer owns the grouping).
176
+ */
177
+ export function resolveClientChunks(
178
+ option: ClientChunks | undefined,
179
+ ctx?: ClientChunkContext,
180
+ ): RscClientChunksFn | undefined {
181
+ if (!option) return undefined;
182
+ if (option === true) return (meta) => directoryClientChunks(meta, ctx);
183
+ return option;
184
+ }
@@ -0,0 +1,171 @@
1
+ import type { Plugin, ResolvedConfig, UserConfig } from "vite";
2
+
3
+ /**
4
+ * Whether a user plugin must NOT be forwarded into the discovery temp server.
5
+ *
6
+ * Framework-owned plugins are matched precisely -- by exact name or a
7
+ * namespaced prefix -- rather than by a loose substring/word prefix, so an
8
+ * unrelated user resolver named e.g. `rsc-paths` or `cloudflare-kv-alias` is
9
+ * still forwarded (it would otherwise reproduce issue #500).
10
+ *
11
+ * - `vite:*` Vite core + official plugins (incl.
12
+ * @vitejs/plugin-react's `vite:react-*`). The temp
13
+ * server already provides its own core; forwarding
14
+ * these would duplicate or conflict with it.
15
+ * - `rsc` / `rsc:*` @vitejs/plugin-rsc. createTempRscServer instantiates
16
+ * its own rsc() plugin; forwarding would duplicate it.
17
+ * Matched exactly (`rsc`) or by the `rsc:` namespace --
18
+ * NOT every `rsc`-prefixed name.
19
+ * - `@rangojs/router*` Our own plugins. The discovery plugin spawns the
20
+ * temp server, so forwarding would recurse infinitely.
21
+ * - `@cloudflare/vite-plugin*` / @cloudflare/vite-plugin (emits the scoped
22
+ * `vite-plugin-cloudflare*` `@cloudflare/vite-plugin` and unscoped
23
+ * `vite-plugin-cloudflare` / `vite-plugin-cloudflare:*`).
24
+ * Forwarding re-inits workerd, defeating the Node temp
25
+ * server. Matched specifically so a scoped user resolver
26
+ * like `@cloudflare/kv-alias` is still forwarded.
27
+ */
28
+ function isDenied(name: string): boolean {
29
+ return (
30
+ name.startsWith("vite:") ||
31
+ name === "rsc" ||
32
+ name.startsWith("rsc:") ||
33
+ name.startsWith("@rangojs/router") ||
34
+ name.startsWith("@cloudflare/vite-plugin") ||
35
+ name.startsWith("vite-plugin-cloudflare")
36
+ );
37
+ }
38
+
39
+ /**
40
+ * A plugin participates in resolution if it exposes `resolveId` or `load`.
41
+ * Plugins that only transform, configure the server, or hook into the build
42
+ * lifecycle do not affect how bare specifiers resolve, so we skip them to keep
43
+ * the forwarded surface minimal.
44
+ */
45
+ function hasResolutionHooks(p: Plugin): boolean {
46
+ return Boolean((p as any).resolveId || (p as any).load);
47
+ }
48
+
49
+ /**
50
+ * Strip a resolved plugin instance down to its resolution hooks plus the
51
+ * gating fields that decide whether/where it runs.
52
+ *
53
+ * We reuse the SAME instance objects captured from `config.plugins`. By the
54
+ * time `configResolved` fires on the discovery plugin, every plugin's own
55
+ * `config`/`configResolved` has already run on the main server, so any state
56
+ * a `resolveId` hook depends on (e.g. vite-tsconfig-paths' compiled path
57
+ * matcher, held in closure) is already populated. Forwarding only the
58
+ * resolution hooks therefore preserves correct resolution while avoiding a
59
+ * second `buildStart`/`configureServer`/`config` lifecycle in the temp server.
60
+ *
61
+ * `enforce` and `applyToEnvironment` are preserved so ordering and per-environment
62
+ * gating match the real pipeline.
63
+ *
64
+ * `apply` is intentionally dropped. Vite filters plugins by `apply` against the
65
+ * command during config resolution, so the `config.plugins` we read here is
66
+ * already command-filtered by the main server (build: `apply: "build"` +
67
+ * unconditional; dev: `apply: "serve"` + unconditional). The discovery temp
68
+ * server is always created with `createServer` (`command === "serve"`), so a
69
+ * forwarded `apply: "build"` plugin would be filtered straight back out -- even
70
+ * during a production build, where build-only resolvers are exactly what
71
+ * static/prerender rendering needs. Since the source list is already correct for
72
+ * the current command, the forwarded copy must carry no `apply` gate.
73
+ */
74
+ function stripToResolutionHooks(p: Plugin): Plugin {
75
+ const stripped: Plugin = { name: p.name };
76
+ if ((p as any).enforce) (stripped as any).enforce = (p as any).enforce;
77
+ if ((p as any).applyToEnvironment)
78
+ (stripped as any).applyToEnvironment = (p as any).applyToEnvironment;
79
+ if ((p as any).resolveId) (stripped as any).resolveId = (p as any).resolveId;
80
+ if ((p as any).load) (stripped as any).load = (p as any).load;
81
+ return stripped;
82
+ }
83
+
84
+ /**
85
+ * Pick the user's resolution plugins from the resolved plugin list, denylist
86
+ * framework-owned plugins, keep only those with resolution hooks, and strip
87
+ * each to its resolution surface. Returns plugin objects safe to drop into the
88
+ * discovery temp server's `plugins` array.
89
+ */
90
+ export function selectForwardableResolvePlugins(
91
+ plugins: readonly Plugin[] | undefined,
92
+ ): Plugin[] {
93
+ if (!plugins) return [];
94
+ const forwarded: Plugin[] = [];
95
+ for (const p of plugins) {
96
+ const name = p?.name;
97
+ if (!name || isDenied(name)) continue;
98
+ if (!hasResolutionHooks(p)) continue;
99
+ forwarded.push(stripToResolutionHooks(p));
100
+ }
101
+ return forwarded;
102
+ }
103
+
104
+ /**
105
+ * The resolution-relevant slice of the user's resolved config that is plain
106
+ * data (no plugin re-execution): everything under `resolve` that influences
107
+ * how specifiers map to files, plus `define` and `oxc` so transforms and
108
+ * compile-time constants match request time.
109
+ */
110
+ export interface ForwardedRunnerConfig {
111
+ resolve: UserConfig["resolve"];
112
+ define: UserConfig["define"];
113
+ oxc: UserConfig["oxc"];
114
+ }
115
+
116
+ /**
117
+ * Extract the data-only config slice to mirror into the discovery temp server.
118
+ * `alias` is included here so callers no longer need to thread it separately.
119
+ *
120
+ * `tsconfigPaths` is forwarded so Vite 8's native tsconfig `paths` resolution
121
+ * (a top-level `resolve` flag, off by default) reaches the temp server. The
122
+ * server is created with `configFile: false` and an explicit, allowlisted
123
+ * resolve slice, so a flag that is not copied here is simply absent during
124
+ * discovery — which would make path-aliased imports fail at prerender/static
125
+ * time the same way unforwarded resolveId plugins did (issue #500).
126
+ *
127
+ * `oxc` keeps the user's options but always pins the RSC-required JSX runtime
128
+ * (automatic, react), since the temp server compiles the handler graph as
129
+ * React server components regardless of the user's app-level JSX config. Vite 8
130
+ * replaced the deprecated `esbuild` transform option with `oxc`, so we read and
131
+ * forward `oxc` exclusively — no `esbuild` field is touched.
132
+ */
133
+ export function pickForwardedRunnerConfig(
134
+ config: ResolvedConfig,
135
+ ): ForwardedRunnerConfig {
136
+ const r = config.resolve ?? ({} as ResolvedConfig["resolve"]);
137
+ const resolve: NonNullable<UserConfig["resolve"]> = {};
138
+ if (r.alias !== undefined) resolve.alias = r.alias as any;
139
+ if (r.dedupe !== undefined) resolve.dedupe = r.dedupe;
140
+ if (r.conditions !== undefined) resolve.conditions = r.conditions;
141
+ if (r.mainFields !== undefined) resolve.mainFields = r.mainFields;
142
+ if (r.extensions !== undefined) resolve.extensions = r.extensions;
143
+ if (r.preserveSymlinks !== undefined)
144
+ resolve.preserveSymlinks = r.preserveSymlinks;
145
+ if (r.tsconfigPaths !== undefined) resolve.tsconfigPaths = r.tsconfigPaths;
146
+
147
+ // Pin the RSC JSX runtime on top of the user's oxc options. The user's
148
+ // jsx sub-options (e.g. `development`) are preserved when present; only
149
+ // `runtime`/`importSource` are forced to the values the RSC compile needs.
150
+ const userOxc = config.oxc;
151
+ const userJsx =
152
+ userOxc &&
153
+ typeof userOxc === "object" &&
154
+ typeof userOxc.jsx === "object" &&
155
+ userOxc.jsx !== null
156
+ ? userOxc.jsx
157
+ : {};
158
+ const oxc: UserConfig["oxc"] =
159
+ userOxc && typeof userOxc === "object"
160
+ ? {
161
+ ...userOxc,
162
+ jsx: { ...userJsx, runtime: "automatic", importSource: "react" },
163
+ }
164
+ : { jsx: { runtime: "automatic", importSource: "react" } };
165
+
166
+ return {
167
+ resolve,
168
+ define: config.define,
169
+ oxc,
170
+ };
171
+ }
@@ -1,62 +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
- export function flattenLeafEntries(
9
- prefixTree: Record<string, any>,
10
- routeManifest: Record<string, string>,
11
- result: Array<{ staticPrefix: string; routes: Record<string, string> }>,
12
- ): void {
13
- function visit(node: any): void {
14
- const children = node.children || {};
15
- if (
16
- Object.keys(children).length === 0 &&
17
- node.routes &&
18
- node.routes.length > 0
19
- ) {
20
- // Leaf node: collect its routes from the manifest
21
- const routes: Record<string, string> = {};
22
- for (const name of node.routes) {
23
- if (name in routeManifest) {
24
- routes[name] = routeManifest[name];
25
- }
26
- }
27
- result.push({ staticPrefix: node.staticPrefix, routes });
28
- } else {
29
- // Non-leaf: recurse into children
30
- for (const child of Object.values(children)) {
31
- visit(child);
32
- }
33
- }
34
- }
35
- for (const node of Object.values(prefixTree)) {
36
- visit(node);
37
- }
38
- }
39
-
40
- /**
41
- * Walk prefix tree to map each route name to its scope's staticPrefix.
42
- */
43
- export function buildRouteToStaticPrefix(
44
- prefixTree: Record<string, any>,
45
- result: Record<string, string>,
46
- ): void {
47
- function visit(node: any): void {
48
- const sp = node.staticPrefix || "";
49
- for (const name of node.routes || []) {
50
- result[name] = sp;
51
- }
52
- for (const child of Object.values(node.children || {})) {
53
- visit(child);
54
- }
55
- }
56
- for (const node of Object.values(prefixTree)) {
57
- visit(node);
58
- }
59
- }
1
+ export {
2
+ flattenLeafEntries,
3
+ buildRouteToStaticPrefix,
4
+ } from "../../build/prefix-tree-utils.js";
60
5
 
61
6
  /**
62
7
  * Wrap a value as `JSON.parse('...')` instead of a JS object literal.