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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (449) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +303 -741
  3. package/dist/bin/rango.js +730 -184
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +4344 -1335
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +86 -15
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +85 -6
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +251 -24
  13. package/skills/caching/SKILL.md +375 -17
  14. package/skills/catalog.json +271 -0
  15. package/skills/comparison/SKILL.md +50 -0
  16. package/skills/comparison/agents/openai.yaml +4 -0
  17. package/skills/comparison/references/framework-comparison.md +837 -0
  18. package/skills/composability/SKILL.md +110 -4
  19. package/skills/css/SKILL.md +76 -0
  20. package/skills/debug-manifest/SKILL.md +5 -3
  21. package/skills/defer-hydration/SKILL.md +235 -0
  22. package/skills/document-cache/SKILL.md +87 -56
  23. package/skills/fonts/SKILL.md +1 -1
  24. package/skills/handler-use/SKILL.md +364 -0
  25. package/skills/hooks/SKILL.md +73 -691
  26. package/skills/hooks/data.md +273 -0
  27. package/skills/hooks/handle-and-actions.md +103 -0
  28. package/skills/hooks/navigation.md +110 -0
  29. package/skills/hooks/outlets.md +41 -0
  30. package/skills/hooks/state.md +228 -0
  31. package/skills/hooks/urls.md +135 -0
  32. package/skills/host-router/SKILL.md +129 -27
  33. package/skills/i18n/SKILL.md +276 -0
  34. package/skills/intercept/SKILL.md +94 -18
  35. package/skills/layout/SKILL.md +62 -19
  36. package/skills/links/SKILL.md +249 -17
  37. package/skills/loader/SKILL.md +302 -54
  38. package/skills/middleware/SKILL.md +59 -16
  39. package/skills/migrate-nextjs/SKILL.md +745 -0
  40. package/skills/migrate-react-router/SKILL.md +153 -0
  41. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  42. package/skills/migrate-react-router/component-migration.md +196 -0
  43. package/skills/migrate-react-router/data-and-actions.md +225 -0
  44. package/skills/migrate-react-router/route-mapping.md +271 -0
  45. package/skills/mime-routes/SKILL.md +29 -2
  46. package/skills/observability/SKILL.md +202 -0
  47. package/skills/parallel/SKILL.md +225 -10
  48. package/skills/ppr/SKILL.md +622 -0
  49. package/skills/prerender/SKILL.md +178 -124
  50. package/skills/rango/SKILL.md +318 -24
  51. package/skills/react-compiler/SKILL.md +168 -0
  52. package/skills/response-routes/SKILL.md +138 -49
  53. package/skills/route/SKILL.md +172 -9
  54. package/skills/router-setup/SKILL.md +131 -11
  55. package/skills/scripts/SKILL.md +179 -0
  56. package/skills/server-actions/SKILL.md +776 -0
  57. package/skills/shell-manifest/SKILL.md +185 -0
  58. package/skills/streams-and-websockets/SKILL.md +283 -0
  59. package/skills/tailwind/SKILL.md +28 -4
  60. package/skills/testing/SKILL.md +130 -0
  61. package/skills/testing/bindings.md +103 -0
  62. package/skills/testing/cache-prerender.md +127 -0
  63. package/skills/testing/client-components.md +124 -0
  64. package/skills/testing/e2e-parity.md +125 -0
  65. package/skills/testing/flight.md +91 -0
  66. package/skills/testing/handles.md +131 -0
  67. package/skills/testing/loader.md +128 -0
  68. package/skills/testing/middleware.md +99 -0
  69. package/skills/testing/render-handler.md +122 -0
  70. package/skills/testing/response-routes.md +95 -0
  71. package/skills/testing/reverse-and-types.md +85 -0
  72. package/skills/testing/server-actions.md +107 -0
  73. package/skills/testing/server-tree.md +128 -0
  74. package/skills/testing/setup.md +123 -0
  75. package/skills/theme/SKILL.md +1 -1
  76. package/skills/typesafety/SKILL.md +45 -616
  77. package/skills/typesafety/env-and-bindings.md +254 -0
  78. package/skills/typesafety/generated-files-and-cli.md +335 -0
  79. package/skills/typesafety/params-and-search.md +153 -0
  80. package/skills/typesafety/route-types.md +209 -0
  81. package/skills/use-cache/SKILL.md +74 -15
  82. package/skills/vercel/SKILL.md +128 -0
  83. package/skills/view-transitions/SKILL.md +337 -0
  84. package/src/__augment-tests__/augment.ts +81 -0
  85. package/src/__augment-tests__/augmented.check.ts +116 -0
  86. package/src/__internal.ts +1 -66
  87. package/src/browser/action-coordinator.ts +53 -36
  88. package/src/browser/action-fence.ts +47 -0
  89. package/src/browser/app-shell.ts +39 -0
  90. package/src/browser/app-version.ts +14 -0
  91. package/src/browser/connection-warmup.ts +134 -0
  92. package/src/browser/cookie-name.ts +140 -0
  93. package/src/browser/event-controller.ts +257 -158
  94. package/src/browser/history-state.ts +21 -0
  95. package/src/browser/index.ts +3 -3
  96. package/src/browser/invalidate-client-cache.ts +52 -0
  97. package/src/browser/logging.ts +28 -0
  98. package/src/browser/merge-segment-loaders.ts +6 -4
  99. package/src/browser/navigation-bridge.ts +132 -33
  100. package/src/browser/navigation-client.ts +218 -68
  101. package/src/browser/navigation-store-handle.ts +38 -0
  102. package/src/browser/navigation-store.ts +203 -80
  103. package/src/browser/navigation-transaction.ts +18 -66
  104. package/src/browser/network-error-handler.ts +34 -7
  105. package/src/browser/partial-update.ts +241 -127
  106. package/src/browser/prefetch/cache.ts +271 -44
  107. package/src/browser/prefetch/fetch.ts +367 -40
  108. package/src/browser/prefetch/queue.ts +144 -23
  109. package/src/browser/prefetch/resource-ready.ts +77 -0
  110. package/src/browser/rango-state.ts +158 -76
  111. package/src/browser/react/Link.tsx +121 -16
  112. package/src/browser/react/NavigationProvider.tsx +240 -122
  113. package/src/browser/react/ScrollRestoration.tsx +10 -6
  114. package/src/browser/react/context.ts +7 -2
  115. package/src/browser/react/filter-segment-order.ts +66 -7
  116. package/src/browser/react/index.ts +0 -48
  117. package/src/browser/react/location-state-shared.ts +178 -8
  118. package/src/browser/react/location-state.ts +39 -14
  119. package/src/browser/react/use-action.ts +6 -15
  120. package/src/browser/react/use-handle.ts +23 -69
  121. package/src/browser/react/use-href.tsx +8 -1
  122. package/src/browser/react/use-link-status.ts +33 -8
  123. package/src/browser/react/use-navigation.ts +32 -7
  124. package/src/browser/react/use-params.ts +20 -10
  125. package/src/browser/react/use-reverse.ts +106 -0
  126. package/src/browser/react/use-router.ts +46 -11
  127. package/src/browser/react/use-search-params.ts +0 -5
  128. package/src/browser/react/use-segments.ts +11 -21
  129. package/src/browser/response-adapter.ts +99 -8
  130. package/src/browser/rsc-router.tsx +272 -80
  131. package/src/browser/scroll-restoration.ts +56 -22
  132. package/src/browser/segment-reconciler.ts +44 -7
  133. package/src/browser/segment-structure-assert.ts +2 -2
  134. package/src/browser/server-action-bridge.ts +244 -71
  135. package/src/browser/types.ts +136 -12
  136. package/src/browser/validate-redirect-origin.ts +43 -16
  137. package/src/build/collect-fallback-refs.ts +107 -0
  138. package/src/build/generate-manifest.ts +207 -158
  139. package/src/build/generate-route-types.ts +6 -1
  140. package/src/build/index.ts +11 -3
  141. package/src/build/prefix-tree-utils.ts +123 -0
  142. package/src/build/route-trie.ts +198 -41
  143. package/src/build/route-types/ast-route-extraction.ts +15 -8
  144. package/src/build/route-types/codegen.ts +16 -5
  145. package/src/build/route-types/include-resolution.ts +464 -63
  146. package/src/build/route-types/param-extraction.ts +6 -3
  147. package/src/build/route-types/per-module-writer.ts +22 -6
  148. package/src/build/route-types/router-processing.ts +336 -110
  149. package/src/build/route-types/scan-filter.ts +9 -2
  150. package/src/build/route-types/source-scan.ts +216 -0
  151. package/src/build/runtime-discovery.ts +13 -21
  152. package/src/cache/cache-error.ts +104 -0
  153. package/src/cache/cache-key-utils.ts +58 -13
  154. package/src/cache/cache-policy.ts +108 -34
  155. package/src/cache/cache-runtime.ts +454 -97
  156. package/src/cache/cache-scope.ts +235 -103
  157. package/src/cache/cache-tag.ts +149 -0
  158. package/src/cache/cf/cf-base64.ts +33 -0
  159. package/src/cache/cf/cf-cache-constants.ts +127 -0
  160. package/src/cache/cf/cf-cache-store.ts +2446 -170
  161. package/src/cache/cf/cf-cache-types.ts +349 -0
  162. package/src/cache/cf/cf-kv-utils.ts +46 -0
  163. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  164. package/src/cache/cf/index.ts +11 -17
  165. package/src/cache/document-cache.ts +144 -49
  166. package/src/cache/handle-snapshot.ts +70 -0
  167. package/src/cache/index.ts +24 -20
  168. package/src/cache/memory-segment-store.ts +243 -37
  169. package/src/cache/profile-registry.ts +46 -31
  170. package/src/cache/read-through-swr.ts +56 -12
  171. package/src/cache/segment-codec.ts +13 -21
  172. package/src/cache/shell-snapshot.ts +417 -0
  173. package/src/cache/tag-invalidation.ts +230 -0
  174. package/src/cache/taint.ts +55 -0
  175. package/src/cache/types.ts +194 -99
  176. package/src/cache/vercel/index.ts +11 -0
  177. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  178. package/src/client.rsc.tsx +41 -21
  179. package/src/client.tsx +116 -290
  180. package/src/cloudflare/index.ts +11 -0
  181. package/src/cloudflare/tracing.ts +108 -0
  182. package/src/component-utils.ts +19 -0
  183. package/src/components/DefaultDocument.tsx +8 -2
  184. package/src/context-var.ts +84 -2
  185. package/src/debug.ts +2 -2
  186. package/src/decode-loader-results.ts +52 -0
  187. package/src/defer.ts +185 -0
  188. package/src/deps/ssr.ts +0 -1
  189. package/src/encode-kv.ts +49 -0
  190. package/src/errors.ts +30 -4
  191. package/src/escape-script.ts +52 -0
  192. package/src/handle.ts +104 -34
  193. package/src/handles/MetaTags.tsx +24 -53
  194. package/src/handles/Scripts.tsx +183 -0
  195. package/src/handles/breadcrumbs.ts +35 -8
  196. package/src/handles/deferred-resolution.ts +127 -0
  197. package/src/handles/is-thenable.ts +18 -0
  198. package/src/handles/meta.ts +14 -40
  199. package/src/handles/script.ts +244 -0
  200. package/src/host/cookie-handler.ts +9 -60
  201. package/src/host/errors.ts +13 -22
  202. package/src/host/index.ts +9 -2
  203. package/src/host/pattern-matcher.ts +23 -52
  204. package/src/host/router.ts +107 -99
  205. package/src/host/testing.ts +40 -27
  206. package/src/host/types.ts +37 -4
  207. package/src/host/utils.ts +1 -1
  208. package/src/href-client.ts +137 -22
  209. package/src/index.rsc.ts +100 -13
  210. package/src/index.ts +143 -19
  211. package/src/internal-debug.ts +11 -10
  212. package/src/loader-store.ts +500 -0
  213. package/src/loader.rsc.ts +20 -13
  214. package/src/loader.ts +12 -11
  215. package/src/missing-id-error.ts +68 -0
  216. package/src/outlet-context.ts +1 -1
  217. package/src/outlet-provider.tsx +1 -5
  218. package/src/prerender/param-hash.ts +16 -16
  219. package/src/prerender/store.ts +37 -41
  220. package/src/prerender.ts +215 -86
  221. package/src/redirect-origin.ts +114 -0
  222. package/src/regex-escape.ts +8 -0
  223. package/src/render-error-thrower.tsx +20 -0
  224. package/src/response-utils.ts +62 -0
  225. package/src/reverse.ts +65 -15
  226. package/src/root-error-boundary.tsx +1 -19
  227. package/src/route-content-wrapper.tsx +19 -77
  228. package/src/route-definition/dsl-helpers.ts +485 -303
  229. package/src/route-definition/helper-factories.ts +28 -140
  230. package/src/route-definition/helpers-types.ts +153 -77
  231. package/src/route-definition/index.ts +4 -2
  232. package/src/route-definition/redirect.ts +53 -12
  233. package/src/route-definition/resolve-handler-use.ts +160 -0
  234. package/src/route-definition/use-item-types.ts +29 -0
  235. package/src/route-map-builder.ts +48 -21
  236. package/src/route-types.ts +37 -46
  237. package/src/router/basename.ts +14 -0
  238. package/src/router/content-negotiation.ts +164 -17
  239. package/src/router/error-handling.ts +45 -18
  240. package/src/router/find-match.ts +130 -29
  241. package/src/router/handler-context.ts +83 -39
  242. package/src/router/instrument.ts +355 -0
  243. package/src/router/intercept-resolution.ts +50 -24
  244. package/src/router/lazy-includes.ts +89 -63
  245. package/src/router/loader-resolution.ts +286 -56
  246. package/src/router/logging.ts +5 -8
  247. package/src/router/manifest.ts +105 -56
  248. package/src/router/match-api.ts +178 -218
  249. package/src/router/match-context.ts +0 -22
  250. package/src/router/match-handlers.ts +211 -165
  251. package/src/router/match-middleware/background-revalidation.ts +66 -22
  252. package/src/router/match-middleware/cache-lookup.ts +214 -263
  253. package/src/router/match-middleware/cache-store.ts +105 -50
  254. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  255. package/src/router/match-middleware/segment-resolution.ts +52 -18
  256. package/src/router/match-pipelines.ts +1 -42
  257. package/src/router/match-result.ts +128 -44
  258. package/src/router/metrics.ts +5 -34
  259. package/src/router/middleware-types.ts +13 -142
  260. package/src/router/middleware.ts +301 -177
  261. package/src/router/navigation-snapshot.ts +133 -0
  262. package/src/router/params-util.ts +23 -0
  263. package/src/router/parse-pattern.ts +115 -0
  264. package/src/router/pattern-matching.ts +181 -150
  265. package/src/router/prefetch-cache-ttl.ts +51 -0
  266. package/src/router/prefetch-limits.ts +37 -0
  267. package/src/router/prerender-match.ts +203 -58
  268. package/src/router/preview-match.ts +35 -103
  269. package/src/router/request-classification.ts +291 -0
  270. package/src/router/revalidation.ts +123 -73
  271. package/src/router/route-snapshot.ts +256 -0
  272. package/src/router/router-context.ts +11 -29
  273. package/src/router/router-interfaces.ts +146 -35
  274. package/src/router/router-options.ts +202 -15
  275. package/src/router/router-registry.ts +2 -5
  276. package/src/router/segment-resolution/fresh.ts +301 -78
  277. package/src/router/segment-resolution/helpers.ts +115 -30
  278. package/src/router/segment-resolution/loader-cache.ts +156 -39
  279. package/src/router/segment-resolution/loader-mask.ts +60 -0
  280. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  281. package/src/router/segment-resolution/mask-nested.ts +83 -0
  282. package/src/router/segment-resolution/revalidation.ts +477 -385
  283. package/src/router/segment-resolution/static-store.ts +19 -5
  284. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  285. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  286. package/src/router/segment-resolution.ts +5 -1
  287. package/src/router/segment-wrappers.ts +8 -5
  288. package/src/router/state-cookie-name.ts +33 -0
  289. package/src/router/substitute-pattern-params.ts +75 -0
  290. package/src/router/telemetry-otel.ts +160 -200
  291. package/src/router/telemetry.ts +105 -20
  292. package/src/router/timeout.ts +0 -20
  293. package/src/router/tracing.ts +215 -0
  294. package/src/router/trie-matching.ts +171 -59
  295. package/src/router/types.ts +10 -63
  296. package/src/router/url-params.ts +57 -0
  297. package/src/router.ts +210 -71
  298. package/src/rsc/full-payload.ts +70 -0
  299. package/src/rsc/handler-context.ts +3 -2
  300. package/src/rsc/handler.ts +682 -508
  301. package/src/rsc/helpers.ts +168 -46
  302. package/src/rsc/index.ts +2 -5
  303. package/src/rsc/json-route-result.ts +38 -0
  304. package/src/rsc/loader-fetch.ts +127 -31
  305. package/src/rsc/manifest-init.ts +33 -42
  306. package/src/rsc/nonce.ts +10 -1
  307. package/src/rsc/origin-guard.ts +39 -25
  308. package/src/rsc/progressive-enhancement.ts +138 -15
  309. package/src/rsc/redirect-guard.ts +100 -0
  310. package/src/rsc/response-cache-serve.ts +238 -0
  311. package/src/rsc/response-error.ts +79 -12
  312. package/src/rsc/response-route-handler.ts +99 -189
  313. package/src/rsc/rsc-rendering.ts +509 -73
  314. package/src/rsc/runtime-warnings.ts +23 -10
  315. package/src/rsc/server-action.ts +287 -113
  316. package/src/rsc/shell-capture.ts +1190 -0
  317. package/src/rsc/shell-serve.ts +181 -0
  318. package/src/rsc/ssr-setup.ts +18 -2
  319. package/src/rsc/transition-gate.ts +89 -0
  320. package/src/rsc/types.ts +62 -6
  321. package/src/runtime-env.ts +18 -0
  322. package/src/search-params.ts +35 -30
  323. package/src/segment-content-promise.ts +67 -0
  324. package/src/segment-loader-promise.ts +167 -0
  325. package/src/segment-system.tsx +449 -132
  326. package/src/serialize.ts +243 -0
  327. package/src/server/context.ts +367 -61
  328. package/src/server/cookie-parse.ts +32 -0
  329. package/src/server/cookie-store.ts +152 -5
  330. package/src/server/handle-store.ts +40 -38
  331. package/src/server/loader-registry.ts +38 -46
  332. package/src/server/request-context.ts +558 -173
  333. package/src/ssr/index.tsx +491 -174
  334. package/src/ssr/inject-rsc-eager.ts +167 -0
  335. package/src/ssr/ssr-root.tsx +228 -0
  336. package/src/static-handler.ts +27 -18
  337. package/src/testing/cache-status.ts +162 -0
  338. package/src/testing/collect-handle.ts +46 -0
  339. package/src/testing/dispatch.ts +813 -0
  340. package/src/testing/dom.entry.ts +22 -0
  341. package/src/testing/e2e/fixture.ts +188 -0
  342. package/src/testing/e2e/index.ts +128 -0
  343. package/src/testing/e2e/matchers.ts +35 -0
  344. package/src/testing/e2e/page-helpers.ts +272 -0
  345. package/src/testing/e2e/parity.ts +387 -0
  346. package/src/testing/e2e/server.ts +195 -0
  347. package/src/testing/flight-matchers.ts +97 -0
  348. package/src/testing/flight-normalize.ts +11 -0
  349. package/src/testing/flight-runtime.d.ts +57 -0
  350. package/src/testing/flight-tree.ts +682 -0
  351. package/src/testing/flight.entry.ts +52 -0
  352. package/src/testing/flight.ts +257 -0
  353. package/src/testing/generated-routes.ts +199 -0
  354. package/src/testing/index.ts +105 -0
  355. package/src/testing/internal/context.ts +371 -0
  356. package/src/testing/internal/flight-client-globals.ts +30 -0
  357. package/src/testing/internal/seed-vars.ts +54 -0
  358. package/src/testing/render-handler.ts +357 -0
  359. package/src/testing/render-route.tsx +584 -0
  360. package/src/testing/run-loader.ts +385 -0
  361. package/src/testing/run-middleware.ts +205 -0
  362. package/src/testing/run-transition-when.ts +164 -0
  363. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  364. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  365. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  366. package/src/testing/vitest-stubs/version.ts +5 -0
  367. package/src/testing/vitest.ts +305 -0
  368. package/src/theme/ThemeProvider.tsx +56 -84
  369. package/src/theme/ThemeScript.tsx +7 -9
  370. package/src/theme/constants.ts +52 -13
  371. package/src/theme/index.ts +0 -7
  372. package/src/theme/theme-context.ts +1 -5
  373. package/src/theme/theme-script.ts +22 -21
  374. package/src/theme/use-theme.ts +0 -3
  375. package/src/types/boundaries.ts +0 -35
  376. package/src/types/cache-types.ts +17 -8
  377. package/src/types/error-types.ts +30 -90
  378. package/src/types/global-namespace.ts +54 -41
  379. package/src/types/handler-context.ts +234 -82
  380. package/src/types/index.ts +3 -10
  381. package/src/types/loader-types.ts +44 -15
  382. package/src/types/request-scope.ts +112 -0
  383. package/src/types/route-config.ts +20 -52
  384. package/src/types/route-entry.ts +19 -7
  385. package/src/types/segments.ts +137 -14
  386. package/src/urls/include-helper.ts +40 -75
  387. package/src/urls/include-provider.ts +71 -0
  388. package/src/urls/index.ts +2 -11
  389. package/src/urls/path-helper-types.ts +102 -23
  390. package/src/urls/path-helper.ts +62 -111
  391. package/src/urls/pattern-types.ts +84 -19
  392. package/src/urls/response-types.ts +25 -22
  393. package/src/urls/type-extraction.ts +98 -154
  394. package/src/urls/urls-function.ts +1 -19
  395. package/src/use-loader.tsx +346 -89
  396. package/src/vercel/index.ts +11 -0
  397. package/src/vercel/tracing.ts +88 -0
  398. package/src/vite/debug.ts +185 -0
  399. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  400. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  401. package/src/vite/discovery/discover-routers.ts +130 -85
  402. package/src/vite/discovery/discovery-errors.ts +255 -0
  403. package/src/vite/discovery/gate-state.ts +171 -0
  404. package/src/vite/discovery/prerender-collection.ts +214 -132
  405. package/src/vite/discovery/route-types-writer.ts +40 -84
  406. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  407. package/src/vite/discovery/state.ts +57 -6
  408. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  409. package/src/vite/index.ts +15 -0
  410. package/src/vite/inject-client-debug.ts +88 -0
  411. package/src/vite/plugin-types.ts +234 -62
  412. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  413. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  414. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  415. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  416. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  417. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  418. package/src/vite/plugins/expose-action-id.ts +49 -98
  419. package/src/vite/plugins/expose-id-utils.ts +96 -51
  420. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  421. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  422. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  423. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  424. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  425. package/src/vite/plugins/performance-tracks.ts +89 -0
  426. package/src/vite/plugins/refresh-cmd.ts +89 -27
  427. package/src/vite/plugins/use-cache-transform.ts +73 -83
  428. package/src/vite/plugins/vercel-output.ts +384 -0
  429. package/src/vite/plugins/version-injector.ts +40 -29
  430. package/src/vite/plugins/version-plugin.ts +46 -37
  431. package/src/vite/plugins/virtual-entries.ts +138 -27
  432. package/src/vite/rango.ts +353 -303
  433. package/src/vite/router-discovery.ts +1090 -166
  434. package/src/vite/utils/ast-handler-extract.ts +26 -35
  435. package/src/vite/utils/banner.ts +4 -4
  436. package/src/vite/utils/bundle-analysis.ts +10 -15
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +4 -59
  441. package/src/vite/utils/package-resolution.ts +20 -52
  442. package/src/vite/utils/prerender-utils.ts +98 -38
  443. package/src/vite/utils/shared-utils.ts +144 -44
  444. package/src/browser/action-response-classifier.ts +0 -99
  445. package/src/browser/react/use-client-cache.ts +0 -58
  446. package/src/browser/shallow.ts +0 -40
  447. package/src/handles/index.ts +0 -7
  448. package/src/network-error-thrower.tsx +0 -23
  449. 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,27 +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
18
  import {
17
- createScanFilter,
18
19
  findRouterFiles,
20
+ findHostRouterFiles,
19
21
  } from "../build/generate-route-types.js";
20
22
  import { createVersionPlugin } from "./plugins/version-plugin.js";
21
23
  import {
22
- sharedEsbuildOptions,
24
+ sharedRolldownOptions,
23
25
  createVirtualEntriesPlugin,
26
+ normalizeHostRouterEntry,
24
27
  onwarn,
25
28
  getManualChunks,
26
29
  } from "./utils/shared-utils.js";
30
+ import {
31
+ resolveClientChunks,
32
+ type ClientChunkContext,
33
+ } from "./utils/client-chunks.js";
27
34
  import type {
28
35
  RangoOptions,
29
36
  RangoNodeOptions,
30
- RscPluginOptions,
37
+ RangoVercelOptions,
31
38
  } from "./plugin-types.js";
39
+ import { createVercelOutputPlugin } from "./plugins/vercel-output.js";
32
40
  import { printBanner, rangoVersion } from "./utils/banner.js";
33
41
  import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
34
42
  import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
35
43
  import { createRouterDiscoveryPlugin } from "./router-discovery.js";
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 };
36
55
 
37
56
  /**
38
57
  * Vite plugin for @rangojs/router.
@@ -43,7 +62,7 @@ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
43
62
  * @example Node.js (default)
44
63
  * ```ts
45
64
  * export default defineConfig({
46
- * plugins: [react(), rango({ router: './src/router.tsx' })],
65
+ * plugins: [react(), rango()],
47
66
  * });
48
67
  * ```
49
68
  *
@@ -59,37 +78,69 @@ import { createRouterDiscoveryPlugin } from "./router-discovery.js";
59
78
  * ```
60
79
  */
61
80
  export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
81
+ const rangoStart = performance.now();
62
82
  const resolvedOptions: RangoOptions = options ?? { preset: "node" };
63
83
  const preset = resolvedOptions.preset ?? "node";
64
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);
65
92
 
66
93
  const plugins: PluginOption[] = [];
67
94
 
68
- // Get package resolution info (workspace vs npm install)
69
- const rangoAliases = getPackageAliases();
70
- const excludeDeps = getExcludeDeps();
71
-
72
- // Track RSC entry path for version injection
73
- let rscEntryPath: string | null = null;
74
-
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 };
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() };
101
+ const excludeDeps = [
102
+ ...getExcludeDeps(),
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.
111
+ "@vitejs/plugin-rsc/browser",
112
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser",
113
+ ];
114
+
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,232 +240,253 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
191
240
  });
192
241
 
193
242
  plugins.push(createVirtualEntriesPlugin(finalEntries));
194
-
195
- // Add RSC plugin with cloudflare-specific options
196
- // Note: loadModuleDevProxy should NOT be used with childEnvironments
197
- // since SSR runs in workerd alongside RSC
243
+ plugins.push(performanceTracksPlugin());
198
244
  plugins.push(
199
245
  rsc({
200
246
  entries: finalEntries,
201
247
  serverHandler: false,
248
+ clientChunks,
202
249
  }) as PluginOption,
203
250
  );
204
-
205
- // Deduplicate client references from third-party packages in dev mode.
206
- // Prevents module duplication when server components import "use client"
207
- // packages that are also imported directly by client components.
208
251
  plugins.push(clientRefDedup());
209
252
  } else {
210
- // Node preset: full RSC plugin integration
211
- const nodeOptions = resolvedOptions as RangoNodeOptions;
212
-
213
- routerRef.path = nodeOptions.router;
214
-
215
- // Auto-discover router using Vite's resolved root (not process.cwd())
216
- if (!routerRef.path) {
217
- plugins.push({
218
- name: "@rangojs/router:auto-discover",
219
- config(userConfig) {
220
- if (routerRef.path) return;
221
- const root = userConfig.root
222
- ? resolve(process.cwd(), userConfig.root)
223
- : process.cwd();
224
- const filter = createScanFilter(root, {
225
- include: resolvedOptions.include,
226
- exclude: resolvedOptions.exclude,
227
- });
228
- const candidates = findRouterFiles(root, filter);
229
- if (candidates.length === 1) {
230
- const abs = candidates[0];
231
- routerRef.path = (
232
- abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs
233
- ).replaceAll("\\", "/");
234
- } else if (candidates.length > 1) {
235
- const list = candidates
236
- .map(
237
- (f) =>
238
- " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f),
239
- )
240
- .join("\n");
241
- throw new Error(
242
- `[rsc-router] Multiple routers found. Specify \`router\` to choose one:\n${list}`,
243
- );
244
- }
245
- // 0 found: routerRef.path stays undefined, warn at startup via discovery plugin
246
- },
247
- });
248
- }
249
-
250
- const rscOption = nodeOptions.rsc ?? true;
251
-
252
- // Add RSC plugin by default (can be disabled with rsc: false)
253
- if (rscOption !== false) {
254
- // Dynamically import @vitejs/plugin-rsc
255
- const { default: rsc } = await import("@vitejs/plugin-rsc");
256
-
257
- // Resolve entry paths: use explicit config or virtual modules
258
- const userEntries =
259
- typeof rscOption === "boolean" ? {} : rscOption.entries || {};
260
- const finalEntries = {
261
- client: userEntries.client ?? VIRTUAL_IDS.browser,
262
- ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
263
- rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc,
264
- };
265
-
266
- // Track RSC entry for version injection (only if custom entry provided)
267
- rscEntryPath = userEntries.rsc ?? null;
268
-
269
- // Create wrapper plugin that checks for duplicates
270
- let hasWarnedDuplicate = false;
271
-
272
- plugins.push({
273
- name: "@rangojs/router:rsc-integration",
274
- enforce: "pre",
275
-
276
- config() {
277
- // Configure environments for RSC
278
- // When using virtual entries, we need to explicitly configure optimizeDeps
279
- // so Vite pre-bundles React before processing the virtual modules.
280
- // Without this, the dep optimizer may run multiple times with different hashes,
281
- // causing React instance mismatches.
282
- const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
283
- const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
284
- const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
285
-
286
- return {
287
- // Exclude rsc-router modules from optimization to prevent module duplication
288
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
289
- optimizeDeps: {
290
- exclude: excludeDeps,
291
- esbuildOptions: sharedEsbuildOptions,
292
- },
293
- build: {
294
- rollupOptions: { onwarn },
295
- },
296
- resolve: {
297
- alias: rangoAliases,
298
- },
299
- environments: {
300
- client: {
301
- build: {
302
- rollupOptions: {
303
- output: {
304
- manualChunks: getManualChunks,
305
- },
253
+ plugins.push({
254
+ name: "@rangojs/router:auto-discover",
255
+ config(userConfig) {
256
+ if (routerRef.path) return;
257
+ const root = userConfig.root
258
+ ? resolve(process.cwd(), userConfig.root)
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.
308
+ const candidates = findRouterFiles(root);
309
+ if (candidates.length === 1) {
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
+ );
319
+ }
320
+ },
321
+ });
322
+
323
+ const finalEntries = {
324
+ client: VIRTUAL_IDS.browser,
325
+ ssr: VIRTUAL_IDS.ssr,
326
+ rsc: VIRTUAL_IDS.rsc,
327
+ };
328
+
329
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
330
+
331
+ let hasWarnedDuplicate = false;
332
+
333
+ plugins.push({
334
+ name: "@rangojs/router:rsc-integration",
335
+ enforce: "pre",
336
+
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;
363
+ return {
364
+ ...(vercelDefine ? { define: vercelDefine } : {}),
365
+ optimizeDeps: {
366
+ exclude: excludeDeps,
367
+ rolldownOptions: sharedRolldownOptions,
368
+ },
369
+ build: {
370
+ rollupOptions: { onwarn },
371
+ },
372
+ resolve: {
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"],
382
+ },
383
+ environments: {
384
+ client: {
385
+ build: {
386
+ rollupOptions: {
387
+ onwarn,
388
+ output: {
389
+ manualChunks: getManualChunks,
306
390
  },
307
391
  },
308
- // Always exclude rsc-router modules, conditionally add virtual entry
309
- optimizeDeps: {
310
- // Pre-bundle React and rsc-html-stream to prevent late discovery
311
- // triggering ERR_OUTDATED_OPTIMIZED_DEP on cold starts
312
- include: [
313
- "react",
314
- "react-dom",
315
- "react/jsx-runtime",
316
- "react/jsx-dev-runtime",
317
- "rsc-html-stream/client",
318
- ],
319
- exclude: excludeDeps,
320
- esbuildOptions: sharedEsbuildOptions,
321
- ...(useVirtualClient && {
322
- // Tell Vite to scan the virtual entry for dependencies
323
- entries: [VIRTUAL_IDS.browser],
324
- }),
325
- },
326
392
  },
327
- ...(useVirtualSSR && {
328
- ssr: {
329
- optimizeDeps: {
330
- entries: [VIRTUAL_IDS.ssr],
331
- // Pre-bundle all SSR deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
332
- include: [
333
- "react",
334
- "react-dom",
335
- "react-dom/server.edge",
336
- "react-dom/static.edge",
337
- "react/jsx-runtime",
338
- "react/jsx-dev-runtime",
339
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
340
- ],
341
- exclude: excludeDeps,
342
- esbuildOptions: sharedEsbuildOptions,
343
- },
344
- },
345
- }),
346
- ...(useVirtualRSC && {
347
- rsc: {
348
- optimizeDeps: {
349
- entries: [VIRTUAL_IDS.rsc],
350
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
351
- include: [
352
- "react",
353
- "react/jsx-runtime",
354
- "react/jsx-dev-runtime",
355
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
356
- ],
357
- esbuildOptions: sharedEsbuildOptions,
358
- },
359
- },
360
- }),
393
+ optimizeDeps: {
394
+ include: [
395
+ "react",
396
+ "react-dom",
397
+ "react/jsx-runtime",
398
+ "react/jsx-dev-runtime",
399
+ nested("rsc-html-stream/client"),
400
+ ],
401
+ exclude: excludeDeps,
402
+ rolldownOptions: sharedRolldownOptions,
403
+ entries: [VIRTUAL_IDS.browser],
404
+ },
405
+ },
406
+ ssr: {
407
+ ...(vercelServerEnv ?? {}),
408
+ optimizeDeps: {
409
+ entries: [VIRTUAL_IDS.ssr],
410
+ include: [
411
+ "react",
412
+ "react-dom",
413
+ "react-dom/server.edge",
414
+ "react-dom/static.edge",
415
+ "react/jsx-runtime",
416
+ "react/jsx-dev-runtime",
417
+ nested(
418
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
419
+ ),
420
+ ],
421
+ exclude: excludeDeps,
422
+ rolldownOptions: sharedRolldownOptions,
423
+ },
361
424
  },
362
- };
363
- },
364
-
365
- configResolved(config) {
366
- if (showBanner) {
367
- const mode =
368
- config.command === "serve"
369
- ? process.argv.includes("preview")
370
- ? "preview"
371
- : "dev"
372
- : "build";
373
- printBanner(mode, "node", rangoVersion);
374
- }
375
-
376
- // Count how many RSC base plugins there are (rsc:minimal is the main one)
377
- const rscMinimalCount = config.plugins.filter(
378
- (p) => p.name === "rsc:minimal",
379
- ).length;
380
-
381
- if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
382
- hasWarnedDuplicate = true;
383
- console.warn(
384
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. " +
385
- "Remove rsc() from your config or use rango({ rsc: false }) for manual configuration.",
386
- );
387
- }
388
- },
389
- });
390
-
391
- // Add virtual entries plugin (RSC entry generated lazily from routerRef)
392
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
393
-
394
- // Add the RSC plugin directly
395
- // Cast to PluginOption to handle type differences between bundled vite types
396
- plugins.push(
397
- rsc({
398
- entries: finalEntries,
399
- }) as PluginOption,
400
- );
401
- }
402
-
403
- // Deduplicate client references from third-party packages in dev mode.
404
- // Prevents module duplication when server components import "use client"
405
- // packages that are also imported directly by client components.
425
+ rsc: {
426
+ ...(vercelServerEnv ?? {}),
427
+ optimizeDeps: {
428
+ entries: [VIRTUAL_IDS.rsc],
429
+ include: [
430
+ "react",
431
+ "react/jsx-runtime",
432
+ "react/jsx-dev-runtime",
433
+ nested(
434
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge",
435
+ ),
436
+ ],
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,
444
+ },
445
+ },
446
+ },
447
+ };
448
+ },
449
+
450
+ configResolved(config) {
451
+ if (showBanner) {
452
+ const mode =
453
+ config.command === "serve"
454
+ ? process.argv.includes("preview")
455
+ ? "preview"
456
+ : "dev"
457
+ : "build";
458
+ printBanner(
459
+ mode,
460
+ preset === "vercel" ? "vercel" : "node",
461
+ rangoVersion,
462
+ );
463
+ }
464
+
465
+ const rscMinimalCount = config.plugins.filter(
466
+ (p) => p.name === "rsc:minimal",
467
+ ).length;
468
+
469
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
470
+ hasWarnedDuplicate = true;
471
+ console.warn(
472
+ "[rango] Duplicate @vitejs/plugin-rsc detected. " +
473
+ "Remove rsc() from your vite config — rango() includes it automatically.",
474
+ );
475
+ }
476
+ },
477
+ });
478
+
479
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
480
+ plugins.push(performanceTracksPlugin());
481
+ plugins.push(
482
+ rsc({
483
+ entries: finalEntries,
484
+ clientChunks,
485
+ }) as PluginOption,
486
+ );
406
487
  plugins.push(clientRefDedup());
407
488
  }
408
489
 
409
- // Fix HMR for "use client" components.
410
- //
411
- // @vitejs/plugin-rsc's hotUpdate returns undefined for "use client" files
412
- // in the RSC environment. Vite then tries to propagate through the RSC
413
- // module graph, but the proxy module has no import.meta.hot.accept()
414
- // boundary, causing a full page reload. The client env would handle it
415
- // fine via React Refresh, but the RSC env's full-reload arrives first.
416
- //
417
- // Fix: in the RSC env, return [] for "use client" files to signal
418
- // "handled, nothing to propagate". The client env is left alone so
419
- // React Refresh processes the update normally.
420
490
  plugins.push({
421
491
  name: "@rangojs/router:client-component-hmr",
422
492
  hotUpdate(ctx) {
@@ -435,76 +505,56 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
435
505
 
436
506
  try {
437
507
  const source = readFileSync(file, "utf-8");
438
- const trimmed = source.trimStart();
439
- if (
440
- trimmed.startsWith('"use client"') ||
441
- trimmed.startsWith("'use client'")
442
- ) {
443
- // Consume the update in RSC/SSR envs. The proxy module was already
444
- // re-transformed by the RSC plugin's hotUpdate. Without this, Vite
445
- // tries to propagate through the RSC/SSR module graph where the proxy
446
- // has no import.meta.hot.accept() boundary, triggering a full reload.
447
- // The actual component update is handled by React Refresh in the
448
- // client environment.
508
+ if (hasUseClientDirective(source)) {
449
509
  return [];
450
510
  }
451
- } catch {
452
- // File deleted/moved during HMR, let default handling proceed
453
- }
511
+ } catch {}
454
512
  },
455
513
  });
456
514
 
457
515
  plugins.push(exposeActionId());
458
-
459
- // "use cache" directive transform (enforce: "post"):
460
- // Wraps exports with registerCachedFunction() for function-level caching.
461
516
  plugins.push(useCacheTransform());
462
-
463
- // Consolidated plugin for create* ID injection (enforce: "post"):
464
- // loaders, handles, location state, and prerender handlers.
465
517
  plugins.push(exposeInternalIds());
466
-
467
- // Router ID injection runs at normal priority (no enforce) to avoid
468
- // changing Vite's dep optimization timing.
469
518
  plugins.push(exposeRouterId());
470
-
471
- // Add version virtual module plugin for cache invalidation
472
519
  plugins.push(createVersionPlugin());
473
520
 
474
- // Entry path for discovery: user-specified value (if any) or undefined.
475
- // Auto-discovered path is passed separately via routerRef.
476
- // Cloudflare preset: deferred to configResolved (read from resolved Vite env config).
477
521
  const discoveryEntryPath =
478
522
  preset !== "cloudflare" ? routerRef.path : undefined;
479
- // Ref for deferred auto-discovery (node preset only, undefined for cloudflare)
480
523
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
481
524
 
482
- // Version injector: auto-injects VERSION and routes-manifest into custom entry.rsc files.
483
- // Only applies when there's an explicit rscEntryPath or for cloudflare preset (resolved
484
- // lazily in configResolved). For node preset without a custom entry, the router file
485
- // must NOT be transformed — injecting routes-manifest there creates a circular dependency.
486
- const injectorEntryPath =
487
- rscEntryPath ?? (preset === "cloudflare" ? undefined : null);
488
- if (injectorEntryPath !== null) {
489
- plugins.push(createVersionInjectorPlugin(injectorEntryPath));
525
+ if (preset === "cloudflare") {
526
+ plugins.push(createVersionInjectorPlugin(undefined));
490
527
  }
491
528
 
492
- // Transform CJS vendor files to ESM for browser compatibility
493
- // optimizeDeps.include doesn't work because the file is loaded after initial optimization
494
529
  plugins.push(createCjsToEsmPlugin());
495
-
496
- // Router discovery plugin for build-time manifest generation.
497
- // For cloudflare, the entry is resolved lazily in configResolved from the RSC environment.
498
- // For node, discoveryRouterRef provides the auto-discovered path when not user-specified.
499
530
  plugins.push(
500
531
  createRouterDiscoveryPlugin(discoveryEntryPath, {
501
532
  routerPathRef: discoveryRouterRef,
502
533
  enableBuildPrerender: prerenderEnabled,
503
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
504
- include: resolvedOptions.include,
505
- exclude: resolvedOptions.exclude,
534
+ buildEnv: options?.buildEnv,
535
+ preset,
536
+ prerenderOnError: options?.prerender?.onError,
537
+ discovery: options?.discovery,
538
+ clientChunkCtx,
506
539
  }),
507
540
  );
508
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
+ );
509
559
  return plugins;
510
560
  }