@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327

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 (440) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +301 -797
  3. package/dist/bin/rango.js +603 -145
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3750 -1160
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +96 -24
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +85 -6
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +228 -33
  12. package/skills/caching/SKILL.md +336 -19
  13. package/skills/catalog.json +271 -0
  14. package/skills/comparison/SKILL.md +50 -0
  15. package/skills/comparison/agents/openai.yaml +4 -0
  16. package/skills/comparison/references/framework-comparison.md +837 -0
  17. package/skills/composability/SKILL.md +110 -4
  18. package/skills/css/SKILL.md +76 -0
  19. package/skills/debug-manifest/SKILL.md +5 -3
  20. package/skills/defer-hydration/SKILL.md +235 -0
  21. package/skills/document-cache/SKILL.md +87 -56
  22. package/skills/fonts/SKILL.md +1 -1
  23. package/skills/handler-use/SKILL.md +12 -10
  24. package/skills/hooks/SKILL.md +73 -691
  25. package/skills/hooks/data.md +273 -0
  26. package/skills/hooks/handle-and-actions.md +103 -0
  27. package/skills/hooks/navigation.md +110 -0
  28. package/skills/hooks/outlets.md +41 -0
  29. package/skills/hooks/state.md +228 -0
  30. package/skills/hooks/urls.md +135 -0
  31. package/skills/host-router/SKILL.md +129 -27
  32. package/skills/i18n/SKILL.md +276 -0
  33. package/skills/intercept/SKILL.md +75 -19
  34. package/skills/layout/SKILL.md +40 -19
  35. package/skills/links/SKILL.md +247 -17
  36. package/skills/loader/SKILL.md +248 -10
  37. package/skills/middleware/SKILL.md +25 -13
  38. package/skills/migrate-nextjs/SKILL.md +205 -20
  39. package/skills/migrate-react-router/SKILL.md +59 -670
  40. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  41. package/skills/migrate-react-router/component-migration.md +196 -0
  42. package/skills/migrate-react-router/data-and-actions.md +225 -0
  43. package/skills/migrate-react-router/route-mapping.md +271 -0
  44. package/skills/mime-routes/SKILL.md +29 -2
  45. package/skills/observability/SKILL.md +202 -0
  46. package/skills/parallel/SKILL.md +40 -10
  47. package/skills/ppr/SKILL.md +616 -0
  48. package/skills/prerender/SKILL.md +72 -60
  49. package/skills/rango/SKILL.md +318 -26
  50. package/skills/react-compiler/SKILL.md +168 -0
  51. package/skills/response-routes/SKILL.md +138 -49
  52. package/skills/route/SKILL.md +117 -9
  53. package/skills/router-setup/SKILL.md +44 -9
  54. package/skills/scripts/SKILL.md +179 -0
  55. package/skills/server-actions/SKILL.md +776 -0
  56. package/skills/shell-manifest/SKILL.md +185 -0
  57. package/skills/streams-and-websockets/SKILL.md +283 -0
  58. package/skills/tailwind/SKILL.md +28 -4
  59. package/skills/testing/SKILL.md +130 -0
  60. package/skills/testing/bindings.md +103 -0
  61. package/skills/testing/cache-prerender.md +127 -0
  62. package/skills/testing/client-components.md +124 -0
  63. package/skills/testing/e2e-parity.md +125 -0
  64. package/skills/testing/flight.md +91 -0
  65. package/skills/testing/handles.md +131 -0
  66. package/skills/testing/loader.md +128 -0
  67. package/skills/testing/middleware.md +99 -0
  68. package/skills/testing/render-handler.md +122 -0
  69. package/skills/testing/response-routes.md +95 -0
  70. package/skills/testing/reverse-and-types.md +85 -0
  71. package/skills/testing/server-actions.md +107 -0
  72. package/skills/testing/server-tree.md +128 -0
  73. package/skills/testing/setup.md +123 -0
  74. package/skills/theme/SKILL.md +1 -1
  75. package/skills/typesafety/SKILL.md +45 -626
  76. package/skills/typesafety/env-and-bindings.md +254 -0
  77. package/skills/typesafety/generated-files-and-cli.md +335 -0
  78. package/skills/typesafety/params-and-search.md +153 -0
  79. package/skills/typesafety/route-types.md +209 -0
  80. package/skills/use-cache/SKILL.md +74 -15
  81. package/skills/vercel/SKILL.md +128 -0
  82. package/skills/view-transitions/SKILL.md +337 -0
  83. package/src/__augment-tests__/augment.ts +81 -0
  84. package/src/__augment-tests__/augmented.check.ts +116 -0
  85. package/src/__internal.ts +0 -65
  86. package/src/browser/action-coordinator.ts +53 -36
  87. package/src/browser/action-fence.ts +47 -0
  88. package/src/browser/app-shell.ts +39 -0
  89. package/src/browser/connection-warmup.ts +134 -0
  90. package/src/browser/cookie-name.ts +140 -0
  91. package/src/browser/event-controller.ts +252 -158
  92. package/src/browser/history-state.ts +21 -0
  93. package/src/browser/index.ts +3 -3
  94. package/src/browser/invalidate-client-cache.ts +52 -0
  95. package/src/browser/logging.ts +28 -0
  96. package/src/browser/merge-segment-loaders.ts +6 -4
  97. package/src/browser/navigation-bridge.ts +94 -25
  98. package/src/browser/navigation-client.ts +144 -79
  99. package/src/browser/navigation-store-handle.ts +38 -0
  100. package/src/browser/navigation-store.ts +161 -73
  101. package/src/browser/navigation-transaction.ts +9 -59
  102. package/src/browser/network-error-handler.ts +34 -7
  103. package/src/browser/partial-update.ts +183 -144
  104. package/src/browser/prefetch/cache.ts +242 -77
  105. package/src/browser/prefetch/fetch.ts +325 -69
  106. package/src/browser/prefetch/queue.ts +61 -12
  107. package/src/browser/rango-state.ts +158 -76
  108. package/src/browser/react/Link.tsx +58 -20
  109. package/src/browser/react/NavigationProvider.tsx +202 -120
  110. package/src/browser/react/ScrollRestoration.tsx +10 -6
  111. package/src/browser/react/filter-segment-order.ts +66 -7
  112. package/src/browser/react/index.ts +0 -48
  113. package/src/browser/react/location-state-shared.ts +178 -8
  114. package/src/browser/react/location-state.ts +39 -14
  115. package/src/browser/react/use-action.ts +6 -15
  116. package/src/browser/react/use-handle.ts +17 -14
  117. package/src/browser/react/use-href.tsx +8 -1
  118. package/src/browser/react/use-link-status.ts +33 -8
  119. package/src/browser/react/use-navigation.ts +32 -7
  120. package/src/browser/react/use-params.ts +20 -10
  121. package/src/browser/react/use-reverse.ts +106 -0
  122. package/src/browser/react/use-router.ts +25 -3
  123. package/src/browser/react/use-search-params.ts +0 -5
  124. package/src/browser/react/use-segments.ts +11 -21
  125. package/src/browser/response-adapter.ts +99 -8
  126. package/src/browser/rsc-router.tsx +145 -28
  127. package/src/browser/scroll-restoration.ts +37 -22
  128. package/src/browser/segment-reconciler.ts +31 -21
  129. package/src/browser/segment-structure-assert.ts +2 -2
  130. package/src/browser/server-action-bridge.ts +236 -65
  131. package/src/browser/types.ts +102 -9
  132. package/src/browser/validate-redirect-origin.ts +43 -16
  133. package/src/build/collect-fallback-refs.ts +107 -0
  134. package/src/build/generate-manifest.ts +203 -154
  135. package/src/build/generate-route-types.ts +3 -1
  136. package/src/build/index.ts +11 -3
  137. package/src/build/prefix-tree-utils.ts +123 -0
  138. package/src/build/route-trie.ts +152 -21
  139. package/src/build/route-types/ast-route-extraction.ts +15 -8
  140. package/src/build/route-types/codegen.ts +16 -5
  141. package/src/build/route-types/include-resolution.ts +456 -62
  142. package/src/build/route-types/param-extraction.ts +6 -3
  143. package/src/build/route-types/per-module-writer.ts +22 -6
  144. package/src/build/route-types/router-processing.ts +128 -51
  145. package/src/build/route-types/scan-filter.ts +1 -1
  146. package/src/build/route-types/source-scan.ts +216 -0
  147. package/src/build/runtime-discovery.ts +13 -21
  148. package/src/cache/cache-error.ts +104 -0
  149. package/src/cache/cache-key-utils.ts +58 -13
  150. package/src/cache/cache-policy.ts +108 -34
  151. package/src/cache/cache-runtime.ts +421 -58
  152. package/src/cache/cache-scope.ts +187 -96
  153. package/src/cache/cache-tag.ts +149 -0
  154. package/src/cache/cf/cf-base64.ts +33 -0
  155. package/src/cache/cf/cf-cache-constants.ts +127 -0
  156. package/src/cache/cf/cf-cache-store.ts +2202 -372
  157. package/src/cache/cf/cf-cache-types.ts +349 -0
  158. package/src/cache/cf/cf-kv-utils.ts +46 -0
  159. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  160. package/src/cache/cf/index.ts +6 -16
  161. package/src/cache/document-cache.ts +126 -41
  162. package/src/cache/handle-snapshot.ts +70 -0
  163. package/src/cache/index.ts +23 -20
  164. package/src/cache/memory-segment-store.ts +243 -37
  165. package/src/cache/profile-registry.ts +46 -31
  166. package/src/cache/read-through-swr.ts +56 -12
  167. package/src/cache/segment-codec.ts +13 -21
  168. package/src/cache/shell-snapshot.ts +417 -0
  169. package/src/cache/tag-invalidation.ts +230 -0
  170. package/src/cache/types.ts +180 -99
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1127 -0
  173. package/src/client.rsc.tsx +41 -21
  174. package/src/client.tsx +33 -61
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +108 -0
  177. package/src/component-utils.ts +19 -0
  178. package/src/components/DefaultDocument.tsx +8 -2
  179. package/src/context-var.ts +18 -6
  180. package/src/decode-loader-results.ts +52 -0
  181. package/src/defer.ts +185 -0
  182. package/src/deps/ssr.ts +0 -1
  183. package/src/encode-kv.ts +49 -0
  184. package/src/errors.ts +30 -4
  185. package/src/escape-script.ts +52 -0
  186. package/src/handle.ts +67 -37
  187. package/src/handles/MetaTags.tsx +24 -53
  188. package/src/handles/Scripts.tsx +183 -0
  189. package/src/handles/breadcrumbs.ts +35 -8
  190. package/src/handles/deferred-resolution.ts +127 -0
  191. package/src/handles/is-thenable.ts +18 -0
  192. package/src/handles/meta.ts +14 -40
  193. package/src/handles/script.ts +244 -0
  194. package/src/host/cookie-handler.ts +9 -60
  195. package/src/host/errors.ts +13 -22
  196. package/src/host/index.ts +9 -2
  197. package/src/host/pattern-matcher.ts +23 -52
  198. package/src/host/router.ts +107 -99
  199. package/src/host/testing.ts +40 -27
  200. package/src/host/types.ts +37 -4
  201. package/src/host/utils.ts +1 -1
  202. package/src/href-client.ts +137 -22
  203. package/src/index.rsc.ts +97 -12
  204. package/src/index.ts +98 -14
  205. package/src/internal-debug.ts +11 -10
  206. package/src/loader-store.ts +500 -0
  207. package/src/loader.rsc.ts +20 -13
  208. package/src/loader.ts +12 -11
  209. package/src/missing-id-error.ts +68 -0
  210. package/src/outlet-context.ts +1 -1
  211. package/src/outlet-provider.tsx +1 -5
  212. package/src/prerender/param-hash.ts +16 -16
  213. package/src/prerender/store.ts +32 -37
  214. package/src/prerender.ts +78 -10
  215. package/src/redirect-origin.ts +114 -0
  216. package/src/regex-escape.ts +8 -0
  217. package/src/render-error-thrower.tsx +20 -0
  218. package/src/response-utils.ts +62 -0
  219. package/src/reverse.ts +65 -39
  220. package/src/root-error-boundary.tsx +1 -19
  221. package/src/route-content-wrapper.tsx +19 -77
  222. package/src/route-definition/dsl-helpers.ts +304 -309
  223. package/src/route-definition/helper-factories.ts +28 -140
  224. package/src/route-definition/helpers-types.ts +87 -59
  225. package/src/route-definition/index.ts +1 -2
  226. package/src/route-definition/redirect.ts +44 -11
  227. package/src/route-definition/resolve-handler-use.ts +12 -1
  228. package/src/route-definition/use-item-types.ts +29 -0
  229. package/src/route-map-builder.ts +41 -20
  230. package/src/route-types.ts +19 -46
  231. package/src/router/basename.ts +14 -0
  232. package/src/router/content-negotiation.ts +73 -25
  233. package/src/router/error-handling.ts +45 -18
  234. package/src/router/find-match.ts +129 -30
  235. package/src/router/handler-context.ts +27 -42
  236. package/src/router/instrument.ts +355 -0
  237. package/src/router/intercept-resolution.ts +39 -20
  238. package/src/router/lazy-includes.ts +82 -59
  239. package/src/router/loader-resolution.ts +167 -72
  240. package/src/router/logging.ts +0 -6
  241. package/src/router/manifest.ts +74 -40
  242. package/src/router/match-api.ts +80 -55
  243. package/src/router/match-context.ts +0 -22
  244. package/src/router/match-handlers.ts +211 -165
  245. package/src/router/match-middleware/background-revalidation.ts +40 -24
  246. package/src/router/match-middleware/cache-lookup.ts +159 -285
  247. package/src/router/match-middleware/cache-store.ts +64 -52
  248. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  249. package/src/router/match-middleware/segment-resolution.ts +0 -22
  250. package/src/router/match-pipelines.ts +1 -42
  251. package/src/router/match-result.ts +69 -79
  252. package/src/router/metrics.ts +0 -34
  253. package/src/router/middleware-types.ts +7 -134
  254. package/src/router/middleware.ts +298 -172
  255. package/src/router/navigation-snapshot.ts +7 -56
  256. package/src/router/params-util.ts +23 -0
  257. package/src/router/parse-pattern.ts +115 -0
  258. package/src/router/pattern-matching.ts +181 -150
  259. package/src/router/prefetch-cache-ttl.ts +51 -0
  260. package/src/router/prefetch-limits.ts +37 -0
  261. package/src/router/prerender-match.ts +112 -67
  262. package/src/router/preview-match.ts +6 -2
  263. package/src/router/request-classification.ts +50 -69
  264. package/src/router/revalidation.ts +123 -73
  265. package/src/router/route-snapshot.ts +14 -3
  266. package/src/router/router-context.ts +6 -29
  267. package/src/router/router-interfaces.ts +115 -36
  268. package/src/router/router-options.ts +166 -5
  269. package/src/router/router-registry.ts +2 -5
  270. package/src/router/segment-resolution/fresh.ts +131 -86
  271. package/src/router/segment-resolution/helpers.ts +86 -6
  272. package/src/router/segment-resolution/loader-cache.ts +139 -39
  273. package/src/router/segment-resolution/loader-mask.ts +67 -0
  274. package/src/router/segment-resolution/loader-snapshot.ts +251 -0
  275. package/src/router/segment-resolution/revalidation.ts +272 -320
  276. package/src/router/segment-resolution/static-store.ts +19 -5
  277. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  278. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  279. package/src/router/segment-resolution.ts +5 -1
  280. package/src/router/segment-wrappers.ts +6 -5
  281. package/src/router/state-cookie-name.ts +33 -0
  282. package/src/router/substitute-pattern-params.ts +75 -0
  283. package/src/router/telemetry-otel.ts +160 -200
  284. package/src/router/telemetry.ts +105 -20
  285. package/src/router/timeout.ts +0 -20
  286. package/src/router/tracing.ts +215 -0
  287. package/src/router/trie-matching.ts +171 -59
  288. package/src/router/types.ts +9 -63
  289. package/src/router/url-params.ts +57 -0
  290. package/src/router.ts +157 -71
  291. package/src/rsc/full-payload.ts +70 -0
  292. package/src/rsc/handler-context.ts +3 -2
  293. package/src/rsc/handler.ts +291 -217
  294. package/src/rsc/helpers.ts +168 -46
  295. package/src/rsc/index.ts +2 -5
  296. package/src/rsc/json-route-result.ts +38 -0
  297. package/src/rsc/loader-fetch.ts +114 -38
  298. package/src/rsc/manifest-init.ts +29 -42
  299. package/src/rsc/nonce.ts +10 -1
  300. package/src/rsc/origin-guard.ts +39 -25
  301. package/src/rsc/progressive-enhancement.ts +124 -13
  302. package/src/rsc/redirect-guard.ts +100 -0
  303. package/src/rsc/response-cache-serve.ts +238 -0
  304. package/src/rsc/response-error.ts +79 -12
  305. package/src/rsc/response-route-handler.ts +99 -189
  306. package/src/rsc/rsc-rendering.ts +421 -76
  307. package/src/rsc/runtime-warnings.ts +23 -10
  308. package/src/rsc/server-action.ts +282 -116
  309. package/src/rsc/shell-capture.ts +1158 -0
  310. package/src/rsc/shell-serve.ts +150 -0
  311. package/src/rsc/ssr-setup.ts +16 -0
  312. package/src/rsc/transition-gate.ts +89 -0
  313. package/src/rsc/types.ts +53 -5
  314. package/src/runtime-env.ts +18 -0
  315. package/src/search-params.ts +35 -30
  316. package/src/segment-loader-promise.ts +49 -4
  317. package/src/segment-system.tsx +350 -149
  318. package/src/serialize.ts +243 -0
  319. package/src/server/context.ts +208 -51
  320. package/src/server/cookie-parse.ts +32 -0
  321. package/src/server/cookie-store.ts +152 -5
  322. package/src/server/handle-store.ts +21 -38
  323. package/src/server/loader-registry.ts +33 -42
  324. package/src/server/request-context.ts +395 -176
  325. package/src/ssr/index.tsx +458 -178
  326. package/src/ssr/ssr-root.tsx +228 -0
  327. package/src/static-handler.ts +10 -13
  328. package/src/testing/cache-status.ts +162 -0
  329. package/src/testing/collect-handle.ts +46 -0
  330. package/src/testing/dispatch.ts +813 -0
  331. package/src/testing/dom.entry.ts +22 -0
  332. package/src/testing/e2e/fixture.ts +188 -0
  333. package/src/testing/e2e/index.ts +128 -0
  334. package/src/testing/e2e/matchers.ts +35 -0
  335. package/src/testing/e2e/page-helpers.ts +272 -0
  336. package/src/testing/e2e/parity.ts +387 -0
  337. package/src/testing/e2e/server.ts +195 -0
  338. package/src/testing/flight-matchers.ts +97 -0
  339. package/src/testing/flight-normalize.ts +11 -0
  340. package/src/testing/flight-runtime.d.ts +57 -0
  341. package/src/testing/flight-tree.ts +682 -0
  342. package/src/testing/flight.entry.ts +52 -0
  343. package/src/testing/flight.ts +257 -0
  344. package/src/testing/generated-routes.ts +199 -0
  345. package/src/testing/index.ts +105 -0
  346. package/src/testing/internal/context.ts +371 -0
  347. package/src/testing/internal/flight-client-globals.ts +30 -0
  348. package/src/testing/internal/seed-vars.ts +54 -0
  349. package/src/testing/render-handler.ts +357 -0
  350. package/src/testing/render-route.tsx +584 -0
  351. package/src/testing/run-loader.ts +385 -0
  352. package/src/testing/run-middleware.ts +205 -0
  353. package/src/testing/run-transition-when.ts +164 -0
  354. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  355. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  356. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  357. package/src/testing/vitest-stubs/version.ts +5 -0
  358. package/src/testing/vitest.ts +305 -0
  359. package/src/theme/ThemeProvider.tsx +56 -84
  360. package/src/theme/ThemeScript.tsx +7 -9
  361. package/src/theme/constants.ts +52 -13
  362. package/src/theme/index.ts +0 -7
  363. package/src/theme/theme-context.ts +1 -5
  364. package/src/theme/theme-script.ts +22 -21
  365. package/src/theme/use-theme.ts +0 -3
  366. package/src/types/boundaries.ts +0 -35
  367. package/src/types/cache-types.ts +13 -4
  368. package/src/types/error-types.ts +30 -90
  369. package/src/types/global-namespace.ts +54 -41
  370. package/src/types/handler-context.ts +110 -62
  371. package/src/types/index.ts +3 -10
  372. package/src/types/loader-types.ts +11 -9
  373. package/src/types/request-scope.ts +112 -0
  374. package/src/types/route-config.ts +20 -52
  375. package/src/types/route-entry.ts +0 -6
  376. package/src/types/segments.ts +135 -14
  377. package/src/urls/include-helper.ts +19 -64
  378. package/src/urls/include-provider.ts +71 -0
  379. package/src/urls/index.ts +2 -11
  380. package/src/urls/path-helper-types.ts +63 -17
  381. package/src/urls/path-helper.ts +22 -106
  382. package/src/urls/pattern-types.ts +72 -19
  383. package/src/urls/response-types.ts +22 -29
  384. package/src/urls/type-extraction.ts +98 -154
  385. package/src/urls/urls-function.ts +1 -19
  386. package/src/use-loader.tsx +292 -107
  387. package/src/vercel/index.ts +11 -0
  388. package/src/vercel/tracing.ts +88 -0
  389. package/src/vite/debug.ts +185 -0
  390. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  391. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  392. package/src/vite/discovery/discover-routers.ts +127 -86
  393. package/src/vite/discovery/discovery-errors.ts +255 -0
  394. package/src/vite/discovery/gate-state.ts +171 -0
  395. package/src/vite/discovery/prerender-collection.ts +96 -68
  396. package/src/vite/discovery/route-types-writer.ts +40 -84
  397. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  398. package/src/vite/discovery/state.ts +45 -1
  399. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  400. package/src/vite/index.ts +4 -0
  401. package/src/vite/inject-client-debug.ts +88 -0
  402. package/src/vite/plugin-types.ts +210 -10
  403. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  404. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  405. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  406. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  407. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  408. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  409. package/src/vite/plugins/expose-action-id.ts +48 -95
  410. package/src/vite/plugins/expose-id-utils.ts +88 -55
  411. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  412. package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
  413. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  414. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  415. package/src/vite/plugins/expose-internal-ids.ts +505 -486
  416. package/src/vite/plugins/performance-tracks.ts +26 -25
  417. package/src/vite/plugins/refresh-cmd.ts +1 -1
  418. package/src/vite/plugins/use-cache-transform.ts +73 -83
  419. package/src/vite/plugins/vercel-output.ts +384 -0
  420. package/src/vite/plugins/version-injector.ts +40 -29
  421. package/src/vite/plugins/version-plugin.ts +37 -40
  422. package/src/vite/plugins/virtual-entries.ts +138 -27
  423. package/src/vite/rango.ts +236 -138
  424. package/src/vite/router-discovery.ts +927 -136
  425. package/src/vite/utils/ast-handler-extract.ts +26 -35
  426. package/src/vite/utils/banner.ts +1 -1
  427. package/src/vite/utils/bundle-analysis.ts +10 -15
  428. package/src/vite/utils/client-chunks.ts +184 -0
  429. package/src/vite/utils/directive-prologue.ts +40 -0
  430. package/src/vite/utils/forward-user-plugins.ts +171 -0
  431. package/src/vite/utils/manifest-utils.ts +4 -59
  432. package/src/vite/utils/package-resolution.ts +20 -52
  433. package/src/vite/utils/prerender-utils.ts +71 -43
  434. package/src/vite/utils/shared-utils.ts +142 -43
  435. package/src/browser/action-response-classifier.ts +0 -99
  436. package/src/browser/react/use-client-cache.ts +0 -58
  437. package/src/browser/shallow.ts +0 -40
  438. package/src/handles/index.ts +0 -7
  439. package/src/network-error-thrower.tsx +0 -23
  440. package/src/router/middleware-cookies.ts +0 -55
package/src/vite/rango.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { PluginOption } from "vite";
2
- import { readFileSync } from "node:fs";
1
+ import { type PluginOption } from "vite";
2
+ import { readFileSync, existsSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { exposeActionId } from "./plugins/expose-action-id.js";
5
5
  import {
@@ -12,21 +12,46 @@ import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
12
12
  import {
13
13
  getExcludeDeps,
14
14
  getPackageAliases,
15
+ getPublishedPackageName,
16
+ getVendorAliases,
15
17
  } from "./utils/package-resolution.js";
16
- import { findRouterFiles } from "../build/generate-route-types.js";
18
+ import {
19
+ findRouterFiles,
20
+ findHostRouterFiles,
21
+ } from "../build/generate-route-types.js";
17
22
  import { createVersionPlugin } from "./plugins/version-plugin.js";
18
23
  import {
19
- sharedEsbuildOptions,
24
+ sharedRolldownOptions,
20
25
  createVirtualEntriesPlugin,
26
+ normalizeHostRouterEntry,
21
27
  onwarn,
22
28
  getManualChunks,
23
29
  } from "./utils/shared-utils.js";
24
- import type { RangoOptions } from "./plugin-types.js";
30
+ import {
31
+ resolveClientChunks,
32
+ type ClientChunkContext,
33
+ } from "./utils/client-chunks.js";
34
+ import type {
35
+ RangoOptions,
36
+ RangoNodeOptions,
37
+ RangoVercelOptions,
38
+ } from "./plugin-types.js";
39
+ import { createVercelOutputPlugin } from "./plugins/vercel-output.js";
25
40
  import { printBanner, rangoVersion } from "./utils/banner.js";
26
41
  import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
27
42
  import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
28
43
  import { createRouterDiscoveryPlugin } from "./router-discovery.js";
29
44
  import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
45
+ import { createRangoDebugger, NS } from "./debug.js";
46
+
47
+ const debugConfig = createRangoDebugger(NS.config);
48
+
49
+ // The leading-directive 'use client' sniff is shared with version-plugin's
50
+ // getClientModuleSignature so the two cannot drift. Imported for local use by the
51
+ // HMR transform below and re-exported because the E8 sniff test imports it from
52
+ // this module.
53
+ import { hasUseClientDirective } from "./utils/directive-prologue.js";
54
+ export { hasUseClientDirective };
30
55
 
31
56
  /**
32
57
  * Vite plugin for @rangojs/router.
@@ -53,43 +78,69 @@ import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
53
78
  * ```
54
79
  */
55
80
  export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
81
+ const rangoStart = performance.now();
56
82
  const resolvedOptions: RangoOptions = options ?? { preset: "node" };
57
83
  const preset = resolvedOptions.preset ?? "node";
58
84
  const showBanner = resolvedOptions.banner ?? true;
85
+ const clientChunksOption = resolvedOptions.clientChunks ?? true;
86
+ const useBuiltInClientChunks = clientChunksOption === true;
87
+ const clientChunkCtx: ClientChunkContext | undefined = useBuiltInClientChunks
88
+ ? { fallbackRefs: new Set<string>() }
89
+ : undefined;
90
+ const clientChunks = resolveClientChunks(clientChunksOption, clientChunkCtx);
91
+ debugConfig?.("rango(%s) setup start", preset);
59
92
 
60
93
  const plugins: PluginOption[] = [];
61
94
 
62
- // Get package resolution info (workspace vs npm install)
63
- const rangoAliases = getPackageAliases();
95
+ // Get package resolution info (workspace vs npm install).
96
+ // Vendor aliases redirect the bare plugin-rsc vendor specs (which plugin-rsc
97
+ // itself injects into optimizeDeps.include) to absolute paths resolved from
98
+ // this package — so strict-pnpm consumers don't hit "Failed to resolve
99
+ // dependency" warnings when those deps aren't hoisted to their app root.
100
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
64
101
  const excludeDeps = [
65
102
  ...getExcludeDeps(),
66
- // The public browser entry re-exports the RSDW browser client.
67
- // Excluding both keeps Vite from freezing the unpatched bundle into
68
- // .vite/deps before our source transforms run.
103
+ // plugin-rsc itself injects these into the client env's
104
+ // optimizeDeps.include, which overrides exclude for the dep's own
105
+ // pre-bundle entry. What exclude still controls is how *other*
106
+ // pre-bundled deps treat imports of these specs (external vs inlined)
107
+ // via esbuildCjsExternalPlugin. The cjs-to-esm transform in
108
+ // plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
109
+ // where client.browser's bare include fails to resolve and Vite ends up
110
+ // serving the raw CJS file at dev-serve time.
69
111
  "@vitejs/plugin-rsc/browser",
70
- // Keep the browser RSDW client out of Vite's dep optimizer so our
71
- // cjs-to-esm transform can patch the real file.
72
112
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
73
113
  ];
74
114
 
75
- // Mutable ref for router path (node preset only).
76
- // Set immediately when user-specified, or populated by the auto-discover
77
- // config() hook using Vite's resolved root.
78
- const routerRef: { path: string | undefined } = { path: undefined };
115
+ // Vite supports a nested `A > B` syntax in optimizeDeps.include that resolves
116
+ // B from A's location. We anchor transitive deps (rsc-html-stream,
117
+ // @vitejs/plugin-rsc/vendor/*) to @rangojs/router so pnpm consumers — where
118
+ // these aren't visible at the app root can still pre-bundle them.
119
+ const pkg = getPublishedPackageName();
120
+ const nested = (spec: string) => `${pkg} > ${spec}`;
121
+
122
+ // Mutable ref for the served entry path (node/vercel presets only). Populated
123
+ // by the auto-discover config() hook using Vite's resolved root. `kind` selects
124
+ // the RSC entry template: "router" wraps a single createRouter() app in
125
+ // createRSCHandler; "host" wraps a createHostRouter() instance and serves it
126
+ // via hostRouter.match().
127
+ const routerRef: { path: string | undefined; kind: "router" | "host" } = {
128
+ path: undefined,
129
+ kind: "router",
130
+ };
131
+ // Explicit host-router entry (node/vercel `hostRouter` option), root-relative.
132
+ const explicitHostRouter =
133
+ preset !== "cloudflare"
134
+ ? (resolvedOptions as RangoNodeOptions | RangoVercelOptions).hostRouter
135
+ : undefined;
79
136
 
80
137
  // Build-time prerendering is enabled for both presets.
81
138
  // Collection runs in-process via the RSC dev environment runner during discoverRouters().
82
139
  const prerenderEnabled = true;
83
140
 
84
141
  if (preset === "cloudflare") {
85
- // Cloudflare preset: configure entries for cloudflare worker setup
86
- // Router is not needed here - worker.rsc.tsx imports it directly
87
-
88
- // Dynamically import @vitejs/plugin-rsc
89
142
  const { default: rsc } = await import("@vitejs/plugin-rsc");
90
143
 
91
- // Only client and ssr entries - rsc entry is handled by cloudflare plugin
92
- // Always use virtual modules for cloudflare preset
93
144
  const finalEntries: { client: string; ssr: string } = {
94
145
  client: VIRTUAL_IDS.browser,
95
146
  ssr: VIRTUAL_IDS.ssr,
@@ -100,16 +151,21 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
100
151
  enforce: "pre",
101
152
 
102
153
  config() {
103
- // Configure environments for cloudflare deployment
104
154
  return {
105
- // Exclude rsc-router modules from optimization to prevent module duplication
106
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
107
155
  optimizeDeps: {
108
156
  exclude: excludeDeps,
109
- esbuildOptions: sharedEsbuildOptions,
157
+ rolldownOptions: sharedRolldownOptions,
110
158
  },
111
159
  resolve: {
112
160
  alias: rangoAliases,
161
+ // Force a single React/React-DOM copy across all three RSC
162
+ // environments. RSC requires exactly one react/react-dom instance
163
+ // per environment runtime; consumer install topologies (pnpm
164
+ // strict layout, experimental React pins, third-party "use client"
165
+ // packages) can otherwise resolve duplicate copies, causing
166
+ // "Invalid hook call" / lost context. Child environments inherit
167
+ // this root dedupe, and Vite merges it with any consumer dedupe.
168
+ dedupe: ["react", "react-dom"],
113
169
  },
114
170
  build: {
115
171
  rollupOptions: { onwarn },
@@ -118,30 +174,22 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
118
174
  client: {
119
175
  build: {
120
176
  rollupOptions: {
177
+ onwarn,
121
178
  output: {
122
179
  manualChunks: getManualChunks,
123
180
  },
124
181
  },
125
182
  },
126
- // Pre-bundle rsc-html-stream to prevent discovery during first request
127
- // Exclude rsc-router modules to ensure same Context instance
128
183
  optimizeDeps: {
129
- include: ["rsc-html-stream/client"],
184
+ include: [nested("rsc-html-stream/client")],
130
185
  exclude: excludeDeps,
131
- esbuildOptions: sharedEsbuildOptions,
186
+ rolldownOptions: sharedRolldownOptions,
132
187
  },
133
188
  },
134
189
  ssr: {
135
- // Build SSR inside RSC directory so wrangler can deploy self-contained dist/rsc
136
190
  build: {
137
191
  outDir: "./dist/rsc/ssr",
138
192
  },
139
- resolve: {
140
- // Ensure single React instance in SSR child environment
141
- dedupe: ["react", "react-dom"],
142
- },
143
- // Pre-bundle SSR entry and React for proper module linking with childEnvironments
144
- // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
145
193
  optimizeDeps: {
146
194
  entries: [finalEntries.ssr],
147
195
  include: [
@@ -151,26 +199,27 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
151
199
  "react-dom/static.edge",
152
200
  "react/jsx-runtime",
153
201
  "react/jsx-dev-runtime",
154
- "rsc-html-stream/server",
155
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
202
+ nested("rsc-html-stream/server"),
203
+ nested(
204
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
205
+ ),
156
206
  ],
157
207
  exclude: excludeDeps,
158
- esbuildOptions: sharedEsbuildOptions,
208
+ rolldownOptions: sharedRolldownOptions,
159
209
  },
160
210
  },
161
211
  rsc: {
162
- // RSC environment needs exclude list and esbuild options
163
- // Exclude rsc-router modules to prevent createContext in RSC environment
164
212
  optimizeDeps: {
165
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
166
213
  include: [
167
214
  "react",
168
215
  "react/jsx-runtime",
169
216
  "react/jsx-dev-runtime",
170
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
217
+ nested(
218
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
219
+ ),
171
220
  ],
172
221
  exclude: excludeDeps,
173
- esbuildOptions: sharedEsbuildOptions,
222
+ rolldownOptions: sharedRolldownOptions,
174
223
  },
175
224
  },
176
225
  },
@@ -191,26 +240,16 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
191
240
  });
192
241
 
193
242
  plugins.push(createVirtualEntriesPlugin(finalEntries));
194
-
195
- // Dev-only: RSDW client patch for React Performance Tracks
196
243
  plugins.push(performanceTracksPlugin());
197
-
198
- // Add RSC plugin with cloudflare-specific options
199
- // Note: loadModuleDevProxy should NOT be used with childEnvironments
200
- // since SSR runs in workerd alongside RSC
201
244
  plugins.push(
202
245
  rsc({
203
246
  entries: finalEntries,
204
247
  serverHandler: false,
248
+ clientChunks,
205
249
  }) as PluginOption,
206
250
  );
207
-
208
- // Deduplicate client references from third-party packages in dev mode.
209
- // Prevents module duplication when server components import "use client"
210
- // packages that are also imported directly by client components.
211
251
  plugins.push(clientRefDedup());
212
252
  } else {
213
- // Auto-discover router using Vite's resolved root (not process.cwd())
214
253
  plugins.push({
215
254
  name: "@rangojs/router:auto-discover",
216
255
  config(userConfig) {
@@ -218,33 +257,75 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
218
257
  const root = userConfig.root
219
258
  ? resolve(process.cwd(), userConfig.root)
220
259
  : process.cwd();
260
+ const toRootRelative = (abs: string) =>
261
+ (abs.startsWith(root)
262
+ ? "./" + abs.slice(root.length + 1)
263
+ : abs
264
+ ).replaceAll("\\", "/");
265
+ const bulletList = (files: string[]) =>
266
+ files.map((f) => " - " + toRootRelative(f)).join("\n");
267
+
268
+ // 1. Explicit host entry wins: serve the createHostRouter() instance.
269
+ if (explicitHostRouter) {
270
+ routerRef.path = normalizeHostRouterEntry(
271
+ explicitHostRouter,
272
+ root,
273
+ existsSync,
274
+ );
275
+ routerRef.kind = "host";
276
+ return;
277
+ }
278
+
279
+ // 2. A createHostRouter() file means this is a multi-app host deploy:
280
+ // serve it via the host entry. Checked BEFORE single-router discovery,
281
+ // because a host app may compose just one createRouter() sub-app plus
282
+ // host middleware / domain rules / inline .map() routes -- and a lone
283
+ // sub-app must not shadow the host entry (it would bypass
284
+ // hostRouter.match()).
285
+ const hostCandidates = findHostRouterFiles(root);
286
+ if (hostCandidates.length === 1) {
287
+ const hostPath = toRootRelative(hostCandidates[0]);
288
+ // Auto-detection preempts single-router discovery, so name the winning
289
+ // file: a stale prototype still containing `createHostRouter(` would
290
+ // otherwise silently become the served entry in place of the app.
291
+ // eslint-disable-next-line no-console
292
+ console.info(
293
+ `[rango] Serving host router entry ${hostPath} (auto-detected). ` +
294
+ `Set the \`hostRouter\` option to override.`,
295
+ );
296
+ routerRef.path = hostPath;
297
+ routerRef.kind = "host";
298
+ return;
299
+ }
300
+ if (hostCandidates.length > 1) {
301
+ throw new Error(
302
+ `[rango] Multiple host routers found:\n${bulletList(hostCandidates)}\n\n` +
303
+ `Set the \`hostRouter\` option to the entry to serve, e.g. rango({ preset: "${preset}", hostRouter: "./src/worker.rsc.tsx" }).`,
304
+ );
305
+ }
306
+
307
+ // 3. No host entry: single createRouter() app.
221
308
  const candidates = findRouterFiles(root);
222
309
  if (candidates.length === 1) {
223
- const abs = candidates[0];
224
- routerRef.path = (
225
- abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
226
- ).replaceAll("\\", "/");
227
- } else if (candidates.length > 1) {
228
- const list = candidates
229
- .map(
230
- (f) =>
231
- " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
232
- )
233
- .join("\n");
234
- throw new Error(`[rsc-router] Multiple routers found:\n${list}`);
310
+ routerRef.path = toRootRelative(candidates[0]);
311
+ routerRef.kind = "router";
312
+ return;
313
+ }
314
+ if (candidates.length > 1) {
315
+ throw new Error(
316
+ `[rango] Multiple routers found:\n${bulletList(candidates)}\n\n` +
317
+ `If this is a multi-app host router, export a createHostRouter() instance and set the \`hostRouter\` option (e.g. rango({ preset: "${preset}", hostRouter: "./src/worker.rsc.tsx" })), or use preset: "cloudflare" where you own the worker entry.`,
318
+ );
235
319
  }
236
- // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
237
320
  },
238
321
  });
239
322
 
240
- // Always use virtual entries for client, ssr, and rsc
241
323
  const finalEntries = {
242
324
  client: VIRTUAL_IDS.browser,
243
325
  ssr: VIRTUAL_IDS.ssr,
244
326
  rsc: VIRTUAL_IDS.rsc,
245
327
  };
246
328
 
247
- // Dynamically import @vitejs/plugin-rsc
248
329
  const { default: rsc } = await import("@vitejs/plugin-rsc");
249
330
 
250
331
  let hasWarnedDuplicate = false;
@@ -253,22 +334,57 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
253
334
  name: "@rangojs/router:rsc-integration",
254
335
  enforce: "pre",
255
336
 
256
- config() {
337
+ config(_userConfig, configEnv) {
338
+ // Fold NODE_ENV for the vercel preset's build. The cloudflare plugin
339
+ // does this automatically and node apps do it themselves; vercel has no
340
+ // platform plugin, so without this React's CJS dev branch survives and
341
+ // doubles the SSR/RSC bundle (Bundle Hygiene rule #2). Only the exact
342
+ // `process.env.NODE_ENV` token is replaced.
343
+ const vercelDefine =
344
+ preset === "vercel" && configEnv.command === "build"
345
+ ? { "process.env.NODE_ENV": JSON.stringify("production") }
346
+ : undefined;
347
+ // The vercel preset's deployed function has no node_modules, so the
348
+ // server bundles must be fully self-contained. Bundle every dependency
349
+ // into the rsc + ssr builds instead of externalizing them (the node
350
+ // default, which only works because `vite preview` runs where
351
+ // node_modules exists). node: builtins stay external automatically.
352
+ //
353
+ // BUILD ONLY. In `vite dev` this must NOT apply: noExternal forces every
354
+ // server dependency through the RSC/SSR dev module runners, which cannot
355
+ // load many CJS packages (pg, mysql2, most SDKs, @vercel/functions) and
356
+ // crashes the dev server -- while the same app runs fine under
357
+ // preset: "node" and in the production build. Gating on `build` restores
358
+ // node-preset dev semantics (deps externalized to Node's require).
359
+ const vercelServerEnv =
360
+ preset === "vercel" && configEnv.command === "build"
361
+ ? { resolve: { noExternal: true as const } }
362
+ : undefined;
257
363
  return {
364
+ ...(vercelDefine ? { define: vercelDefine } : {}),
258
365
  optimizeDeps: {
259
366
  exclude: excludeDeps,
260
- esbuildOptions: sharedEsbuildOptions,
367
+ rolldownOptions: sharedRolldownOptions,
261
368
  },
262
369
  build: {
263
370
  rollupOptions: { onwarn },
264
371
  },
265
372
  resolve: {
266
373
  alias: rangoAliases,
374
+ // Force a single React/React-DOM copy across all three RSC
375
+ // environments. RSC requires exactly one react/react-dom instance
376
+ // per environment runtime; consumer install topologies (pnpm
377
+ // strict layout, experimental React pins, third-party "use client"
378
+ // packages) can otherwise resolve duplicate copies, causing
379
+ // "Invalid hook call" / lost context. Child environments inherit
380
+ // this root dedupe, and Vite merges it with any consumer dedupe.
381
+ dedupe: ["react", "react-dom"],
267
382
  },
268
383
  environments: {
269
384
  client: {
270
385
  build: {
271
386
  rollupOptions: {
387
+ onwarn,
272
388
  output: {
273
389
  manualChunks: getManualChunks,
274
390
  },
@@ -280,14 +396,15 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
280
396
  "react-dom",
281
397
  "react/jsx-runtime",
282
398
  "react/jsx-dev-runtime",
283
- "rsc-html-stream/client",
399
+ nested("rsc-html-stream/client"),
284
400
  ],
285
401
  exclude: excludeDeps,
286
- esbuildOptions: sharedEsbuildOptions,
402
+ rolldownOptions: sharedRolldownOptions,
287
403
  entries: [VIRTUAL_IDS.browser],
288
404
  },
289
405
  },
290
406
  ssr: {
407
+ ...(vercelServerEnv ?? {}),
291
408
  optimizeDeps: {
292
409
  entries: [VIRTUAL_IDS.ssr],
293
410
  include: [
@@ -297,22 +414,33 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
297
414
  "react-dom/static.edge",
298
415
  "react/jsx-runtime",
299
416
  "react/jsx-dev-runtime",
300
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
417
+ nested(
418
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
419
+ ),
301
420
  ],
302
421
  exclude: excludeDeps,
303
- esbuildOptions: sharedEsbuildOptions,
422
+ rolldownOptions: sharedRolldownOptions,
304
423
  },
305
424
  },
306
425
  rsc: {
426
+ ...(vercelServerEnv ?? {}),
307
427
  optimizeDeps: {
308
428
  entries: [VIRTUAL_IDS.rsc],
309
429
  include: [
310
430
  "react",
311
431
  "react/jsx-runtime",
312
432
  "react/jsx-dev-runtime",
313
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
433
+ nested(
434
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
435
+ ),
314
436
  ],
315
- esbuildOptions: sharedEsbuildOptions,
437
+ // Vite 8 does not propagate the top-level optimizeDeps.exclude
438
+ // (set in config()) to non-client envs, so the rsc env must set
439
+ // it explicitly — mirroring the node ssr env and the cloudflare
440
+ // rsc env. Without it a strict-pnpm npm-installed app can try to
441
+ // pre-bundle the router's own subpath entries and fail.
442
+ exclude: excludeDeps,
443
+ rolldownOptions: sharedRolldownOptions,
316
444
  },
317
445
  },
318
446
  },
@@ -327,7 +455,11 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
327
455
  ? "preview"
328
456
  : "dev"
329
457
  : "build";
330
- printBanner(mode, "node", rangoVersion);
458
+ printBanner(
459
+ mode,
460
+ preset === "vercel" ? "vercel" : "node",
461
+ rangoVersion,
462
+ );
331
463
  }
332
464
 
333
465
  const rscMinimalCount = config.plugins.filter(
@@ -337,42 +469,24 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
337
469
  if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
338
470
  hasWarnedDuplicate = true;
339
471
  console.warn(
340
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
472
+ "[rango] Duplicate @vitejs/plugin-rsc detected. " +
341
473
  "Remove rsc() from your vite config — rango() includes it automatically.",
342
474
  );
343
475
  }
344
476
  },
345
477
  });
346
478
 
347
- // Add virtual entries plugin (RSC entry generated lazily from routerRef)
348
479
  plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
349
-
350
- // Dev-only: RSDW client patch for React Performance Tracks
351
480
  plugins.push(performanceTracksPlugin());
352
-
353
481
  plugins.push(
354
482
  rsc({
355
483
  entries: finalEntries,
484
+ clientChunks,
356
485
  }) as PluginOption,
357
486
  );
358
-
359
- // Deduplicate client references from third-party packages in dev mode.
360
- // Prevents module duplication when server components import "use client"
361
- // packages that are also imported directly by client components.
362
487
  plugins.push(clientRefDedup());
363
488
  }
364
489
 
365
- // Fix HMR for "use client" components.
366
- //
367
- // @vitejs/plugin-rsc's hotUpdate returns undefined for "use client" files
368
- // in the RSC environment. Vite then tries to propagate through the RSC
369
- // module graph, but the proxy module has no import.meta.hot.accept()
370
- // boundary, causing a full page reload. The client env would handle it
371
- // fine via React Refresh, but the RSC env's full-reload arrives first.
372
- //
373
- // Fix: in the RSC env, return [] for "use client" files to signal
374
- // "handled, nothing to propagate". The client env is left alone so
375
- // React Refresh processes the update normally.
376
490
  plugins.push({
377
491
  name: "@rangojs/router:client-component-hmr",
378
492
  hotUpdate(ctx) {
@@ -391,72 +505,56 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
391
505
 
392
506
  try {
393
507
  const source = readFileSync(file, "utf-8");
394
- const trimmed = source.trimStart();
395
- if (
396
- trimmed.startsWith('"use client"') ||
397
- trimmed.startsWith("'use client'")
398
- ) {
399
- // Consume the update in RSC/SSR envs. The proxy module was already
400
- // re-transformed by the RSC plugin's hotUpdate. Without this, Vite
401
- // tries to propagate through the RSC/SSR module graph where the proxy
402
- // has no import.meta.hot.accept() boundary, triggering a full reload.
403
- // The actual component update is handled by React Refresh in the
404
- // client environment.
508
+ if (hasUseClientDirective(source)) {
405
509
  return [];
406
510
  }
407
- } catch {
408
- // File deleted/moved during HMR, let default handling proceed
409
- }
511
+ } catch {}
410
512
  },
411
513
  });
412
514
 
413
515
  plugins.push(exposeActionId());
414
-
415
- // "use cache" directive transform (enforce: "post"):
416
- // Wraps exports with registerCachedFunction() for function-level caching.
417
516
  plugins.push(useCacheTransform());
418
-
419
- // Consolidated plugin for create* ID injection (enforce: "post"):
420
- // loaders, handles, location state, and prerender handlers.
421
517
  plugins.push(exposeInternalIds());
422
-
423
- // Router ID injection runs at normal priority (no enforce) to avoid
424
- // changing Vite's dep optimization timing.
425
518
  plugins.push(exposeRouterId());
426
-
427
- // Add version virtual module plugin for cache invalidation
428
519
  plugins.push(createVersionPlugin());
429
520
 
430
- // Entry path for discovery: user-specified value (if any) or undefined.
431
- // Auto-discovered path is passed separately via routerRef.
432
- // Cloudflare preset: deferred to configResolved (read from resolved Vite env config).
433
521
  const discoveryEntryPath =
434
522
  preset !== "cloudflare" ? routerRef.path : undefined;
435
- // Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
436
523
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
437
524
 
438
- // Version injector: auto-injects VERSION and routes-manifest into the RSC entry.
439
- // For cloudflare preset, the entry is resolved lazily in configResolved.
440
- // For node preset, the virtual entry already includes these imports.
441
525
  if (preset === "cloudflare") {
442
526
  plugins.push(createVersionInjectorPlugin(undefined));
443
527
  }
444
528
 
445
- // Transform CJS vendor files to ESM for browser compatibility
446
- // optimizeDeps.include doesn't work because the file is loaded after initial optimization
447
529
  plugins.push(createCjsToEsmPlugin());
448
-
449
- // Router discovery plugin for build-time manifest generation.
450
- // For cloudflare, the entry is resolved lazily in configResolved from the RSC environment.
451
- // For node, discoveryRouterRef provides the auto-discovered path when not user-specified.
452
530
  plugins.push(
453
531
  createRouterDiscoveryPlugin(discoveryEntryPath, {
454
532
  routerPathRef: discoveryRouterRef,
455
533
  enableBuildPrerender: prerenderEnabled,
456
534
  buildEnv: options?.buildEnv,
457
535
  preset,
536
+ prerenderOnError: options?.prerender?.onError,
537
+ discovery: options?.discovery,
538
+ clientChunkCtx,
458
539
  }),
459
540
  );
460
541
 
542
+ // Vercel preset: assemble .vercel/output from dist/ after the build. Pushed
543
+ // last so its buildApp (order "post") hook runs after the discovery plugin's
544
+ // rsc-env postprocess. buildApp fires once after the whole multi-environment
545
+ // build, so dist/ is complete (closeBundle is unusable here -- it fires per
546
+ // environment, twice for ssr; see the plugin's own note).
547
+ if (preset === "vercel") {
548
+ plugins.push(
549
+ createVercelOutputPlugin(resolvedOptions as RangoVercelOptions),
550
+ );
551
+ }
552
+
553
+ debugConfig?.(
554
+ "rango(%s) setup done: %d plugin(s) (%sms)",
555
+ preset,
556
+ plugins.length,
557
+ (performance.now() - rangoStart).toFixed(1),
558
+ );
461
559
  return plugins;
462
560
  }