@rangojs/router 0.20.0 → 0.22.0

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 (331) hide show
  1. package/dist/bin/rango.js +39 -15
  2. package/dist/types/browser/event-controller.d.ts +49 -0
  3. package/dist/types/browser/history-state.d.ts +16 -4
  4. package/dist/types/browser/navigation-store.d.ts +9 -0
  5. package/dist/types/browser/navigation-transaction.d.ts +7 -0
  6. package/dist/types/browser/partial-update.d.ts +2 -1
  7. package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
  8. package/dist/types/browser/rango-state.d.ts +3 -1
  9. package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
  10. package/dist/types/browser/react/context.d.ts +25 -0
  11. package/dist/types/browser/react/location-state-shared.d.ts +84 -10
  12. package/dist/types/browser/react/location-state.d.ts +7 -2
  13. package/dist/types/browser/rsc-router.d.ts +13 -2
  14. package/dist/types/browser/types.d.ts +10 -0
  15. package/dist/types/build/generate-manifest.d.ts +2 -0
  16. package/dist/types/cache/background-task.d.ts +22 -0
  17. package/dist/types/cache/cache-key-utils.d.ts +50 -5
  18. package/dist/types/cache/cache-policy.d.ts +17 -0
  19. package/dist/types/cache/cache-scope.d.ts +5 -6
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
  21. package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
  22. package/dist/types/cache/document-cache.d.ts +8 -5
  23. package/dist/types/cache/handle-snapshot.d.ts +59 -50
  24. package/dist/types/cache/index.d.ts +3 -1
  25. package/dist/types/cache/invalidation-order.d.ts +14 -0
  26. package/dist/types/cache/memory-segment-store.d.ts +33 -1
  27. package/dist/types/cache/shell-snapshot.d.ts +29 -4
  28. package/dist/types/cache/store-scope.d.ts +22 -0
  29. package/dist/types/cache/tag-invalidation.d.ts +7 -6
  30. package/dist/types/cache/types.d.ts +84 -24
  31. package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
  32. package/dist/types/client-urls/navigation.d.ts +7 -1
  33. package/dist/types/client-urls/optimistic-location.d.ts +10 -4
  34. package/dist/types/errors.d.ts +6 -0
  35. package/dist/types/handles/deferred-resolution.d.ts +18 -1
  36. package/dist/types/internal-suspense-audit.d.ts +1 -0
  37. package/dist/types/loader-store.d.ts +4 -8
  38. package/dist/types/outlet-provider.d.ts +2 -1
  39. package/dist/types/prerender/build-shell-capture.d.ts +8 -6
  40. package/dist/types/prerender/cloudflare.d.ts +49 -0
  41. package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
  42. package/dist/types/prerender/index.d.ts +12 -0
  43. package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
  44. package/dist/types/prerender/on-demand.d.ts +272 -0
  45. package/dist/types/prerender/producer-guard.d.ts +33 -0
  46. package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
  47. package/dist/types/prerender/warm-request.d.ts +65 -0
  48. package/dist/types/prerender/warm.d.ts +65 -0
  49. package/dist/types/prerender/writable-store.d.ts +170 -0
  50. package/dist/types/prerender.d.ts +27 -0
  51. package/dist/types/reverse.d.ts +1 -1
  52. package/dist/types/route-content-wrapper.d.ts +3 -1
  53. package/dist/types/router/error-handling.d.ts +2 -0
  54. package/dist/types/router/handler-context.d.ts +1 -1
  55. package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
  56. package/dist/types/router/navigation-snapshot.d.ts +8 -1
  57. package/dist/types/router/pattern-matching.d.ts +2 -0
  58. package/dist/types/router/prefetch-default.d.ts +4 -4
  59. package/dist/types/router/prerender-match.d.ts +23 -1
  60. package/dist/types/router/route-trie-builder.d.ts +8 -1
  61. package/dist/types/router/router-context.d.ts +1 -0
  62. package/dist/types/router/router-interfaces.d.ts +32 -1
  63. package/dist/types/router/router-options.d.ts +39 -5
  64. package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
  65. package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
  66. package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
  67. package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
  68. package/dist/types/router/trie-matching.d.ts +2 -0
  69. package/dist/types/router-versions.d.ts +39 -0
  70. package/dist/types/rsc/handler-context.d.ts +5 -0
  71. package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
  72. package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
  73. package/dist/types/rsc/shell-capture.d.ts +14 -1
  74. package/dist/types/rsc/shell-serve.d.ts +2 -2
  75. package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
  76. package/dist/types/rsc/types.d.ts +12 -6
  77. package/dist/types/segment-boundary-content.d.ts +17 -0
  78. package/dist/types/segment-loader-promise.d.ts +2 -6
  79. package/dist/types/server/build-version-table.d.ts +53 -0
  80. package/dist/types/server/context.d.ts +12 -4
  81. package/dist/types/server/handle-store.d.ts +89 -46
  82. package/dist/types/server/request-context.d.ts +84 -9
  83. package/dist/types/suspense-audit-react.d.ts +26 -0
  84. package/dist/types/suspense-audit.d.ts +157 -0
  85. package/dist/types/testing/action-form.d.ts +34 -0
  86. package/dist/types/testing/build-versions.d.ts +32 -0
  87. package/dist/types/testing/dom.entry.d.ts +1 -1
  88. package/dist/types/testing/flight.entry.d.ts +3 -1
  89. package/dist/types/testing/index.d.ts +6 -0
  90. package/dist/types/testing/internal/seed-vars.d.ts +7 -2
  91. package/dist/types/testing/internal/server-modules.d.ts +8 -0
  92. package/dist/types/testing/location-state-key.d.ts +5 -1
  93. package/dist/types/testing/render-route.d.ts +77 -6
  94. package/dist/types/testing/serve-shell-request.d.ts +55 -4
  95. package/dist/types/testing/shell-status.d.ts +14 -6
  96. package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
  97. package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
  98. package/dist/types/thenable-status.d.ts +6 -0
  99. package/dist/types/types/handler-context.d.ts +15 -5
  100. package/dist/types/types/route-entry.d.ts +5 -0
  101. package/dist/types/urls/path-helper.d.ts +18 -0
  102. package/dist/types/vite/discovery/build-versions.d.ts +393 -0
  103. package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
  104. package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
  105. package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
  106. package/dist/types/vite/discovery/state.d.ts +25 -0
  107. package/dist/types/vite/encryption-key.d.ts +18 -0
  108. package/dist/types/vite/inject-client-debug.d.ts +6 -25
  109. package/dist/types/vite/plugin-types.d.ts +27 -0
  110. package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
  111. package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
  112. package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
  113. package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
  114. package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
  115. package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
  116. package/dist/types/vite/router-discovery.d.ts +6 -0
  117. package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
  118. package/dist/types/vite/utils/client-chunks.d.ts +2 -0
  119. package/dist/types/vite/utils/compare-strings.d.ts +7 -0
  120. package/dist/types/vite/utils/package-resolution.d.ts +8 -0
  121. package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
  122. package/dist/vite/index.js +1707 -931
  123. package/package.json +12 -3
  124. package/skills/cache-guide/SKILL.md +102 -37
  125. package/skills/caching/SKILL.md +56 -9
  126. package/skills/catalog.json +1 -1
  127. package/skills/client-urls/SKILL.md +16 -0
  128. package/skills/cloudflare/SKILL.md +133 -1
  129. package/skills/comparison/references/framework-comparison.md +17 -13
  130. package/skills/deployment-caching/SKILL.md +33 -12
  131. package/skills/document-cache/SKILL.md +16 -14
  132. package/skills/hooks/SKILL.md +25 -0
  133. package/skills/hooks/handle-and-actions.md +5 -0
  134. package/skills/hooks/navigation.md +38 -0
  135. package/skills/hooks/state.md +213 -11
  136. package/skills/hooks/urls.md +40 -1
  137. package/skills/host-router/SKILL.md +31 -0
  138. package/skills/intercept/SKILL.md +6 -0
  139. package/skills/loader/SKILL.md +25 -5
  140. package/skills/middleware/SKILL.md +2 -0
  141. package/skills/migrate-nextjs/SKILL.md +82 -9
  142. package/skills/ppr/SKILL.md +150 -48
  143. package/skills/prerender/SKILL.md +651 -10
  144. package/skills/rango/SKILL.md +9 -7
  145. package/skills/response-routes/SKILL.md +5 -5
  146. package/skills/route/SKILL.md +29 -0
  147. package/skills/router-setup/SKILL.md +15 -1
  148. package/skills/testing/SKILL.md +32 -27
  149. package/skills/testing/cache-prerender.md +18 -17
  150. package/skills/testing/client-components.md +193 -8
  151. package/skills/testing/handles.md +2 -0
  152. package/skills/testing/loader.md +1 -1
  153. package/skills/testing/middleware.md +1 -1
  154. package/skills/testing/render-handler.md +18 -18
  155. package/skills/testing/serve-shell-request.md +167 -22
  156. package/skills/testing/server-actions.md +17 -17
  157. package/skills/testing/server-tree.md +1 -1
  158. package/skills/typesafety/generated-files-and-cli.md +1 -1
  159. package/skills/use-cache/SKILL.md +25 -10
  160. package/skills/vercel/SKILL.md +5 -3
  161. package/skills/view-transitions/SKILL.md +9 -4
  162. package/src/bin/rango.ts +1 -1
  163. package/src/browser/event-controller.ts +119 -2
  164. package/src/browser/history-state.ts +39 -17
  165. package/src/browser/link-interceptor.ts +2 -2
  166. package/src/browser/navigation-bridge.ts +104 -41
  167. package/src/browser/navigation-store.ts +26 -0
  168. package/src/browser/navigation-transaction.ts +24 -12
  169. package/src/browser/network-error-handler.ts +2 -0
  170. package/src/browser/partial-update.ts +58 -14
  171. package/src/browser/prefetch/default-strategy.ts +22 -42
  172. package/src/browser/rango-state.ts +3 -1
  173. package/src/browser/react/Link.tsx +37 -43
  174. package/src/browser/react/NavigationProvider.tsx +85 -1
  175. package/src/browser/react/context.ts +30 -0
  176. package/src/browser/react/location-state-shared.ts +200 -34
  177. package/src/browser/react/location-state.ts +28 -77
  178. package/src/browser/react/use-action.ts +4 -1
  179. package/src/browser/react/use-handle.ts +3 -0
  180. package/src/browser/react/use-link-status.ts +4 -3
  181. package/src/browser/react/use-navigation.ts +4 -1
  182. package/src/browser/react/use-params.ts +4 -1
  183. package/src/browser/react/use-pathname.ts +4 -1
  184. package/src/browser/react/use-search-params.ts +5 -3
  185. package/src/browser/react/use-segments.ts +36 -54
  186. package/src/browser/rsc-router.tsx +93 -31
  187. package/src/browser/segment-reconciler.ts +3 -3
  188. package/src/browser/server-action-bridge.ts +43 -7
  189. package/src/browser/types.ts +11 -0
  190. package/src/build/generate-manifest.ts +17 -0
  191. package/src/build/merge-full-manifests.ts +5 -0
  192. package/src/build/route-trie.ts +1 -0
  193. package/src/cache/background-task.ts +61 -0
  194. package/src/cache/cache-key-utils.ts +91 -12
  195. package/src/cache/cache-policy.ts +11 -2
  196. package/src/cache/cache-runtime.ts +23 -23
  197. package/src/cache/cache-scope.ts +48 -36
  198. package/src/cache/cf/cf-cache-store.ts +91 -66
  199. package/src/cache/cf/cf-cache-types.ts +12 -4
  200. package/src/cache/document-cache.ts +35 -14
  201. package/src/cache/handle-snapshot.ts +118 -86
  202. package/src/cache/index.ts +5 -0
  203. package/src/cache/invalidation-order.ts +53 -0
  204. package/src/cache/memory-segment-store.ts +52 -1
  205. package/src/cache/shell-snapshot.ts +52 -5
  206. package/src/cache/store-scope.ts +36 -0
  207. package/src/cache/tag-invalidation.ts +7 -5
  208. package/src/cache/types.ts +90 -24
  209. package/src/cache/vercel/vercel-cache-store.ts +68 -26
  210. package/src/client-urls/client-root.tsx +1 -0
  211. package/src/client-urls/navigation.ts +10 -0
  212. package/src/client-urls/optimistic-location.ts +10 -4
  213. package/src/client.tsx +40 -3
  214. package/src/errors.ts +13 -6
  215. package/src/handles/deferred-resolution.ts +36 -0
  216. package/src/internal-suspense-audit.ts +12 -0
  217. package/src/loader-store.ts +4 -9
  218. package/src/outlet-provider.tsx +25 -1
  219. package/src/prerender/build-shell-capture.ts +11 -9
  220. package/src/prerender/cloudflare.ts +130 -0
  221. package/src/prerender/create-prerender-trigger.ts +780 -0
  222. package/src/prerender/index.ts +39 -0
  223. package/src/prerender/memory-prerender-store.ts +115 -0
  224. package/src/prerender/on-demand.ts +339 -0
  225. package/src/prerender/producer-guard.ts +66 -0
  226. package/src/prerender/shell-manifest-key.ts +12 -17
  227. package/src/prerender/warm-request.ts +154 -0
  228. package/src/prerender/warm.ts +193 -0
  229. package/src/prerender/writable-store.ts +278 -0
  230. package/src/prerender.ts +29 -0
  231. package/src/reverse.ts +1 -1
  232. package/src/route-content-wrapper.tsx +107 -13
  233. package/src/router/error-handling.ts +12 -12
  234. package/src/router/find-match.ts +1 -0
  235. package/src/router/handler-context.ts +2 -0
  236. package/src/router/intercept-resolution.ts +15 -8
  237. package/src/router/loader-resolution.ts +2 -10
  238. package/src/router/manifest.ts +2 -0
  239. package/src/router/match-middleware/background-revalidation.ts +2 -0
  240. package/src/router/match-middleware/cache-lookup.ts +239 -54
  241. package/src/router/navigation-snapshot.ts +25 -6
  242. package/src/router/pattern-matching.ts +14 -0
  243. package/src/router/prefetch-default.ts +4 -4
  244. package/src/router/prerender-match.ts +460 -352
  245. package/src/router/request-classification.ts +15 -12
  246. package/src/router/route-trie-builder.ts +9 -0
  247. package/src/router/router-context.ts +1 -0
  248. package/src/router/router-interfaces.ts +35 -1
  249. package/src/router/router-options.ts +45 -5
  250. package/src/router/segment-resolution/fresh.ts +20 -19
  251. package/src/router/segment-resolution/loader-cache.ts +110 -77
  252. package/src/router/segment-resolution/revalidation.ts +34 -24
  253. package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
  254. package/src/router/trie-matching.ts +3 -0
  255. package/src/router-versions.ts +60 -0
  256. package/src/router.ts +107 -1
  257. package/src/rsc/full-payload.ts +6 -2
  258. package/src/rsc/handler-context.ts +5 -0
  259. package/src/rsc/handler.ts +64 -7
  260. package/src/rsc/progressive-enhancement.ts +5 -1
  261. package/src/rsc/response-cache-serve.ts +10 -4
  262. package/src/rsc/rsc-rendering.ts +65 -12
  263. package/src/rsc/shell-build-manifest.ts +17 -12
  264. package/src/rsc/shell-capture-constants.ts +39 -7
  265. package/src/rsc/shell-capture.ts +144 -45
  266. package/src/rsc/shell-serve.ts +12 -6
  267. package/src/rsc/ssr-module-loader.ts +26 -0
  268. package/src/rsc/types.ts +12 -6
  269. package/src/segment-boundary-content.ts +25 -0
  270. package/src/segment-loader-promise.ts +28 -38
  271. package/src/segment-system.tsx +117 -57
  272. package/src/server/build-version-table.ts +130 -0
  273. package/src/server/context.ts +40 -17
  274. package/src/server/cookie-store.ts +5 -0
  275. package/src/server/handle-store.ts +357 -127
  276. package/src/server/request-context.ts +149 -12
  277. package/src/ssr/ssr-root.tsx +32 -13
  278. package/src/suspense-audit-react.tsx +98 -0
  279. package/src/suspense-audit.ts +1030 -0
  280. package/src/testing/action-form.ts +66 -0
  281. package/src/testing/build-versions.ts +64 -0
  282. package/src/testing/dispatch.ts +6 -0
  283. package/src/testing/dom.entry.ts +1 -0
  284. package/src/testing/flight-runtime.d.ts +27 -0
  285. package/src/testing/flight.entry.ts +4 -0
  286. package/src/testing/index.ts +22 -0
  287. package/src/testing/internal/context.ts +2 -1
  288. package/src/testing/internal/seed-vars.ts +14 -2
  289. package/src/testing/internal/server-modules.ts +32 -0
  290. package/src/testing/location-state-key.ts +18 -8
  291. package/src/testing/render-handler.ts +2 -1
  292. package/src/testing/render-route.tsx +293 -46
  293. package/src/testing/serve-shell-request.ts +262 -37
  294. package/src/testing/shell-status.ts +17 -6
  295. package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
  296. package/src/testing/vitest-stubs/version.ts +4 -1
  297. package/src/thenable-status.ts +10 -0
  298. package/src/types/handler-context.ts +15 -5
  299. package/src/types/route-entry.ts +6 -0
  300. package/src/urls/path-helper.ts +69 -9
  301. package/src/use-loader.tsx +57 -30
  302. package/src/vite/discovery/build-versions.ts +1281 -0
  303. package/src/vite/discovery/bundle-postprocess.ts +4 -0
  304. package/src/vite/discovery/discover-routers.ts +15 -0
  305. package/src/vite/discovery/installed-packages.ts +159 -0
  306. package/src/vite/discovery/prerender-collection.ts +38 -1
  307. package/src/vite/discovery/router-versions-phase.ts +336 -0
  308. package/src/vite/discovery/shell-prerender-phase.ts +33 -43
  309. package/src/vite/discovery/state.ts +36 -1
  310. package/src/vite/discovery/virtual-module-codegen.ts +7 -2
  311. package/src/vite/encryption-key.ts +101 -9
  312. package/src/vite/inject-client-debug.ts +42 -20
  313. package/src/vite/plugin-types.ts +28 -0
  314. package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
  315. package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
  316. package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
  317. package/src/vite/plugins/expose-internal-ids.ts +12 -3
  318. package/src/vite/plugins/stable-rsc-output.ts +147 -0
  319. package/src/vite/plugins/version-plugin.ts +19 -24
  320. package/src/vite/plugins/version.d.ts +17 -2
  321. package/src/vite/plugins/virtual-entries.ts +39 -6
  322. package/src/vite/rango.ts +28 -3
  323. package/src/vite/router-discovery.ts +96 -13
  324. package/src/vite/utils/bundle-analysis.ts +39 -7
  325. package/src/vite/utils/client-chunks.ts +5 -1
  326. package/src/vite/utils/compare-strings.ts +9 -0
  327. package/src/vite/utils/package-resolution.ts +9 -0
  328. package/src/vite/utils/prerender-utils.ts +4 -1
  329. package/src/vite/utils/shared-utils.ts +1 -1
  330. package/dist/types/segment-content-promise.d.ts +0 -13
  331. package/src/segment-content-promise.ts +0 -67
@@ -8,10 +8,11 @@ import {
8
8
  findCallParenAfterGenerics,
9
9
  skipStringOrComment,
10
10
  } from "../expose-id-utils.js";
11
- import { escapeRegExp } from "../../../regex-escape.js";
12
11
  import {
13
12
  getImportedFnNames,
14
13
  buildUnsupportedShapeWarning,
14
+ createCallPattern,
15
+ offsetToLineColumn,
15
16
  } from "./export-analysis.js";
16
17
  import { codeMatchIndices } from "../../../build/route-types/source-scan.js";
17
18
  import { createRangoDebugger, createCounter, NS } from "../../debug.js";
@@ -42,6 +43,26 @@ function skipLeadingTrivia(code: string, start: number, end: number): number {
42
43
  return i;
43
44
  }
44
45
 
46
+ /**
47
+ * A router's `$$id`: a hash of its root-relative file and of its position
48
+ * among the file's createRouter() calls.
49
+ *
50
+ * Not of the call's line. `code` is TS-transformed, and a dev server keeps
51
+ * comments there that a build drops, so a line of `code` gave build-time
52
+ * discovery and the bundle different ids for any router with a comment above
53
+ * it (e2e/test-app, tests/cloudflare-basic): the build registered the lazy
54
+ * route manifest under an id no running router had. And a line of the file as
55
+ * written changes with every blank line or comment added above the call,
56
+ * which renamed the state cookie, the route manifest chunk and the router's
57
+ * cache versions for an edit that changed nothing.
58
+ */
59
+ export function routerId(filePath: string, callIndex: number): string {
60
+ return createHash("sha256")
61
+ .update(`${filePath}#${callIndex}`)
62
+ .digest("hex")
63
+ .slice(0, 8);
64
+ }
65
+
45
66
  export function transformRouter(
46
67
  code: string,
47
68
  filePath: string,
@@ -53,14 +74,12 @@ export function transformRouter(
53
74
  // e.g. createRouter<Config<Env>>(...)) and the opening paren are located
54
75
  // separately via findCallParenAfterGenerics so a nested `>` does not defeat
55
76
  // the scan (a `<[^>]*>` regex stopped at the first `>`).
56
- const pat = new RegExp(
57
- `\\b(?:${routerFnNames.map(escapeRegExp).join("|")})\\b`,
58
- "g",
59
- );
77
+ const pat = createCallPattern(routerFnNames);
60
78
  let match: RegExpExecArray | null;
61
79
  const s = new MagicString(code);
62
80
  let changed = false;
63
81
  const unsupportedSites: Array<{ line: number; column: number }> = [];
82
+ let callIndex = -1;
64
83
 
65
84
  // Compute the import path for the generated route names file.
66
85
  // filePath is relative to project root (e.g., "src/router.tsx")
@@ -86,6 +105,7 @@ export function transformRouter(
86
105
  // type position) yields -1 and is skipped.
87
106
  const parenPos = findCallParenAfterGenerics(code, calleeEnd);
88
107
  if (parenPos === -1) continue;
108
+ callIndex++;
89
109
 
90
110
  const closeParen = findMatchingParen(code, parenPos + 1);
91
111
  const callArgs = code.slice(parenPos + 1, closeParen);
@@ -98,12 +118,7 @@ export function transformRouter(
98
118
  if (callArgs.includes(`$$routeNames: ${routeNamesVar}`)) continue;
99
119
 
100
120
  const sourceFilePath = absolutePath ?? filePath;
101
- const lineNumber = code.slice(0, callStart).split("\n").length;
102
- const hash = createHash("sha256")
103
- .update(`${filePath}:${lineNumber}`)
104
- .digest("hex")
105
- .slice(0, 8);
106
- const injected = ` $$id: "${hash}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
121
+ const injected = ` $$id: "${routerId(filePath, callIndex)}", $$sourceFile: "${sourceFilePath}", $$routeNames: ${routeNamesVar},`;
107
122
 
108
123
  // Skip a leading comment/whitespace run so `createRouter(/* c *\/ {...})`
109
124
  // and a newline-then-comment prefix still resolve to the object literal.
@@ -124,9 +139,7 @@ export function transformRouter(
124
139
  // stable $$id can be injected here. Record it so the plugin can warn —
125
140
  // and crucially do NOT mark changed, so a dead named-routes.gen import is
126
141
  // not prepended for a call we never touched.
127
- const lastNl = code.lastIndexOf("\n", callStart - 1);
128
- const column = callStart - (lastNl + 1) + 1;
129
- unsupportedSites.push({ line: lineNumber, column });
142
+ unsupportedSites.push(offsetToLineColumn(code, callStart));
130
143
  }
131
144
  }
132
145
 
@@ -156,11 +169,19 @@ export function transformRouter(
156
169
  */
157
170
  export function exposeRouterId(): Plugin {
158
171
  let projectRoot = "";
172
+ // $$sourceFile is read by route discovery and the CLI, which run on a dev
173
+ // server (the build's discovery temp server included) and need the absolute
174
+ // path. Nothing reads it when serving, so a production build ships the
175
+ // root-relative path: an absolute one would put the build machine's checkout
176
+ // directory in every router's chunk and give the same source a different
177
+ // cache version per directory (discovery/build-versions.ts hashes the chunk).
178
+ let rootRelativeSourceFile = false;
159
179
  const counter = createCounter(debug, "expose-router-id");
160
180
  return {
161
181
  name: "@rangojs/router:expose-router-id",
162
182
  configResolved(config) {
163
183
  projectRoot = config.root;
184
+ rootRelativeSourceFile = config.command === "build";
164
185
  },
165
186
  buildEnd() {
166
187
  counter?.flush();
@@ -188,7 +209,7 @@ export function exposeRouterId(): Plugin {
188
209
  code,
189
210
  filePath,
190
211
  routerFnNames,
191
- normalizePath(id),
212
+ rootRelativeSourceFile ? undefined : normalizePath(id),
192
213
  warn,
193
214
  );
194
215
  } finally {
@@ -45,6 +45,8 @@ import {
45
45
  transformHandlerIds,
46
46
  } from "./expose-ids/handler-transform.js";
47
47
  import { createRangoDebugger, createCounter, NS } from "../debug.js";
48
+ import { compareStrings } from "../utils/compare-strings.js";
49
+ import { VIRTUAL_LOADER_MANIFEST_ID } from "../discovery/state.js";
48
50
 
49
51
  const debug = createRangoDebugger(NS.transform);
50
52
 
@@ -56,7 +58,7 @@ export type { ExposeInternalIdsApi } from "./expose-ids/types.js";
56
58
  // Virtual module for loader manifest
57
59
  // ---------------------------------------------------------------------------
58
60
 
59
- const VIRTUAL_LOADER_MANIFEST = "virtual:rsc-router/loader-manifest";
61
+ const VIRTUAL_LOADER_MANIFEST = VIRTUAL_LOADER_MANIFEST_ID;
60
62
  const RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
61
63
 
62
64
  // ---------------------------------------------------------------------------
@@ -245,10 +247,17 @@ setLoaderImports({});
245
247
  `;
246
248
  }
247
249
 
248
- // Build mode: generate lazy import map
250
+ // Build mode: generate lazy import map. Sorted by id: the registry fills
251
+ // in directory-scan order (the pre-scan below) and transform order, both
252
+ // of which vary between machines and runs, and this module's bytes feed
253
+ // the cache version of every router bundled with it
254
+ // (discovery/build-versions.ts).
249
255
  const lazyImports: string[] = [];
256
+ const sortedLoaders = [...loaderRegistry].sort(([a], [b]) =>
257
+ compareStrings(a, b),
258
+ );
250
259
 
251
- for (const [hashedId, { filePath, exportName }] of loaderRegistry) {
260
+ for (const [hashedId, { filePath, exportName }] of sortedLoaders) {
252
261
  lazyImports.push(
253
262
  ` "${hashedId}": () => import("/${filePath}").then(m => m.${exportName})`,
254
263
  );
@@ -0,0 +1,147 @@
1
+ import { parseAst, type Plugin } from "vite";
2
+ import { escapeRegExp } from "../../regex-escape.js";
3
+ import { compareStrings } from "../utils/compare-strings.js";
4
+ import { SERVER_REFERENCES_MODULE_ID } from "../discovery/build-versions.js";
5
+
6
+ /** One property of an object literal, by its position in the source. */
7
+ export interface LiteralProperty {
8
+ readonly key: string;
9
+ /** Range of the whole property, key included. */
10
+ readonly start: number;
11
+ readonly end: number;
12
+ /** The value's AST node. */
13
+ readonly value: any;
14
+ }
15
+
16
+ /**
17
+ * The properties of an object-literal AST node, or undefined when it is not an
18
+ * object literal of plain keys (a spread, a computed key).
19
+ */
20
+ export function literalProperties(object: any): LiteralProperty[] | undefined {
21
+ if (object?.type !== "ObjectExpression") return undefined;
22
+ const properties: LiteralProperty[] = [];
23
+ for (const property of object.properties ?? []) {
24
+ const key =
25
+ property?.type === "Property" && !property.computed
26
+ ? (property.key?.value ?? property.key?.name)
27
+ : undefined;
28
+ if (typeof key !== "string") return undefined;
29
+ properties.push({
30
+ key,
31
+ start: property.start,
32
+ end: property.end,
33
+ value: property.value,
34
+ });
35
+ }
36
+ return properties;
37
+ }
38
+
39
+ /**
40
+ * The properties of a module's `export default { ... }`, or undefined when
41
+ * the module does not parse or is not that shape. plugin-rsc generates its
42
+ * registries (the server-reference map, the assets manifest) in this form.
43
+ */
44
+ export function defaultExportProperties(
45
+ code: string,
46
+ ): LiteralProperty[] | undefined {
47
+ let program: any;
48
+ try {
49
+ program = parseAst(code);
50
+ } catch {
51
+ return undefined;
52
+ }
53
+ return literalProperties(
54
+ (program.body ?? []).find(
55
+ (node: any) => node?.type === "ExportDefaultDeclaration",
56
+ )?.declaration,
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Return `code` with the properties of its default-exported object literal
62
+ * sorted by key, or undefined when there is nothing to reorder (or the module
63
+ * is not that shape).
64
+ */
65
+ export function sortDefaultExportProperties(code: string): string | undefined {
66
+ const properties = defaultExportProperties(code);
67
+ if (!properties || properties.length < 2) return undefined;
68
+ const sorted = [...properties].sort((a, b) => compareStrings(a.key, b.key));
69
+ if (sorted.every((property, index) => property === properties[index])) {
70
+ return undefined;
71
+ }
72
+ return (
73
+ code.slice(0, properties[0]!.start) +
74
+ sorted
75
+ .map((property) => code.slice(property.start, property.end))
76
+ .join(",\n") +
77
+ code.slice(properties[properties.length - 1]!.end)
78
+ );
79
+ }
80
+
81
+ const ASSETS_MANIFEST_ID = "virtual:vite-rsc/assets-manifest";
82
+ const ASSETS_MANIFEST_IMPORT =
83
+ /import\s+([A-Za-z_$][\w$]*)\s+from\s*["']virtual:vite-rsc\/assets-manifest["']/;
84
+ /** The local name plugin-rsc's own runtime modules import the manifest under. */
85
+ const ASSETS_MANIFEST_BINDING = "assetsManifest";
86
+ /**
87
+ * The names plugin-rsc's GENERATED code imports it under. Only these are
88
+ * renamed: the rename is textual, and a name an app author picked
89
+ * (`import manifest from ...`) can also be a word in that module's strings.
90
+ */
91
+ const GENERATED_BINDINGS: ReadonlySet<string> = new Set([
92
+ "__vite_rsc_assets_manifest__",
93
+ "__vite_rsc_assets_manifest",
94
+ ]);
95
+
96
+ function identifier(name: string): RegExp {
97
+ return new RegExp(`(?<![\\w$.])${escapeRegExp(name)}(?![\\w$])`, "g");
98
+ }
99
+
100
+ /**
101
+ * Return `code` with its default import of plugin-rsc's assets manifest
102
+ * renamed to {@link ASSETS_MANIFEST_BINDING}, or undefined when there is
103
+ * nothing to rename (no such import under a generated name, or the module
104
+ * uses the canonical name for something else).
105
+ */
106
+ export function canonicalAssetsManifestBinding(
107
+ code: string,
108
+ ): string | undefined {
109
+ const local = code.match(ASSETS_MANIFEST_IMPORT)?.[1];
110
+ if (local === undefined || !GENERATED_BINDINGS.has(local)) return undefined;
111
+ if (identifier(ASSETS_MANIFEST_BINDING).test(code)) return undefined;
112
+ return code.replace(identifier(local), ASSETS_MANIFEST_BINDING);
113
+ }
114
+
115
+ /**
116
+ * Removes two run-to-run differences from plugin-rsc 0.5.35's build output.
117
+ * The cache versions hash these bytes (discovery/build-versions.ts).
118
+ *
119
+ * 1. Property order of the server-reference map. plugin-rsc writes it in the
120
+ * order its transforms first saw each `"use server"` module
121
+ * (`ServerReferencesManager.claimMap`), which varies with the concurrent
122
+ * scan builds (e2e/test-app: 2 of 2 consecutive builds differed). Sorted
123
+ * here so a minified server build, where stripServerReferenceMap finds no
124
+ * region comments, still hashes the same bytes.
125
+ * 2. Local name of the assets-manifest import. plugin-rsc imports the manifest
126
+ * under three names; the bundler merges them into one import per chunk and
127
+ * keeps a different one from build to build (the cache-versions fixture:
128
+ * 1 of 10 concurrent builds). One name in, one name out.
129
+ * @internal
130
+ */
131
+ export function stableRscOutput(): Plugin {
132
+ return {
133
+ name: "@rangojs/router:stable-rsc-output",
134
+ apply: "build",
135
+ // After plugin-rsc's transforms, which add the manifest import.
136
+ enforce: "post",
137
+ transform(code, id) {
138
+ const stable =
139
+ id === SERVER_REFERENCES_MODULE_ID
140
+ ? sortDefaultExportProperties(code)
141
+ : code.includes(ASSETS_MANIFEST_ID)
142
+ ? canonicalAssetsManifestBinding(code)
143
+ : undefined;
144
+ return stable === undefined ? null : { code: stable, map: null };
145
+ },
146
+ };
147
+ }
@@ -1,5 +1,9 @@
1
1
  import { parseAst, type Plugin } from "vite";
2
- import { VIRTUAL_IDS, getVirtualVersionContent } from "./virtual-entries.js";
2
+ import {
3
+ VIRTUAL_IDS,
4
+ getVirtualBuildVersionContent,
5
+ getVirtualVersionContent,
6
+ } from "./virtual-entries.js";
3
7
  import { hasUseClientDirective } from "../utils/directive-prologue.js";
4
8
 
5
9
  interface ClientModuleSignature {
@@ -100,25 +104,22 @@ function getClientModuleSignature(
100
104
  }
101
105
 
102
106
  /**
103
- * Plugin providing rsc-router:version virtual module.
104
- * Exports VERSION that changes when RSC modules change (dev) or at build time (production).
107
+ * Plugin providing the `@rangojs/router:version` virtual module.
105
108
  *
106
- * The version is used for:
107
- * 1. Cache invalidation - CFCacheStore uses VERSION to invalidate stale cache
108
- * 2. Version mismatch detection - client sends version, server reloads on mismatch
109
+ * Dev: exports a VERSION stamp used for both versions of every router. It
110
+ * updates when the server starts and when an RSC module changes via HMR (the
111
+ * version module is invalidated). Client-only HMR changes do not update it:
112
+ * they do not affect server-rendered content or cached RSC payloads.
109
113
  *
110
- * In dev mode, the version updates when:
111
- * - Server starts (initial version)
112
- * - RSC modules change via HMR (triggers version module invalidation)
113
- *
114
- * Client-only HMR changes don't update the version since they don't affect
115
- * server-rendered content or cached RSC payloads.
114
+ * Build: exports a placeholder for the per-router versions table. The module
115
+ * holds nothing that varies between builds; the table is written into the
116
+ * built chunk after the server output is hashed (the buildApp post hook in
117
+ * router-discovery.ts). See docs/design/per-app-cache-version.md.
116
118
  * @internal
117
119
  */
118
120
  export function createVersionPlugin(): Plugin {
119
- // Generate version at plugin creation time (build/server start)
120
- const buildVersion = Date.now().toString(16);
121
- let currentVersion = buildVersion;
121
+ // Dev stamp, generated at plugin creation (server start).
122
+ let currentVersion = Date.now().toString(16);
122
123
  let isDev = false;
123
124
  let server: any = null;
124
125
  let resolvedCacheDir: string | undefined;
@@ -142,14 +143,6 @@ export function createVersionPlugin(): Plugin {
142
143
  name: "@rangojs/router:version",
143
144
  enforce: "pre",
144
145
 
145
- // The build-time shell capture phase (producer B, #699) stamps entries
146
- // with THIS instance's version — the value folded into the shipped worker
147
- // — never the discovery temp server's own version-plugin value (a
148
- // different Date.now() stamp that would fail the serve-side gate).
149
- api: {
150
- getBuildVersion: (): string => currentVersion,
151
- },
152
-
153
146
  configResolved(config) {
154
147
  isDev = config.command === "serve";
155
148
  resolvedCacheDir = config.cacheDir
@@ -177,7 +170,9 @@ export function createVersionPlugin(): Plugin {
177
170
 
178
171
  load(id) {
179
172
  if (id === "\0" + VIRTUAL_IDS.version) {
180
- return getVirtualVersionContent(currentVersion);
173
+ return isDev
174
+ ? getVirtualVersionContent(currentVersion)
175
+ : getVirtualBuildVersionContent();
181
176
  }
182
177
  return null;
183
178
  },
@@ -5,8 +5,23 @@
5
5
 
6
6
  declare module "@rangojs/router:version" {
7
7
  /**
8
- * Auto-generated version string for cache invalidation.
9
- * Changes on server restart (dev) or build (prod).
8
+ * The build's version string.
9
+ *
10
+ * - Dev: a stamp that changes on server start and on every RSC module edit.
11
+ * - Production: the whole-build document version, a hash of the server
12
+ * output and the client asset names. The same source builds to the same
13
+ * value; any change to any router changes it.
14
+ *
15
+ * A router serves with its own versions (`ROUTER_VERSIONS`), not this one.
10
16
  */
11
17
  export const VERSION: string;
18
+
19
+ /**
20
+ * Per-router cache versions computed at build time: router id to
21
+ * `[data, document]`, with `"*"` holding the whole-build pair.
22
+ * `undefined` in dev, where `VERSION` is used for both.
23
+ */
24
+ export const ROUTER_VERSIONS:
25
+ | Readonly<Record<string, readonly [data: string, document: string]>>
26
+ | undefined;
12
27
  }
@@ -1,4 +1,5 @@
1
1
  import type { HeadScriptsOption } from "../plugin-types.js";
2
+ import { DEFAULT_ROUTER_VERSIONS_KEY } from "../../router-versions.js";
2
3
 
3
4
  export const VIRTUAL_ENTRY_BROWSER: string = `
4
5
  import {
@@ -27,6 +28,12 @@ async function initializeApp() {
27
28
  // is the default; createRouter({ strictMode: false }) ships the opt-out in the
28
29
  // payload metadata. StrictMode emits no DOM, so toggling never changes markup.
29
30
  const { strictMode, initialPayload } = await initBrowserApp({ rscStream, deps });
31
+ // Set only on a document a no-JS useActionState POST rendered: React needs
32
+ // the server's form state to hydrate the hook without resetting it. A state
33
+ // Flight could not carry rejects: hydrate without it, as before.
34
+ const formState = await Promise.resolve(initialPayload.formState).catch(
35
+ () => undefined
36
+ );
30
37
 
31
38
  if (import.meta.hot) {
32
39
  const { startDevDiscoveryHandshake } = await import(
@@ -41,7 +48,8 @@ async function initializeApp() {
41
48
  const app = createElement(Rango);
42
49
  hydrateRoot(
43
50
  document,
44
- strictMode === false ? app : createElement(StrictMode, null, app)
51
+ strictMode === false ? app : createElement(StrictMode, null, app),
52
+ formState ? { formState } : undefined
45
53
  );
46
54
  }
47
55
 
@@ -164,7 +172,7 @@ export const resumeShellHTML = createShellResumeHandler({
164
172
  * registered for the _rsc_loader endpoint and fail in production.
165
173
  *
166
174
  * Single source of truth: both the generated virtual RSC entry below and the
167
- * custom-entry injector (version-injector) consume this list, so a new
175
+ * custom-entry injector (entry-bootstrap-injector) consume this list, so a new
168
176
  * bootstrap manifest cannot be added to one path and forgotten on the other.
169
177
  * That exact drift (loader-manifest present here but missing from the injector)
170
178
  * is what left fetchable loaders unresolved on custom worker entries.
@@ -197,12 +205,11 @@ import {
197
205
  // consults them at router-module evaluation. Router-first would silently push
198
206
  // every clientUrls() app onto the deferred-subscription path and change mount
199
207
  // registration order with no error. The custom-entry injector keeps the same
200
- // guarantee by PREPENDING this list at file top (version-injector.ts).
208
+ // guarantee by PREPENDING this list at file top (entry-bootstrap-injector.ts).
201
209
  ${bootstrapImports}
202
210
 
203
211
  import { router } from "${routerPath}";
204
212
  import { createRSCHandler } from "@rangojs/router/internal/rsc-handler";
205
- import { VERSION } from "@rangojs/router:version";
206
213
 
207
214
  // Lazily create the handler on first request so that ESM live bindings
208
215
  // have resolved by the time we read \`router\`. During HMR the module may
@@ -212,7 +219,6 @@ export default function handler(request, env) {
212
219
  if (!_handler) {
213
220
  _handler = createRSCHandler({
214
221
  router,
215
- version: VERSION,
216
222
  // Forward the router's CSP nonce provider. createRSCHandler reads the
217
223
  // provider only from options.nonce; without this, createRouter({ nonce })
218
224
  // is silently dropped on the Node preset (the Cloudflare path wires it via
@@ -314,6 +320,33 @@ export const VIRTUAL_IDS = {
314
320
  version: "@rangojs/router:version",
315
321
  } as const;
316
322
 
323
+ /** Dev: one stamp for both versions of every router, bumped on RSC edits. */
317
324
  export function getVirtualVersionContent(version: string): string {
318
- return `export const VERSION = ${JSON.stringify(version)};`;
325
+ return [
326
+ `export const VERSION = ${JSON.stringify(version)};`,
327
+ `export const ROUTER_VERSIONS = undefined;`,
328
+ ].join("\n");
329
+ }
330
+
331
+ /**
332
+ * Free identifier the production version module ships in place of the table.
333
+ * The versions are hashes of the built server code, so they cannot be part of
334
+ * it: the buildApp post hook (router-discovery.ts) replaces this token in the
335
+ * written chunk once the hashes are final (runRouterVersionsPhase,
336
+ * discovery/router-versions-phase.ts). An unreplaced token throws a ReferenceError
337
+ * when the module evaluates, so a build that skipped the step cannot serve
338
+ * with a constant version.
339
+ */
340
+ export const ROUTER_VERSIONS_PLACEHOLDER = "__RANGO_ROUTER_VERSIONS__";
341
+
342
+ /**
343
+ * Production: byte-identical in every build, so it never moves the hashes it
344
+ * is later filled with. VERSION is the whole-build document version (the
345
+ * table's "*" entry).
346
+ */
347
+ export function getVirtualBuildVersionContent(): string {
348
+ return [
349
+ `export const ROUTER_VERSIONS = ${ROUTER_VERSIONS_PLACEHOLDER};`,
350
+ `export const VERSION = ROUTER_VERSIONS[${JSON.stringify(DEFAULT_ROUTER_VERSIONS_KEY)}][1];`,
351
+ ].join("\n");
319
352
  }
package/src/vite/rango.ts CHANGED
@@ -2,7 +2,10 @@ import { type PluginOption } from "vite";
2
2
  import { readFileSync, existsSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { exposeActionId } from "./plugins/expose-action-id.js";
5
- import { defineEncryptionKeyExpr } from "./encryption-key.js";
5
+ import {
6
+ configureEncryptionKey,
7
+ defineEncryptionKeyExpr,
8
+ } from "./encryption-key.js";
6
9
  import {
7
10
  createLoaderDirectiveGuardPlugin,
8
11
  createLoaderScanStubPlugin,
@@ -12,6 +15,7 @@ import {
12
15
  import { useCacheTransform } from "./plugins/use-cache-transform.js";
13
16
  import { transitionWhenHoistPlugin } from "./plugins/transition-when-hoist.js";
14
17
  import { clientRefDedup } from "./plugins/client-ref-dedup.js";
18
+ import { stableRscOutput } from "./plugins/stable-rsc-output.js";
15
19
  import { VIRTUAL_IDS } from "./plugins/virtual-entries.js";
16
20
  import {
17
21
  getExcludeDeps,
@@ -43,7 +47,7 @@ import type {
43
47
  } from "./plugin-types.js";
44
48
  import { createVercelOutputPlugin } from "./plugins/vercel-output.js";
45
49
  import { printBanner, rangoVersion } from "./utils/banner.js";
46
- import { createVersionInjectorPlugin } from "./plugins/version-injector.js";
50
+ import { createEntryBootstrapInjectorPlugin } from "./plugins/entry-bootstrap-injector.js";
47
51
  import { createCjsToEsmPlugin } from "./plugins/cjs-to-esm.js";
48
52
  import { createRouterDiscoveryPlugin } from "./router-discovery.js";
49
53
  import { performanceTracksPlugin } from "./plugins/performance-tracks.js";
@@ -76,6 +80,20 @@ const debugConfig = createRangoDebugger(NS.config);
76
80
  */
77
81
  const SERVER_BUILD_TARGET = "esnext";
78
82
 
83
+ /**
84
+ * Entry-signature mode for the node/vercel RSC build, the one
85
+ * `@cloudflare/vite-plugin` forces on its worker entry.
86
+ *
87
+ * Vite's default for a server build, "allow-extension", lets the bundler park
88
+ * modules shared with lazy chunks in the entry chunk. Under a host router that
89
+ * put the router runtime (80 modules, measured on e2e/test-app/.host-fixture
90
+ * and examples/vercel-multi-router) and every sub-app's route names into
91
+ * `index.js`, which every sub-app chunk imported back and which names each
92
+ * sub-app chunk by its content hash: no router's cache version was independent
93
+ * of the others (discovery/build-versions.ts hashes a router's chunks).
94
+ */
95
+ const RSC_ENTRY_SIGNATURES = "strict";
96
+
79
97
  // The leading-directive 'use client' sniff is shared with version-plugin's
80
98
  // getClientModuleSignature so the two cannot drift. Imported for local use by the
81
99
  // HMR transform below and re-exported because the E8 sniff test imports it from
@@ -111,6 +129,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
111
129
  const rangoStart = performance.now();
112
130
  const resolvedOptions: RangoOptions = options ?? { preset: "node" };
113
131
  const preset = resolvedOptions.preset ?? "node";
132
+ // Before any plugin-rsc instance (this build's, the discovery temp server's)
133
+ // reads the key, and so an invalid key fails the config load.
134
+ configureEncryptionKey(resolvedOptions.encryptionKey);
114
135
  const showBanner = resolvedOptions.banner ?? true;
115
136
  const clientChunksOption = resolvedOptions.clientChunks ?? true;
116
137
  const useBuiltInClientChunks = clientChunksOption === true;
@@ -467,6 +488,9 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
467
488
  ...(vercelServerEnv ?? {}),
468
489
  build: {
469
490
  target: SERVER_BUILD_TARGET,
491
+ rollupOptions: {
492
+ preserveEntrySignatures: RSC_ENTRY_SIGNATURES,
493
+ },
470
494
  },
471
495
  optimizeDeps: {
472
496
  entries: [VIRTUAL_IDS.rsc],
@@ -539,6 +563,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
539
563
  plugins.push(clientRefDedup());
540
564
  }
541
565
  plugins.push(clientChunksReport());
566
+ plugins.push(stableRscOutput());
542
567
 
543
568
  plugins.push({
544
569
  name: "@rangojs/router:client-component-hmr",
@@ -583,7 +608,7 @@ export async function rango(options?: RangoOptions): Promise<PluginOption[]> {
583
608
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : undefined;
584
609
 
585
610
  if (preset === "cloudflare") {
586
- plugins.push(createVersionInjectorPlugin(undefined));
611
+ plugins.push(createEntryBootstrapInjectorPlugin(undefined));
587
612
  }
588
613
 
589
614
  plugins.push(createCjsToEsmPlugin());