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

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