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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (449) hide show
  1. package/AGENTS.md +8 -4
  2. package/README.md +303 -741
  3. package/dist/bin/rango.js +730 -184
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +4344 -1335
  6. package/dist/vite/index.js.bak +5448 -0
  7. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  8. package/package.json +86 -15
  9. package/skills/api-client/SKILL.md +211 -0
  10. package/skills/breadcrumbs/SKILL.md +85 -6
  11. package/skills/bundle-analysis/SKILL.md +159 -0
  12. package/skills/cache-guide/SKILL.md +251 -24
  13. package/skills/caching/SKILL.md +375 -17
  14. package/skills/catalog.json +271 -0
  15. package/skills/comparison/SKILL.md +50 -0
  16. package/skills/comparison/agents/openai.yaml +4 -0
  17. package/skills/comparison/references/framework-comparison.md +837 -0
  18. package/skills/composability/SKILL.md +110 -4
  19. package/skills/css/SKILL.md +76 -0
  20. package/skills/debug-manifest/SKILL.md +5 -3
  21. package/skills/defer-hydration/SKILL.md +235 -0
  22. package/skills/document-cache/SKILL.md +87 -56
  23. package/skills/fonts/SKILL.md +1 -1
  24. package/skills/handler-use/SKILL.md +364 -0
  25. package/skills/hooks/SKILL.md +73 -691
  26. package/skills/hooks/data.md +273 -0
  27. package/skills/hooks/handle-and-actions.md +103 -0
  28. package/skills/hooks/navigation.md +110 -0
  29. package/skills/hooks/outlets.md +41 -0
  30. package/skills/hooks/state.md +228 -0
  31. package/skills/hooks/urls.md +135 -0
  32. package/skills/host-router/SKILL.md +129 -27
  33. package/skills/i18n/SKILL.md +276 -0
  34. package/skills/intercept/SKILL.md +94 -18
  35. package/skills/layout/SKILL.md +62 -19
  36. package/skills/links/SKILL.md +249 -17
  37. package/skills/loader/SKILL.md +302 -54
  38. package/skills/middleware/SKILL.md +59 -16
  39. package/skills/migrate-nextjs/SKILL.md +745 -0
  40. package/skills/migrate-react-router/SKILL.md +153 -0
  41. package/skills/migrate-react-router/cloudflare-workers.md +129 -0
  42. package/skills/migrate-react-router/component-migration.md +196 -0
  43. package/skills/migrate-react-router/data-and-actions.md +225 -0
  44. package/skills/migrate-react-router/route-mapping.md +271 -0
  45. package/skills/mime-routes/SKILL.md +29 -2
  46. package/skills/observability/SKILL.md +202 -0
  47. package/skills/parallel/SKILL.md +225 -10
  48. package/skills/ppr/SKILL.md +622 -0
  49. package/skills/prerender/SKILL.md +178 -124
  50. package/skills/rango/SKILL.md +318 -24
  51. package/skills/react-compiler/SKILL.md +168 -0
  52. package/skills/response-routes/SKILL.md +138 -49
  53. package/skills/route/SKILL.md +172 -9
  54. package/skills/router-setup/SKILL.md +131 -11
  55. package/skills/scripts/SKILL.md +179 -0
  56. package/skills/server-actions/SKILL.md +776 -0
  57. package/skills/shell-manifest/SKILL.md +185 -0
  58. package/skills/streams-and-websockets/SKILL.md +283 -0
  59. package/skills/tailwind/SKILL.md +28 -4
  60. package/skills/testing/SKILL.md +130 -0
  61. package/skills/testing/bindings.md +103 -0
  62. package/skills/testing/cache-prerender.md +127 -0
  63. package/skills/testing/client-components.md +124 -0
  64. package/skills/testing/e2e-parity.md +125 -0
  65. package/skills/testing/flight.md +91 -0
  66. package/skills/testing/handles.md +131 -0
  67. package/skills/testing/loader.md +128 -0
  68. package/skills/testing/middleware.md +99 -0
  69. package/skills/testing/render-handler.md +122 -0
  70. package/skills/testing/response-routes.md +95 -0
  71. package/skills/testing/reverse-and-types.md +85 -0
  72. package/skills/testing/server-actions.md +107 -0
  73. package/skills/testing/server-tree.md +128 -0
  74. package/skills/testing/setup.md +123 -0
  75. package/skills/theme/SKILL.md +1 -1
  76. package/skills/typesafety/SKILL.md +45 -616
  77. package/skills/typesafety/env-and-bindings.md +254 -0
  78. package/skills/typesafety/generated-files-and-cli.md +335 -0
  79. package/skills/typesafety/params-and-search.md +153 -0
  80. package/skills/typesafety/route-types.md +209 -0
  81. package/skills/use-cache/SKILL.md +74 -15
  82. package/skills/vercel/SKILL.md +128 -0
  83. package/skills/view-transitions/SKILL.md +337 -0
  84. package/src/__augment-tests__/augment.ts +81 -0
  85. package/src/__augment-tests__/augmented.check.ts +116 -0
  86. package/src/__internal.ts +1 -66
  87. package/src/browser/action-coordinator.ts +53 -36
  88. package/src/browser/action-fence.ts +47 -0
  89. package/src/browser/app-shell.ts +39 -0
  90. package/src/browser/app-version.ts +14 -0
  91. package/src/browser/connection-warmup.ts +134 -0
  92. package/src/browser/cookie-name.ts +140 -0
  93. package/src/browser/event-controller.ts +257 -158
  94. package/src/browser/history-state.ts +21 -0
  95. package/src/browser/index.ts +3 -3
  96. package/src/browser/invalidate-client-cache.ts +52 -0
  97. package/src/browser/logging.ts +28 -0
  98. package/src/browser/merge-segment-loaders.ts +6 -4
  99. package/src/browser/navigation-bridge.ts +132 -33
  100. package/src/browser/navigation-client.ts +218 -68
  101. package/src/browser/navigation-store-handle.ts +38 -0
  102. package/src/browser/navigation-store.ts +203 -80
  103. package/src/browser/navigation-transaction.ts +18 -66
  104. package/src/browser/network-error-handler.ts +34 -7
  105. package/src/browser/partial-update.ts +241 -127
  106. package/src/browser/prefetch/cache.ts +271 -44
  107. package/src/browser/prefetch/fetch.ts +367 -40
  108. package/src/browser/prefetch/queue.ts +144 -23
  109. package/src/browser/prefetch/resource-ready.ts +77 -0
  110. package/src/browser/rango-state.ts +158 -76
  111. package/src/browser/react/Link.tsx +121 -16
  112. package/src/browser/react/NavigationProvider.tsx +240 -122
  113. package/src/browser/react/ScrollRestoration.tsx +10 -6
  114. package/src/browser/react/context.ts +7 -2
  115. package/src/browser/react/filter-segment-order.ts +66 -7
  116. package/src/browser/react/index.ts +0 -48
  117. package/src/browser/react/location-state-shared.ts +178 -8
  118. package/src/browser/react/location-state.ts +39 -14
  119. package/src/browser/react/use-action.ts +6 -15
  120. package/src/browser/react/use-handle.ts +23 -69
  121. package/src/browser/react/use-href.tsx +8 -1
  122. package/src/browser/react/use-link-status.ts +33 -8
  123. package/src/browser/react/use-navigation.ts +32 -7
  124. package/src/browser/react/use-params.ts +20 -10
  125. package/src/browser/react/use-reverse.ts +106 -0
  126. package/src/browser/react/use-router.ts +46 -11
  127. package/src/browser/react/use-search-params.ts +0 -5
  128. package/src/browser/react/use-segments.ts +11 -21
  129. package/src/browser/response-adapter.ts +99 -8
  130. package/src/browser/rsc-router.tsx +272 -80
  131. package/src/browser/scroll-restoration.ts +56 -22
  132. package/src/browser/segment-reconciler.ts +44 -7
  133. package/src/browser/segment-structure-assert.ts +2 -2
  134. package/src/browser/server-action-bridge.ts +244 -71
  135. package/src/browser/types.ts +136 -12
  136. package/src/browser/validate-redirect-origin.ts +43 -16
  137. package/src/build/collect-fallback-refs.ts +107 -0
  138. package/src/build/generate-manifest.ts +207 -158
  139. package/src/build/generate-route-types.ts +6 -1
  140. package/src/build/index.ts +11 -3
  141. package/src/build/prefix-tree-utils.ts +123 -0
  142. package/src/build/route-trie.ts +198 -41
  143. package/src/build/route-types/ast-route-extraction.ts +15 -8
  144. package/src/build/route-types/codegen.ts +16 -5
  145. package/src/build/route-types/include-resolution.ts +464 -63
  146. package/src/build/route-types/param-extraction.ts +6 -3
  147. package/src/build/route-types/per-module-writer.ts +22 -6
  148. package/src/build/route-types/router-processing.ts +336 -110
  149. package/src/build/route-types/scan-filter.ts +9 -2
  150. package/src/build/route-types/source-scan.ts +216 -0
  151. package/src/build/runtime-discovery.ts +13 -21
  152. package/src/cache/cache-error.ts +104 -0
  153. package/src/cache/cache-key-utils.ts +58 -13
  154. package/src/cache/cache-policy.ts +108 -34
  155. package/src/cache/cache-runtime.ts +454 -97
  156. package/src/cache/cache-scope.ts +235 -103
  157. package/src/cache/cache-tag.ts +149 -0
  158. package/src/cache/cf/cf-base64.ts +33 -0
  159. package/src/cache/cf/cf-cache-constants.ts +127 -0
  160. package/src/cache/cf/cf-cache-store.ts +2446 -170
  161. package/src/cache/cf/cf-cache-types.ts +349 -0
  162. package/src/cache/cf/cf-kv-utils.ts +46 -0
  163. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  164. package/src/cache/cf/index.ts +11 -17
  165. package/src/cache/document-cache.ts +144 -49
  166. package/src/cache/handle-snapshot.ts +70 -0
  167. package/src/cache/index.ts +24 -20
  168. package/src/cache/memory-segment-store.ts +243 -37
  169. package/src/cache/profile-registry.ts +46 -31
  170. package/src/cache/read-through-swr.ts +56 -12
  171. package/src/cache/segment-codec.ts +13 -21
  172. package/src/cache/shell-snapshot.ts +417 -0
  173. package/src/cache/tag-invalidation.ts +230 -0
  174. package/src/cache/taint.ts +55 -0
  175. package/src/cache/types.ts +194 -99
  176. package/src/cache/vercel/index.ts +11 -0
  177. package/src/cache/vercel/vercel-cache-store.ts +1132 -0
  178. package/src/client.rsc.tsx +41 -21
  179. package/src/client.tsx +116 -290
  180. package/src/cloudflare/index.ts +11 -0
  181. package/src/cloudflare/tracing.ts +108 -0
  182. package/src/component-utils.ts +19 -0
  183. package/src/components/DefaultDocument.tsx +8 -2
  184. package/src/context-var.ts +84 -2
  185. package/src/debug.ts +2 -2
  186. package/src/decode-loader-results.ts +52 -0
  187. package/src/defer.ts +185 -0
  188. package/src/deps/ssr.ts +0 -1
  189. package/src/encode-kv.ts +49 -0
  190. package/src/errors.ts +30 -4
  191. package/src/escape-script.ts +52 -0
  192. package/src/handle.ts +104 -34
  193. package/src/handles/MetaTags.tsx +24 -53
  194. package/src/handles/Scripts.tsx +183 -0
  195. package/src/handles/breadcrumbs.ts +35 -8
  196. package/src/handles/deferred-resolution.ts +127 -0
  197. package/src/handles/is-thenable.ts +18 -0
  198. package/src/handles/meta.ts +14 -40
  199. package/src/handles/script.ts +244 -0
  200. package/src/host/cookie-handler.ts +9 -60
  201. package/src/host/errors.ts +13 -22
  202. package/src/host/index.ts +9 -2
  203. package/src/host/pattern-matcher.ts +23 -52
  204. package/src/host/router.ts +107 -99
  205. package/src/host/testing.ts +40 -27
  206. package/src/host/types.ts +37 -4
  207. package/src/host/utils.ts +1 -1
  208. package/src/href-client.ts +137 -22
  209. package/src/index.rsc.ts +100 -13
  210. package/src/index.ts +143 -19
  211. package/src/internal-debug.ts +11 -10
  212. package/src/loader-store.ts +500 -0
  213. package/src/loader.rsc.ts +20 -13
  214. package/src/loader.ts +12 -11
  215. package/src/missing-id-error.ts +68 -0
  216. package/src/outlet-context.ts +1 -1
  217. package/src/outlet-provider.tsx +1 -5
  218. package/src/prerender/param-hash.ts +16 -16
  219. package/src/prerender/store.ts +37 -41
  220. package/src/prerender.ts +215 -86
  221. package/src/redirect-origin.ts +114 -0
  222. package/src/regex-escape.ts +8 -0
  223. package/src/render-error-thrower.tsx +20 -0
  224. package/src/response-utils.ts +62 -0
  225. package/src/reverse.ts +65 -15
  226. package/src/root-error-boundary.tsx +1 -19
  227. package/src/route-content-wrapper.tsx +19 -77
  228. package/src/route-definition/dsl-helpers.ts +485 -303
  229. package/src/route-definition/helper-factories.ts +28 -140
  230. package/src/route-definition/helpers-types.ts +153 -77
  231. package/src/route-definition/index.ts +4 -2
  232. package/src/route-definition/redirect.ts +53 -12
  233. package/src/route-definition/resolve-handler-use.ts +160 -0
  234. package/src/route-definition/use-item-types.ts +29 -0
  235. package/src/route-map-builder.ts +48 -21
  236. package/src/route-types.ts +37 -46
  237. package/src/router/basename.ts +14 -0
  238. package/src/router/content-negotiation.ts +164 -17
  239. package/src/router/error-handling.ts +45 -18
  240. package/src/router/find-match.ts +130 -29
  241. package/src/router/handler-context.ts +83 -39
  242. package/src/router/instrument.ts +355 -0
  243. package/src/router/intercept-resolution.ts +50 -24
  244. package/src/router/lazy-includes.ts +89 -63
  245. package/src/router/loader-resolution.ts +286 -56
  246. package/src/router/logging.ts +5 -8
  247. package/src/router/manifest.ts +105 -56
  248. package/src/router/match-api.ts +178 -218
  249. package/src/router/match-context.ts +0 -22
  250. package/src/router/match-handlers.ts +211 -165
  251. package/src/router/match-middleware/background-revalidation.ts +66 -22
  252. package/src/router/match-middleware/cache-lookup.ts +214 -263
  253. package/src/router/match-middleware/cache-store.ts +105 -50
  254. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  255. package/src/router/match-middleware/segment-resolution.ts +52 -18
  256. package/src/router/match-pipelines.ts +1 -42
  257. package/src/router/match-result.ts +128 -44
  258. package/src/router/metrics.ts +5 -34
  259. package/src/router/middleware-types.ts +13 -142
  260. package/src/router/middleware.ts +301 -177
  261. package/src/router/navigation-snapshot.ts +133 -0
  262. package/src/router/params-util.ts +23 -0
  263. package/src/router/parse-pattern.ts +115 -0
  264. package/src/router/pattern-matching.ts +181 -150
  265. package/src/router/prefetch-cache-ttl.ts +51 -0
  266. package/src/router/prefetch-limits.ts +37 -0
  267. package/src/router/prerender-match.ts +203 -58
  268. package/src/router/preview-match.ts +35 -103
  269. package/src/router/request-classification.ts +291 -0
  270. package/src/router/revalidation.ts +123 -73
  271. package/src/router/route-snapshot.ts +256 -0
  272. package/src/router/router-context.ts +11 -29
  273. package/src/router/router-interfaces.ts +146 -35
  274. package/src/router/router-options.ts +202 -15
  275. package/src/router/router-registry.ts +2 -5
  276. package/src/router/segment-resolution/fresh.ts +301 -78
  277. package/src/router/segment-resolution/helpers.ts +115 -30
  278. package/src/router/segment-resolution/loader-cache.ts +156 -39
  279. package/src/router/segment-resolution/loader-mask.ts +60 -0
  280. package/src/router/segment-resolution/loader-snapshot.ts +259 -0
  281. package/src/router/segment-resolution/mask-nested.ts +83 -0
  282. package/src/router/segment-resolution/revalidation.ts +477 -385
  283. package/src/router/segment-resolution/static-store.ts +19 -5
  284. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  285. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  286. package/src/router/segment-resolution.ts +5 -1
  287. package/src/router/segment-wrappers.ts +8 -5
  288. package/src/router/state-cookie-name.ts +33 -0
  289. package/src/router/substitute-pattern-params.ts +75 -0
  290. package/src/router/telemetry-otel.ts +160 -200
  291. package/src/router/telemetry.ts +105 -20
  292. package/src/router/timeout.ts +0 -20
  293. package/src/router/tracing.ts +215 -0
  294. package/src/router/trie-matching.ts +171 -59
  295. package/src/router/types.ts +10 -63
  296. package/src/router/url-params.ts +57 -0
  297. package/src/router.ts +210 -71
  298. package/src/rsc/full-payload.ts +70 -0
  299. package/src/rsc/handler-context.ts +3 -2
  300. package/src/rsc/handler.ts +682 -508
  301. package/src/rsc/helpers.ts +168 -46
  302. package/src/rsc/index.ts +2 -5
  303. package/src/rsc/json-route-result.ts +38 -0
  304. package/src/rsc/loader-fetch.ts +127 -31
  305. package/src/rsc/manifest-init.ts +33 -42
  306. package/src/rsc/nonce.ts +10 -1
  307. package/src/rsc/origin-guard.ts +39 -25
  308. package/src/rsc/progressive-enhancement.ts +138 -15
  309. package/src/rsc/redirect-guard.ts +100 -0
  310. package/src/rsc/response-cache-serve.ts +238 -0
  311. package/src/rsc/response-error.ts +79 -12
  312. package/src/rsc/response-route-handler.ts +99 -189
  313. package/src/rsc/rsc-rendering.ts +509 -73
  314. package/src/rsc/runtime-warnings.ts +23 -10
  315. package/src/rsc/server-action.ts +287 -113
  316. package/src/rsc/shell-capture.ts +1190 -0
  317. package/src/rsc/shell-serve.ts +181 -0
  318. package/src/rsc/ssr-setup.ts +18 -2
  319. package/src/rsc/transition-gate.ts +89 -0
  320. package/src/rsc/types.ts +62 -6
  321. package/src/runtime-env.ts +18 -0
  322. package/src/search-params.ts +35 -30
  323. package/src/segment-content-promise.ts +67 -0
  324. package/src/segment-loader-promise.ts +167 -0
  325. package/src/segment-system.tsx +449 -132
  326. package/src/serialize.ts +243 -0
  327. package/src/server/context.ts +367 -61
  328. package/src/server/cookie-parse.ts +32 -0
  329. package/src/server/cookie-store.ts +152 -5
  330. package/src/server/handle-store.ts +40 -38
  331. package/src/server/loader-registry.ts +38 -46
  332. package/src/server/request-context.ts +558 -173
  333. package/src/ssr/index.tsx +491 -174
  334. package/src/ssr/inject-rsc-eager.ts +167 -0
  335. package/src/ssr/ssr-root.tsx +228 -0
  336. package/src/static-handler.ts +27 -18
  337. package/src/testing/cache-status.ts +162 -0
  338. package/src/testing/collect-handle.ts +46 -0
  339. package/src/testing/dispatch.ts +813 -0
  340. package/src/testing/dom.entry.ts +22 -0
  341. package/src/testing/e2e/fixture.ts +188 -0
  342. package/src/testing/e2e/index.ts +128 -0
  343. package/src/testing/e2e/matchers.ts +35 -0
  344. package/src/testing/e2e/page-helpers.ts +272 -0
  345. package/src/testing/e2e/parity.ts +387 -0
  346. package/src/testing/e2e/server.ts +195 -0
  347. package/src/testing/flight-matchers.ts +97 -0
  348. package/src/testing/flight-normalize.ts +11 -0
  349. package/src/testing/flight-runtime.d.ts +57 -0
  350. package/src/testing/flight-tree.ts +682 -0
  351. package/src/testing/flight.entry.ts +52 -0
  352. package/src/testing/flight.ts +257 -0
  353. package/src/testing/generated-routes.ts +199 -0
  354. package/src/testing/index.ts +105 -0
  355. package/src/testing/internal/context.ts +371 -0
  356. package/src/testing/internal/flight-client-globals.ts +30 -0
  357. package/src/testing/internal/seed-vars.ts +54 -0
  358. package/src/testing/render-handler.ts +357 -0
  359. package/src/testing/render-route.tsx +584 -0
  360. package/src/testing/run-loader.ts +385 -0
  361. package/src/testing/run-middleware.ts +205 -0
  362. package/src/testing/run-transition-when.ts +164 -0
  363. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  364. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  365. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  366. package/src/testing/vitest-stubs/version.ts +5 -0
  367. package/src/testing/vitest.ts +305 -0
  368. package/src/theme/ThemeProvider.tsx +56 -84
  369. package/src/theme/ThemeScript.tsx +7 -9
  370. package/src/theme/constants.ts +52 -13
  371. package/src/theme/index.ts +0 -7
  372. package/src/theme/theme-context.ts +1 -5
  373. package/src/theme/theme-script.ts +22 -21
  374. package/src/theme/use-theme.ts +0 -3
  375. package/src/types/boundaries.ts +0 -35
  376. package/src/types/cache-types.ts +17 -8
  377. package/src/types/error-types.ts +30 -90
  378. package/src/types/global-namespace.ts +54 -41
  379. package/src/types/handler-context.ts +234 -82
  380. package/src/types/index.ts +3 -10
  381. package/src/types/loader-types.ts +44 -15
  382. package/src/types/request-scope.ts +112 -0
  383. package/src/types/route-config.ts +20 -52
  384. package/src/types/route-entry.ts +19 -7
  385. package/src/types/segments.ts +137 -14
  386. package/src/urls/include-helper.ts +40 -75
  387. package/src/urls/include-provider.ts +71 -0
  388. package/src/urls/index.ts +2 -11
  389. package/src/urls/path-helper-types.ts +102 -23
  390. package/src/urls/path-helper.ts +62 -111
  391. package/src/urls/pattern-types.ts +84 -19
  392. package/src/urls/response-types.ts +25 -22
  393. package/src/urls/type-extraction.ts +98 -154
  394. package/src/urls/urls-function.ts +1 -19
  395. package/src/use-loader.tsx +346 -89
  396. package/src/vercel/index.ts +11 -0
  397. package/src/vercel/tracing.ts +88 -0
  398. package/src/vite/debug.ts +185 -0
  399. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  400. package/src/vite/discovery/dev-prerender-cache.ts +117 -0
  401. package/src/vite/discovery/discover-routers.ts +130 -85
  402. package/src/vite/discovery/discovery-errors.ts +255 -0
  403. package/src/vite/discovery/gate-state.ts +171 -0
  404. package/src/vite/discovery/prerender-collection.ts +214 -132
  405. package/src/vite/discovery/route-types-writer.ts +40 -84
  406. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  407. package/src/vite/discovery/state.ts +57 -6
  408. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  409. package/src/vite/index.ts +15 -0
  410. package/src/vite/inject-client-debug.ts +88 -0
  411. package/src/vite/plugin-types.ts +234 -62
  412. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  413. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  414. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  415. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  416. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  417. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  418. package/src/vite/plugins/expose-action-id.ts +49 -98
  419. package/src/vite/plugins/expose-id-utils.ts +96 -51
  420. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  421. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  422. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  423. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  424. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  425. package/src/vite/plugins/performance-tracks.ts +89 -0
  426. package/src/vite/plugins/refresh-cmd.ts +89 -27
  427. package/src/vite/plugins/use-cache-transform.ts +73 -83
  428. package/src/vite/plugins/vercel-output.ts +384 -0
  429. package/src/vite/plugins/version-injector.ts +40 -29
  430. package/src/vite/plugins/version-plugin.ts +46 -37
  431. package/src/vite/plugins/virtual-entries.ts +138 -27
  432. package/src/vite/rango.ts +353 -303
  433. package/src/vite/router-discovery.ts +1090 -166
  434. package/src/vite/utils/ast-handler-extract.ts +26 -35
  435. package/src/vite/utils/banner.ts +4 -4
  436. package/src/vite/utils/bundle-analysis.ts +10 -15
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +4 -59
  441. package/src/vite/utils/package-resolution.ts +20 -52
  442. package/src/vite/utils/prerender-utils.ts +98 -38
  443. package/src/vite/utils/shared-utils.ts +144 -44
  444. package/src/browser/action-response-classifier.ts +0 -99
  445. package/src/browser/react/use-client-cache.ts +0 -58
  446. package/src/browser/shallow.ts +0 -40
  447. package/src/handles/index.ts +0 -7
  448. package/src/network-error-thrower.tsx +0 -23
  449. package/src/router/middleware-cookies.ts +0 -55
@@ -8,30 +8,26 @@ export function normalizePath(p: string): string {
8
8
  return p.split(path.sep).join("/");
9
9
  }
10
10
 
11
- /**
12
- * Generate a short hash for an ID.
13
- * Uses first 8 chars of SHA-256 hash for uniqueness while keeping IDs short.
14
- * Appends export name for easier debugging in production: "abc123#ExportName"
15
- */
16
11
  export function hashId(filePath: string, exportName: string): string {
17
12
  const input = `${filePath}#${exportName}`;
18
13
  const hash = crypto.createHash("sha256").update(input).digest("hex");
19
14
  return `${hash.slice(0, 8)}#${exportName}`;
20
15
  }
21
16
 
22
- /**
23
- * Generate an 8-char hex hash for an inline static handler call site.
24
- * Uses file path and line number (plus optional index for same-line collisions).
25
- */
17
+ export function makeStubId(
18
+ filePath: string,
19
+ exportName: string,
20
+ isBuild: boolean,
21
+ ): string {
22
+ return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
23
+ }
24
+
26
25
  export function hashInlineId(
27
26
  filePath: string,
28
- lineNumber: number,
29
- index?: number,
27
+ fnName: string,
28
+ index: number,
30
29
  ): string {
31
- const input =
32
- index !== undefined && index > 0
33
- ? `${filePath}:${lineNumber}:${index}`
34
- : `${filePath}:${lineNumber}`;
30
+ const input = `${filePath}:${fnName}:${index}`;
35
31
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
36
32
  }
37
33
 
@@ -45,11 +41,6 @@ export interface DetectedImports {
45
41
  any: boolean;
46
42
  }
47
43
 
48
- /**
49
- * Build a map from local binding name to exported names by walking
50
- * ExportNamedDeclaration nodes. Handles `export const X`, `export { X }`,
51
- * and `export { X as Y }`. Skips re-exports (`export { X } from "..."`).
52
- */
53
44
  export function buildExportMap(program: any): Map<string, string[]> {
54
45
  const exportMap = new Map<string, string[]>();
55
46
 
@@ -89,10 +80,6 @@ export function buildExportMap(program: any): Map<string, string[]> {
89
80
  return exportMap;
90
81
  }
91
82
 
92
- /**
93
- * Single-pass detection of all create* imports from @rangojs/router.
94
- * Returns which create functions are imported so we can skip unnecessary transforms.
95
- */
96
83
  export function detectImports(code: string): DetectedImports {
97
84
  // Extract all import declarations from @rangojs/router in one scan
98
85
  const importPattern =
@@ -119,10 +106,6 @@ export function detectImports(code: string): DetectedImports {
119
106
  if (/\bcreateRouter\b/.test(imports)) result.router = true;
120
107
  }
121
108
 
122
- // createRouter has a stricter check: only from "@rangojs/router" (not sub-paths).
123
- // NOTE: This is intentional — detectImports is used as a fast pre-filter in
124
- // exposeInternalIds (which does NOT handle router transforms). The separate
125
- // exposeRouterId plugin handles createRouter and DOES accept the /server subpath.
126
109
  if (result.router) {
127
110
  result.router =
128
111
  /import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router["']/.test(
@@ -141,11 +124,6 @@ export function detectImports(code: string): DetectedImports {
141
124
  return result;
142
125
  }
143
126
 
144
- /**
145
- * Skip past a string literal, template literal, or comment starting at pos.
146
- * Returns the index after the closing delimiter, or pos if not at a
147
- * string/comment start. Handles escape sequences and nested ${} in templates.
148
- */
149
127
  export function skipStringOrComment(code: string, pos: number): number {
150
128
  const ch = code[pos];
151
129
 
@@ -203,11 +181,6 @@ export function skipStringOrComment(code: string, pos: number): number {
203
181
  return pos;
204
182
  }
205
183
 
206
- /**
207
- * Find the matching closing paren starting after an already-opened paren.
208
- * Skips strings, template literals, and comments so parens inside them
209
- * don't affect depth tracking. Returns the index after the closing paren.
210
- */
211
184
  export function findMatchingParen(code: string, startPos: number): number {
212
185
  let depth = 1;
213
186
  let i = startPos;
@@ -224,10 +197,6 @@ export function findMatchingParen(code: string, startPos: number): number {
224
197
  return i;
225
198
  }
226
199
 
227
- /**
228
- * Count the number of top-level arguments in a function call.
229
- * Skips nested parens, brackets, braces, strings, and comments.
230
- */
231
200
  export function countArgs(
232
201
  code: string,
233
202
  startPos: number,
@@ -241,7 +210,16 @@ export function countArgs(
241
210
  while (i < endPos) {
242
211
  const skipped = skipStringOrComment(code, i);
243
212
  if (skipped > i) {
244
- hasContent = true;
213
+ // A comment is transparent: `createHandle(/* meta */)` has ZERO real
214
+ // arguments, so a comment-only region must NOT set hasContent (else the
215
+ // fallback path would count it as 1 arg and inject `, "id"` over an elided
216
+ // first slot — a syntax error). A string/template literal IS content.
217
+ const ch = code[i];
218
+ const isComment =
219
+ ch === "/" &&
220
+ i + 1 < code.length &&
221
+ (code[i + 1] === "/" || code[i + 1] === "*");
222
+ if (!isComment) hasContent = true;
245
223
  i = skipped;
246
224
  continue;
247
225
  }
@@ -263,10 +241,6 @@ export function countArgs(
263
241
  return hasContent ? argCount + 1 : 0;
264
242
  }
265
243
 
266
- /**
267
- * Find the end of a statement: skip whitespace and optional semicolon after
268
- * a closing paren position.
269
- */
270
244
  export function findStatementEnd(code: string, pos: number): number {
271
245
  let i = pos;
272
246
  while (i < code.length && /\s/.test(code[i])) {
@@ -278,10 +252,81 @@ export function findStatementEnd(code: string, pos: number): number {
278
252
  return i;
279
253
  }
280
254
 
255
+ export { escapeRegExp } from "../../regex-escape.js";
256
+
281
257
  /**
282
- * Escape special regex characters in a string so it can be safely
283
- * interpolated into a RegExp pattern.
258
+ * Given an index pointing just past a `create*` callee identifier, return the
259
+ * index of the call's opening `(`, accounting for an optional generic type
260
+ * argument list `<...>` with arbitrarily nested `<>`. Returns -1 when the next
261
+ * non-trivia token is neither `<` nor `(` (i.e. not a call site).
262
+ *
263
+ * Replaces the single-level `<[^>]*>` regex assumption, which stopped at the
264
+ * first `>` and so never reached `(` for `createRouter<Config<Env>>(`.
265
+ *
266
+ * Whitespace, comments, and strings between the callee and `(` are skipped via
267
+ * skipStringOrComment. The `<...>` scan only balances angle brackets; it does
268
+ * not attempt to parse full TS type syntax (sufficient for locating the paren).
284
269
  */
285
- export function escapeRegExp(input: string): string {
286
- return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
270
+ export function findCallParenAfterGenerics(
271
+ code: string,
272
+ afterCalleeIndex: number,
273
+ ): number {
274
+ let i = afterCalleeIndex;
275
+ // Skip leading whitespace/comments/strings before `<` or `(`.
276
+ while (i < code.length) {
277
+ const skipped = skipStringOrComment(code, i);
278
+ if (skipped > i) {
279
+ i = skipped;
280
+ continue;
281
+ }
282
+ if (/\s/.test(code[i])) {
283
+ i++;
284
+ continue;
285
+ }
286
+ break;
287
+ }
288
+
289
+ if (i >= code.length) return -1;
290
+
291
+ if (code[i] === "(") return i;
292
+
293
+ if (code[i] === "<") {
294
+ let depth = 0;
295
+ while (i < code.length) {
296
+ const skipped = skipStringOrComment(code, i);
297
+ if (skipped > i) {
298
+ i = skipped;
299
+ continue;
300
+ }
301
+ const ch = code[i];
302
+ if (ch === "<") {
303
+ depth++;
304
+ } else if (ch === ">") {
305
+ depth--;
306
+ if (depth === 0) {
307
+ i++;
308
+ break;
309
+ }
310
+ }
311
+ i++;
312
+ }
313
+ if (depth !== 0) return -1;
314
+ // After the balanced generic list, skip trivia and expect `(`.
315
+ while (i < code.length) {
316
+ const skipped = skipStringOrComment(code, i);
317
+ if (skipped > i) {
318
+ i = skipped;
319
+ continue;
320
+ }
321
+ if (/\s/.test(code[i])) {
322
+ i++;
323
+ continue;
324
+ }
325
+ break;
326
+ }
327
+ if (i < code.length && code[i] === "(") return i;
328
+ return -1;
329
+ }
330
+
331
+ return -1;
287
332
  }
@@ -5,15 +5,11 @@ import {
5
5
  findStatementEnd,
6
6
  buildExportMap,
7
7
  escapeRegExp,
8
+ findCallParenAfterGenerics,
8
9
  } from "../expose-id-utils.js";
10
+ import { codeMatchIndices } from "../../../build/route-types/source-scan.js";
9
11
  import type { CreateExportBinding } from "./types.js";
10
12
 
11
- /**
12
- * Check whether every non-type export in `code` is accounted for by the given
13
- * bindings. Returns false if any export exists that is not one of the known
14
- * create* call locals/exports, allowing callers to bail out for mixed-export
15
- * files.
16
- */
17
13
  export function isExportOnlyFile(
18
14
  code: string,
19
15
  bindings: CreateExportBinding[],
@@ -27,10 +23,8 @@ export function isExportOnlyFile(
27
23
  for (const e of b.exportNames) knownExports.add(e);
28
24
  }
29
25
 
30
- // Bail on star re-exports (unknown exports)
31
26
  if (/export\s*\*/.test(code)) return false;
32
27
 
33
- // Check `export const/let/var/function/class/default X` declarations
34
28
  const declExportPattern =
35
29
  /export\s+(const|let|var|function|class|default)\s+(\w+)/g;
36
30
  let match: RegExpExecArray | null;
@@ -38,8 +32,6 @@ export function isExportOnlyFile(
38
32
  if (!knownExports.has(match[2])) return false;
39
33
  }
40
34
 
41
- // Check `export { X }` and `export { X as Y }` specifiers: the local name
42
- // must reference a known create* binding.
43
35
  const specExportPattern = /export\s*\{([^}]+)\}/g;
44
36
  while ((match = specExportPattern.exec(code)) !== null) {
45
37
  const specifiers = match[1]
@@ -59,19 +51,69 @@ export function isExportOnlyFile(
59
51
  return true;
60
52
  }
61
53
 
62
- // NOTE: This regex may over-count when the fn name appears inside strings or
63
- // comments, but it's only used for the warning heuristic (totalCalls >
64
- // supportedBindings) and the inline-extraction pre-check, so over-counting
65
- // triggers a harmless extra AST parse rather than affecting correctness.
54
+ // Matches the callee identifier only. The optional generic argument list
55
+ // (which may be nested, e.g. createLoader<A<B>>(...)) and the call paren are
56
+ // resolved per match via findCallParenAfterGenerics, so a nested `>` no longer
57
+ // defeats the scan the way `<[^>]*>` did (it stopped at the first `>`).
58
+ function createCallPattern(fnNames: string[]): RegExp {
59
+ return new RegExp(`\\b(?:${fnNames.map(escapeRegExp).join("|")})\\b`, "g");
60
+ }
61
+
62
+ /**
63
+ * Byte offsets of every create*-call site in real code: a callee-identifier
64
+ * match that is actually followed by a call `(` (after an optional nested
65
+ * generic list). Non-call references (type positions, the import specifier
66
+ * itself) yield -1 from findCallParenAfterGenerics and are dropped.
67
+ */
68
+ function createCallStartIndices(code: string, fnNames: string[]): number[] {
69
+ return codeMatchIndices(code, createCallPattern(fnNames)).filter(
70
+ (index) =>
71
+ findCallParenAfterGenerics(
72
+ code,
73
+ index + matchedNameLength(code, index),
74
+ ) !== -1,
75
+ );
76
+ }
77
+
78
+ // Length of the identifier match at `index` (run of identifier chars).
79
+ function matchedNameLength(code: string, index: number): number {
80
+ let i = index;
81
+ while (i < code.length && /[A-Za-z0-9_$]/.test(code[i])) i++;
82
+ return i - index;
83
+ }
84
+
66
85
  export function countCreateCallsForNames(
67
86
  code: string,
68
87
  fnNames: string[],
69
88
  ): number {
70
- const pattern = new RegExp(
71
- `\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
72
- "g",
73
- );
74
- return (code.match(pattern) || []).length;
89
+ return createCallStartIndices(code, fnNames).length;
90
+ }
91
+
92
+ export function offsetToLineColumn(
93
+ code: string,
94
+ index: number,
95
+ ): { line: number; column: number } {
96
+ let line = 1;
97
+ let lineStart = 0;
98
+ const end = Math.min(index, code.length);
99
+ for (let i = 0; i < end; i++) {
100
+ if (code[i] === "\n") {
101
+ line++;
102
+ lineStart = i + 1;
103
+ }
104
+ }
105
+ return { line, column: index - lineStart + 1 };
106
+ }
107
+
108
+ export function findUnsupportedCreateCallSites(
109
+ code: string,
110
+ fnNames: string[],
111
+ supportedBindings: CreateExportBinding[],
112
+ ): Array<{ line: number; column: number }> {
113
+ const supported = new Set(supportedBindings.map((b) => b.callExprStart));
114
+ return createCallStartIndices(code, fnNames)
115
+ .filter((index) => !supported.has(index))
116
+ .map((index) => offsetToLineColumn(code, index));
75
117
  }
76
118
 
77
119
  export function getImportedFnNames(
@@ -119,6 +161,18 @@ export function getCalledIdentifierFromCall(callExpr: any): string | null {
119
161
  return null;
120
162
  }
121
163
 
164
+ function unwrapSignatureWrappedCall(init: any, fnNameSet: Set<string>): any {
165
+ if (init?.type !== "CallExpression") return init;
166
+ const directId = getCalledIdentifierFromCall(init);
167
+ if (directId && fnNameSet.has(directId)) return init;
168
+ const firstArg = init.arguments?.[0];
169
+ if (firstArg?.type === "CallExpression") {
170
+ const innerId = getCalledIdentifierFromCall(firstArg);
171
+ if (innerId && fnNameSet.has(innerId)) return firstArg;
172
+ }
173
+ return init;
174
+ }
175
+
122
176
  export function collectCreateExportBindingsFallback(
123
177
  code: string,
124
178
  fnNames: string[],
@@ -196,7 +250,7 @@ export function collectCreateExportBindings(
196
250
  ): CreateExportBinding[] {
197
251
  if (!program) {
198
252
  try {
199
- program = parseAst(code, { jsx: true });
253
+ program = parseAst(code, { lang: "tsx" });
200
254
  } catch {
201
255
  return collectCreateExportBindingsFallback(code, fnNames);
202
256
  }
@@ -212,10 +266,13 @@ export function collectCreateExportBindings(
212
266
  }
213
267
 
214
268
  for (const decl of varDecl.declarations ?? []) {
215
- const calledIdentifier = getCalledIdentifierFromCall(decl?.init);
269
+ // Unwrap a Fast Refresh signature wrapper (`_s(createLoader(...), ...)`)
270
+ // so injection targets the inner create* call. Falls back to decl.init.
271
+ const callExpr = unwrapSignatureWrappedCall(decl?.init, fnNameSet);
272
+ const calledIdentifier = getCalledIdentifierFromCall(callExpr);
216
273
  if (
217
274
  decl?.id?.type !== "Identifier" ||
218
- decl?.init?.type !== "CallExpression" ||
275
+ callExpr?.type !== "CallExpression" ||
219
276
  !calledIdentifier ||
220
277
  !fnNameSet.has(calledIdentifier)
221
278
  ) {
@@ -226,9 +283,8 @@ export function collectCreateExportBindings(
226
283
  const exportNames = exportMap.get(localName) ?? [];
227
284
  if (exportNames.length === 0) continue;
228
285
 
229
- const callStart = decl.init.start as number;
230
- const callEnd = decl.init.end as number;
231
- const calleeEnd = decl.init.callee.end as number;
286
+ const callEnd = callExpr.end as number;
287
+ const calleeEnd = callExpr.callee.end as number;
232
288
 
233
289
  let openParenPos = -1;
234
290
  for (let i = calleeEnd; i < callEnd; i++) {
@@ -245,10 +301,10 @@ export function collectCreateExportBindings(
245
301
  bindings.push({
246
302
  localName,
247
303
  exportNames,
248
- callExprStart: decl.init.start as number,
304
+ callExprStart: callExpr.start as number,
249
305
  callOpenParenPos: openParenPos,
250
306
  callCloseParenPos: closeParenPos,
251
- argCount: decl.init.arguments?.length ?? 0,
307
+ argCount: callExpr.arguments?.length ?? 0,
252
308
  statementEnd,
253
309
  });
254
310
  }
@@ -268,10 +324,6 @@ export function collectCreateExportBindings(
268
324
  }
269
325
  }
270
326
 
271
- // When the JS parser misidentifies TypeScript generics (e.g.,
272
- // createLocationState<{ text: string }>({...})) as binary expressions,
273
- // the AST path finds 0 bindings even though calls exist. Fall back to
274
- // regex-based detection which handles generics via <[^>]*> matching.
275
327
  if (bindings.length === 0) {
276
328
  return collectCreateExportBindingsFallback(code, fnNames);
277
329
  }
@@ -282,9 +334,23 @@ export function collectCreateExportBindings(
282
334
  export function buildUnsupportedShapeWarning(
283
335
  filePath: string,
284
336
  fnName: string,
337
+ sites: Array<{ line: number; column: number }> = [],
285
338
  ): string {
286
- return [
287
- `[rsc-router] Unsupported ${fnName} shape in "${filePath}".`,
339
+ const lines = [`[rango] Unsupported ${fnName} shape in "${filePath}".`];
340
+
341
+ if (sites.length === 1) {
342
+ const s = sites[0];
343
+ lines.push(
344
+ `The ${fnName}(...) call at ${filePath}:${s.line}:${s.column} has no stable $$id injected — it is not in a supported shape.`,
345
+ );
346
+ } else if (sites.length > 1) {
347
+ lines.push(
348
+ `These ${fnName}(...) calls have no stable $$id injected — they are not in a supported shape:`,
349
+ );
350
+ for (const s of sites) lines.push(` - ${filePath}:${s.line}:${s.column}`);
351
+ }
352
+
353
+ lines.push(
288
354
  `Supported shapes are:`,
289
355
  ` - export const X = ${fnName}(...)`,
290
356
  ` - const X = ${fnName}(...); export { X }`,
@@ -292,5 +358,6 @@ export function buildUnsupportedShapeWarning(
292
358
  `Potentially unsupported forms include:`,
293
359
  ` - export let/var X = ${fnName}(...)`,
294
360
  ` - inline ${fnName}(...) calls`,
295
- ].join("\n");
361
+ );
362
+ return lines.join("\n");
296
363
  }
@@ -1,39 +1,26 @@
1
1
  import MagicString from "magic-string";
2
- import { hashId } from "../expose-id-utils.js";
2
+ import { makeStubId } from "../expose-id-utils.js";
3
3
  import type { HandlerTransformConfig, CreateExportBinding } from "./types.js";
4
4
  import { isExportOnlyFile } from "./export-analysis.js";
5
5
 
6
- function analyzeCreateHandleArgs(
7
- code: string,
8
- startPos: number,
9
- endPos: number,
10
- ): { hasArgs: boolean } {
11
- const content = code.slice(startPos, endPos).trim();
12
- return { hasArgs: content.length > 0 };
13
- }
14
-
15
6
  export function transformHandles(
16
7
  bindings: CreateExportBinding[],
17
8
  s: MagicString,
18
- code: string,
19
9
  filePath: string,
20
10
  isBuild: boolean,
21
11
  ): boolean {
22
12
  let hasChanges = false;
23
13
  for (const binding of bindings) {
24
14
  const exportName = binding.exportNames[0];
25
- const args = analyzeCreateHandleArgs(
26
- code,
27
- binding.callOpenParenPos + 1,
28
- binding.callCloseParenPos,
29
- );
30
15
 
31
- const handleId = isBuild
32
- ? hashId(filePath, exportName)
33
- : `${filePath}#${exportName}`;
16
+ const handleId = makeStubId(filePath, exportName, isBuild);
34
17
 
18
+ // Branch on the AST-derived argCount, not a string-trim of the raw arg
19
+ // slice. A comment-only arg list (`createHandle(/* meta */)`) trims to
20
+ // non-empty but is zero real arguments; injecting `, "id"` would emit
21
+ // `createHandle(/* meta */, "id")` — an elided first arg / syntax error.
35
22
  let paramInjection: string;
36
- if (!args.hasArgs) {
23
+ if (binding.argCount === 0) {
37
24
  paramInjection = `undefined, "${handleId}"`;
38
25
  } else {
39
26
  paramInjection = `, "${handleId}"`;
@@ -58,9 +45,7 @@ export function transformLocationState(
58
45
  for (const binding of bindings) {
59
46
  const exportName = binding.exportNames[0];
60
47
 
61
- const stateKey = isBuild
62
- ? hashId(filePath, exportName)
63
- : `${filePath}#${exportName}`;
48
+ const stateKey = makeStubId(filePath, exportName, isBuild);
64
49
 
65
50
  // Key is injected as a property assignment (not as a function argument).
66
51
  // This allows createLocationState to accept options like { flash: true }
@@ -73,10 +58,6 @@ export function transformLocationState(
73
58
  return hasChanges;
74
59
  }
75
60
 
76
- /**
77
- * Replace the entire file with lightweight stubs when ALL non-type exports are
78
- * handler calls of the given type. Returns null for files with mixed exports.
79
- */
80
61
  export function generateWholeFileStubs(
81
62
  cfg: HandlerTransformConfig,
82
63
  bindings: CreateExportBinding[],
@@ -88,35 +69,24 @@ export function generateWholeFileStubs(
88
69
 
89
70
  const exportNames = bindings.flatMap((b) => b.exportNames);
90
71
  const stubs = exportNames.map((name) => {
91
- const handlerId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
72
+ const handlerId = makeStubId(filePath, name, isBuild);
92
73
  return `export const ${name} = { __brand: "${cfg.brand}", $$id: "${handlerId}" };`;
93
74
  });
94
75
 
95
76
  return { code: stubs.join("\n") + "\n", map: null };
96
77
  }
97
78
 
98
- /**
99
- * Replace handler call expressions with lightweight stub objects in non-RSC
100
- * environments. Other exports, imports, and module-level code remain untouched.
101
- */
102
- export function generateExprStubs(
79
+ export function stubHandlerExprs(
103
80
  cfg: HandlerTransformConfig,
104
81
  bindings: CreateExportBinding[],
105
- code: string,
82
+ s: MagicString,
106
83
  filePath: string,
107
- sourceId: string,
108
84
  isBuild: boolean,
109
- ): { code: string; map: ReturnType<MagicString["generateMap"]> } | null {
110
- if (bindings.length === 0) return null;
111
-
112
- const s = new MagicString(code);
85
+ ): boolean {
113
86
  let hasChanges = false;
114
-
115
87
  for (const binding of bindings) {
116
88
  const exportName = binding.exportNames[0];
117
- const handlerId = isBuild
118
- ? hashId(filePath, exportName)
119
- : `${filePath}#${exportName}`;
89
+ const handlerId = makeStubId(filePath, exportName, isBuild);
120
90
 
121
91
  s.overwrite(
122
92
  binding.callExprStart,
@@ -125,22 +95,9 @@ export function generateExprStubs(
125
95
  );
126
96
  hasChanges = true;
127
97
  }
128
-
129
- if (!hasChanges) return null;
130
-
131
- return {
132
- code: s.toString(),
133
- map: s.generateMap({
134
- source: sourceId,
135
- includeContent: true,
136
- hires: "boundary",
137
- }),
138
- };
98
+ return hasChanges;
139
99
  }
140
100
 
141
- /**
142
- * Inject $$id into export const handler calls in RSC environments.
143
- */
144
101
  export function transformHandlerIds(
145
102
  cfg: HandlerTransformConfig,
146
103
  bindings: CreateExportBinding[],
@@ -152,14 +109,8 @@ export function transformHandlerIds(
152
109
  for (const binding of bindings) {
153
110
  const exportName = binding.exportNames[0];
154
111
 
155
- const handlerId = isBuild
156
- ? hashId(filePath, exportName)
157
- : `${filePath}#${exportName}`;
112
+ const handlerId = makeStubId(filePath, exportName, isBuild);
158
113
 
159
- // Injection strategy matches the runtime overload signatures:
160
- // 0 args -> inject undefined, "id"
161
- // 1 arg (handler) -> inject , undefined, "id"
162
- // 2+ args -> inject , "id"
163
114
  let paramInjection: string;
164
115
  if (binding.argCount === 0) {
165
116
  paramInjection = `undefined, "${handlerId}"`;
@@ -1,5 +1,5 @@
1
1
  import type MagicString from "magic-string";
2
- import { hashId } from "../expose-id-utils.js";
2
+ import { makeStubId } from "../expose-id-utils.js";
3
3
  import type { CreateExportBinding } from "./types.js";
4
4
  import { isExportOnlyFile } from "./export-analysis.js";
5
5
 
@@ -9,18 +9,6 @@ export function hasCreateLoaderImport(code: string): boolean {
9
9
  );
10
10
  }
11
11
 
12
- /**
13
- * Generate lightweight client stubs for loader files.
14
- *
15
- * When a loader file is imported from a client component (e.g., for useLoader()),
16
- * the client only needs { __brand: "loader", $$id: "..." } objects.
17
- * This function replaces the entire file contents with just those stub exports,
18
- * preventing server-only data (constants, DB queries, etc.) from leaking into
19
- * the client bundle.
20
- *
21
- * Only applies when ALL named exports are createLoader() calls (plus type exports
22
- * which are erased at compile time). Files with mixed exports are left untouched.
23
- */
24
12
  export function generateClientLoaderStubs(
25
13
  bindings: CreateExportBinding[],
26
14
  code: string,
@@ -32,11 +20,18 @@ export function generateClientLoaderStubs(
32
20
  const lines: string[] = [];
33
21
 
34
22
  for (const binding of bindings) {
35
- for (const name of binding.exportNames) {
36
- const loaderId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
37
- lines.push(
38
- `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`,
39
- );
23
+ // Aliases share the primary export's id (matches the server side, which
24
+ // registers only exportNames[0] in the loader registry, and the mixed-type
25
+ // whole-file path). Emitting a distinct makeStubId per alias would make a
26
+ // client component importing the alias fetch an id absent from the server
27
+ // registry, so the fetchable-loader request would 404.
28
+ const primaryName = binding.exportNames[0];
29
+ const loaderId = makeStubId(filePath, primaryName, isBuild);
30
+ lines.push(
31
+ `export const ${primaryName} = { __brand: "loader", $$id: "${loaderId}" };`,
32
+ );
33
+ for (const alias of binding.exportNames.slice(1)) {
34
+ lines.push(`export const ${alias} = ${primaryName};`);
40
35
  }
41
36
  }
42
37
 
@@ -54,13 +49,8 @@ export function transformLoaders(
54
49
  for (const binding of bindings) {
55
50
  const exportName = binding.exportNames[0];
56
51
 
57
- const loaderId = isBuild
58
- ? hashId(filePath, exportName)
59
- : `${filePath}#${exportName}`;
52
+ const loaderId = makeStubId(filePath, exportName, isBuild);
60
53
 
61
- // Inject $$id as hidden third parameter.
62
- // createLoader(fn) -> createLoader(fn, undefined, "id")
63
- // createLoader(fn, true) -> createLoader(fn, true, "id")
64
54
  const paramInjection =
65
55
  binding.argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
66
56
  s.appendLeft(binding.callCloseParenPos, paramInjection);