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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/README.md +24 -9
  2. package/dist/bin/rango.js +157 -63
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +1584 -639
  5. package/package.json +60 -11
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +60 -0
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +222 -30
  10. package/skills/caching/SKILL.md +263 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +235 -28
  16. package/skills/host-router/SKILL.md +122 -22
  17. package/skills/intercept/SKILL.md +29 -5
  18. package/skills/layout/SKILL.md +13 -9
  19. package/skills/links/SKILL.md +173 -17
  20. package/skills/loader/SKILL.md +170 -23
  21. package/skills/middleware/SKILL.md +16 -10
  22. package/skills/migrate-nextjs/SKILL.md +38 -16
  23. package/skills/mime-routes/SKILL.md +27 -0
  24. package/skills/observability/SKILL.md +137 -0
  25. package/skills/parallel/SKILL.md +11 -7
  26. package/skills/prerender/SKILL.md +14 -33
  27. package/skills/rango/SKILL.md +250 -26
  28. package/skills/react-compiler/SKILL.md +168 -0
  29. package/skills/response-routes/SKILL.md +114 -47
  30. package/skills/route/SKILL.md +22 -5
  31. package/skills/router-setup/SKILL.md +3 -3
  32. package/skills/server-actions/SKILL.md +78 -42
  33. package/skills/tailwind/SKILL.md +27 -3
  34. package/skills/testing/SKILL.md +129 -0
  35. package/skills/testing/bindings.md +89 -0
  36. package/skills/testing/cache-prerender.md +124 -0
  37. package/skills/testing/client-components.md +122 -0
  38. package/skills/testing/e2e-parity.md +125 -0
  39. package/skills/testing/flight.md +92 -0
  40. package/skills/testing/handles.md +129 -0
  41. package/skills/testing/loader.md +128 -0
  42. package/skills/testing/middleware.md +99 -0
  43. package/skills/testing/render-handler.md +121 -0
  44. package/skills/testing/response-routes.md +95 -0
  45. package/skills/testing/reverse-and-types.md +84 -0
  46. package/skills/testing/server-actions.md +107 -0
  47. package/skills/testing/server-tree.md +128 -0
  48. package/skills/testing/setup.md +120 -0
  49. package/skills/typesafety/SKILL.md +310 -26
  50. package/skills/use-cache/SKILL.md +36 -5
  51. package/skills/vercel/SKILL.md +107 -0
  52. package/skills/view-transitions/SKILL.md +294 -0
  53. package/src/__augment-tests__/augment.ts +81 -0
  54. package/src/__augment-tests__/augmented.check.ts +116 -0
  55. package/src/__internal.ts +0 -65
  56. package/src/browser/action-coordinator.ts +53 -36
  57. package/src/browser/action-fence.ts +47 -0
  58. package/src/browser/app-shell.ts +14 -27
  59. package/src/browser/cookie-name.ts +140 -0
  60. package/src/browser/event-controller.ts +37 -143
  61. package/src/browser/history-state.ts +21 -0
  62. package/src/browser/index.ts +3 -3
  63. package/src/browser/invalidate-client-cache.ts +52 -0
  64. package/src/browser/navigation-bridge.ts +30 -59
  65. package/src/browser/navigation-client.ts +96 -84
  66. package/src/browser/navigation-store-handle.ts +38 -0
  67. package/src/browser/navigation-store.ts +32 -82
  68. package/src/browser/navigation-transaction.ts +9 -59
  69. package/src/browser/partial-update.ts +60 -127
  70. package/src/browser/prefetch/cache.ts +82 -72
  71. package/src/browser/prefetch/fetch.ts +108 -33
  72. package/src/browser/prefetch/queue.ts +6 -3
  73. package/src/browser/rango-state.ts +157 -115
  74. package/src/browser/react/Link.tsx +0 -2
  75. package/src/browser/react/NavigationProvider.tsx +41 -48
  76. package/src/browser/react/ScrollRestoration.tsx +10 -6
  77. package/src/browser/react/filter-segment-order.ts +0 -2
  78. package/src/browser/react/index.ts +0 -48
  79. package/src/browser/react/location-state-shared.ts +166 -8
  80. package/src/browser/react/location-state.ts +39 -14
  81. package/src/browser/react/use-action.ts +6 -15
  82. package/src/browser/react/use-handle.ts +17 -14
  83. package/src/browser/react/use-link-status.ts +0 -4
  84. package/src/browser/react/use-navigation.ts +0 -3
  85. package/src/browser/react/use-params.ts +3 -6
  86. package/src/browser/react/use-reverse.ts +106 -0
  87. package/src/browser/react/use-router.ts +20 -5
  88. package/src/browser/react/use-search-params.ts +0 -5
  89. package/src/browser/react/use-segments.ts +0 -13
  90. package/src/browser/response-adapter.ts +52 -1
  91. package/src/browser/rsc-router.tsx +70 -34
  92. package/src/browser/scroll-restoration.ts +22 -14
  93. package/src/browser/segment-structure-assert.ts +2 -2
  94. package/src/browser/server-action-bridge.ts +168 -44
  95. package/src/browser/types.ts +36 -21
  96. package/src/browser/validate-redirect-origin.ts +43 -16
  97. package/src/build/collect-fallback-refs.ts +107 -0
  98. package/src/build/generate-manifest.ts +60 -35
  99. package/src/build/generate-route-types.ts +3 -0
  100. package/src/build/index.ts +8 -2
  101. package/src/build/prefix-tree-utils.ts +123 -0
  102. package/src/build/route-trie.ts +89 -11
  103. package/src/build/route-types/codegen.ts +4 -4
  104. package/src/build/route-types/include-resolution.ts +1 -1
  105. package/src/build/route-types/param-extraction.ts +6 -3
  106. package/src/build/route-types/per-module-writer.ts +7 -4
  107. package/src/build/route-types/router-processing.ts +122 -22
  108. package/src/build/route-types/scan-filter.ts +1 -1
  109. package/src/build/route-types/source-scan.ts +118 -0
  110. package/src/build/runtime-discovery.ts +9 -20
  111. package/src/cache/cache-error.ts +104 -0
  112. package/src/cache/cache-policy.ts +68 -28
  113. package/src/cache/cache-runtime.ts +134 -32
  114. package/src/cache/cache-scope.ts +100 -74
  115. package/src/cache/cache-tag.ts +98 -0
  116. package/src/cache/cf/cf-cache-store.ts +2255 -238
  117. package/src/cache/cf/index.ts +6 -16
  118. package/src/cache/document-cache.ts +61 -20
  119. package/src/cache/handle-snapshot.ts +63 -0
  120. package/src/cache/index.ts +22 -20
  121. package/src/cache/memory-segment-store.ts +136 -37
  122. package/src/cache/profile-registry.ts +6 -30
  123. package/src/cache/read-through-swr.ts +41 -11
  124. package/src/cache/segment-codec.ts +0 -16
  125. package/src/cache/tag-invalidation.ts +230 -0
  126. package/src/cache/types.ts +33 -100
  127. package/src/cache/vercel/index.ts +11 -0
  128. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  129. package/src/client.rsc.tsx +6 -21
  130. package/src/client.tsx +25 -61
  131. package/src/component-utils.ts +19 -0
  132. package/src/context-var.ts +17 -5
  133. package/src/decode-loader-results.ts +36 -0
  134. package/src/defer.ts +196 -0
  135. package/src/deps/ssr.ts +0 -1
  136. package/src/errors.ts +30 -4
  137. package/src/handle.ts +31 -23
  138. package/src/handles/MetaTags.tsx +0 -14
  139. package/src/handles/breadcrumbs.ts +16 -5
  140. package/src/handles/meta.ts +0 -39
  141. package/src/host/cookie-handler.ts +0 -36
  142. package/src/host/errors.ts +0 -24
  143. package/src/host/index.ts +8 -2
  144. package/src/host/pattern-matcher.ts +7 -50
  145. package/src/host/router.ts +107 -99
  146. package/src/host/testing.ts +40 -27
  147. package/src/host/types.ts +37 -4
  148. package/src/host/utils.ts +1 -1
  149. package/src/href-client.ts +137 -22
  150. package/src/index.rsc.ts +63 -9
  151. package/src/index.ts +64 -9
  152. package/src/internal-debug.ts +2 -4
  153. package/src/loader-store.ts +500 -0
  154. package/src/loader.rsc.ts +20 -13
  155. package/src/loader.ts +12 -11
  156. package/src/missing-id-error.ts +68 -0
  157. package/src/network-error-thrower.tsx +1 -6
  158. package/src/outlet-provider.tsx +1 -5
  159. package/src/prerender/param-hash.ts +10 -11
  160. package/src/prerender/store.ts +32 -37
  161. package/src/prerender.ts +61 -6
  162. package/src/redirect-origin.ts +100 -0
  163. package/src/response-utils.ts +9 -0
  164. package/src/reverse.ts +65 -41
  165. package/src/root-error-boundary.tsx +1 -19
  166. package/src/route-content-wrapper.tsx +7 -72
  167. package/src/route-definition/dsl-helpers.ts +244 -281
  168. package/src/route-definition/helper-factories.ts +29 -139
  169. package/src/route-definition/helpers-types.ts +40 -17
  170. package/src/route-definition/redirect.ts +43 -9
  171. package/src/route-definition/resolve-handler-use.ts +6 -0
  172. package/src/route-definition/use-item-types.ts +32 -0
  173. package/src/route-map-builder.ts +0 -16
  174. package/src/route-types.ts +19 -41
  175. package/src/router/basename.ts +14 -0
  176. package/src/router/content-negotiation.ts +15 -15
  177. package/src/router/error-handling.ts +13 -17
  178. package/src/router/find-match.ts +44 -23
  179. package/src/router/handler-context.ts +4 -42
  180. package/src/router/intercept-resolution.ts +14 -19
  181. package/src/router/lazy-includes.ts +9 -46
  182. package/src/router/loader-resolution.ts +91 -46
  183. package/src/router/logging.ts +0 -6
  184. package/src/router/manifest.ts +18 -29
  185. package/src/router/match-api.ts +0 -20
  186. package/src/router/match-context.ts +0 -22
  187. package/src/router/match-handlers.ts +57 -58
  188. package/src/router/match-middleware/background-revalidation.ts +0 -7
  189. package/src/router/match-middleware/cache-lookup.ts +150 -271
  190. package/src/router/match-middleware/cache-store.ts +3 -33
  191. package/src/router/match-middleware/intercept-resolution.ts +0 -22
  192. package/src/router/match-middleware/segment-resolution.ts +0 -22
  193. package/src/router/match-pipelines.ts +1 -42
  194. package/src/router/match-result.ts +31 -80
  195. package/src/router/metrics.ts +0 -34
  196. package/src/router/middleware-types.ts +0 -116
  197. package/src/router/middleware.ts +118 -133
  198. package/src/router/navigation-snapshot.ts +0 -51
  199. package/src/router/params-util.ts +23 -0
  200. package/src/router/pattern-matching.ts +20 -58
  201. package/src/router/prerender-match.ts +99 -63
  202. package/src/router/preview-match.ts +3 -1
  203. package/src/router/request-classification.ts +28 -62
  204. package/src/router/revalidation.ts +50 -56
  205. package/src/router/route-snapshot.ts +0 -1
  206. package/src/router/router-context.ts +0 -27
  207. package/src/router/router-interfaces.ts +68 -35
  208. package/src/router/router-options.ts +55 -1
  209. package/src/router/router-registry.ts +2 -5
  210. package/src/router/segment-resolution/fresh.ts +44 -63
  211. package/src/router/segment-resolution/helpers.ts +34 -0
  212. package/src/router/segment-resolution/loader-cache.ts +40 -37
  213. package/src/router/segment-resolution/revalidation.ts +203 -285
  214. package/src/router/segment-resolution/static-store.ts +19 -5
  215. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  216. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  217. package/src/router/segment-resolution.ts +4 -1
  218. package/src/router/segment-wrappers.ts +0 -3
  219. package/src/router/state-cookie-name.ts +33 -0
  220. package/src/router/substitute-pattern-params.ts +56 -0
  221. package/src/router/telemetry-otel.ts +0 -20
  222. package/src/router/telemetry.ts +96 -19
  223. package/src/router/timeout.ts +0 -20
  224. package/src/router/trie-matching.ts +87 -47
  225. package/src/router/types.ts +9 -63
  226. package/src/router/url-params.ts +0 -5
  227. package/src/router.ts +80 -41
  228. package/src/rsc/handler-context.ts +3 -2
  229. package/src/rsc/handler.ts +83 -78
  230. package/src/rsc/helpers.ts +93 -5
  231. package/src/rsc/index.ts +1 -1
  232. package/src/rsc/json-route-result.ts +38 -0
  233. package/src/rsc/manifest-init.ts +28 -41
  234. package/src/rsc/origin-guard.ts +39 -25
  235. package/src/rsc/progressive-enhancement.ts +12 -1
  236. package/src/rsc/redirect-guard.ts +99 -0
  237. package/src/rsc/response-error.ts +79 -12
  238. package/src/rsc/response-route-handler.ts +76 -62
  239. package/src/rsc/rsc-rendering.ts +41 -60
  240. package/src/rsc/runtime-warnings.ts +23 -10
  241. package/src/rsc/server-action.ts +62 -67
  242. package/src/rsc/ssr-setup.ts +16 -0
  243. package/src/rsc/types.ts +10 -5
  244. package/src/runtime-env.ts +18 -0
  245. package/src/search-params.ts +4 -20
  246. package/src/segment-loader-promise.ts +14 -2
  247. package/src/segment-system.tsx +199 -142
  248. package/src/serialize.ts +243 -0
  249. package/src/server/context.ts +150 -51
  250. package/src/server/cookie-store.ts +80 -5
  251. package/src/server/handle-store.ts +7 -24
  252. package/src/server/loader-registry.ts +5 -24
  253. package/src/server/request-context.ts +165 -87
  254. package/src/ssr/index.tsx +14 -14
  255. package/src/static-handler.ts +10 -13
  256. package/src/testing/cache-status.ts +162 -0
  257. package/src/testing/collect-handle.ts +40 -0
  258. package/src/testing/dispatch.ts +618 -0
  259. package/src/testing/dom.entry.ts +22 -0
  260. package/src/testing/e2e/fixture.ts +188 -0
  261. package/src/testing/e2e/index.ts +128 -0
  262. package/src/testing/e2e/matchers.ts +35 -0
  263. package/src/testing/e2e/page-helpers.ts +272 -0
  264. package/src/testing/e2e/parity.ts +387 -0
  265. package/src/testing/e2e/server.ts +195 -0
  266. package/src/testing/flight-matchers.ts +97 -0
  267. package/src/testing/flight-normalize.ts +11 -0
  268. package/src/testing/flight-runtime.d.ts +57 -0
  269. package/src/testing/flight-tree.ts +682 -0
  270. package/src/testing/flight.entry.ts +52 -0
  271. package/src/testing/flight.ts +232 -0
  272. package/src/testing/generated-routes.ts +183 -0
  273. package/src/testing/index.ts +99 -0
  274. package/src/testing/internal/context.ts +348 -0
  275. package/src/testing/internal/flight-client-globals.ts +30 -0
  276. package/src/testing/internal/seed-vars.ts +54 -0
  277. package/src/testing/render-handler.ts +330 -0
  278. package/src/testing/render-route.tsx +566 -0
  279. package/src/testing/run-loader.ts +378 -0
  280. package/src/testing/run-middleware.ts +205 -0
  281. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  282. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  283. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  284. package/src/testing/vitest-stubs/version.ts +5 -0
  285. package/src/testing/vitest.ts +305 -0
  286. package/src/theme/ThemeProvider.tsx +0 -52
  287. package/src/theme/ThemeScript.tsx +0 -6
  288. package/src/theme/constants.ts +0 -12
  289. package/src/theme/index.ts +0 -7
  290. package/src/theme/theme-context.ts +1 -5
  291. package/src/theme/theme-script.ts +0 -14
  292. package/src/theme/use-theme.ts +0 -3
  293. package/src/types/boundaries.ts +0 -35
  294. package/src/types/cache-types.ts +13 -4
  295. package/src/types/error-types.ts +30 -90
  296. package/src/types/global-namespace.ts +54 -41
  297. package/src/types/handler-context.ts +97 -22
  298. package/src/types/index.ts +1 -10
  299. package/src/types/loader-types.ts +6 -3
  300. package/src/types/request-scope.ts +0 -19
  301. package/src/types/route-config.ts +6 -50
  302. package/src/types/route-entry.ts +0 -6
  303. package/src/types/segments.ts +18 -14
  304. package/src/urls/include-helper.ts +9 -56
  305. package/src/urls/index.ts +1 -11
  306. package/src/urls/path-helper-types.ts +19 -5
  307. package/src/urls/path-helper.ts +17 -106
  308. package/src/urls/pattern-types.ts +36 -19
  309. package/src/urls/response-types.ts +20 -19
  310. package/src/urls/type-extraction.ts +58 -139
  311. package/src/urls/urls-function.ts +1 -18
  312. package/src/use-loader.tsx +292 -107
  313. package/src/vite/debug.ts +1 -0
  314. package/src/vite/discovery/bundle-postprocess.ts +8 -7
  315. package/src/vite/discovery/discover-routers.ts +95 -82
  316. package/src/vite/discovery/discovery-errors.ts +194 -0
  317. package/src/vite/discovery/prerender-collection.ts +26 -34
  318. package/src/vite/discovery/route-types-writer.ts +40 -84
  319. package/src/vite/discovery/state.ts +39 -1
  320. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  321. package/src/vite/index.ts +4 -0
  322. package/src/vite/plugin-types.ts +185 -10
  323. package/src/vite/plugins/cjs-to-esm.ts +3 -18
  324. package/src/vite/plugins/client-ref-dedup.ts +0 -11
  325. package/src/vite/plugins/client-ref-hashing.ts +12 -11
  326. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
  327. package/src/vite/plugins/expose-action-id.ts +4 -75
  328. package/src/vite/plugins/expose-id-utils.ts +3 -54
  329. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  330. package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
  331. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  332. package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
  333. package/src/vite/plugins/expose-internal-ids.ts +57 -67
  334. package/src/vite/plugins/performance-tracks.ts +9 -16
  335. package/src/vite/plugins/refresh-cmd.ts +1 -1
  336. package/src/vite/plugins/use-cache-transform.ts +26 -49
  337. package/src/vite/plugins/vercel-output.ts +258 -0
  338. package/src/vite/plugins/version-injector.ts +2 -32
  339. package/src/vite/plugins/version-plugin.ts +32 -23
  340. package/src/vite/plugins/virtual-entries.ts +35 -17
  341. package/src/vite/rango.ts +148 -115
  342. package/src/vite/router-discovery.ts +220 -68
  343. package/src/vite/utils/ast-handler-extract.ts +15 -31
  344. package/src/vite/utils/bundle-analysis.ts +10 -15
  345. package/src/vite/utils/client-chunks.ts +184 -0
  346. package/src/vite/utils/forward-user-plugins.ts +171 -0
  347. package/src/vite/utils/manifest-utils.ts +4 -59
  348. package/src/vite/utils/package-resolution.ts +1 -73
  349. package/src/vite/utils/prerender-utils.ts +0 -35
  350. package/src/vite/utils/shared-utils.ts +95 -43
  351. package/src/browser/action-response-classifier.ts +0 -99
  352. package/src/browser/react/use-client-cache.ts +0 -58
  353. package/src/browser/shallow.ts +0 -40
  354. package/src/handles/index.ts +0 -7
  355. package/src/router/middleware-cookies.ts +0 -55
@@ -16,7 +16,9 @@ import {
16
16
  formatNestedRouterConflictError,
17
17
  findNestedRouterConflict,
18
18
  findRouterFiles,
19
+ createScanFilter,
19
20
  } from "../build/generate-route-types.js";
21
+ import { firstCodeMatchIndex } from "../build/route-types/source-scan.js";
20
22
  import { createVersionPlugin } from "./plugins/version-plugin.js";
21
23
  import { createVirtualStubPlugin } from "./plugins/virtual-stub-plugin.js";
22
24
  import {
@@ -52,6 +54,11 @@ import {
52
54
  import { postprocessBundle } from "./discovery/bundle-postprocess.js";
53
55
  import { createDiscoveryGate } from "./discovery/gate-state.js";
54
56
  import { resetStagedBuildAssets } from "./utils/prerender-utils.js";
57
+ import { resolveRscEntryFromConfig } from "./utils/shared-utils.js";
58
+ import {
59
+ pickForwardedRunnerConfig,
60
+ selectForwardableResolvePlugins,
61
+ } from "./utils/forward-user-plugins.js";
55
62
  import { createRangoDebugger, timed, timedSync, NS } from "./debug.js";
56
63
 
57
64
  const debugDiscovery = createRangoDebugger(NS.discovery);
@@ -99,7 +106,7 @@ function ensureCloudflareProtocolLoaderRegistered(): void {
99
106
  // register() requires Node 18.19+ / 20.6+. Older Node still has the
100
107
  // Vite transform as primary defense.
101
108
  console.warn(
102
- `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`,
109
+ `[rango] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`,
103
110
  );
104
111
  }
105
112
  }
@@ -129,14 +136,26 @@ async function createTempRscServer(
129
136
  // instead of crashing Node's native loader.
130
137
  ensureCloudflareProtocolLoaderRegistered();
131
138
  const { default: rsc } = await import("@vitejs/plugin-rsc");
139
+ // Mirror the user's resolution config + plugins so discovery (and the
140
+ // prerender/static rendering that shares this runner) resolves modules the
141
+ // same way the real environment does. Falls back to the legacy alias-only
142
+ // behavior if configResolved hasn't populated the parity slice yet.
143
+ const runnerConfig = state.userRunnerConfig;
144
+ const resolveConfig = runnerConfig?.resolve ?? {
145
+ alias: state.userResolveAlias,
146
+ };
147
+ const oxcConfig = runnerConfig?.oxc ?? {
148
+ jsx: { runtime: "automatic", importSource: "react" },
149
+ };
132
150
  return createViteServer({
133
151
  root: state.projectRoot,
134
152
  configFile: false,
135
153
  server: { middlewareMode: true },
136
154
  appType: "custom",
137
155
  logLevel: "silent",
138
- resolve: { alias: state.userResolveAlias },
139
- esbuild: { jsx: "automatic", jsxImportSource: "react" },
156
+ resolve: resolveConfig,
157
+ ...(runnerConfig?.define ? { define: runnerConfig.define } : {}),
158
+ oxc: oxcConfig as any,
140
159
  ...(options.cacheDir && { cacheDir: options.cacheDir }),
141
160
  plugins: [
142
161
  rsc({
@@ -155,6 +174,10 @@ async function createTempRscServer(
155
174
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
156
175
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : undefined),
157
176
  exposeRouterId(),
177
+ // Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
178
+ // to resolveId/load and placed last so framework resolution runs first;
179
+ // Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
180
+ ...state.userResolvePlugins,
158
181
  ],
159
182
  });
160
183
  }
@@ -182,7 +205,7 @@ async function resolveBuildEnv(
182
205
  if (option === "auto") {
183
206
  if (factoryCtx.preset !== "cloudflare") {
184
207
  throw new Error(
185
- '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". ' +
208
+ '[rango] buildEnv: "auto" is only supported with preset: "cloudflare". ' +
186
209
  "Use a factory function or plain object for other presets.",
187
210
  );
188
211
  }
@@ -204,7 +227,7 @@ async function resolveBuildEnv(
204
227
  };
205
228
  } catch (err: any) {
206
229
  throw new Error(
207
- '[rsc-router] buildEnv: "auto" requires wrangler to be installed.\n' +
230
+ '[rango] buildEnv: "auto" requires wrangler to be installed.\n' +
208
231
  `Install it with: pnpm add -D wrangler\n${err.message}`,
209
232
  );
210
233
  }
@@ -256,7 +279,7 @@ async function releaseBuildEnv(s: DiscoveryState): Promise<void> {
256
279
  try {
257
280
  await s.buildEnvDispose();
258
281
  } catch (err: any) {
259
- console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
282
+ console.warn(`[rango] buildEnv dispose failed: ${err.message}`);
260
283
  }
261
284
  s.buildEnvDispose = null;
262
285
  }
@@ -304,28 +327,39 @@ export function createRouterDiscoveryPlugin(
304
327
 
305
328
  configResolved(config) {
306
329
  s.projectRoot = config.root;
330
+ // Compile the optional discovery scan filter (glob include/exclude) now
331
+ // that the project root is known. findRouterFiles() below — and the
332
+ // build/HMR rediscovery paths — honor s.scanFilter.
333
+ s.scanFilter = opts?.discovery
334
+ ? createScanFilter(s.projectRoot, opts.discovery)
335
+ : undefined;
307
336
  s.isBuildMode = config.command === "build";
308
337
  viteCommand = config.command as "serve" | "build";
309
338
  viteMode = config.mode;
310
339
  // Capture user's resolve aliases for the temp server
311
340
  s.userResolveAlias = config.resolve.alias;
341
+ // Capture the data-only resolution config (resolve.*, define, oxc) and
342
+ // the user's resolution plugins (resolveId/load) so the discovery temp
343
+ // server resolves modules the same way the real environment does.
344
+ // Without this, both flavors of user resolution are absent during
345
+ // discovery/prerender/static rendering even though they apply at request
346
+ // time: third-party resolvers (e.g. vite-tsconfig-paths, forwarded as
347
+ // plugins) and Vite 8's native resolve.tsconfigPaths (forwarded in the
348
+ // data slice). See utils/forward-user-plugins.ts.
349
+ s.userRunnerConfig = pickForwardedRunnerConfig(config);
350
+ s.userResolvePlugins = selectForwardableResolvePlugins(
351
+ config.plugins as any,
352
+ );
312
353
  // Node preset: pick up auto-discovered router path from the config() hook.
313
354
  // The auto-discover plugin runs in config() using Vite's resolved root,
314
355
  // populating the mutable ref before configResolved fires.
315
356
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
316
357
  s.resolvedEntryPath = opts.routerPathRef.path;
317
358
  }
318
- // Cloudflare preset: read entry from resolved environment config.
319
- // The @cloudflare/vite-plugin reads wrangler config (toml/json/jsonc)
320
- // and sets optimizeDeps.entries on the RSC environment.
359
+ // Cloudflare preset: entry comes from the resolved RSC env config.
321
360
  if (!s.resolvedEntryPath) {
322
- const rscEnvConfig = (config.environments as any)?.["rsc"];
323
- const entries = rscEnvConfig?.optimizeDeps?.entries;
324
- if (typeof entries === "string") {
325
- s.resolvedEntryPath = entries;
326
- } else if (Array.isArray(entries) && entries.length > 0) {
327
- s.resolvedEntryPath = entries[0];
328
- }
361
+ const entry = resolveRscEntryFromConfig(config);
362
+ if (entry) s.resolvedEntryPath = entry;
329
363
  }
330
364
  // Generate combined named-routes.gen.ts from static source parsing.
331
365
  // Runs before the dev server starts so the gen file exists immediately for IDE.
@@ -397,7 +431,8 @@ export function createRouterDiscoveryPlugin(
397
431
  releaseBuildEnv(s).catch(() => {});
398
432
  });
399
433
 
400
- // Mirror the build-path contract (router-discovery.ts ~line 878):
434
+ // Mirror the build-path contract (the buildStart hook below, which sets
435
+ // __rscRouterDiscoveryActive before running user modules):
401
436
  // set __rscRouterDiscoveryActive before running user modules so any
402
437
  // module-level router.reverse() calls return a placeholder instead
403
438
  // of throwing. The temp Vite server's module runner has its own
@@ -512,9 +547,7 @@ export function createRouterDiscoveryPlugin(
512
547
  "getOrCreateTempServer: FAILED message=%s",
513
548
  err.message,
514
549
  );
515
- console.warn(
516
- `[rsc-router] Failed to create temp runner: ${err.message}`,
517
- );
550
+ console.warn(`[rango] Failed to create temp runner: ${err.message}`);
518
551
  }
519
552
  return null;
520
553
  }
@@ -653,7 +686,7 @@ export function createRouterDiscoveryPlugin(
653
686
  }
654
687
  } catch (err: any) {
655
688
  console.warn(
656
- `[rsc-router] Cloudflare dev discovery failed: ${err.message}\n${err.stack}`,
689
+ `[rango] Cloudflare dev discovery failed: ${err.message}\n${err.stack}`,
657
690
  );
658
691
  }
659
692
 
@@ -705,7 +738,7 @@ export function createRouterDiscoveryPlugin(
705
738
  );
706
739
  } catch (err: any) {
707
740
  console.warn(
708
- `[rsc-router] Router discovery failed: ${err.message}\n${err.stack}`,
741
+ `[rango] Router discovery failed: ${err.message}\n${err.stack}`,
709
742
  );
710
743
  } finally {
711
744
  debugDiscovery?.(
@@ -764,20 +797,15 @@ export function createRouterDiscoveryPlugin(
764
797
  if (s.mergedRouteTrie && serverMod.setRouteTrie) {
765
798
  serverMod.setRouteTrie(s.mergedRouteTrie);
766
799
  }
767
- if (serverMod.setRouterManifest) {
768
- for (const [routerId, manifest] of s.perRouterManifestDataMap) {
769
- serverMod.setRouterManifest(routerId, manifest);
770
- }
771
- }
772
- if (serverMod.setRouterTrie) {
773
- for (const [routerId, trie] of s.perRouterTrieMap) {
774
- serverMod.setRouterTrie(routerId, trie);
775
- }
776
- }
777
- if (serverMod.setRouterPrecomputedEntries) {
778
- for (const [routerId, entries] of s.perRouterPrecomputedMap) {
779
- serverMod.setRouterPrecomputedEntries(routerId, entries);
780
- }
800
+ const perRouterSetters: Array<[Map<string, any>, string]> = [
801
+ [s.perRouterManifestDataMap, "setRouterManifest"],
802
+ [s.perRouterTrieMap, "setRouterTrie"],
803
+ [s.perRouterPrecomputedMap, "setRouterPrecomputedEntries"],
804
+ ];
805
+ for (const [map, fn] of perRouterSetters) {
806
+ const setter = serverMod[fn];
807
+ if (typeof setter !== "function") continue;
808
+ for (const [routerId, value] of map) setter(routerId, value);
781
809
  }
782
810
  };
783
811
 
@@ -820,7 +848,7 @@ export function createRouterDiscoveryPlugin(
820
848
  registry = serverMod.RouterRegistry ?? null;
821
849
  } catch (err: any) {
822
850
  console.warn(
823
- `[rsc-router] Dev prerender module refresh failed: ${err.message}`,
851
+ `[rango] Dev prerender module refresh failed: ${err.message}`,
824
852
  );
825
853
  res.statusCode = 500;
826
854
  res.end(`Prerender handler error: ${err.message}`);
@@ -873,10 +901,9 @@ export function createRouterDiscoveryPlugin(
873
901
  if (wantIntercept && result.interceptSegments?.length) {
874
902
  payload = {
875
903
  segments: [...result.segments, ...result.interceptSegments],
876
- handles: {
877
- ...result.handles,
878
- ...(result.interceptHandles || {}),
879
- },
904
+ // Pre-encoded MERGED handle string from the producer (handles are
905
+ // Flight-encoded so Promise/ReactNode values survive the wire).
906
+ handles: result.interceptHandles ?? "",
880
907
  };
881
908
  } else {
882
909
  payload = { segments: result.segments, handles: result.handles };
@@ -886,7 +913,7 @@ export function createRouterDiscoveryPlugin(
886
913
  return;
887
914
  } catch (err: any) {
888
915
  console.warn(
889
- `[rsc-router] Dev prerender failed for ${pathname}: ${err.message}`,
916
+ `[rango] Dev prerender failed for ${pathname}: ${err.message}`,
890
917
  );
891
918
  }
892
919
  }
@@ -987,9 +1014,34 @@ export function createRouterDiscoveryPlugin(
987
1014
  writeRouteTypesFiles(s),
988
1015
  );
989
1016
  }
1017
+ if (s.lastDiscoveryError) {
1018
+ debugDiscovery?.(
1019
+ "hmr: cleared lastDiscoveryError (%s) after successful rediscovery",
1020
+ s.lastDiscoveryError.message,
1021
+ );
1022
+ s.lastDiscoveryError = null;
1023
+ }
1024
+ // Cloudflare dev: on a successful cycle drop the workerd runner's
1025
+ // cached worker-entry chain so the next request re-evaluates
1026
+ // createRouter() with the new routes. Fired here in the work path
1027
+ // (not the caller's .then()) so a queued follow-up cycle that
1028
+ // succeeds after an earlier failed cycle still reloads:
1029
+ // runRefreshCycle recurses queued work without awaiting it, so the
1030
+ // original call already resolved on the failed cycle. A failed
1031
+ // cycle throws above and never reaches here, so a broken edit
1032
+ // never reloads the worker onto bad source.
1033
+ if (rscEnv && !rscEnv.runner) forceCloudflareWorkerReload(rscEnv);
990
1034
  } catch (err: any) {
1035
+ s.lastDiscoveryError = {
1036
+ message: err?.message ?? String(err),
1037
+ at: Date.now(),
1038
+ };
991
1039
  console.warn(
992
- `[rsc-router] Runtime re-discovery failed: ${err.message}`,
1040
+ `[rango] Runtime re-discovery failed: ${err.message}`,
1041
+ );
1042
+ debugDiscovery?.(
1043
+ "hmr: lastDiscoveryError set (%s) — manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
1044
+ err?.message,
993
1045
  );
994
1046
  } finally {
995
1047
  debugDiscovery?.(
@@ -1000,6 +1052,49 @@ export function createRouterDiscoveryPlugin(
1000
1052
  });
1001
1053
  };
1002
1054
 
1055
+ // Cloudflare dev only. workerd serves every request through the
1056
+ // runner-worker singleton, which re-resolves the worker entry per
1057
+ // request via runner.import("virtual:cloudflare/worker-entry"). The
1058
+ // route table lives in the user's createRouter() instance, captured
1059
+ // when that entry chain (entry -> router -> urls) was last evaluated
1060
+ // and then cached in the runner's evaluatedModules. The route-file
1061
+ // watcher refreshes discovery + types on the Node side, but the worker
1062
+ // keeps serving the cached (stale) router: route-definition modules
1063
+ // have no import.meta.hot boundary, so Vite never sends the worker an
1064
+ // HMR update for them and the entry chain is never evicted.
1065
+ //
1066
+ // Fix: after discovery completes, (1) invalidate the worker env's
1067
+ // Node-side module graph, then (2) send a full-reload to the worker.
1068
+ // Step (2) alone is insufficient: the full-reload handler clears the
1069
+ // runner's evaluatedModules and re-imports entrypoints, but each
1070
+ // re-import fetches the module back through this Node-side graph, which
1071
+ // still holds the pre-edit transform of urls.tsx — so createRouter()
1072
+ // rebuilds the stale route table and the new route 404s/hits the
1073
+ // catch-all. Invalidating the graph forces a fresh transform on
1074
+ // re-fetch (the same mechanism refreshTempRscEnv uses for discovery),
1075
+ // so the re-import re-runs createRouter() with the new routes. This is
1076
+ // the programmatic equivalent of the dev-server "r + enter" restart,
1077
+ // scoped to the worker environment instead of tearing down the server.
1078
+ const forceCloudflareWorkerReload = (rscEnv: any) => {
1079
+ if (!rscEnv?.hot) return;
1080
+ try {
1081
+ const graph = rscEnv.moduleGraph;
1082
+ if (graph?.invalidateAll) {
1083
+ graph.invalidateAll();
1084
+ debugDiscovery?.("hmr: invalidated workerd rsc module graph");
1085
+ }
1086
+ rscEnv.hot.send({ type: "full-reload" });
1087
+ debugDiscovery?.(
1088
+ "hmr: forced workerd rsc env reload (full-reload)",
1089
+ );
1090
+ } catch (err: any) {
1091
+ debugDiscovery?.(
1092
+ "hmr: workerd reload failed: %s",
1093
+ err?.message ?? err,
1094
+ );
1095
+ }
1096
+ };
1097
+
1003
1098
  const scheduleRouteRegeneration = () => {
1004
1099
  clearTimeout(routeChangeTimer);
1005
1100
  routeChangeTimer = setTimeout(() => {
@@ -1028,9 +1123,7 @@ export function createRouterDiscoveryPlugin(
1028
1123
  }
1029
1124
  }
1030
1125
  } catch (err: any) {
1031
- console.error(
1032
- `[rsc-router] Route regeneration error: ${err.message}`,
1033
- );
1126
+ console.error(`[rango] Route regeneration error: ${err.message}`);
1034
1127
  }
1035
1128
  debugDiscovery?.(
1036
1129
  "watcher: regenerated gen files (%sms)",
@@ -1040,12 +1133,15 @@ export function createRouterDiscoveryPlugin(
1040
1133
  // routes that the static parser cannot resolve. Resolves the
1041
1134
  // discovery gate when complete.
1042
1135
  if (s.perRouterManifests.length > 0) {
1136
+ // The cloudflare workerd reload fires inside refreshRuntimeDiscovery
1137
+ // on the successful cycle (see forceCloudflareWorkerReload call
1138
+ // there) so queued follow-up cycles also trigger it.
1043
1139
  refreshRuntimeDiscovery().catch((err: any) => {
1044
1140
  console.warn(
1045
- `[rsc-router] Runtime re-discovery error: ${err.message}`,
1141
+ `[rango] Runtime re-discovery error: ${err.message}`,
1046
1142
  );
1047
- // Even on error, unblock the gate so workerd's reload
1048
- // doesn't hang indefinitely against the previous manifest.
1143
+ // Even on error, unblock the gate so workerd's reload doesn't
1144
+ // hang indefinitely against the previous manifest.
1049
1145
  resolveDiscoveryGate();
1050
1146
  });
1051
1147
  }
@@ -1059,27 +1155,74 @@ export function createRouterDiscoveryPlugin(
1059
1155
  !filePath.endsWith(".tsx") &&
1060
1156
  !filePath.endsWith(".js") &&
1061
1157
  !filePath.endsWith(".jsx")
1062
- )
1158
+ ) {
1159
+ if (s.lastDiscoveryError) {
1160
+ debugDiscovery?.(
1161
+ "watcher: skip non-source %s [LASTERR %s]",
1162
+ filePath,
1163
+ s.lastDiscoveryError.message,
1164
+ );
1165
+ }
1063
1166
  return;
1167
+ }
1064
1168
  // Apply scan filter as early-exit before reading file
1065
- if (s.scanFilter && !s.scanFilter(filePath)) return;
1169
+ if (s.scanFilter && !s.scanFilter(filePath)) {
1170
+ if (s.lastDiscoveryError) {
1171
+ debugDiscovery?.(
1172
+ "watcher: skip scan-filter %s [LASTERR %s]",
1173
+ filePath,
1174
+ s.lastDiscoveryError.message,
1175
+ );
1176
+ }
1177
+ return;
1178
+ }
1179
+ // Recovery mode: when the previous HMR re-discovery failed, the
1180
+ // import graph is incomplete and the manifest is stuck at the
1181
+ // last-good state. The fix may land in a non-route file (e.g. a
1182
+ // helper imported by the router, a missing module being created,
1183
+ // or a "use client" component) that the narrow content sniff
1184
+ // would otherwise filter out. While in recovery, treat any
1185
+ // in-scan source change as a candidate for rediscovery; the
1186
+ // tighter filter resumes once discovery succeeds again.
1187
+ const inRecoveryMode = !!s.lastDiscoveryError;
1066
1188
  try {
1067
1189
  const source = readFileSync(filePath, "utf-8");
1068
1190
  const trimmed = source.trimStart();
1069
- if (
1191
+ const isUseClient =
1070
1192
  trimmed.startsWith('"use client"') ||
1071
- trimmed.startsWith("'use client'")
1072
- )
1073
- return;
1074
- const hasUrls = source.includes("urls(");
1075
- const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
1076
- if (!hasUrls && !hasCreateRouter) return;
1077
- debugDiscovery?.(
1078
- "watcher: %s matches (urls=%s, router=%s)",
1079
- filePath,
1080
- hasUrls,
1081
- hasCreateRouter,
1082
- );
1193
+ trimmed.startsWith("'use client'");
1194
+ if (!inRecoveryMode && isUseClient) return;
1195
+ // Cheap raw pre-check first; only when a candidate token is present
1196
+ // do we confirm it occurs in real code (not a comment/string) via a
1197
+ // single allocation-free code-region scan. Most saved files contain
1198
+ // neither token and skip the scan entirely. This avoids a comment or
1199
+ // string mention spuriously marking a file relevant and triggering an
1200
+ // unnecessary re-discovery on save.
1201
+ let hasUrls = source.includes("urls(");
1202
+ let hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
1203
+ if (hasUrls) hasUrls = firstCodeMatchIndex(source, /urls\(/g) >= 0;
1204
+ if (hasCreateRouter) {
1205
+ hasCreateRouter =
1206
+ firstCodeMatchIndex(source, /\bcreateRouter\s*[<(]/g) >= 0;
1207
+ }
1208
+ if (!inRecoveryMode && !hasUrls && !hasCreateRouter) return;
1209
+ if (inRecoveryMode) {
1210
+ debugDiscovery?.(
1211
+ "watcher: recovery rediscovery for %s (urls=%s, router=%s, useClient=%s) [LASTERR %s]",
1212
+ filePath,
1213
+ hasUrls,
1214
+ hasCreateRouter,
1215
+ isUseClient,
1216
+ s.lastDiscoveryError!.message,
1217
+ );
1218
+ } else {
1219
+ debugDiscovery?.(
1220
+ "watcher: %s matches (urls=%s, router=%s)",
1221
+ filePath,
1222
+ hasUrls,
1223
+ hasCreateRouter,
1224
+ );
1225
+ }
1083
1226
  // Invalidate cache when a router file changes (new router added/removed)
1084
1227
  if (hasCreateRouter) {
1085
1228
  const nestedRouterConflict = findNestedRouterConflict([
@@ -1106,7 +1249,15 @@ export function createRouterDiscoveryPlugin(
1106
1249
  gate.noteRouteEvent();
1107
1250
  }
1108
1251
  scheduleRouteRegeneration();
1109
- } catch {
1252
+ } catch (readErr: any) {
1253
+ if (s.lastDiscoveryError) {
1254
+ debugDiscovery?.(
1255
+ "watcher: read error %s: %s [LASTERR %s]",
1256
+ filePath,
1257
+ readErr?.message,
1258
+ s.lastDiscoveryError.message,
1259
+ );
1260
+ }
1110
1261
  // Ignore read errors for deleted/moved files
1111
1262
  }
1112
1263
  };
@@ -1169,7 +1320,7 @@ export function createRouterDiscoveryPlugin(
1169
1320
  const rscEnv = (tempServer.environments as any)?.rsc;
1170
1321
  if (!rscEnv?.runner) {
1171
1322
  console.warn(
1172
- "[rsc-router] RSC environment runner not available during build, skipping manifest generation",
1323
+ "[rango] RSC environment runner not available during build, skipping manifest generation",
1173
1324
  );
1174
1325
  return;
1175
1326
  }
@@ -1212,7 +1363,8 @@ export function createRouterDiscoveryPlugin(
1212
1363
  .filter(Boolean)
1213
1364
  .join("\n");
1214
1365
  throw new Error(
1215
- `[rsc-router] Build-time router discovery failed:\n${details}`,
1366
+ `[rango] Build-time router discovery failed:\n${details}`,
1367
+ { cause: err },
1216
1368
  );
1217
1369
  } finally {
1218
1370
  delete (globalThis as any).__rscRouterDiscoveryActive;
@@ -1239,7 +1391,7 @@ export function createRouterDiscoveryPlugin(
1239
1391
  // `consumeSelfGenWrite` inside `maybeHandleGeneratedRouteFileMutation`),
1240
1392
  // AND vite's own HMR pipeline (which invalidates the gen file's
1241
1393
  // importers and triggers a second workerd full reload — visible to the
1242
- // user as a duplicate "[RSCRouter] HMR: version changed" on the client).
1394
+ // user as a duplicate "[Rango] HMR: version changed" on the client).
1243
1395
  //
1244
1396
  // `peekSelfGenWrite` is the authoritative filter: its map only contains
1245
1397
  // paths that `markSelfGenWrite` has registered, so it natively works
@@ -1,10 +1,6 @@
1
1
  import type MagicString from "magic-string";
2
2
  import { hashInlineId, buildExportMap } from "../plugins/expose-id-utils.js";
3
3
 
4
- // ---------------------------------------------------------------------------
5
- // Types
6
- // ---------------------------------------------------------------------------
7
-
8
4
  /** Minimal ESTree Program node — avoids importing from `rollup` (not a direct dep). */
9
5
  interface ProgramNode {
10
6
  type: "Program";
@@ -48,7 +44,7 @@ function findImportInsertionPos(
48
44
  ): number {
49
45
  let program: ProgramNode;
50
46
  try {
51
- program = parseAst(code, { jsx: true });
47
+ program = parseAst(code, { lang: "tsx" });
52
48
  } catch {
53
49
  return 0;
54
50
  }
@@ -70,10 +66,6 @@ function findImportInsertionPos(
70
66
  return insertionPos;
71
67
  }
72
68
 
73
- // ---------------------------------------------------------------------------
74
- // AST walking helper
75
- // ---------------------------------------------------------------------------
76
-
77
69
  /**
78
70
  * Recursively walk an ESTree AST node, calling `enter` on each node.
79
71
  * Parent is passed for context.
@@ -111,10 +103,6 @@ function walkNode(
111
103
  ancestors.pop();
112
104
  }
113
105
 
114
- // ---------------------------------------------------------------------------
115
- // AST analysis
116
- // ---------------------------------------------------------------------------
117
-
118
106
  /**
119
107
  * Parse the file with Vite's parseAst and find all calls to `fnName`.
120
108
  * Distinguishes between `export const X = fnName(...)` (exportInfo set)
@@ -127,7 +115,7 @@ export function findHandlerCalls(
127
115
  ): HandlerCallSite[] {
128
116
  let program: ProgramNode;
129
117
  try {
130
- program = parseAst(code, { jsx: true });
118
+ program = parseAst(code, { lang: "tsx" });
131
119
  } catch {
132
120
  return [];
133
121
  }
@@ -239,7 +227,7 @@ export function getImportedLocalNames(
239
227
  parseAst: (code: string, options?: any) => ProgramNode,
240
228
  ): Set<string> {
241
229
  try {
242
- const program = parseAst(code, { jsx: true });
230
+ const program = parseAst(code, { lang: "tsx" });
243
231
  return getImportedLocalNamesFromProgram(program, importedName);
244
232
  } catch {
245
233
  return new Set<string>();
@@ -256,7 +244,7 @@ export function extractImportDeclarations(
256
244
  ): string[] {
257
245
  let program: ProgramNode;
258
246
  try {
259
- program = parseAst(code, { jsx: true });
247
+ program = parseAst(code, { lang: "tsx" });
260
248
  } catch {
261
249
  return [];
262
250
  }
@@ -380,7 +368,7 @@ export function extractModuleLevelDeclarations(
380
368
  ): string[] {
381
369
  let program: ProgramNode;
382
370
  try {
383
- program = parseAst(code, { jsx: true });
371
+ program = parseAst(code, { lang: "tsx" });
384
372
  } catch {
385
373
  return [];
386
374
  }
@@ -426,10 +414,6 @@ export function extractModuleLevelDeclarations(
426
414
  return declarations;
427
415
  }
428
416
 
429
- // ---------------------------------------------------------------------------
430
- // Transform
431
- // ---------------------------------------------------------------------------
432
-
433
417
  /**
434
418
  * Transform inline handler calls by extracting them into virtual modules.
435
419
  * Only processes inline calls (exportInfo === null); export const calls are
@@ -468,19 +452,19 @@ export function transformInlineHandlers(
468
452
  handlerNames,
469
453
  );
470
454
 
471
- // Track line occurrences for same-line collision handling
472
- const lineCounts = new Map<number, number>();
473
-
474
455
  // Collect all import statements to prepend
475
456
  const importStatements: string[] = [];
476
457
 
477
- for (const site of inlineSites) {
478
- const lineCount = lineCounts.get(site.lineNumber) ?? 0;
479
- lineCounts.set(site.lineNumber, lineCount + 1);
480
-
481
- const hash = hashInlineId(filePath, site.lineNumber, lineCount);
458
+ for (const [siteIndex, site] of inlineSites.entries()) {
459
+ // Key the extracted handler on its source-order index (per fnName), NOT its
460
+ // line number. The id flows into BOTH the export name and the virtual module
461
+ // path (which hashId hashes for the runtime $$id), and line numbers shift
462
+ // between the prerender and production build contexts. The index is invariant
463
+ // to those shifts, keeping the prerender manifest key == the runtime id.
464
+ const hash = hashInlineId(filePath, fnName, siteIndex);
482
465
  const exportName = `__sh_${hash}`;
483
- const virtualId = `\0${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
466
+ const idSuffix = `${filePath}:${fnName}:${siteIndex}`;
467
+ const virtualId = `\0${virtualPrefix}${idSuffix}`;
484
468
 
485
469
  // Extract the full handler call expression text
486
470
  const handlerCode = code.slice(site.callStart, site.callEnd);
@@ -498,7 +482,7 @@ export function transformInlineHandlers(
498
482
  s.overwrite(site.callStart, site.callEnd, exportName);
499
483
 
500
484
  // Build the import specifier for this virtual module
501
- const importId = `${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
485
+ const importId = `${virtualPrefix}${idSuffix}`;
502
486
  importStatements.push(`import { ${exportName} } from "${importId}";`);
503
487
  }
504
488