@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140

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 (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  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 +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +426 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2500 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +29 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-cache.ts +386 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +156 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1102 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +177 -35
  205. package/src/index.ts +255 -71
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +337 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +439 -0
  313. package/src/rsc/ssr-setup.ts +144 -0
  314. package/src/rsc/transition-gate.ts +89 -0
  315. package/src/rsc/types.ts +95 -12
  316. package/src/runtime-env.ts +18 -0
  317. package/src/search-params.ts +99 -82
  318. package/src/segment-content-promise.ts +67 -0
  319. package/src/segment-loader-promise.ts +149 -0
  320. package/src/segment-system.tsx +349 -134
  321. package/src/serialize.ts +243 -0
  322. package/src/server/context.ts +452 -85
  323. package/src/server/cookie-parse.ts +32 -0
  324. package/src/server/cookie-store.ts +310 -0
  325. package/src/server/fetchable-loader-store.ts +11 -6
  326. package/src/server/handle-store.ts +123 -42
  327. package/src/server/live.ts +130 -0
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +842 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +412 -136
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +40 -72
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +43 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +275 -0
  389. package/src/urls/pattern-types.ts +124 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Bake the resolved INTERNAL_RANGO_DEBUG value into the router's `internal-debug`
3
+ * module so the flag reaches the CLIENT debug logs by just setting the env var.
4
+ *
5
+ * internal-debug.ts normally reads the flag via `typeof __RANGO_DEBUG__`, a Vite
6
+ * define. That delivery is unreliable on the client: in dev Vite ships the define
7
+ * only as an injected global whose presence varies across consumer setups, so the
8
+ * module can fall through to `process.env` (undefined in the browser) and the FE
9
+ * debug flag silently stays false while the server logs work. A Vite `transform`
10
+ * runs on the module regardless of how (or whether) the define is delivered, in
11
+ * both dev and build and for every environment, so the discovery plugin uses this
12
+ * to replace the module with the resolved literal.
13
+ *
14
+ * Returns null for any module that is not the router's internal-debug module.
15
+ */
16
+ export function injectClientDebugFlag(
17
+ id: string,
18
+ ): { code: string; map: null } | null {
19
+ // Cheap early-out: this hook runs on every module in every environment.
20
+ if (!id.includes("internal-debug")) return null;
21
+ const norm = id.replace(/\\/g, "/");
22
+ // Scope to the router's own internal-debug module: the published package
23
+ // (`/@rangojs/router/`, incl. pnpm's nested layout) or the monorepo workspace
24
+ // (`/packages/rangojs-router/`). The package-anchored path avoids matching a
25
+ // consumer file that merely sits under a directory named `rangojs-router`.
26
+ const isInternalDebug =
27
+ /\/internal-debug\.[cm]?[jt]sx?(\?|$)/.test(norm) &&
28
+ (norm.includes("/@rangojs/router/") ||
29
+ norm.includes("/packages/rangojs-router/"));
30
+ if (!isInternalDebug) return null;
31
+ // Emit the whole module: internal-debug.ts has a single export, kept in sync.
32
+ return {
33
+ code: `export const INTERNAL_RANGO_DEBUG = ${!!process.env.INTERNAL_RANGO_DEBUG};\n`,
34
+ map: null,
35
+ };
36
+ }
@@ -0,0 +1,303 @@
1
+ // -- Build-time environment types -------------------------------------------
2
+
3
+ /**
4
+ * Context passed to a buildEnv factory function.
5
+ * Provides Vite config details for conditional env setup.
6
+ */
7
+ export interface BuildEnvFactoryContext {
8
+ /** Vite project root directory. */
9
+ root: string;
10
+ /** Vite mode (e.g. "development", "production"). */
11
+ mode: string;
12
+ /** Vite command ("serve" for dev, "build" for production). */
13
+ command: "serve" | "build";
14
+ /** Router deployment preset. */
15
+ preset: "node" | "cloudflare" | "vercel";
16
+ }
17
+
18
+ /**
19
+ * Factory function that creates build-time environment bindings.
20
+ * Called once at plugin startup. Return `dispose` to clean up resources.
21
+ */
22
+ export type BuildEnvFactory = (
23
+ ctx: BuildEnvFactoryContext,
24
+ ) => Promise<BuildEnvResult> | BuildEnvResult;
25
+
26
+ /**
27
+ * Result of resolving build-time environment bindings.
28
+ */
29
+ export interface BuildEnvResult {
30
+ /** Environment bindings available to Prerender/Static handlers via ctx.env. */
31
+ env: Record<string, unknown>;
32
+ /** Called after build completes to clean up resources (e.g., miniflare). */
33
+ dispose?: () => Promise<void> | void;
34
+ }
35
+
36
+ /**
37
+ * Build-time environment configuration for Prerender and Static handlers.
38
+ *
39
+ * - `false` (default): no build-time env, `ctx.env` throws.
40
+ * - `"auto"`: calls `wrangler.getPlatformProxy()` (cloudflare preset only).
41
+ * - Object: used directly as `ctx.env` during build.
42
+ * - Factory: called once at startup, must return `{ env, dispose? }`.
43
+ */
44
+ export type BuildEnvOption =
45
+ | false
46
+ | "auto"
47
+ | Record<string, unknown>
48
+ | BuildEnvFactory;
49
+
50
+ // -- Client chunking --------------------------------------------------------
51
+
52
+ /**
53
+ * Metadata for one client ("use client") module, passed to a {@link ClientChunks}
54
+ * function. Mirrors the shape `@vitejs/plugin-rsc` passes to its own
55
+ * `clientChunks` option.
56
+ */
57
+ export interface ClientChunkMeta {
58
+ /** Absolute module id of the "use client" file. */
59
+ id: string;
60
+ /** Normalized (posix) module id — convenient for path-based matching. */
61
+ normalizedId: string;
62
+ /**
63
+ * The RSC/server chunk that statically imports this client reference. This is
64
+ * the key used for the default grouping when no override is supplied: a single
65
+ * router that statically imports every route yields ONE `serverChunk`, hence
66
+ * one client chunk for all routes.
67
+ */
68
+ serverChunk: string;
69
+ }
70
+
71
+ /**
72
+ * Controls how client ("use client") components are grouped into browser
73
+ * chunks, i.e. per-route / per-feature code splitting of the client bundle.
74
+ *
75
+ * Without splitting, a single router ships ONE client chunk containing every
76
+ * route's client components (and their CSS) — navigating to one route downloads
77
+ * every other route's client code. (Host sub-apps loaded via a dynamic `import()`
78
+ * are the exception: each forms its own chunk.) This option controls how that
79
+ * monolith is split.
80
+ *
81
+ * Behavior branches:
82
+ * - `true` / omitted (**default**, pre-1.0): Rango's built-in **directory
83
+ * strategy**. It splits app `"use client"` modules by **route id** — the segment
84
+ * after a route-root directory (`routes`, `app`, `pages`, `features`, `handlers`,
85
+ * …) — so `routes/dashboard/**` becomes `app-dashboard` at any nesting depth.
86
+ * Where it finds NO route structure (a flat `src/components/`, or host sub-apps
87
+ * already split by a dynamic `import()`), it inherits the default grouping
88
+ * unchanged — so the shared `src/components` chunk stays shared and host apps do
89
+ * not leak across each other. Shared runtime (React, the router, `node_modules`)
90
+ * is never split.
91
+ * - `false`: opt out — inherit `@vitejs/plugin-rsc`'s default grouping everywhere
92
+ * (one chunk per router / per host sub-app).
93
+ * - function: full override. Return a chunk group name, or `undefined` to fall
94
+ * back to the default grouping for that one module. Forwarded directly to
95
+ * `@vitejs/plugin-rsc`'s `clientChunks`.
96
+ *
97
+ * Every module maps to exactly one group, so there is no byte duplication: a
98
+ * component used by two routes lives in one group and is fetched whenever it
99
+ * renders. Put genuinely shared client components OUTSIDE route directories so
100
+ * they land in the shared group rather than one route's chunk.
101
+ *
102
+ * @default true
103
+ */
104
+ export type ClientChunks =
105
+ | boolean
106
+ | ((meta: ClientChunkMeta) => string | undefined);
107
+
108
+ // -- Plugin options ---------------------------------------------------------
109
+
110
+ /**
111
+ * Base options shared by all presets
112
+ */
113
+ interface RangoBaseOptions {
114
+ /**
115
+ * Show startup banner. Set to false to disable.
116
+ * @default true
117
+ */
118
+ banner?: boolean;
119
+
120
+ /**
121
+ * Group client ("use client") components into browser chunks for per-route /
122
+ * per-feature code splitting. On by default (pre-1.0); pass `false` to opt out.
123
+ * See {@link ClientChunks}.
124
+ *
125
+ * @default true
126
+ */
127
+ clientChunks?: ClientChunks;
128
+
129
+ /**
130
+ * Filter which files route discovery scans, by glob. Paths are matched
131
+ * root-relative (e.g. `src/routes/**`). `include` restricts discovery to
132
+ * matching files; `exclude` removes matches (the defaults cover tests, dist,
133
+ * coverage, etc.). Mirrors the CLI's `--include`/`--exclude`.
134
+ *
135
+ * @example
136
+ * rango({ discovery: { include: ["src/routes/**"] } })
137
+ */
138
+ discovery?: {
139
+ include?: string[];
140
+ exclude?: string[];
141
+ };
142
+
143
+ /**
144
+ * What to do when a `Prerender` route's or `Static` handler's render throws at
145
+ * build time. Otherwise the route error boundary catches it and the rendered
146
+ * error page is baked as the artifact, then served as an HTTP 200 — a silent,
147
+ * user-visible breakage (issue #587). Independent of this setting, a render may
148
+ * `throw new Skip()` (from `@rangojs/router`) to skip a single URL/handler.
149
+ *
150
+ * - `"fail"` (**default**): fail the build, naming the URL/handler and the
151
+ * original render error.
152
+ * - `"warn"`: log a warning and skip baking the artifact — it is never served as a
153
+ * baked 200 error page. `"warn"` is a build-unblock, NOT a runtime contract for
154
+ * the skipped entry: the route falls through to normal resolution, which may
155
+ * render live (its handler is still bundled — e.g. when nothing else baked) or
156
+ * 404 (once prerender handler eviction has run for other baked entries), so the
157
+ * outcome depends on the rest of the build, and a skipped `Static()` handler's
158
+ * evicted code can surface as an error. For DEFINED runtime behavior use
159
+ * `Passthrough()` (a live fallback) or `throw new Skip()` (an intentional skip);
160
+ * otherwise prefer the default `"fail"`.
161
+ *
162
+ * @default "fail"
163
+ */
164
+ prerender?: {
165
+ onError?: "fail" | "warn";
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Options for Node.js deployment (default)
171
+ */
172
+ export interface RangoNodeOptions extends RangoBaseOptions {
173
+ /**
174
+ * Deployment preset. Defaults to 'node' when not specified.
175
+ */
176
+ preset?: "node";
177
+
178
+ /**
179
+ * Path to a host-router entry (a module that calls `createHostRouter()` and
180
+ * exports the instance) to serve instead of a single `createRouter()` app.
181
+ * Root-relative (e.g. `"./src/worker.rsc.tsx"`).
182
+ *
183
+ * Set this when the app is a multi-app host router: auto-discovery otherwise
184
+ * finds the sub-apps' multiple `createRouter()` files and cannot pick an entry.
185
+ * When omitted, rango auto-detects a single `createHostRouter()` file if the
186
+ * app has several `createRouter()` files. The host module must export the
187
+ * `HostRouter` instance (default export or a named `hostRouter`/`router`
188
+ * export), not a Cloudflare-style `{ fetch }` object.
189
+ */
190
+ hostRouter?: string;
191
+
192
+ /**
193
+ * Environment bindings available to Prerender and Static handlers at build
194
+ * time via `ctx.env`. Shared across all prerender invocations for the build.
195
+ *
196
+ * `"auto"` is Cloudflare-only (it resolves the wrangler platform proxy), so it
197
+ * is not accepted on the Node preset — pass an object or a factory instead.
198
+ *
199
+ * @default false
200
+ */
201
+ buildEnv?: Exclude<BuildEnvOption, "auto">;
202
+ }
203
+
204
+ /**
205
+ * Options for Cloudflare Workers deployment
206
+ */
207
+ export interface RangoCloudflareOptions extends RangoBaseOptions {
208
+ /**
209
+ * Deployment preset for Cloudflare Workers.
210
+ * When using cloudflare preset:
211
+ * - @vitejs/plugin-rsc IS still added by rango(), but with `serverHandler: false`
212
+ * (the cloudflare plugin owns the RSC worker/server entry); only `client` and
213
+ * `ssr` virtual entries are configured, no rsc entry
214
+ * - Your worker entry (e.g., worker.rsc.tsx) imports the router directly
215
+ * - Browser and SSR use virtual entries
216
+ * - Build-time manifest generation is auto-detected from the resolved RSC environment config
217
+ */
218
+ preset: "cloudflare";
219
+
220
+ /**
221
+ * Environment bindings available to Prerender and Static handlers at build
222
+ * time via `ctx.env`. Shared across all prerender invocations for the build.
223
+ *
224
+ * `"auto"` resolves the Cloudflare platform proxy via wrangler
225
+ * `getPlatformProxy()`.
226
+ *
227
+ * @default false
228
+ */
229
+ buildEnv?: BuildEnvOption;
230
+ }
231
+
232
+ /**
233
+ * Per-function knobs for the Vercel deployment, written into the generated
234
+ * `.vc-config.json` (and `config.json` for `functionName`).
235
+ */
236
+ export interface VercelPresetOptions {
237
+ /** Node runtime for the function. @default "nodejs22.x" */
238
+ runtime?: string;
239
+ /** Max execution time in seconds. @default 30 */
240
+ maxDuration?: number;
241
+ /** Function memory in MB (platform default when omitted). */
242
+ memory?: number;
243
+ /** Regions to pin the function to (platform default when omitted). */
244
+ regions?: string[];
245
+ /**
246
+ * Function name — the `<name>.func` directory and the `config.json` route
247
+ * destination. @default "index"
248
+ */
249
+ functionName?: string;
250
+ }
251
+
252
+ /**
253
+ * Options for Vercel Functions deployment.
254
+ *
255
+ * Builds like the node preset (Vercel runs Node Functions, not Workers): rango
256
+ * owns the RSC entry, `process.env.NODE_ENV` is folded for the build, and after
257
+ * the build a `.vercel/output` directory (Build Output API v3) is assembled from
258
+ * `dist/` — a single streaming Node Function plus the static client assets. The
259
+ * app must install `@vercel/functions` (used by `VercelCacheStore` and the
260
+ * generated function launcher).
261
+ */
262
+ export interface RangoVercelOptions extends RangoBaseOptions {
263
+ /**
264
+ * Deployment preset for Vercel Functions.
265
+ */
266
+ preset: "vercel";
267
+
268
+ /**
269
+ * Path to a host-router entry (a module that calls `createHostRouter()` and
270
+ * exports the instance) to serve instead of a single `createRouter()` app.
271
+ * Root-relative (e.g. `"./src/worker.rsc.tsx"`).
272
+ *
273
+ * Set this when the app is a multi-app host router: auto-discovery otherwise
274
+ * finds the sub-apps' multiple `createRouter()` files and cannot pick an entry.
275
+ * When omitted, rango auto-detects a single `createHostRouter()` file if the
276
+ * app has several `createRouter()` files. The host module must export the
277
+ * `HostRouter` instance (default export or a named `hostRouter`/`router`
278
+ * export), not a Cloudflare-style `{ fetch }` object. The Vercel function then
279
+ * runs `hostRouter.match()` for every request (single-function deploy).
280
+ */
281
+ hostRouter?: string;
282
+
283
+ /**
284
+ * Environment bindings available to Prerender and Static handlers at build
285
+ * time via `ctx.env`. `"auto"` is Cloudflare-only; pass an object or a factory.
286
+ *
287
+ * @default false
288
+ */
289
+ buildEnv?: Exclude<BuildEnvOption, "auto">;
290
+
291
+ /**
292
+ * Vercel function configuration written into the Build Output.
293
+ */
294
+ vercel?: VercelPresetOptions;
295
+ }
296
+
297
+ /**
298
+ * Options for rango() Vite plugin
299
+ */
300
+ export type RangoOptions =
301
+ | RangoNodeOptions
302
+ | RangoCloudflareOptions
303
+ | RangoVercelOptions;
@@ -0,0 +1,90 @@
1
+ import type { Plugin } from "vite";
2
+ import { createRangoDebugger, NS } from "../debug.js";
3
+
4
+ const debug = createRangoDebugger(NS.transform);
5
+
6
+ /**
7
+ * Transform CJS vendor files from @vitejs/plugin-rsc to ESM for browser compatibility.
8
+ * The react-server-dom vendor files are shipped as CJS which doesn't work in browsers.
9
+ */
10
+ export function createCjsToEsmPlugin(): Plugin {
11
+ // Picked from Vite's resolved mode, not process.env.NODE_ENV, so the dev vs
12
+ // production vendor variant tracks the build mode the user actually ran.
13
+ let isProduction = false;
14
+
15
+ return {
16
+ name: "@rangojs/router:cjs-to-esm",
17
+ enforce: "pre",
18
+ configResolved(config) {
19
+ isProduction = config.isProduction;
20
+ },
21
+ transform(code, id) {
22
+ const cleanId = id.split("?")[0].replaceAll("\\", "/");
23
+
24
+ if (cleanId.includes("vendor/react-server-dom/client.browser.js")) {
25
+ const isProd = isProduction;
26
+ const cjsFile = isProd
27
+ ? "./cjs/react-server-dom-webpack-client.browser.production.js"
28
+ : "./cjs/react-server-dom-webpack-client.browser.development.js";
29
+
30
+ debug?.("cjs-to-esm entry redirect %s", id);
31
+ return {
32
+ code: `export * from "${cjsFile}";`,
33
+ map: null,
34
+ };
35
+ }
36
+
37
+ if (
38
+ cleanId.includes("vendor/react-server-dom/cjs/") &&
39
+ cleanId.includes("client.browser")
40
+ ) {
41
+ let transformed = code;
42
+
43
+ const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
44
+ const license = licenseMatch ? licenseMatch[0] : "";
45
+ if (license) {
46
+ transformed = transformed.slice(license.length);
47
+ }
48
+
49
+ transformed = transformed.replace(/^\s*["']use strict["'];\s*/, "");
50
+
51
+ transformed = transformed.replace(
52
+ /^\s*["']production["']\s*!==\s*process\.env\.NODE_ENV\s*&&\s*\(function\s*\(\)\s*\{/,
53
+ "",
54
+ );
55
+
56
+ transformed = transformed.replace(/\}\)\(\);?\s*$/, "");
57
+
58
+ transformed = transformed.replace(
59
+ /var\s+React\s*=\s*require\s*\(\s*["']react["']\s*\)\s*,[\s\n]+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
60
+ 'import React from "react";\nimport ReactDOM from "react-dom";\nvar ',
61
+ );
62
+
63
+ transformed = transformed.replace(
64
+ /var\s+ReactDOM\s*=\s*require\s*\(\s*["']react-dom["']\s*\)\s*,/g,
65
+ 'import ReactDOM from "react-dom";\nvar ',
66
+ );
67
+
68
+ transformed = transformed.replace(
69
+ /exports\.(\w+)\s*=\s*function\s*\(/g,
70
+ "export function $1(",
71
+ );
72
+
73
+ transformed = transformed.replace(
74
+ /exports\.(\w+)\s*=/g,
75
+ "export const $1 =",
76
+ );
77
+
78
+ transformed = license + "\n" + transformed;
79
+
80
+ debug?.("cjs-to-esm body rewrite %s", id);
81
+ return {
82
+ code: transformed,
83
+ map: null,
84
+ };
85
+ }
86
+
87
+ return null;
88
+ },
89
+ };
90
+ }
@@ -0,0 +1,120 @@
1
+ import type { Plugin, ResolvedConfig } from "vite";
2
+ import { createRangoDebugger, NS } from "../debug.js";
3
+
4
+ const debug = createRangoDebugger(NS.transform);
5
+
6
+ const CLIENT_IN_SERVER_PROXY_PREFIX =
7
+ "virtual:vite-rsc/client-in-server-package-proxy/";
8
+
9
+ /**
10
+ * Extract the bare package name from an absolute node_modules path.
11
+ * Handles scoped packages (@org/name) and nested node_modules.
12
+ * Returns null if the path doesn't contain a valid package reference.
13
+ *
14
+ * NOTE: This is a lossy transformation. It maps a specific submodule path
15
+ * (e.g., pkg/internal/context.js) to the package root (pkg). The load()
16
+ * hook then re-exports via the bare specifier, which resolves to the
17
+ * package entry point. This works for packages that barrel-export their
18
+ * "use client" symbols from the root, which covers the common case
19
+ * (component libraries like @mantine/core, @chakra-ui/react, etc.).
20
+ * Packages whose client symbols are only available from deep subpaths
21
+ * (not re-exported from the root) would lose those symbols after the
22
+ * rewrite. A more precise approach would resolve through the package's
23
+ * exports map to find the correct entry point, but that adds significant
24
+ * complexity for a rare edge case.
25
+ * See: https://github.com/cloudflare/vinext/pull/413
26
+ */
27
+ export function extractPackageName(absolutePath: string): string | null {
28
+ // Find the last /node_modules/ segment (handles nested node_modules)
29
+ const marker = "/node_modules/";
30
+ const idx = absolutePath.lastIndexOf(marker);
31
+ if (idx === -1) return null;
32
+
33
+ const afterModules = absolutePath.slice(idx + marker.length);
34
+
35
+ if (afterModules.startsWith("@")) {
36
+ // Scoped package: @org/name
37
+ const parts = afterModules.split("/");
38
+ if (parts.length < 2 || !parts[1]) return null;
39
+ return `${parts[0]}/${parts[1]}`;
40
+ }
41
+
42
+ // Unscoped package: name
43
+ const name = afterModules.split("/")[0];
44
+ return name || null;
45
+ }
46
+
47
+ /**
48
+ * Vite plugin that deduplicates client references from third-party packages
49
+ * in dev mode.
50
+ *
51
+ * When @vitejs/plugin-rsc encounters a "use client" submodule inside a
52
+ * package imported from a server component, it creates a
53
+ * client-in-server-package-proxy virtual module that re-exports from the
54
+ * absolute file path. In the client environment, this absolute path bypasses
55
+ * Vite's pre-bundling, while direct client imports of the same package go
56
+ * through .vite/deps/. Two separate module instances are created, breaking
57
+ * React contexts (createContext runs twice, provider/consumer mismatch).
58
+ *
59
+ * This plugin intercepts absolute node_modules imports from proxy modules
60
+ * in the client environment and rewrites them to bare specifier imports
61
+ * that go through pre-bundling, ensuring a single module instance.
62
+ *
63
+ * Dev-only: production builds use the SSR manifest which handles module
64
+ * identity correctly.
65
+ */
66
+ export function clientRefDedup(): Plugin {
67
+ let clientExclude: string[] = [];
68
+ const dedupedPackages = new Set<string>();
69
+
70
+ return {
71
+ name: "@rangojs/router:client-ref-dedup",
72
+ enforce: "pre",
73
+ apply: "serve",
74
+
75
+ configResolved(config: ResolvedConfig) {
76
+ const clientEnv = config.environments?.["client"];
77
+ clientExclude =
78
+ clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
79
+ },
80
+
81
+ buildEnd() {
82
+ if (debug && dedupedPackages.size > 0) {
83
+ debug(
84
+ "client-ref-dedup: redirected %d package(s) (%s)",
85
+ dedupedPackages.size,
86
+ [...dedupedPackages].join(","),
87
+ );
88
+ }
89
+ },
90
+
91
+ resolveId(source, importer, options) {
92
+ if (this.environment?.name !== "client") return;
93
+
94
+ if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
95
+
96
+ if (!source.includes("/node_modules/")) return;
97
+
98
+ const packageName = extractPackageName(source);
99
+ if (!packageName) return;
100
+
101
+ if (clientExclude.includes(packageName)) return;
102
+
103
+ if (debug) dedupedPackages.add(packageName);
104
+
105
+ return `\0rango:dedup/${packageName}`;
106
+ },
107
+
108
+ load(id) {
109
+ if (!id.startsWith("\0rango:dedup/")) return;
110
+
111
+ const packageName = id.slice("\0rango:dedup/".length);
112
+
113
+ return [
114
+ `export * from ${JSON.stringify(packageName)};`,
115
+ `import * as __all__ from ${JSON.stringify(packageName)};`,
116
+ `export default __all__.default;`,
117
+ ].join("\n");
118
+ },
119
+ };
120
+ }
@@ -0,0 +1,118 @@
1
+ import type { Plugin } from "vite";
2
+ import { relative } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { createRangoDebugger, createCounter, NS } from "../debug.js";
5
+
6
+ const debug = createRangoDebugger(NS.transform);
7
+
8
+ const CLIENT_PKG_PROXY_PREFIX =
9
+ "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
10
+ const CLIENT_IN_SERVER_PKG_PROXY_PREFIX =
11
+ "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
12
+ const FS_PREFIX = "/@fs/";
13
+
14
+ /**
15
+ * Compute the production SHA-256 hash for a dev-mode client reference key.
16
+ * Mirrors the hashing logic in @vitejs/plugin-rsc's build mode:
17
+ * - Local files: hashString(toRelativeId(id)) where toRelativeId = relative(root, id)
18
+ * - Package proxies: hashString(packageSource)
19
+ * - client-in-server-package proxies: hashString(relative(root, decodedAbsPath))
20
+ *
21
+ * Returns the input unchanged if it doesn't match a known dev-mode pattern
22
+ * (e.g., already a production hash).
23
+ */
24
+ /**
25
+ * The production client-reference key hash: `sha256(relativeId).slice(0,12)`,
26
+ * matching @vitejs/plugin-rsc's `hashString`. Exported so the client-chunks
27
+ * strategy can hash a `clientChunks` callback's `meta.normalizedId` (already the
28
+ * project-root-relative id) and compare it against fallback hashes collected
29
+ * during discovery.
30
+ */
31
+ export function hashRefKey(relativeId: string): string {
32
+ return createHash("sha256").update(relativeId).digest("hex").slice(0, 12);
33
+ }
34
+
35
+ export function computeProductionHash(
36
+ projectRoot: string,
37
+ refKey: string,
38
+ ): string {
39
+ let toHash: string;
40
+
41
+ if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
42
+ toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
43
+ } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
44
+ const absPath = decodeURIComponent(
45
+ refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length),
46
+ );
47
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
48
+ } else if (refKey.startsWith(FS_PREFIX)) {
49
+ const absPath = refKey.slice(FS_PREFIX.length - 1); // keep leading /
50
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
51
+ } else if (refKey.startsWith("/")) {
52
+ toHash = refKey.slice(1);
53
+ } else {
54
+ return refKey;
55
+ }
56
+
57
+ return hashRefKey(toHash);
58
+ }
59
+
60
+ const REGISTER_CLIENT_REF_RE =
61
+ /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
62
+
63
+ /**
64
+ * Transform source code by replacing dev-mode client reference keys with
65
+ * production hashes. Exported for testing; used internally by hashClientRefs.
66
+ * Returns null if no replacements were made.
67
+ */
68
+ export function transformClientRefs(
69
+ code: string,
70
+ projectRoot: string,
71
+ ): string | null {
72
+ if (!code.includes("registerClientReference")) return null;
73
+
74
+ let hasReplacement = false;
75
+ const result = code.replace(
76
+ REGISTER_CLIENT_REF_RE,
77
+ (match, refKey: string) => {
78
+ const hash = computeProductionHash(projectRoot, refKey);
79
+ if (hash === refKey) return match;
80
+ hasReplacement = true;
81
+ return match.replace(`"${refKey}"`, `"${hash}"`);
82
+ },
83
+ );
84
+
85
+ return hasReplacement ? result : null;
86
+ }
87
+
88
+ /**
89
+ * Vite plugin that rewrites registerClientReference() calls in the RSC
90
+ * environment, replacing dev-mode reference keys with production hashes.
91
+ *
92
+ * This runs AFTER the RSC plugin's transform so the Flight serializer
93
+ * naturally emits production IDs, eliminating the need for post-build
94
+ * regex replacement of Flight payloads.
95
+ */
96
+ export function hashClientRefs(projectRoot: string): Plugin {
97
+ const counter = createCounter(debug, "hash-client-refs");
98
+ return {
99
+ name: "@rangojs/router:hash-client-refs",
100
+ enforce: "post",
101
+ applyToEnvironment(env) {
102
+ return env.name === "rsc";
103
+ },
104
+ buildEnd() {
105
+ counter?.flush();
106
+ },
107
+ transform(code, id) {
108
+ const start = counter ? performance.now() : 0;
109
+ try {
110
+ const result = transformClientRefs(code, projectRoot);
111
+ if (result === null) return;
112
+ return { code: result, map: null };
113
+ } finally {
114
+ counter?.record(id, performance.now() - start);
115
+ }
116
+ },
117
+ };
118
+ }
@@ -0,0 +1,23 @@
1
+ export interface LoaderResolveContext {
2
+ parentURL?: string;
3
+ conditions?: readonly string[];
4
+ importAttributes?: Record<string, string>;
5
+ }
6
+
7
+ export interface LoaderResolveResult {
8
+ shortCircuit?: boolean;
9
+ url: string;
10
+ format?: "module" | "commonjs" | "json" | "wasm" | null;
11
+ importAttributes?: Record<string, string>;
12
+ }
13
+
14
+ export type NextResolve = (
15
+ specifier: string,
16
+ context?: LoaderResolveContext,
17
+ ) => Promise<LoaderResolveResult>;
18
+
19
+ export function resolve(
20
+ specifier: string,
21
+ context: LoaderResolveContext,
22
+ nextResolve: NextResolve,
23
+ ): Promise<LoaderResolveResult>;