@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
@@ -0,0 +1,384 @@
1
+ /**
2
+ * Vercel Build Output (Build Output API v3) emitter for `preset: "vercel"`.
3
+ *
4
+ * After the full app build, restructures dist/ into .vercel/output:
5
+ *
6
+ * .vercel/output/
7
+ * config.json routing: static first, else the function
8
+ * static/ dist/client (browser assets, served at /)
9
+ * functions/<name>.func/
10
+ * .vc-config.json Node serverless, response streaming
11
+ * index.mjs bundled launcher (srvx + @vercel/functions)
12
+ * rsc/ dist/rsc (self-contained RSC server bundle)
13
+ * ssr/ dist/ssr (rsc imports ../ssr/index.js)
14
+ *
15
+ * A prebuilt .vercel/output gets no `npm install`, so everything the function
16
+ * imports must physically live inside the .func directory. This relies on two
17
+ * things the vercel preset arranges (each is a failure only a real deploy — or
18
+ * the isolated smoke test — catches, since a local in-place run is masked by the
19
+ * app's own package.json + node_modules up the tree):
20
+ *
21
+ * 1. The rsc/ssr builds are fully bundled (`resolve.noExternal`, set in
22
+ * rango.ts for this preset). The node default externalizes node_modules
23
+ * deps, which works under `vite preview` but leaves bare imports
24
+ * (@vercel/functions, react-dom/server.edge, ...) that have no node_modules
25
+ * to resolve against on Vercel.
26
+ * 2. A `package.json` with `"type": "module"` is written into the .func dir.
27
+ * The rsc/ssr bundles are ESM but use a `.js` extension; without a
28
+ * type:module in scope the deployed (isolated) function loads them as
29
+ * CommonJS and fails on the first `import`.
30
+ *
31
+ * The launcher is bundled with srvx (the Web->Node streaming bridge, a
32
+ * @rangojs/router dependency) and @vercel/functions (resolved from the app)
33
+ * inlined, keeping the RSC bundle a runtime-relative external.
34
+ *
35
+ * Timing: this runs in the `buildApp` hook (order "post"), which fires once
36
+ * after every environment has built, so dist/{client,rsc,ssr} all exist.
37
+ * closeBundle is unusable here — it fires per environment, and twice for ssr
38
+ * (the server-reference scan and the real build), so it would run before
39
+ * dist/client exists. rango's own prerender/static emitters hardcode dist/rsc,
40
+ * so we build to dist/ and restructure here rather than retargeting outDir.
41
+ */
42
+
43
+ import type { Plugin } from "vite";
44
+ import { rm, mkdir, cp, writeFile } from "node:fs/promises";
45
+ import { existsSync } from "node:fs";
46
+ import { resolve, join } from "node:path";
47
+ import { createRequire } from "node:module";
48
+ import { pathToFileURL } from "node:url";
49
+ import { escapeRegExp } from "../../regex-escape.js";
50
+ import type {
51
+ RangoVercelOptions,
52
+ VercelPresetOptions,
53
+ } from "../plugin-types.js";
54
+
55
+ // Minimal structural types for the esbuild API we use, resolved dynamically from
56
+ // the app so @rangojs/router does not depend on esbuild's type package.
57
+ interface EsbuildPluginBuild {
58
+ onResolve(
59
+ options: { filter: RegExp },
60
+ callback: () => { path: string; external: boolean },
61
+ ): void;
62
+ }
63
+ type EsbuildBuild = (options: Record<string, unknown>) => Promise<unknown>;
64
+ interface EsbuildModule {
65
+ build?: EsbuildBuild;
66
+ default?: { build?: EsbuildBuild };
67
+ }
68
+
69
+ const LAUNCHER_SOURCE = `import { toNodeHandler } from "srvx/node";
70
+ import { waitUntil } from "@vercel/functions";
71
+ import rscHandler from "./rsc/index.js";
72
+
73
+ // The Vercel Node launcher invokes a Node (req, res) handler, not a Web fetch
74
+ // handler. srvx's toNodeHandler bridges the Rango Web fetch handler and pipes
75
+ // the streamed Response to the Node response (set supportsResponseStreaming).
76
+ const onVercel = Boolean(process.env.VERCEL);
77
+
78
+ const fetchHandler = (request) =>
79
+ rscHandler(request, {
80
+ env: process.env,
81
+ // Forward Vercel's waitUntil so cache writes / revalidation run off the
82
+ // response path. Omitted off-platform so those writes settle inline.
83
+ ctx: onVercel ? { waitUntil } : undefined,
84
+ });
85
+
86
+ export default toNodeHandler(fetchHandler);
87
+ `;
88
+
89
+ /**
90
+ * Reject a non-Node runtime for the vercel preset. The preset only emits a Node
91
+ * serverless function (launcherType "Nodejs", bundled Node APIs, response
92
+ * streaming). Vercel's Edge runtime needs a different Build Output primitive
93
+ * (EdgeFunction, a Web-handler entry, no node_modules) that this assembler does
94
+ * not produce, so a non-nodejs runtime would emit a config the platform rejects
95
+ * or mis-runs. Fail fast at build time instead of shipping a broken function.
96
+ */
97
+ export function assertVercelNodeRuntime(runtime: string | undefined): void {
98
+ if (runtime != null && !runtime.startsWith("nodejs")) {
99
+ throw new Error(
100
+ `[rango] preset "vercel": runtime "${runtime}" is not supported. ` +
101
+ `This preset emits a Node serverless function; use a "nodejs*" runtime ` +
102
+ `(default "nodejs22.x"). The Edge runtime is not supported.`,
103
+ );
104
+ }
105
+ }
106
+
107
+ /**
108
+ * The function name becomes a `.func` directory segment and the config.json
109
+ * route `dest` (`/${functionName}`). An empty or space/slash-containing value
110
+ * would land raw in both, producing a broken function path, so restrict it to a
111
+ * safe single path segment and fail loudly rather than emit unroutable output.
112
+ */
113
+ export function assertValidVercelFunctionName(functionName: string): void {
114
+ if (!/^[A-Za-z0-9._-]+$/.test(functionName)) {
115
+ throw new Error(
116
+ `[rango] preset "vercel": invalid functionName ${JSON.stringify(
117
+ functionName,
118
+ )}. Use letters, digits, ".", "_" or "-" only (it becomes the .func directory and the routing dest).`,
119
+ );
120
+ }
121
+ }
122
+
123
+ /** The `.vc-config.json` body: a Node serverless function with streaming. */
124
+ export function buildVercelVcConfig(
125
+ vercel: VercelPresetOptions,
126
+ ): Record<string, unknown> {
127
+ const vcConfig: Record<string, unknown> = {
128
+ runtime: vercel.runtime ?? "nodejs22.x",
129
+ handler: "index.mjs",
130
+ launcherType: "Nodejs",
131
+ shouldAddHelpers: false,
132
+ supportsResponseStreaming: true,
133
+ maxDuration: vercel.maxDuration ?? 30,
134
+ };
135
+ if (vercel.memory != null) vcConfig.memory = vercel.memory;
136
+ if (vercel.regions != null) vcConfig.regions = vercel.regions;
137
+ return vcConfig;
138
+ }
139
+
140
+ /**
141
+ * The Build Output API v3 `config.json` body. Routes, in order: long-cache the
142
+ * content-hashed assets under `${assetsDir}/` (safe to serve `immutable`;
143
+ * without this Vercel serves them `max-age=0, must-revalidate` and browsers
144
+ * re-request every asset on each visit), then the filesystem handler, then
145
+ * everything to the function. The header route uses `continue: true` so it falls
146
+ * through to the filesystem handler that actually serves the file.
147
+ *
148
+ * Route `src` values are REGEXES on Vercel, so the prefix is regex-escaped
149
+ * (an unescaped `assetsDir: "static.v2"` would immutable-stamp any
150
+ * `/static?v2/...` path, including function-rendered pages). An empty
151
+ * assetsDir (assets at the outDir root) gets no header route at all: there is
152
+ * no prefix separating hashed from non-hashed files, so fall back to Vercel's
153
+ * safe default headers. Two accepted edges, matching what other framework
154
+ * adapters (Astro, SvelteKit) emit: files a user places in
155
+ * `public/${assetsDir}/` land under the same prefix and are also stamped
156
+ * immutable (assemble() warns about the collision), and a request for a
157
+ * MISSING asset falls through to the function whose 404 carries the header.
158
+ */
159
+ export function buildVercelOutputConfig(
160
+ functionName: string,
161
+ assetsDir: string,
162
+ ): { version: number; routes: unknown[] } {
163
+ const assetsPrefix = assetsDir.replace(/^\/+|\/+$/g, "");
164
+ const assetHeaderRoute = assetsPrefix
165
+ ? [
166
+ {
167
+ src: `/${escapeRegExp(assetsPrefix)}/(.*)`,
168
+ headers: { "cache-control": "public, max-age=31536000, immutable" },
169
+ continue: true,
170
+ },
171
+ ]
172
+ : [];
173
+ return {
174
+ version: 3,
175
+ routes: [
176
+ ...assetHeaderRoute,
177
+ { handle: "filesystem" },
178
+ { src: "/(.*)", dest: `/${functionName}` },
179
+ ],
180
+ };
181
+ }
182
+
183
+ async function assemble(
184
+ root: string,
185
+ options: RangoVercelOptions,
186
+ assetsDir: string,
187
+ publicDir: string,
188
+ ): Promise<void> {
189
+ const vercel = options.vercel ?? {};
190
+ // Validate config before touching the build output.
191
+ assertVercelNodeRuntime(vercel.runtime);
192
+
193
+ // Files in public/<assetsDir>/ are copied into the same output prefix as the
194
+ // content-hashed build assets, so the immutable cache-control route stamps
195
+ // them too -- replacing such a file after a deploy never reaches returning
196
+ // visitors. Warn instead of silently pinning.
197
+ const assetsPrefix = assetsDir.replace(/^\/+|\/+$/g, "");
198
+ if (publicDir && assetsPrefix && existsSync(join(publicDir, assetsPrefix))) {
199
+ console.warn(
200
+ `[rango] preset "vercel": ${join(publicDir, assetsPrefix)} exists. ` +
201
+ `Files under public/${assetsPrefix}/ share the /${assetsPrefix}/ URL prefix ` +
202
+ `with hashed build assets and will be served with a one-year immutable ` +
203
+ `cache-control header. Move un-hashed public files out of "${assetsPrefix}/".`,
204
+ );
205
+ }
206
+
207
+ const dist = join(root, "dist");
208
+ for (const dir of ["client", "rsc", "ssr"]) {
209
+ if (!existsSync(join(dist, dir))) {
210
+ throw new Error(
211
+ `[rango] preset "vercel": missing dist/${dir}. Run the production build first.`,
212
+ );
213
+ }
214
+ }
215
+ const functionName = vercel.functionName ?? "index";
216
+ assertValidVercelFunctionName(functionName);
217
+ const out = join(root, ".vercel", "output");
218
+ const funcDir = join(out, "functions", `${functionName}.func`);
219
+
220
+ await rm(out, { recursive: true, force: true });
221
+ await mkdir(funcDir, { recursive: true });
222
+
223
+ // 1. Static client assets -> served from the CDN at the root.
224
+ await cp(join(dist, "client"), join(out, "static"), { recursive: true });
225
+
226
+ // 2. Server bundle into the function. Preserve the rsc -> ../ssr/index.js
227
+ // relative import by mirroring the dist/{rsc,ssr} layout inside the func.
228
+ await cp(join(dist, "rsc"), join(funcDir, "rsc"), { recursive: true });
229
+ await cp(join(dist, "ssr"), join(funcDir, "ssr"), { recursive: true });
230
+
231
+ // Prerender/static payload manifests (present only when routes are prerendered).
232
+ if (existsSync(join(dist, "static"))) {
233
+ await cp(join(dist, "static"), join(funcDir, "static"), {
234
+ recursive: true,
235
+ });
236
+ }
237
+
238
+ // 3. Bundle the Node launcher. srvx (a @rangojs/router dependency) is aliased
239
+ // to its resolved path; @vercel/functions resolves from the app; the RSC
240
+ // server bundle stays a runtime-relative external.
241
+ const rangoRequire = createRequire(import.meta.url);
242
+ let srvxNodePath: string;
243
+ try {
244
+ srvxNodePath = rangoRequire.resolve("srvx/node");
245
+ } catch {
246
+ throw new Error(
247
+ '[rango] preset "vercel" requires "srvx" (a dependency of @rangojs/router). Reinstall dependencies.',
248
+ );
249
+ }
250
+
251
+ // esbuild ships with Vite, so we never add it as a @rangojs/router dependency.
252
+ // It is a DIRECT dependency of Vite but only a TRANSITIVE one from the app's
253
+ // view, so under strict pnpm it is NOT resolvable from the app root. Resolve it
254
+ // through Vite's module location (Vite is a direct app dependency, and esbuild
255
+ // is a direct dependency of Vite). Minimal structural types avoid coupling to
256
+ // esbuild's type package at compile time.
257
+ const appRequire = createRequire(join(root, "package.json"));
258
+ const resolveEsbuildPath = (): string => {
259
+ try {
260
+ const viteRequire = createRequire(appRequire.resolve("vite"));
261
+ return viteRequire.resolve("esbuild");
262
+ } catch {
263
+ // Intentionally empty: fall through to the app/rango fallbacks below.
264
+ }
265
+ try {
266
+ return appRequire.resolve("esbuild");
267
+ } catch {
268
+ // Intentionally empty: last resort is @rangojs/router's own resolver.
269
+ }
270
+ return rangoRequire.resolve("esbuild");
271
+ };
272
+ let esbuildModule: EsbuildModule;
273
+ try {
274
+ esbuildModule = (await import(
275
+ pathToFileURL(resolveEsbuildPath()).href
276
+ )) as EsbuildModule;
277
+ } catch {
278
+ throw new Error(
279
+ '[rango] preset "vercel" requires "esbuild" to bundle the function launcher. It ships with Vite; reinstall dependencies (or add esbuild to your app dependencies).',
280
+ );
281
+ }
282
+ const esbuildBuild = esbuildModule.build ?? esbuildModule.default?.build;
283
+ if (typeof esbuildBuild !== "function") {
284
+ throw new Error('[rango] preset "vercel": could not load esbuild.build.');
285
+ }
286
+
287
+ try {
288
+ await esbuildBuild({
289
+ stdin: {
290
+ contents: LAUNCHER_SOURCE,
291
+ resolveDir: root,
292
+ sourcefile: "func-entry.mjs",
293
+ loader: "js",
294
+ },
295
+ outfile: join(funcDir, "index.mjs"),
296
+ bundle: true,
297
+ format: "esm",
298
+ platform: "node",
299
+ target: "node18",
300
+ alias: { "srvx/node": srvxNodePath },
301
+ plugins: [
302
+ {
303
+ name: "external-rsc-entry",
304
+ setup(b: EsbuildPluginBuild) {
305
+ b.onResolve({ filter: /^\.\/rsc\/index\.js$/ }, () => ({
306
+ path: "./rsc/index.js",
307
+ external: true,
308
+ }));
309
+ },
310
+ },
311
+ ],
312
+ });
313
+ } catch (error) {
314
+ const message = error instanceof Error ? error.message : String(error);
315
+ if (/@vercel\/functions/.test(message)) {
316
+ throw new Error(
317
+ '[rango] preset "vercel": could not resolve "@vercel/functions". Add it to your app dependencies (it also backs VercelCacheStore).\n' +
318
+ message,
319
+ );
320
+ }
321
+ throw error;
322
+ }
323
+
324
+ // 3b. Mark the function as ESM. The rsc/ssr bundles are .js ESM files with no
325
+ // package.json in scope on the deployed function (it is isolated at
326
+ // /var/task), so Node would load them as CommonJS and fail on `import`.
327
+ // Locally this is masked because the func inherits the app's
328
+ // "type": "module" up the tree; the deployed func has nothing above it.
329
+ await writeFile(
330
+ join(funcDir, "package.json"),
331
+ JSON.stringify({ type: "module" }, null, 2) + "\n",
332
+ );
333
+
334
+ // 4. Function config: Node serverless with response streaming.
335
+ await writeFile(
336
+ join(funcDir, ".vc-config.json"),
337
+ JSON.stringify(buildVercelVcConfig(vercel), null, 2) + "\n",
338
+ );
339
+
340
+ // 5. Routing config (immutable assets -> filesystem -> function).
341
+ await writeFile(
342
+ join(out, "config.json"),
343
+ JSON.stringify(buildVercelOutputConfig(functionName, assetsDir), null, 2) +
344
+ "\n",
345
+ );
346
+
347
+ console.log(
348
+ `[rango] assembled .vercel/output (function: ${functionName}.func)`,
349
+ );
350
+ }
351
+
352
+ export function createVercelOutputPlugin(options: RangoVercelOptions): Plugin {
353
+ let root = process.cwd();
354
+ let isBuild = false;
355
+ // The client build's assetsDir (Vite default "assets"); used to scope the
356
+ // immutable cache-control route to the content-hashed asset output.
357
+ let assetsDir = "assets";
358
+ // Resolved publicDir ("" when disabled); used to warn when public/<assetsDir>
359
+ // exists, since its un-hashed files share the immutable-header prefix.
360
+ let publicDir = "";
361
+ return {
362
+ name: "@rangojs/router:vercel-output",
363
+ configResolved(config) {
364
+ root = resolve(config.root);
365
+ isBuild = config.command === "build";
366
+ assetsDir =
367
+ config.environments?.client?.build?.assetsDir ??
368
+ config.build?.assetsDir ??
369
+ "assets";
370
+ publicDir = config.publicDir || "";
371
+ },
372
+ // buildApp runs once after the whole multi-environment build (rsc, client,
373
+ // ssr), so dist/ is complete here. closeBundle is unusable for this: it
374
+ // fires per environment, and twice for ssr (the server-reference scan and
375
+ // the real build), so it would run before dist/client exists.
376
+ buildApp: {
377
+ order: "post",
378
+ async handler() {
379
+ if (!isBuild) return;
380
+ await assemble(root, options, assetsDir, publicDir);
381
+ },
382
+ },
383
+ };
384
+ }
@@ -2,12 +2,25 @@ import type { Plugin } from "vite";
2
2
  import { resolve } from "node:path";
3
3
  import * as Vite from "vite";
4
4
  import { resolveRscEntryFromConfig } from "../utils/shared-utils.js";
5
+ import { RSC_ENTRY_BOOTSTRAP_IMPORTS } from "./virtual-entries.js";
5
6
 
6
7
  /**
7
- * Plugin that auto-injects VERSION and routes-manifest into custom entry.rsc files.
8
- * If a custom entry.rsc file uses createRSCHandler but doesn't pass version,
9
- * this transform adds the import and property automatically.
10
- * Also ensures the routes-manifest virtual module is always imported.
8
+ * Plugin that auto-injects VERSION, routes-manifest, and loader-manifest into
9
+ * custom entry.rsc files. If a custom entry.rsc file uses createRSCHandler but
10
+ * doesn't pass version, this transform adds the import and property
11
+ * automatically. It also ensures the routes-manifest and loader-manifest
12
+ * virtual modules are always imported.
13
+ *
14
+ * The loader-manifest import is what makes fetchable loaders resolvable on a
15
+ * custom worker entry. The virtual RSC entry (getVirtualEntryRSC) imports the
16
+ * loader manifest itself, but a hand-written entry (e.g. a Cloudflare
17
+ * worker.rsc.tsx) does not — so without this injection, setLoaderImports() is
18
+ * never bundled, lazyLoaderImports stays null at runtime, and a fetchable
19
+ * loader that is never imported by the server graph (not registered via
20
+ * loader(), reachable only through a client component) cannot be found by the
21
+ * _rsc_loader endpoint in production. Dev still works because it resolves
22
+ * loaders by parsing their id into a file path; only production relied on the
23
+ * manifest, hence the production-only failure this fixes.
11
24
  * @internal
12
25
  */
13
26
  export function createVersionInjectorPlugin(
@@ -29,7 +42,6 @@ export function createVersionInjectorPlugin(
29
42
 
30
43
  transform(code, id) {
31
44
  if (!resolvedEntryPath) return null;
32
- // Only transform the RSC entry file
33
45
  const normalizedId = Vite.normalizePath(id);
34
46
  const normalizedEntry = Vite.normalizePath(resolvedEntryPath);
35
47
 
@@ -37,25 +49,12 @@ export function createVersionInjectorPlugin(
37
49
  return null;
38
50
  }
39
51
 
40
- // Always prepend `import "virtual:rsc-router/routes-manifest"` as the
41
- // first side-effect import. The manifest virtual module's `load()` hook
42
- // awaits `s.discoveryDone` so that, by the time the rest of the entry
43
- // including any module-level `router.reverse()` calls under `./router.js`
44
- // evaluates, runtime discovery has rewritten `router.named-routes.gen.ts`
45
- // with the full route table.
46
- //
47
- // ES module evaluation order matters here: while imports are *parsed*
48
- // hoisted, side-effect imports are evaluated in source order in the
49
- // dependency graph. A user-authored `import "virtual:rsc-router/..."`
50
- // placed after `import "./router.js"` runs too late: the manifest
51
- // gate fires after router.tsx has already crashed on a stale gen file.
52
- // We always prepend; ESM dedups any user-written duplicate, so module
53
- // initialization still runs once.
54
- const prepend: string[] = [
55
- `import "virtual:rsc-router/routes-manifest";`,
56
- ];
52
+ // Same startup bootstrap imports the generated virtual RSC entry uses,
53
+ // from the single shared list so the two paths cannot drift.
54
+ const prepend: string[] = RSC_ENTRY_BOOTSTRAP_IMPORTS.map(
55
+ (id) => `import "${id}";`,
56
+ );
57
57
 
58
- // Auto-inject VERSION if file uses createRSCHandler without version
59
58
  let newCode = code;
60
59
  const needsVersion =
61
60
  code.includes("createRSCHandler") &&
@@ -70,10 +69,6 @@ export function createVersionInjectorPlugin(
70
69
  );
71
70
  }
72
71
 
73
- // Insert after any leading `/// <reference ... />` triple-slash
74
- // directives (and surrounding blank lines). TypeScript requires those
75
- // directives to precede all other code; putting our imports above
76
- // them silently demotes the directives to plain comments.
77
72
  const lines = newCode.split("\n");
78
73
  let insertAt = 0;
79
74
  while (insertAt < lines.length) {
@@ -1,5 +1,6 @@
1
1
  import { parseAst, type Plugin } from "vite";
2
2
  import { VIRTUAL_IDS, getVirtualVersionContent } from "./virtual-entries.js";
3
+ import { hasUseClientDirective } from "../utils/directive-prologue.js";
3
4
 
4
5
  interface ClientModuleSignature {
5
6
  key: string;
@@ -16,6 +17,10 @@ function normalizeModuleId(id: string): string {
16
17
  function getClientModuleSignature(
17
18
  source: string,
18
19
  ): ClientModuleSignature | undefined {
20
+ // Same leading-directive sniff the rango HMR plugin uses (shared helper, so the
21
+ // two agree on what counts as a client module). A parse failure yields false.
22
+ if (!hasUseClientDirective(source)) return undefined;
23
+
19
24
  let program: any;
20
25
  try {
21
26
  program = parseAst(source, { lang: "tsx" });
@@ -23,23 +28,6 @@ function getClientModuleSignature(
23
28
  return undefined;
24
29
  }
25
30
 
26
- let isUseClient = false;
27
- for (const node of program.body ?? []) {
28
- if (
29
- node?.type === "ExpressionStatement" &&
30
- node.expression?.type === "Literal" &&
31
- typeof node.expression.value === "string"
32
- ) {
33
- if (node.expression.value === "use client") {
34
- isUseClient = true;
35
- }
36
- continue;
37
- }
38
- break;
39
- }
40
-
41
- if (!isUseClient) return undefined;
42
-
43
31
  const exports = new Set<string>();
44
32
  let hasDefault = false;
45
33
  let hasExportAll = false;
@@ -138,8 +126,6 @@ export function createVersionPlugin(): Plugin {
138
126
 
139
127
  let versionCounter = 0;
140
128
  const bumpVersion = (reason: string) => {
141
- // Use timestamp + counter to guarantee uniqueness even when multiple
142
- // bumps happen within the same millisecond (e.g. cascading HMR events).
143
129
  currentVersion = Date.now().toString(16) + String(++versionCounter);
144
130
  console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
145
131
 
@@ -158,9 +144,6 @@ export function createVersionPlugin(): Plugin {
158
144
 
159
145
  configResolved(config) {
160
146
  isDev = config.command === "serve";
161
- // Capture the resolved cacheDir so we can ignore optimizer-output
162
- // writes inside it. Vite resolves cacheDir against the project root,
163
- // so this is a stable absolute path for the lifetime of the server.
164
147
  resolvedCacheDir = config.cacheDir
165
148
  ? String(config.cacheDir).replace(/\\/g, "/")
166
149
  : undefined;
@@ -210,27 +193,15 @@ export function createVersionPlugin(): Plugin {
210
193
  return null;
211
194
  },
212
195
 
213
- // Track RSC module changes and update version
214
196
  async hotUpdate(ctx) {
215
197
  if (!isDev) return;
216
198
 
217
- // Check if this is an RSC environment update (not client/ssr)
218
- // RSC modules affect server-rendered content and cached payloads
219
- // In Vite 6, environment is accessed via `this.environment`
220
199
  const isRscModule = this.environment?.name === "rsc";
221
200
 
222
201
  if (!isRscModule) return;
223
202
 
224
- // Skip Vite's own pre-bundled dep cache writes. The optimizer rewrites
225
- // files inside the configured `cacheDir` on every discovery cycle
226
- // (and when other dev servers under the same cwd populate their own
227
- // isolated cache dirs). These are not user-source changes, so bumping
228
- // the app version on them produces spurious version mismatches that
229
- // surface as forced reloads on in-flight actions.
230
203
  if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
231
204
 
232
- // Skip re-bumping when the version virtual module itself is invalidated
233
- // (our own bumpVersion() invalidates it, which re-triggers hotUpdate).
234
205
  if (
235
206
  ctx.modules.length === 1 &&
236
207
  ctx.modules[0].id === "\0" + VIRTUAL_IDS.version
@@ -245,9 +216,6 @@ export function createVersionPlugin(): Plugin {
245
216
  const source = await ctx.read();
246
217
  const nextSignature = getClientModuleSignature(source);
247
218
  if (nextSignature) {
248
- // "use client" file — compare export signatures.
249
- // client-component-hmr may have cleared ctx.modules, so we
250
- // cannot rely on ctx.modules.length for these files.
251
219
  clientModuleSignatures.set(filePath, nextSignature);
252
220
  if (
253
221
  previousSignature &&
@@ -258,20 +226,11 @@ export function createVersionPlugin(): Plugin {
258
226
  } else {
259
227
  clientModuleSignatures.delete(filePath);
260
228
  if (!previousSignature) {
261
- // Not and never was "use client" — use module graph check.
262
- // ctx.modules is reliable for pure server files (only
263
- // client-component-hmr clears it for "use client" modules).
264
229
  if (ctx.modules.length === 0) return;
265
230
  }
266
- // Was "use client" but directive removed — boundary changed,
267
- // bump below.
268
231
  }
269
- } catch {
270
- // Fail open: if we can't read or parse the update, invalidate.
271
- }
232
+ } catch {}
272
233
  } else {
273
- // Non-code file (json, css, etc.) — only bump if it's actually
274
- // referenced by the RSC module graph.
275
234
  if (ctx.modules.length === 0) return;
276
235
  }
277
236
 
@@ -280,20 +239,6 @@ export function createVersionPlugin(): Plugin {
280
239
  };
281
240
  }
282
241
 
283
- /**
284
- * Match Vite's pre-bundled dep cache directories. These paths are rewritten
285
- * by the dep optimizer (and by isolated test fixtures sharing the same cwd),
286
- * not by user source changes, so they should not bump the app version (which
287
- * would force a client reload mid-request).
288
- *
289
- * Two checks:
290
- * 1. Anything inside the resolved `cacheDir` (precise — covers custom paths
291
- * like the `RANGO_E2E_VITE_CACHE_DIR` overrides in the test fixtures).
292
- * 2. Heuristic match for any `node_modules/.vite*` directory or a
293
- * `.vite-isolated/` segment anywhere in the path. This catches the
294
- * *other* dev servers in the same cwd whose cacheDir we cannot read
295
- * (we only see config of the server we're attached to).
296
- */
297
242
  export function isViteDepCachePath(
298
243
  filePath: string | undefined,
299
244
  cacheDir?: string,
@@ -311,11 +256,6 @@ export function isViteDepCachePath(
311
256
  }
312
257
  }
313
258
 
314
- // Vite/optimizer convention: cache dirs always sit directly under
315
- // `node_modules/` and start with `.vite` (e.g. `.vite`, `.vite-temp`,
316
- // `.vite_rango_generate`, `.vite-e2e-test-app`). The `/.vite-isolated/`
317
- // segment covers the test-fixture pattern that places the cache outside
318
- // node_modules.
319
259
  return (
320
260
  /\/node_modules\/\.vite[^/]*\//.test(normalized) ||
321
261
  normalized.includes("/.vite-isolated/")