@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,816 @@
1
+ import type { Plugin, ResolvedConfig } from "vite";
2
+ import { parseAst } from "vite";
3
+ import MagicString from "magic-string";
4
+ import path from "node:path";
5
+ import {
6
+ normalizePath,
7
+ hashId,
8
+ makeStubId,
9
+ detectImports,
10
+ } from "./expose-id-utils.js";
11
+ import {
12
+ transformInlineHandlers,
13
+ type VirtualHandlerEntry,
14
+ } from "../utils/ast-handler-extract.js";
15
+
16
+ import type {
17
+ HandlerTransformConfig,
18
+ CreateExportBinding,
19
+ } from "./expose-ids/types.js";
20
+ import {
21
+ PRERENDER_CONFIG,
22
+ STATIC_CONFIG,
23
+ STRICT_CREATE_CONFIGS,
24
+ type ExposeInternalIdsApi,
25
+ } from "./expose-ids/types.js";
26
+ import {
27
+ countCreateCallsForNames,
28
+ getImportedFnNames,
29
+ collectCreateExportBindings,
30
+ buildUnsupportedShapeWarning,
31
+ isExportOnlyFile,
32
+ } from "./expose-ids/export-analysis.js";
33
+ import {
34
+ hasCreateLoaderImport,
35
+ generateClientLoaderStubs,
36
+ transformLoaders,
37
+ } from "./expose-ids/loader-transform.js";
38
+ import {
39
+ transformHandles,
40
+ transformLocationState,
41
+ generateWholeFileStubs,
42
+ generateExprStubs,
43
+ stubHandlerExprs,
44
+ transformHandlerIds,
45
+ } from "./expose-ids/handler-transform.js";
46
+ import { createRangoDebugger, createCounter, NS } from "../debug.js";
47
+
48
+ const debug = createRangoDebugger(NS.transform);
49
+
50
+ // Re-exports consumed by other packages/plugins
51
+ export { exposeRouterId } from "./expose-ids/router-transform.js";
52
+ export type { ExposeInternalIdsApi } from "./expose-ids/types.js";
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Virtual module for loader manifest
56
+ // ---------------------------------------------------------------------------
57
+
58
+ const VIRTUAL_LOADER_MANIFEST = "virtual:rsc-router/loader-manifest";
59
+ const RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Virtual module prefix for extracted inline handlers
63
+ // ---------------------------------------------------------------------------
64
+
65
+ const VIRTUAL_HANDLER_PREFIX = "virtual:handler-extract:";
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Consolidated plugin
69
+ // ---------------------------------------------------------------------------
70
+
71
+ export function exposeInternalIds(options?: { forceBuild?: boolean }): Plugin {
72
+ let config: ResolvedConfig;
73
+ let isBuild = false;
74
+ let projectRoot = "";
75
+
76
+ // Loader registry: hashedId -> { filePath, exportName }
77
+ const loaderRegistry = new Map<
78
+ string,
79
+ { filePath: string; exportName: string }
80
+ >();
81
+
82
+ // Prerender handler module tracking (consumed via plugin API)
83
+ const prerenderHandlerModules: Map<string, string[]> = new Map();
84
+
85
+ // Static handler module tracking (consumed via plugin API)
86
+ const staticHandlerModules: Map<string, string[]> = new Map();
87
+
88
+ // Virtual module registry for inline handler extraction (both types)
89
+ const virtualHandlers = new Map<string, VirtualHandlerEntry>();
90
+ // De-duplicate unsupported shape warnings across repeated transforms.
91
+ const unsupportedShapeWarnings = new Set<string>();
92
+
93
+ const counter = createCounter(debug, "expose-internal-ids");
94
+
95
+ return {
96
+ name: "@rangojs/router:expose-internal-ids",
97
+ enforce: "post",
98
+
99
+ buildEnd() {
100
+ counter?.flush();
101
+ },
102
+
103
+ api: {
104
+ prerenderHandlerModules,
105
+ staticHandlerModules,
106
+ } satisfies ExposeInternalIdsApi,
107
+
108
+ configResolved(resolved) {
109
+ config = resolved;
110
+ isBuild = options?.forceBuild || config.command === "build";
111
+ projectRoot = config.root;
112
+ },
113
+
114
+ // --------------- Virtual module support ---------------
115
+
116
+ resolveId(id, importer) {
117
+ if (id === VIRTUAL_LOADER_MANIFEST) {
118
+ return RESOLVED_VIRTUAL_LOADER_MANIFEST;
119
+ }
120
+ if (id.startsWith(VIRTUAL_HANDLER_PREFIX)) {
121
+ return "\0" + id;
122
+ }
123
+ // Resolve imports FROM virtual modules against the original file
124
+ if (importer?.startsWith("\0" + VIRTUAL_HANDLER_PREFIX)) {
125
+ const entry = virtualHandlers.get(importer);
126
+ if (entry) {
127
+ return this.resolve(id, entry.originalModuleId, { skipSelf: true });
128
+ }
129
+ }
130
+ },
131
+
132
+ load(id) {
133
+ // Virtual handler modules (both prerender and static)
134
+ if (id.startsWith("\0" + VIRTUAL_HANDLER_PREFIX)) {
135
+ const entry = virtualHandlers.get(id);
136
+ if (!entry) return null;
137
+ return (
138
+ [
139
+ ...entry.imports,
140
+ ...entry.declarations,
141
+ `export const ${entry.exportName} = ${entry.handlerCode};`,
142
+ ].join("\n") + "\n"
143
+ );
144
+ }
145
+
146
+ if (id !== RESOLVED_VIRTUAL_LOADER_MANIFEST) return;
147
+
148
+ if (!isBuild) {
149
+ return `import { setLoaderImports } from "@rangojs/router/server";
150
+
151
+ // Dev mode: empty map, loaders are resolved dynamically via path parsing
152
+ setLoaderImports({});
153
+ `;
154
+ }
155
+
156
+ // Build mode: generate lazy import map
157
+ const lazyImports: string[] = [];
158
+
159
+ for (const [hashedId, { filePath, exportName }] of loaderRegistry) {
160
+ lazyImports.push(
161
+ ` "${hashedId}": () => import("/${filePath}").then(m => m.${exportName})`,
162
+ );
163
+ }
164
+
165
+ if (lazyImports.length === 0) {
166
+ return `import { setLoaderImports } from "@rangojs/router/server";
167
+
168
+ // No fetchable loaders discovered during build
169
+ setLoaderImports({});
170
+ `;
171
+ }
172
+
173
+ return `import { setLoaderImports } from "@rangojs/router/server";
174
+
175
+ // Lazy import map - loaders are loaded on-demand when first requested
176
+ setLoaderImports({
177
+ ${lazyImports.join(",\n")}
178
+ });
179
+ `;
180
+ },
181
+
182
+ // --------------- Loader pre-scan (build mode) ---------------
183
+
184
+ async buildStart() {
185
+ if (!isBuild) return;
186
+
187
+ const fs = await import("node:fs/promises");
188
+
189
+ const SKIP_DIRS = new Set(["node_modules", "dist", "build", "coverage"]);
190
+
191
+ async function scanDir(dir: string): Promise<string[]> {
192
+ const results: string[] = [];
193
+ try {
194
+ const entries = await fs.readdir(dir, { withFileTypes: true });
195
+ for (const entry of entries) {
196
+ const fullPath = path.join(dir, entry.name);
197
+ if (entry.isDirectory()) {
198
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
199
+ results.push(...(await scanDir(fullPath)));
200
+ }
201
+ } else if (/\.(ts|tsx|js|jsx)$/.test(entry.name)) {
202
+ results.push(fullPath);
203
+ }
204
+ }
205
+ } catch {
206
+ // Directory doesn't exist or not readable
207
+ }
208
+ return results;
209
+ }
210
+
211
+ try {
212
+ const files = await scanDir(projectRoot);
213
+
214
+ for (const filePath of files) {
215
+ const content = await fs.readFile(filePath, "utf-8");
216
+
217
+ if (!content.includes("createLoader")) continue;
218
+ if (!hasCreateLoaderImport(content)) continue;
219
+
220
+ const fnNames = getImportedFnNames(content, "createLoader");
221
+ const relativePath = normalizePath(
222
+ path.relative(projectRoot, filePath),
223
+ );
224
+ const bindings = collectCreateExportBindings(content, fnNames);
225
+
226
+ for (const binding of bindings) {
227
+ const exportName = binding.exportNames[0];
228
+ const hashedId = hashId(relativePath, exportName);
229
+ loaderRegistry.set(hashedId, {
230
+ filePath: relativePath,
231
+ exportName,
232
+ });
233
+ }
234
+ }
235
+ } catch (error) {
236
+ console.warn("[exposeInternalIds] Loader pre-scan failed:", error);
237
+ }
238
+ },
239
+
240
+ // --------------- Unified transform ---------------
241
+
242
+ transform(code, id) {
243
+ if (id.includes("/node_modules/")) return;
244
+
245
+ const __t0 = counter ? performance.now() : 0;
246
+ try {
247
+ const filePath = normalizePath(path.relative(projectRoot, id));
248
+ const isRscEnv = this.environment?.name === "rsc";
249
+
250
+ // Warn if named-routes.gen is imported in a client component.
251
+ // NamedRoutes is server-only data and would bloat the client bundle.
252
+ if (
253
+ id.includes(".named-routes.gen.") &&
254
+ !isRscEnv &&
255
+ this.environment?.name === "client"
256
+ ) {
257
+ this.warn(
258
+ `\n` +
259
+ `!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n` +
260
+ `!! !!\n` +
261
+ `!! WARNING: NamedRoutes imported in a CLIENT component! !!\n` +
262
+ `!! !!\n` +
263
+ `!! File: ${filePath.padEnd(53)}!!\n` +
264
+ `!! !!\n` +
265
+ `!! NamedRoutes contains your entire route structure — every !!\n` +
266
+ `!! route name and URL pattern in your application. Shipping !!\n` +
267
+ `!! this to the browser exposes your full routing topology to !!\n` +
268
+ `!! the client, which is a security concern (internal/admin !!\n` +
269
+ `!! routes, API endpoints, hidden paths become visible). !!\n` +
270
+ `!! !!\n` +
271
+ `!! It also bloats the client bundle — this map contains all !!\n` +
272
+ `!! named routes in your application. !!\n` +
273
+ `!! !!\n` +
274
+ `!! Fix: remove the import or move it to a server component. !!\n` +
275
+ `!! !!\n` +
276
+ `!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n`,
277
+ );
278
+ }
279
+
280
+ // Fast exit: if the file doesn't import from @rangojs/router at all,
281
+ // skip all create* analysis and transforms.
282
+ if (!code.includes("@rangojs/router")) return;
283
+
284
+ // Detect all relevant imports in one pass
285
+ const has = detectImports(code);
286
+
287
+ // Quick bail-out: also check for raw create* identifiers.
288
+ // This is safe even with aliases (e.g., `import { createLoader as cl }`)
289
+ // because the import statement itself always contains the canonical name
290
+ // "createLoader", so code.includes("createLoader") will still match.
291
+ const hasLoaderCode = has.loader && code.includes("createLoader");
292
+ const hasHandleCode = has.handle && code.includes("createHandle");
293
+ const hasLocationStateCode =
294
+ has.locationState && code.includes("createLocationState");
295
+ const hasPrerenderHandlerCode =
296
+ has.prerenderHandler && code.includes("Prerender");
297
+ const hasStaticHandlerCode =
298
+ has.staticHandler && code.includes("Static");
299
+ if (
300
+ !hasLoaderCode &&
301
+ !hasHandleCode &&
302
+ !hasLocationStateCode &&
303
+ !hasPrerenderHandlerCode &&
304
+ !hasStaticHandlerCode
305
+ ) {
306
+ return;
307
+ }
308
+
309
+ // Per-invocation caches to avoid redundant AST parsing.
310
+ // getImportedFnNames is cached by canonical name (imports never change).
311
+ // collectCreateExportBindings is cached by fnNames key; the cache is
312
+ // cleared when `code` changes (e.g., after inline handler extraction).
313
+ const _fnNamesCache = new Map<string, string[]>();
314
+ const _bindingsCache = new Map<string, CreateExportBinding[]>();
315
+ let _cachedAst: any;
316
+ let _astParseFailed = false;
317
+ let _astCodeRef = code;
318
+
319
+ const getFnNames = (canonicalName: string): string[] => {
320
+ let result = _fnNamesCache.get(canonicalName);
321
+ if (!result) {
322
+ result = getImportedFnNames(code, canonicalName);
323
+ _fnNamesCache.set(canonicalName, result);
324
+ }
325
+ return result;
326
+ };
327
+
328
+ // Lazy AST parse: parsed once and shared across all
329
+ // collectCreateExportBindings calls for the same code string.
330
+ const lazyAst = (): any | undefined => {
331
+ if (code !== _astCodeRef) {
332
+ _cachedAst = undefined;
333
+ _astParseFailed = false;
334
+ _astCodeRef = code;
335
+ }
336
+ if (_cachedAst !== undefined || _astParseFailed) return _cachedAst;
337
+ try {
338
+ _cachedAst = parseAst(code, { jsx: true });
339
+ } catch {
340
+ _astParseFailed = true;
341
+ }
342
+ return _cachedAst;
343
+ };
344
+
345
+ const getBindings = (
346
+ currentCode: string,
347
+ fnNames: string[],
348
+ ): CreateExportBinding[] => {
349
+ const key = fnNames.join("\0");
350
+ let result = _bindingsCache.get(key);
351
+ if (!result) {
352
+ result = collectCreateExportBindings(
353
+ currentCode,
354
+ fnNames,
355
+ lazyAst(),
356
+ );
357
+ _bindingsCache.set(key, result);
358
+ }
359
+ return result;
360
+ };
361
+
362
+ // Warn on create* declaration shapes that are currently unsupported by
363
+ // non-AST transforms (loader/handle/locationState only).
364
+ for (const cfg of STRICT_CREATE_CONFIGS) {
365
+ const hasCode =
366
+ cfg.fnName === "createLoader"
367
+ ? hasLoaderCode
368
+ : cfg.fnName === "createHandle"
369
+ ? hasHandleCode
370
+ : hasLocationStateCode;
371
+ if (!hasCode) continue;
372
+
373
+ const fnNames = getFnNames(cfg.fnName);
374
+ const totalCalls = countCreateCallsForNames(code, fnNames);
375
+ const supportedBindings = getBindings(code, fnNames).length;
376
+ if (totalCalls <= supportedBindings) continue;
377
+
378
+ const warnKey = `${id}::${cfg.fnName}`;
379
+ if (unsupportedShapeWarnings.has(warnKey)) continue;
380
+ unsupportedShapeWarnings.add(warnKey);
381
+ this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
382
+ }
383
+
384
+ // --- Loader: track for manifest (RSC env only) ---
385
+ if (hasLoaderCode && isRscEnv) {
386
+ const fnNames = getFnNames("createLoader");
387
+ const bindings = getBindings(code, fnNames);
388
+ for (const binding of bindings) {
389
+ const exportName = binding.exportNames[0];
390
+ const hashedId = hashId(filePath, exportName);
391
+ loaderRegistry.set(hashedId, {
392
+ filePath,
393
+ exportName,
394
+ });
395
+ }
396
+ }
397
+
398
+ // --- Loader: client stubs for non-RSC environments ---
399
+ if (hasLoaderCode && !isRscEnv) {
400
+ const fnNames = getFnNames("createLoader");
401
+ const bindings = getBindings(code, fnNames);
402
+ const stubResult = generateClientLoaderStubs(
403
+ bindings,
404
+ code,
405
+ filePath,
406
+ isBuild,
407
+ );
408
+ if (stubResult) return stubResult;
409
+ }
410
+
411
+ // --- PrerenderHandler: non-RSC whole-file stub replacement ---
412
+ // When ALL exports are Prerender() calls, replace the entire file.
413
+ // Mixed-export files are handled in the unified pipeline below.
414
+ if (hasPrerenderHandlerCode && !isRscEnv) {
415
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
416
+ const bindings = getBindings(code, fnNames);
417
+ const wholeFile = generateWholeFileStubs(
418
+ PRERENDER_CONFIG,
419
+ bindings,
420
+ code,
421
+ filePath,
422
+ isBuild,
423
+ );
424
+ if (wholeFile) return wholeFile;
425
+ }
426
+
427
+ // --- PrerenderHandler: RSC build module tracking ---
428
+ if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
429
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
430
+ const exportNames = getBindings(code, fnNames).map(
431
+ (b) => b.exportNames[0],
432
+ );
433
+ if (exportNames.length > 0) {
434
+ prerenderHandlerModules.set(id, exportNames);
435
+ }
436
+ }
437
+
438
+ // --- Inline handler extraction to virtual modules ---
439
+ // Runs before stubs/tracking so inline calls become imports, then
440
+ // the existing regex fast path handles both the original file's
441
+ // export const patterns and the virtual modules independently.
442
+ //
443
+ // Cheap pre-check: count total fnName( occurrences vs export const
444
+ // patterns. If they match, every call is a named export and the
445
+ // regex fast path handles them -- skip the AST parse entirely.
446
+ //
447
+ // Each iteration creates a fresh MagicString so that AST positions
448
+ // from findHandlerCalls always match the string they were parsed from.
449
+ let changed = false;
450
+
451
+ const handlerConfigs = [
452
+ hasStaticHandlerCode && STATIC_CONFIG,
453
+ hasPrerenderHandlerCode && PRERENDER_CONFIG,
454
+ ]
455
+ .filter((c): c is HandlerTransformConfig => !!c)
456
+ .map((cfg) => {
457
+ const fnNames = getFnNames(cfg.fnName);
458
+ return { cfg, fnNames };
459
+ });
460
+
461
+ for (const { cfg, fnNames } of handlerConfigs) {
462
+ const totalCalls = countCreateCallsForNames(code, fnNames);
463
+ const supportedBindings = getBindings(code, fnNames).length;
464
+
465
+ if (totalCalls > supportedBindings) {
466
+ const iterS = new MagicString(code);
467
+ const result = transformInlineHandlers(
468
+ cfg.fnName,
469
+ VIRTUAL_HANDLER_PREFIX,
470
+ iterS,
471
+ code,
472
+ filePath,
473
+ virtualHandlers,
474
+ id,
475
+ parseAst,
476
+ );
477
+ if (result) {
478
+ changed = true;
479
+ code = iterS.toString();
480
+ _bindingsCache.clear();
481
+ }
482
+ }
483
+ }
484
+
485
+ // --- StaticHandler: non-RSC whole-file stub replacement ---
486
+ // When ALL exports are Static() calls, replace the entire file.
487
+ if (hasStaticHandlerCode && !isRscEnv) {
488
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
489
+ const bindings = getBindings(code, fnNames);
490
+ const wholeFile = generateWholeFileStubs(
491
+ STATIC_CONFIG,
492
+ bindings,
493
+ code,
494
+ filePath,
495
+ isBuild,
496
+ );
497
+ if (wholeFile) return wholeFile;
498
+ }
499
+
500
+ // --- Mixed-type whole-file stub replacement (non-RSC) ---
501
+ // When the individual whole-file checks above fail (each only checks
502
+ // one type), the file has mixed exports (e.g. createLoader + Prerender).
503
+ // Gather ALL stub-safe bindings and check if they cover every export.
504
+ // If yes, replace the entire file with stubs — this strips server-only
505
+ // imports (node:fs, DB clients, etc.) that would crash in the browser.
506
+ //
507
+ // Only applies when the file contains Prerender/Static (the handler
508
+ // types that bring server-only code). Files with only loaders, handles,
509
+ // or locationState are handled correctly by the unified pipeline below.
510
+ //
511
+ // Loader, Prerender, and Static exports become plain { __brand, $$id }
512
+ // stubs. createHandle and createLocationState need their create*()
513
+ // functions to execute (collect registration / __rsc_ls_key), so their
514
+ // call expressions are preserved with only a @rangojs/router import.
515
+ // This strips all server-only imports while keeping the correct
516
+ // client contract for every export type.
517
+ if (!isRscEnv && (hasPrerenderHandlerCode || hasStaticHandlerCode)) {
518
+ const prerenderFnNames = hasPrerenderHandlerCode
519
+ ? getFnNames(PRERENDER_CONFIG.fnName)
520
+ : [];
521
+ const staticFnNames = hasStaticHandlerCode
522
+ ? getFnNames(STATIC_CONFIG.fnName)
523
+ : [];
524
+ const loaderFnNames = hasLoaderCode ? getFnNames("createLoader") : [];
525
+ const handleFnNames = hasHandleCode ? getFnNames("createHandle") : [];
526
+ const lsFnNames = hasLocationStateCode
527
+ ? getFnNames("createLocationState")
528
+ : [];
529
+
530
+ // Collect ALL recognized bindings to check export coverage
531
+ const allBindings: CreateExportBinding[] = [];
532
+ for (const fnNames of [
533
+ prerenderFnNames,
534
+ staticFnNames,
535
+ loaderFnNames,
536
+ handleFnNames,
537
+ lsFnNames,
538
+ ]) {
539
+ if (fnNames.length > 0) {
540
+ allBindings.push(...getBindings(code, fnNames));
541
+ }
542
+ }
543
+
544
+ // Check if preserved createHandle/createLocationState calls
545
+ // reference non-exported locals (e.g. helper functions, constants).
546
+ // If so, the whole-file stub would strip those locals, breaking
547
+ // the call. Fall through to the unified pipeline instead.
548
+ let canStubWholeFile =
549
+ allBindings.length > 0 && isExportOnlyFile(code, allBindings);
550
+
551
+ if (
552
+ canStubWholeFile &&
553
+ (handleFnNames.length > 0 || lsFnNames.length > 0)
554
+ ) {
555
+ const exportedLocals = new Set(allBindings.map((b) => b.localName));
556
+ // Collect bindings that would be stripped by whole-file replacement:
557
+ // local declarations and imported bindings from non-@rangojs/router
558
+ // modules. This is a regex-based heuristic — it intentionally skips
559
+ // edge cases (class decls, destructured bindings, combined
560
+ // default+named imports) since those rarely appear in route files.
561
+ const strippedBindings: string[] = [];
562
+
563
+ // Skip React Fast Refresh temporaries (_c, _c2, ...) which are
564
+ // injected by @vitejs/plugin-react in the client environment and
565
+ // would falsely trigger the bailout.
566
+ const localDeclPattern =
567
+ /(?:^|;|\n)\s*(?:const|let|var|function)\s+(\w+)/g;
568
+ let declMatch: RegExpExecArray | null;
569
+ while ((declMatch = localDeclPattern.exec(code)) !== null) {
570
+ const name = declMatch[1];
571
+ if (!exportedLocals.has(name) && !/^_c\d*$/.test(name)) {
572
+ strippedBindings.push(name);
573
+ }
574
+ }
575
+
576
+ const importPattern =
577
+ /import\s*\{([^}]*)\}\s*from\s*["'](?!@rangojs\/router)[^"']*["']/g;
578
+ let importMatch: RegExpExecArray | null;
579
+ while ((importMatch = importPattern.exec(code)) !== null) {
580
+ for (const spec of importMatch[1].split(",")) {
581
+ const m = spec
582
+ .trim()
583
+ .match(/^[A-Za-z_$][\w$]*(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
584
+ if (m)
585
+ strippedBindings.push(m[1] || m[0].trim().split(/\s/)[0]);
586
+ }
587
+ }
588
+ const defaultImportPattern =
589
+ /import\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
590
+ while ((importMatch = defaultImportPattern.exec(code)) !== null) {
591
+ strippedBindings.push(importMatch[1]);
592
+ }
593
+ const nsImportPattern =
594
+ /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
595
+ while ((importMatch = nsImportPattern.exec(code)) !== null) {
596
+ strippedBindings.push(importMatch[1]);
597
+ }
598
+
599
+ if (strippedBindings.length > 0) {
600
+ const preservedBindings = allBindings.filter((b) => {
601
+ const fc = code.slice(b.callExprStart, b.callOpenParenPos + 1);
602
+ return (
603
+ handleFnNames.some((n) => fc.includes(n)) ||
604
+ lsFnNames.some((n) => fc.includes(n))
605
+ );
606
+ });
607
+ const strippedRe = new RegExp(
608
+ `\\b(?:${strippedBindings.join("|")})\\b`,
609
+ );
610
+ canStubWholeFile = !preservedBindings.some((b) => {
611
+ const expr = code.slice(
612
+ b.callExprStart,
613
+ b.callCloseParenPos + 1,
614
+ );
615
+ return strippedRe.test(expr);
616
+ });
617
+ }
618
+ }
619
+
620
+ if (canStubWholeFile) {
621
+ const lines: string[] = [];
622
+ const neededImports: string[] = [];
623
+ if (handleFnNames.length > 0) neededImports.push("createHandle");
624
+ if (lsFnNames.length > 0) neededImports.push("createLocationState");
625
+ if (neededImports.length > 0) {
626
+ lines.push(
627
+ `import { ${neededImports.join(", ")} } from "@rangojs/router";`,
628
+ );
629
+ }
630
+
631
+ for (const binding of allBindings) {
632
+ const fnCall = code.slice(
633
+ binding.callExprStart,
634
+ binding.callOpenParenPos + 1,
635
+ );
636
+ const isHandle = handleFnNames.some((n) => fnCall.includes(n));
637
+ const isLocationState = lsFnNames.some((n) => fnCall.includes(n));
638
+
639
+ // Aliases share the primary name's ID (matches server transforms).
640
+ const primaryName = binding.exportNames[0];
641
+ const stubId = makeStubId(filePath, primaryName, isBuild);
642
+
643
+ if (isHandle || isLocationState) {
644
+ // Rewrite alias to canonical name since the stub file only
645
+ // imports canonical names from @rangojs/router.
646
+ // Strip React Fast Refresh `_c = ` wrappers from args
647
+ // (e.g. `_c = (segments) => ...` → `(segments) => ...`)
648
+ const rawArgs = code
649
+ .slice(
650
+ binding.callOpenParenPos + 1,
651
+ binding.callCloseParenPos,
652
+ )
653
+ .replace(/\b_c\d*\s*=\s*/g, "");
654
+ const canonicalName = isHandle
655
+ ? "createHandle"
656
+ : "createLocationState";
657
+ const activeFnNames = isHandle ? handleFnNames : lsFnNames;
658
+
659
+ // Reconstruct the function name (handling aliases + generics)
660
+ let rawCallee = code.slice(
661
+ binding.callExprStart,
662
+ binding.callOpenParenPos,
663
+ );
664
+ for (const alias of activeFnNames) {
665
+ if (alias !== canonicalName && rawCallee.startsWith(alias)) {
666
+ rawCallee = canonicalName + rawCallee.slice(alias.length);
667
+ break;
668
+ }
669
+ }
670
+
671
+ if (isHandle) {
672
+ // createHandle checks __injectedId DURING the call, so $$id
673
+ // must be a parameter, not a post-call property assignment.
674
+ const idParam =
675
+ binding.argCount === 0
676
+ ? `undefined, "${stubId}"`
677
+ : `, "${stubId}"`;
678
+ lines.push(
679
+ `export const ${primaryName} = ${rawCallee}(${rawArgs}${idParam});`,
680
+ );
681
+ lines.push(`${primaryName}.$$id = "${stubId}";`);
682
+ } else {
683
+ lines.push(
684
+ `export const ${primaryName} = ${rawCallee}(${rawArgs});`,
685
+ );
686
+ lines.push(
687
+ `${primaryName}.__rsc_ls_key = "__rsc_ls_${stubId}";`,
688
+ );
689
+ }
690
+ for (const name of binding.exportNames.slice(1)) {
691
+ lines.push(`export const ${name} = ${primaryName};`);
692
+ }
693
+ } else {
694
+ let brand = "loader";
695
+ if (prerenderFnNames.some((n) => fnCall.includes(n))) {
696
+ brand = PRERENDER_CONFIG.brand;
697
+ } else if (staticFnNames.some((n) => fnCall.includes(n))) {
698
+ brand = STATIC_CONFIG.brand;
699
+ }
700
+ lines.push(
701
+ `export const ${primaryName} = { __brand: "${brand}", $$id: "${stubId}" };`,
702
+ );
703
+ for (const name of binding.exportNames.slice(1)) {
704
+ lines.push(`export const ${name} = ${primaryName};`);
705
+ }
706
+ }
707
+ }
708
+
709
+ return { code: lines.join("\n") + "\n", map: null };
710
+ }
711
+ }
712
+
713
+ // --- StaticHandler: RSC build module tracking ---
714
+ if (hasStaticHandlerCode && isRscEnv && isBuild) {
715
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
716
+ const exportNames = getBindings(code, fnNames).map(
717
+ (b) => b.exportNames[0],
718
+ );
719
+ if (exportNames.length > 0) {
720
+ staticHandlerModules.set(id, exportNames);
721
+ }
722
+ }
723
+
724
+ // --- Unified MagicString transforms ---
725
+ // Single pipeline for all downstream transforms (loaders, handles,
726
+ // locationState, handler IDs). Uses the post-extraction code so
727
+ // positions are always consistent.
728
+ const s = new MagicString(code);
729
+
730
+ if (hasLoaderCode) {
731
+ const fnNames = getFnNames("createLoader");
732
+ changed =
733
+ transformLoaders(
734
+ getBindings(code, fnNames),
735
+ s,
736
+ filePath,
737
+ isBuild,
738
+ ) || changed;
739
+ }
740
+ if (hasHandleCode) {
741
+ const fnNames = getFnNames("createHandle");
742
+ changed =
743
+ transformHandles(
744
+ getBindings(code, fnNames),
745
+ s,
746
+ code,
747
+ filePath,
748
+ isBuild,
749
+ ) || changed;
750
+ }
751
+ if (hasLocationStateCode) {
752
+ const fnNames = getFnNames("createLocationState");
753
+ changed =
754
+ transformLocationState(
755
+ getBindings(code, fnNames),
756
+ s,
757
+ filePath,
758
+ isBuild,
759
+ ) || changed;
760
+ }
761
+ if (hasPrerenderHandlerCode) {
762
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
763
+ const bindings = getBindings(code, fnNames);
764
+ if (isRscEnv) {
765
+ changed =
766
+ transformHandlerIds(
767
+ PRERENDER_CONFIG,
768
+ bindings,
769
+ s,
770
+ filePath,
771
+ isBuild,
772
+ ) || changed;
773
+ } else {
774
+ // Non-RSC mixed-export file: replace Prerender() calls with stubs
775
+ // on the shared MagicString so sourcemaps stay accurate.
776
+ changed =
777
+ stubHandlerExprs(
778
+ PRERENDER_CONFIG,
779
+ bindings,
780
+ s,
781
+ filePath,
782
+ isBuild,
783
+ ) || changed;
784
+ }
785
+ }
786
+ if (hasStaticHandlerCode) {
787
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
788
+ const bindings = getBindings(code, fnNames);
789
+ if (isRscEnv) {
790
+ changed =
791
+ transformHandlerIds(
792
+ STATIC_CONFIG,
793
+ bindings,
794
+ s,
795
+ filePath,
796
+ isBuild,
797
+ ) || changed;
798
+ } else {
799
+ changed =
800
+ stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) ||
801
+ changed;
802
+ }
803
+ }
804
+
805
+ if (!changed) return;
806
+
807
+ return {
808
+ code: s.toString(),
809
+ map: s.generateMap({ source: id, includeContent: true }),
810
+ };
811
+ } finally {
812
+ counter?.record(id, performance.now() - __t0);
813
+ }
814
+ },
815
+ };
816
+ }