@rangojs/router 0.0.0-experimental.97 → 0.0.0-experimental.98914650

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