@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17

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 (376) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +198 -44
  3. package/dist/bin/rango.js +287 -105
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3248 -1117
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +73 -21
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +107 -1
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +245 -21
  12. package/skills/caching/SKILL.md +302 -6
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/document-cache/SKILL.md +78 -55
  16. package/skills/handler-use/SKILL.md +364 -0
  17. package/skills/hooks/SKILL.md +270 -30
  18. package/skills/host-router/SKILL.md +82 -22
  19. package/skills/i18n/SKILL.md +276 -0
  20. package/skills/intercept/SKILL.md +49 -5
  21. package/skills/layout/SKILL.md +35 -9
  22. package/skills/links/SKILL.md +249 -17
  23. package/skills/loader/SKILL.md +294 -30
  24. package/skills/middleware/SKILL.md +52 -13
  25. package/skills/migrate-nextjs/SKILL.md +584 -0
  26. package/skills/migrate-react-router/SKILL.md +769 -0
  27. package/skills/mime-routes/SKILL.md +27 -0
  28. package/skills/observability/SKILL.md +137 -0
  29. package/skills/parallel/SKILL.md +203 -7
  30. package/skills/prerender/SKILL.md +123 -100
  31. package/skills/rango/SKILL.md +250 -22
  32. package/skills/react-compiler/SKILL.md +168 -0
  33. package/skills/response-routes/SKILL.md +122 -47
  34. package/skills/route/SKILL.md +97 -5
  35. package/skills/router-setup/SKILL.md +90 -5
  36. package/skills/server-actions/SKILL.md +775 -0
  37. package/skills/streams-and-websockets/SKILL.md +283 -0
  38. package/skills/tailwind/SKILL.md +27 -3
  39. package/skills/testing/SKILL.md +129 -0
  40. package/skills/testing/bindings.md +89 -0
  41. package/skills/testing/cache-prerender.md +124 -0
  42. package/skills/testing/client-components.md +122 -0
  43. package/skills/testing/e2e-parity.md +125 -0
  44. package/skills/testing/flight.md +92 -0
  45. package/skills/testing/handles.md +129 -0
  46. package/skills/testing/loader.md +128 -0
  47. package/skills/testing/middleware.md +99 -0
  48. package/skills/testing/render-handler.md +121 -0
  49. package/skills/testing/response-routes.md +95 -0
  50. package/skills/testing/reverse-and-types.md +84 -0
  51. package/skills/testing/server-actions.md +107 -0
  52. package/skills/testing/server-tree.md +128 -0
  53. package/skills/testing/setup.md +120 -0
  54. package/skills/typesafety/SKILL.md +329 -27
  55. package/skills/use-cache/SKILL.md +36 -5
  56. package/skills/view-transitions/SKILL.md +294 -0
  57. package/src/__augment-tests__/augment.ts +81 -0
  58. package/src/__augment-tests__/augmented.check.ts +116 -0
  59. package/src/__internal.ts +67 -40
  60. package/src/browser/action-coordinator.ts +53 -36
  61. package/src/browser/action-fence.ts +47 -0
  62. package/src/browser/app-shell.ts +39 -0
  63. package/src/browser/app-version.ts +14 -0
  64. package/src/browser/cookie-name.ts +140 -0
  65. package/src/browser/event-controller.ts +86 -147
  66. package/src/browser/history-state.ts +21 -0
  67. package/src/browser/index.ts +3 -3
  68. package/src/browser/invalidate-client-cache.ts +52 -0
  69. package/src/browser/link-interceptor.ts +4 -0
  70. package/src/browser/navigation-bridge.ts +148 -19
  71. package/src/browser/navigation-client.ts +187 -67
  72. package/src/browser/navigation-store-handle.ts +38 -0
  73. package/src/browser/navigation-store.ts +76 -67
  74. package/src/browser/navigation-transaction.ts +18 -66
  75. package/src/browser/partial-update.ts +123 -94
  76. package/src/browser/prefetch/cache.ts +214 -36
  77. package/src/browser/prefetch/fetch.ts +260 -38
  78. package/src/browser/prefetch/policy.ts +6 -0
  79. package/src/browser/prefetch/queue.ts +126 -20
  80. package/src/browser/prefetch/resource-ready.ts +77 -0
  81. package/src/browser/rango-state.ts +158 -76
  82. package/src/browser/react/Link.tsx +93 -11
  83. package/src/browser/react/NavigationProvider.tsx +115 -34
  84. package/src/browser/react/ScrollRestoration.tsx +10 -6
  85. package/src/browser/react/context.ts +7 -2
  86. package/src/browser/react/filter-segment-order.ts +49 -7
  87. package/src/browser/react/index.ts +0 -48
  88. package/src/browser/react/location-state-shared.ts +166 -8
  89. package/src/browser/react/location-state.ts +39 -14
  90. package/src/browser/react/use-action.ts +6 -15
  91. package/src/browser/react/use-handle.ts +23 -69
  92. package/src/browser/react/use-link-status.ts +0 -4
  93. package/src/browser/react/use-navigation.ts +22 -5
  94. package/src/browser/react/use-params.ts +20 -10
  95. package/src/browser/react/use-reverse.ts +106 -0
  96. package/src/browser/react/use-router.ts +46 -11
  97. package/src/browser/react/use-search-params.ts +0 -5
  98. package/src/browser/react/use-segments.ts +11 -21
  99. package/src/browser/response-adapter.ts +52 -1
  100. package/src/browser/rsc-router.tsx +215 -76
  101. package/src/browser/scroll-restoration.ts +46 -39
  102. package/src/browser/segment-reconciler.ts +36 -9
  103. package/src/browser/segment-structure-assert.ts +2 -2
  104. package/src/browser/server-action-bridge.ts +176 -50
  105. package/src/browser/types.ts +95 -11
  106. package/src/browser/validate-redirect-origin.ts +43 -16
  107. package/src/build/collect-fallback-refs.ts +107 -0
  108. package/src/build/generate-manifest.ts +65 -40
  109. package/src/build/generate-route-types.ts +5 -0
  110. package/src/build/index.ts +8 -2
  111. package/src/build/prefix-tree-utils.ts +123 -0
  112. package/src/build/route-trie.ts +137 -32
  113. package/src/build/route-types/codegen.ts +4 -4
  114. package/src/build/route-types/include-resolution.ts +9 -2
  115. package/src/build/route-types/param-extraction.ts +6 -3
  116. package/src/build/route-types/per-module-writer.ts +7 -4
  117. package/src/build/route-types/router-processing.ts +278 -96
  118. package/src/build/route-types/scan-filter.ts +9 -2
  119. package/src/build/route-types/source-scan.ts +118 -0
  120. package/src/build/runtime-discovery.ts +9 -20
  121. package/src/cache/cache-error.ts +104 -0
  122. package/src/cache/cache-policy.ts +68 -28
  123. package/src/cache/cache-runtime.ts +149 -43
  124. package/src/cache/cache-scope.ts +148 -81
  125. package/src/cache/cache-tag.ts +98 -0
  126. package/src/cache/cf/cf-cache-store.ts +2550 -93
  127. package/src/cache/cf/index.ts +11 -17
  128. package/src/cache/document-cache.ts +78 -27
  129. package/src/cache/handle-snapshot.ts +63 -0
  130. package/src/cache/index.ts +23 -20
  131. package/src/cache/memory-segment-store.ts +136 -37
  132. package/src/cache/profile-registry.ts +6 -30
  133. package/src/cache/read-through-swr.ts +41 -11
  134. package/src/cache/segment-codec.ts +0 -16
  135. package/src/cache/tag-invalidation.ts +230 -0
  136. package/src/cache/taint.ts +55 -0
  137. package/src/cache/types.ts +33 -100
  138. package/src/cache/vercel/index.ts +11 -0
  139. package/src/cache/vercel/vercel-cache-store.ts +799 -0
  140. package/src/client.rsc.tsx +6 -21
  141. package/src/client.tsx +108 -290
  142. package/src/component-utils.ts +19 -0
  143. package/src/context-var.ts +84 -2
  144. package/src/debug.ts +2 -2
  145. package/src/decode-loader-results.ts +36 -0
  146. package/src/defer.ts +196 -0
  147. package/src/deps/ssr.ts +0 -1
  148. package/src/errors.ts +30 -4
  149. package/src/handle.ts +70 -22
  150. package/src/handles/MetaTags.tsx +0 -14
  151. package/src/handles/breadcrumbs.ts +16 -5
  152. package/src/handles/meta.ts +0 -39
  153. package/src/host/cookie-handler.ts +0 -36
  154. package/src/host/errors.ts +0 -24
  155. package/src/host/index.ts +8 -2
  156. package/src/host/pattern-matcher.ts +7 -50
  157. package/src/host/router.ts +107 -99
  158. package/src/host/testing.ts +40 -27
  159. package/src/host/types.ts +37 -4
  160. package/src/host/utils.ts +1 -1
  161. package/src/href-client.ts +137 -22
  162. package/src/index.rsc.ts +52 -26
  163. package/src/index.ts +100 -38
  164. package/src/internal-debug.ts +2 -4
  165. package/src/loader-store.ts +500 -0
  166. package/src/loader.rsc.ts +20 -13
  167. package/src/loader.ts +12 -11
  168. package/src/missing-id-error.ts +68 -0
  169. package/src/network-error-thrower.tsx +1 -6
  170. package/src/outlet-context.ts +1 -1
  171. package/src/outlet-provider.tsx +1 -5
  172. package/src/prerender/param-hash.ts +10 -11
  173. package/src/prerender/store.ts +37 -41
  174. package/src/prerender.ts +198 -82
  175. package/src/redirect-origin.ts +100 -0
  176. package/src/response-utils.ts +37 -0
  177. package/src/reverse.ts +65 -15
  178. package/src/root-error-boundary.tsx +1 -19
  179. package/src/route-content-wrapper.tsx +7 -72
  180. package/src/route-definition/dsl-helpers.ts +437 -274
  181. package/src/route-definition/helper-factories.ts +29 -139
  182. package/src/route-definition/helpers-types.ts +113 -37
  183. package/src/route-definition/index.ts +3 -0
  184. package/src/route-definition/redirect.ts +52 -10
  185. package/src/route-definition/resolve-handler-use.ts +161 -0
  186. package/src/route-definition/use-item-types.ts +32 -0
  187. package/src/route-map-builder.ts +7 -17
  188. package/src/route-types.ts +37 -41
  189. package/src/router/basename.ts +14 -0
  190. package/src/router/content-negotiation.ts +108 -9
  191. package/src/router/error-handling.ts +13 -17
  192. package/src/router/find-match.ts +45 -22
  193. package/src/router/handler-context.ts +83 -41
  194. package/src/router/intercept-resolution.ts +25 -23
  195. package/src/router/lazy-includes.ts +19 -53
  196. package/src/router/loader-resolution.ts +213 -30
  197. package/src/router/logging.ts +5 -8
  198. package/src/router/manifest.ts +49 -45
  199. package/src/router/match-api.ts +121 -205
  200. package/src/router/match-context.ts +0 -22
  201. package/src/router/match-handlers.ts +58 -58
  202. package/src/router/match-middleware/background-revalidation.ts +27 -6
  203. package/src/router/match-middleware/cache-lookup.ts +205 -249
  204. package/src/router/match-middleware/cache-store.ts +45 -32
  205. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  206. package/src/router/match-middleware/segment-resolution.ts +52 -18
  207. package/src/router/match-pipelines.ts +1 -42
  208. package/src/router/match-result.ts +104 -40
  209. package/src/router/metrics.ts +5 -34
  210. package/src/router/middleware-types.ts +13 -142
  211. package/src/router/middleware.ts +173 -143
  212. package/src/router/navigation-snapshot.ts +131 -0
  213. package/src/router/params-util.ts +23 -0
  214. package/src/router/pattern-matching.ts +109 -63
  215. package/src/router/prerender-match.ts +192 -54
  216. package/src/router/preview-match.ts +32 -102
  217. package/src/router/request-classification.ts +276 -0
  218. package/src/router/revalidation.ts +63 -55
  219. package/src/router/route-snapshot.ts +244 -0
  220. package/src/router/router-context.ts +6 -28
  221. package/src/router/router-interfaces.ts +100 -35
  222. package/src/router/router-options.ts +91 -11
  223. package/src/router/router-registry.ts +2 -5
  224. package/src/router/segment-resolution/fresh.ts +242 -75
  225. package/src/router/segment-resolution/helpers.ts +64 -25
  226. package/src/router/segment-resolution/loader-cache.ts +41 -37
  227. package/src/router/segment-resolution/revalidation.ts +456 -372
  228. package/src/router/segment-resolution/static-store.ts +19 -5
  229. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  230. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  231. package/src/router/segment-resolution.ts +4 -1
  232. package/src/router/segment-wrappers.ts +2 -3
  233. package/src/router/state-cookie-name.ts +33 -0
  234. package/src/router/substitute-pattern-params.ts +56 -0
  235. package/src/router/telemetry-otel.ts +0 -20
  236. package/src/router/telemetry.ts +96 -19
  237. package/src/router/timeout.ts +0 -20
  238. package/src/router/trie-matching.ts +91 -46
  239. package/src/router/types.ts +10 -63
  240. package/src/router/url-params.ts +44 -0
  241. package/src/router.ts +134 -43
  242. package/src/rsc/handler-context.ts +3 -2
  243. package/src/rsc/handler.ts +492 -383
  244. package/src/rsc/helpers.ts +162 -46
  245. package/src/rsc/index.ts +1 -1
  246. package/src/rsc/json-route-result.ts +38 -0
  247. package/src/rsc/loader-fetch.ts +23 -3
  248. package/src/rsc/manifest-init.ts +33 -42
  249. package/src/rsc/origin-guard.ts +39 -25
  250. package/src/rsc/progressive-enhancement.ts +30 -3
  251. package/src/rsc/redirect-guard.ts +99 -0
  252. package/src/rsc/response-error.ts +79 -12
  253. package/src/rsc/response-route-handler.ts +90 -63
  254. package/src/rsc/rsc-rendering.ts +56 -54
  255. package/src/rsc/runtime-warnings.ts +23 -10
  256. package/src/rsc/server-action.ts +74 -67
  257. package/src/rsc/ssr-setup.ts +18 -2
  258. package/src/rsc/types.ts +25 -6
  259. package/src/runtime-env.ts +18 -0
  260. package/src/search-params.ts +4 -20
  261. package/src/segment-content-promise.ts +67 -0
  262. package/src/segment-loader-promise.ts +134 -0
  263. package/src/segment-system.tsx +272 -129
  264. package/src/serialize.ts +243 -0
  265. package/src/server/context.ts +309 -61
  266. package/src/server/cookie-store.ts +80 -5
  267. package/src/server/handle-store.ts +26 -24
  268. package/src/server/loader-registry.ts +10 -28
  269. package/src/server/request-context.ts +348 -128
  270. package/src/ssr/index.tsx +23 -15
  271. package/src/static-handler.ts +27 -18
  272. package/src/testing/cache-status.ts +162 -0
  273. package/src/testing/collect-handle.ts +40 -0
  274. package/src/testing/dispatch.ts +618 -0
  275. package/src/testing/dom.entry.ts +22 -0
  276. package/src/testing/e2e/fixture.ts +188 -0
  277. package/src/testing/e2e/index.ts +128 -0
  278. package/src/testing/e2e/matchers.ts +35 -0
  279. package/src/testing/e2e/page-helpers.ts +272 -0
  280. package/src/testing/e2e/parity.ts +387 -0
  281. package/src/testing/e2e/server.ts +195 -0
  282. package/src/testing/flight-matchers.ts +97 -0
  283. package/src/testing/flight-normalize.ts +11 -0
  284. package/src/testing/flight-runtime.d.ts +57 -0
  285. package/src/testing/flight-tree.ts +682 -0
  286. package/src/testing/flight.entry.ts +52 -0
  287. package/src/testing/flight.ts +232 -0
  288. package/src/testing/generated-routes.ts +183 -0
  289. package/src/testing/index.ts +99 -0
  290. package/src/testing/internal/context.ts +348 -0
  291. package/src/testing/internal/flight-client-globals.ts +30 -0
  292. package/src/testing/internal/seed-vars.ts +54 -0
  293. package/src/testing/render-handler.ts +330 -0
  294. package/src/testing/render-route.tsx +566 -0
  295. package/src/testing/run-loader.ts +378 -0
  296. package/src/testing/run-middleware.ts +205 -0
  297. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  298. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  299. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  300. package/src/testing/vitest-stubs/version.ts +5 -0
  301. package/src/testing/vitest.ts +305 -0
  302. package/src/theme/ThemeProvider.tsx +0 -52
  303. package/src/theme/ThemeScript.tsx +0 -6
  304. package/src/theme/constants.ts +0 -12
  305. package/src/theme/index.ts +0 -7
  306. package/src/theme/theme-context.ts +1 -5
  307. package/src/theme/theme-script.ts +0 -14
  308. package/src/theme/use-theme.ts +0 -3
  309. package/src/types/boundaries.ts +0 -35
  310. package/src/types/cache-types.ts +17 -8
  311. package/src/types/error-types.ts +30 -90
  312. package/src/types/global-namespace.ts +54 -41
  313. package/src/types/handler-context.ts +233 -81
  314. package/src/types/index.ts +1 -10
  315. package/src/types/loader-types.ts +44 -15
  316. package/src/types/request-scope.ts +107 -0
  317. package/src/types/route-config.ts +6 -50
  318. package/src/types/route-entry.ts +19 -7
  319. package/src/types/segments.ts +37 -14
  320. package/src/urls/include-helper.ts +33 -70
  321. package/src/urls/index.ts +1 -11
  322. package/src/urls/path-helper-types.ts +58 -11
  323. package/src/urls/path-helper.ts +57 -111
  324. package/src/urls/pattern-types.ts +48 -19
  325. package/src/urls/response-types.ts +25 -22
  326. package/src/urls/type-extraction.ts +58 -139
  327. package/src/urls/urls-function.ts +1 -18
  328. package/src/use-loader.tsx +346 -89
  329. package/src/vite/debug.ts +185 -0
  330. package/src/vite/discovery/bundle-postprocess.ts +36 -38
  331. package/src/vite/discovery/discover-routers.ts +130 -85
  332. package/src/vite/discovery/discovery-errors.ts +194 -0
  333. package/src/vite/discovery/gate-state.ts +171 -0
  334. package/src/vite/discovery/prerender-collection.ts +192 -99
  335. package/src/vite/discovery/route-types-writer.ts +40 -84
  336. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  337. package/src/vite/discovery/state.ts +51 -6
  338. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  339. package/src/vite/index.ts +8 -0
  340. package/src/vite/plugin-types.ts +187 -69
  341. package/src/vite/plugins/cjs-to-esm.ts +8 -18
  342. package/src/vite/plugins/client-ref-dedup.ts +16 -11
  343. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  344. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  345. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  346. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  347. package/src/vite/plugins/expose-action-id.ts +49 -98
  348. package/src/vite/plugins/expose-id-utils.ts +11 -50
  349. package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
  350. package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
  351. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
  352. package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
  353. package/src/vite/plugins/expose-internal-ids.ts +554 -317
  354. package/src/vite/plugins/performance-tracks.ts +89 -0
  355. package/src/vite/plugins/refresh-cmd.ts +89 -27
  356. package/src/vite/plugins/use-cache-transform.ts +73 -83
  357. package/src/vite/plugins/vercel-output.ts +258 -0
  358. package/src/vite/plugins/version-injector.ts +21 -25
  359. package/src/vite/plugins/version-plugin.ts +41 -20
  360. package/src/vite/plugins/virtual-entries.ts +2 -17
  361. package/src/vite/rango.ts +257 -289
  362. package/src/vite/router-discovery.ts +930 -140
  363. package/src/vite/utils/ast-handler-extract.ts +15 -31
  364. package/src/vite/utils/banner.ts +4 -4
  365. package/src/vite/utils/bundle-analysis.ts +10 -15
  366. package/src/vite/utils/client-chunks.ts +184 -0
  367. package/src/vite/utils/forward-user-plugins.ts +171 -0
  368. package/src/vite/utils/manifest-utils.ts +4 -59
  369. package/src/vite/utils/package-resolution.ts +20 -52
  370. package/src/vite/utils/prerender-utils.ts +27 -29
  371. package/src/vite/utils/shared-utils.ts +92 -42
  372. package/src/browser/action-response-classifier.ts +0 -99
  373. package/src/browser/react/use-client-cache.ts +0 -58
  374. package/src/browser/shallow.ts +0 -40
  375. package/src/handles/index.ts +0 -7
  376. package/src/router/middleware-cookies.ts +0 -55
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Debug logging for the Rango Vite plugin.
3
+ *
4
+ * Thin wrapper over the `debug` package (the same one Vite uses for its
5
+ * own `vite:*` namespaces). Enable with either:
6
+ *
7
+ * DEBUG='rango:*' vite dev
8
+ * vite --debug rango:* # vite prepends `vite:`, we bridge it
9
+ *
10
+ * Returns `undefined` when no matching namespace is enabled, so call sites
11
+ * can guard expensive diagnostics with a simple truthiness check:
12
+ *
13
+ * const debug = createRangoDebugger(NS.routes);
14
+ * if (debug) debug("built manifest (%d routes) in %dms", n, ms);
15
+ *
16
+ * Back-compat: INTERNAL_RANGO_DEBUG=1 still enables all rango namespaces.
17
+ *
18
+ * Vite CLI note: `vite --debug <feat>` rewrites to `DEBUG=vite:<feat>` — it
19
+ * always prefixes with `vite:` and cannot enable bare `rango:*` namespaces.
20
+ * We work around this by registering a shadow `vite:rango:*` instance for
21
+ * each debugger, so either invocation works.
22
+ */
23
+
24
+ import debugFactory from "debug";
25
+
26
+ /**
27
+ * Canonical debug namespaces. Import as `NS.xxx` instead of string literals
28
+ * so typos become type errors and the full set lives in one place.
29
+ */
30
+ export const NS = {
31
+ config: "rango:config",
32
+ discovery: "rango:discovery",
33
+ routes: "rango:routes",
34
+ prerender: "rango:prerender",
35
+ build: "rango:build",
36
+ dev: "rango:dev",
37
+ transform: "rango:transform",
38
+ chunks: "rango:chunks",
39
+ } as const;
40
+
41
+ // Back-compat: the legacy INTERNAL_RANGO_DEBUG env var enabled per-site
42
+ // console.logs in this plugin. Map it to `rango:*` so those call sites can
43
+ // be migrated to the `debug` pipeline without breaking existing setups.
44
+ // Uses debug.enable() rather than mutating process.env because the `debug`
45
+ // package already snapshotted DEBUG when it was imported above.
46
+ if (process.env.INTERNAL_RANGO_DEBUG) {
47
+ const existing = debugFactory.disable();
48
+ debugFactory.enable(existing ? `${existing},rango:*` : "rango:*");
49
+ }
50
+
51
+ export type Debugger = (formatter: string, ...args: unknown[]) => void;
52
+
53
+ export function createRangoDebugger(namespace: string): Debugger | undefined {
54
+ const primary = debugFactory(namespace);
55
+ // Shadow namespace so `vite --debug rango:*` (which expands to
56
+ // DEBUG=vite:rango:*) and `vite --debug` (DEBUG=vite:*) both pick us up.
57
+ const shadow = debugFactory(`vite:${namespace}`);
58
+ if (primary.enabled) return primary as Debugger;
59
+ if (shadow.enabled) return shadow as Debugger;
60
+ return undefined;
61
+ }
62
+
63
+ /**
64
+ * Measure an async block and log its duration via `debug`. No-ops (still
65
+ * runs `fn`) when the namespace is disabled, so production cost is a single
66
+ * `.enabled` check per call.
67
+ *
68
+ * await timed(debug, "discover routers", () => discoverRouters(state));
69
+ */
70
+ export async function timed<T>(
71
+ debug: Debugger | undefined,
72
+ label: string,
73
+ fn: () => T | Promise<T>,
74
+ ): Promise<T> {
75
+ if (!debug) return await fn();
76
+ const start = performance.now();
77
+ try {
78
+ return await fn();
79
+ } finally {
80
+ debug("%s (%sms)", label, (performance.now() - start).toFixed(1));
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Synchronous variant of `timed`. Use for sync call sites — wrapping them
86
+ * with the async `timed` would create a floating promise that discards any
87
+ * throw, bypassing the surrounding try/catch.
88
+ */
89
+ export function timedSync<T>(
90
+ debug: Debugger | undefined,
91
+ label: string,
92
+ fn: () => T,
93
+ ): T {
94
+ if (!debug) return fn();
95
+ const start = performance.now();
96
+ try {
97
+ return fn();
98
+ } finally {
99
+ debug("%s (%sms)", label, (performance.now() - start).toFixed(1));
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Aggregate counter for high-frequency call sites (typically Vite
105
+ * `transform` hooks that run on many files). Per-call logging would
106
+ * drown real signal; this collects totals and reports once on flush.
107
+ *
108
+ * const counter = createCounter(debug, "use-cache-transform");
109
+ * // inside transform():
110
+ * return counter?.time(id, () => doWork()) ?? doWork();
111
+ * // or manually:
112
+ * counter?.record(id, ms);
113
+ * // flush on buildEnd (counter resets, so multi-env builds each get
114
+ * // their own summary line):
115
+ * counter?.flush();
116
+ *
117
+ * Returns `undefined` when the namespace is disabled so call sites pay
118
+ * nothing when off.
119
+ */
120
+ export interface Counter {
121
+ record(file: string, ms: number): void;
122
+ /**
123
+ * Convenience: time a sync or async block and record it. Propagates
124
+ * throws; records regardless of outcome. Returns the function's result.
125
+ */
126
+ time<T>(file: string, fn: () => T): T;
127
+ time<T>(file: string, fn: () => Promise<T>): Promise<T>;
128
+ flush(): void;
129
+ }
130
+
131
+ export function createCounter(
132
+ debug: Debugger | undefined,
133
+ label: string,
134
+ ): Counter | undefined {
135
+ if (!debug) return undefined;
136
+ let n = 0;
137
+ let totalMs = 0;
138
+ let slowestMs = 0;
139
+ let slowestFile = "";
140
+ const record = (file: string, ms: number): void => {
141
+ n++;
142
+ totalMs += ms;
143
+ if (ms > slowestMs) {
144
+ slowestMs = ms;
145
+ slowestFile = file;
146
+ }
147
+ };
148
+ return {
149
+ record,
150
+ time<T>(file: string, fn: () => T | Promise<T>): T | Promise<T> {
151
+ const start = performance.now();
152
+ let out: T | Promise<T>;
153
+ try {
154
+ out = fn();
155
+ } catch (err) {
156
+ record(file, performance.now() - start);
157
+ throw err;
158
+ }
159
+ if (out && typeof (out as any).then === "function") {
160
+ return (out as Promise<T>).finally(() =>
161
+ record(file, performance.now() - start),
162
+ );
163
+ }
164
+ record(file, performance.now() - start);
165
+ return out;
166
+ },
167
+ flush(): void {
168
+ if (n === 0) return;
169
+ debug(
170
+ "%s: %d files, %sms total, slowest %sms %s",
171
+ label,
172
+ n,
173
+ totalMs.toFixed(1),
174
+ slowestMs.toFixed(1),
175
+ slowestFile,
176
+ );
177
+ // Reset so buildEnd firing once per environment (Vite 6+ multi-env)
178
+ // gives one log line per env rather than silently dropping later data.
179
+ n = 0;
180
+ totalMs = 0;
181
+ slowestMs = 0;
182
+ slowestFile = "";
183
+ },
184
+ };
185
+ }
@@ -9,6 +9,7 @@ import { resolve } from "node:path";
9
9
  import { readFileSync, writeFileSync, existsSync } from "node:fs";
10
10
  import { evictHandlerCode } from "../utils/bundle-analysis.js";
11
11
  import { copyStagedBuildAssets } from "../utils/prerender-utils.js";
12
+ import { jsonParseExpression } from "../utils/manifest-utils.js";
12
13
  import type { DiscoveryState } from "./state.js";
13
14
 
14
15
  /**
@@ -31,25 +32,25 @@ export function postprocessBundle(state: DiscoveryState): void {
31
32
  state.rscEntryFileName ?? "index.js",
32
33
  );
33
34
 
34
- // 1. Evict handler code from __prerender-handlers and __static-handlers chunks.
35
- // handlerChunkInfo/staticHandlerChunkInfo are populated by generateBundle
35
+ // 1. Evict handler code from whichever chunks contain handler exports.
36
+ // handlerChunkInfoMap/staticHandlerChunkInfoMap are populated by generateBundle
36
37
  // after the production RSC build. In Vite 6 multi-environment builds, the
37
- // RSC build runs twice (analysis + production). Chunk info is only available
38
- // after the production pass, so we run eviction whenever it becomes available.
38
+ // RSC build runs twice (analysis + production). The maps are cleared at the
39
+ // start of each generateBundle pass so only production data is used here.
39
40
  const evictionTargets: Array<{
40
- info: typeof state.handlerChunkInfo;
41
+ infos: Iterable<import("./state.js").ChunkInfo>;
41
42
  fnName: string;
42
43
  brand: string;
43
44
  label: string;
44
45
  }> = [
45
46
  {
46
- info: state.handlerChunkInfo,
47
+ infos: state.handlerChunkInfoMap.values(),
47
48
  fnName: "Prerender",
48
49
  brand: "prerenderHandler",
49
50
  label: "handler code from RSC bundle",
50
51
  },
51
52
  {
52
- info: state.staticHandlerChunkInfo,
53
+ infos: state.staticHandlerChunkInfoMap.values(),
53
54
  fnName: "Static",
54
55
  brand: "staticHandler",
55
56
  label: "static handler code",
@@ -57,35 +58,32 @@ export function postprocessBundle(state: DiscoveryState): void {
57
58
  ];
58
59
 
59
60
  for (const target of evictionTargets) {
60
- if (!target.info) continue;
61
- const chunkPath = resolve(
62
- state.projectRoot,
63
- "dist/rsc",
64
- target.info.fileName,
65
- );
66
- try {
67
- const code = readFileSync(chunkPath, "utf-8");
68
- const result = evictHandlerCode(
69
- code,
70
- target.info.exports,
71
- target.fnName,
72
- target.brand,
73
- );
74
- if (result) {
75
- writeFileSync(chunkPath, result.code);
76
- const savedKB = (result.savedBytes / 1024).toFixed(1);
77
- console.log(
78
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${target.info.fileName}`,
61
+ for (const info of target.infos) {
62
+ const chunkPath = resolve(state.projectRoot, "dist/rsc", info.fileName);
63
+ try {
64
+ const code = readFileSync(chunkPath, "utf-8");
65
+ const result = evictHandlerCode(
66
+ code,
67
+ info.exports,
68
+ target.fnName,
69
+ target.brand,
70
+ );
71
+ if (result) {
72
+ writeFileSync(chunkPath, result.code);
73
+ const savedKB = (result.savedBytes / 1024).toFixed(1);
74
+ console.log(
75
+ `[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
76
+ );
77
+ }
78
+ } catch (replaceErr: any) {
79
+ console.warn(
80
+ `[rango] Failed to evict ${target.label}: ${replaceErr.message}`,
79
81
  );
80
82
  }
81
- } catch (replaceErr: any) {
82
- console.warn(
83
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`,
84
- );
85
83
  }
86
84
  }
87
- state.handlerChunkInfo = null;
88
- state.staticHandlerChunkInfo = null;
85
+ state.handlerChunkInfoMap.clear();
86
+ state.staticHandlerChunkInfoMap.clear();
89
87
 
90
88
  // 2. Write prerender data as separate importable asset modules
91
89
  // and inject a lazy manifest loader into the RSC entry.
@@ -107,7 +105,7 @@ export function postprocessBundle(state: DiscoveryState): void {
107
105
  }
108
106
 
109
107
  const manifestCode = [
110
- `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
108
+ `const m=${jsonParseExpression(manifestMap)};`,
111
109
  `export function loadPrerenderAsset(s){return import(s)}`,
112
110
  `export default m;`,
113
111
  "",
@@ -124,11 +122,11 @@ export function postprocessBundle(state: DiscoveryState): void {
124
122
 
125
123
  const totalKB = (totalBytes / 1024).toFixed(1);
126
124
  console.log(
127
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries!).length} entries)`,
125
+ `[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries!).length} entries)`,
128
126
  );
129
127
  } catch (err: any) {
130
128
  throw new Error(
131
- `[rsc-router] Failed to write prerender assets: ${err.message}`,
129
+ `[rango] Failed to write prerender assets: ${err.message}`,
132
130
  );
133
131
  }
134
132
  }
@@ -138,7 +136,7 @@ export function postprocessBundle(state: DiscoveryState): void {
138
136
  // and inject a __STATIC_MANIFEST import into the RSC entry.
139
137
  if (hasStaticData && existsSync(rscEntryPath)) {
140
138
  const rscCode = readFileSync(rscEntryPath, "utf-8");
141
- if (!rscCode.includes("__STATIC_MANIFEST")) {
139
+ if (!rscCode.includes("__static-manifest.js")) {
142
140
  try {
143
141
  const manifestEntries: string[] = [];
144
142
  let totalBytes = copyStagedBuildAssets(
@@ -172,11 +170,11 @@ export function postprocessBundle(state: DiscoveryState): void {
172
170
 
173
171
  const totalKB = (totalBytes / 1024).toFixed(1);
174
172
  console.log(
175
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries!).length} entries)`,
173
+ `[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries!).length} entries)`,
176
174
  );
177
175
  } catch (err: any) {
178
176
  throw new Error(
179
- `[rsc-router] Failed to write static assets: ${err.message}`,
177
+ `[rango] Failed to write static assets: ${err.message}`,
180
178
  );
181
179
  }
182
180
  }
@@ -11,6 +11,16 @@ import {
11
11
  formatNestedRouterConflictError,
12
12
  findNestedRouterConflict,
13
13
  } from "../../build/generate-route-types.js";
14
+ // Pure data transforms over generateManifestFull's output. Imported directly
15
+ // from source (not the public ./build barrel, and not the runner) because they
16
+ // are realm-independent: buildRouteTrie/buildPerRouterTrie operate on plain
17
+ // manifest data, and collectFallbackClientRefs keys on the global-registry
18
+ // Symbol.for("react.client.reference"), so it detects client references in a
19
+ // boundary tree regardless of which realm imported the walker. Only
20
+ // generateManifestFull must stay on the runner (it invokes user handlers via
21
+ // RangoContext from the runner realm) — see the runner.import below.
22
+ import { buildRouteTrie, buildPerRouterTrie } from "../../build/route-trie.js";
23
+ import { collectFallbackClientRefs } from "../../build/collect-fallback-refs.js";
14
24
  import {
15
25
  flattenLeafEntries,
16
26
  buildRouteToStaticPrefix,
@@ -20,6 +30,15 @@ import {
20
30
  expandPrerenderRoutes,
21
31
  renderStaticHandlers,
22
32
  } from "./prerender-collection.js";
33
+ import {
34
+ resolveHostRouterHandlers,
35
+ DiscoveryError,
36
+ type CaughtDiscoveryError,
37
+ } from "./discovery-errors.js";
38
+ import { createRangoDebugger, timed, NS } from "../debug.js";
39
+ import { computeProductionHash } from "../plugins/client-ref-hashing.js";
40
+
41
+ const debug = createRangoDebugger(NS.discovery);
23
42
 
24
43
  /**
25
44
  * Import the user's entry via RSC runner, generate manifests for each
@@ -38,41 +57,40 @@ export async function discoverRouters(
38
57
  // Import the entry file via RSC environment.
39
58
  // For node preset: this is the router file (createRouter() registers in RouterRegistry).
40
59
  // For cloudflare preset: this is the worker entry (which imports the router).
41
- await rscEnv.runner.import(state.resolvedEntryPath);
60
+ await timed(debug, "inner: import entry", () =>
61
+ rscEnv.runner.import(state.resolvedEntryPath),
62
+ );
42
63
 
43
64
  // Import the router package to access the registry
44
- const serverMod = await rscEnv.runner.import("@rangojs/router/server");
65
+ const serverMod = await timed(
66
+ debug,
67
+ "inner: import @rangojs/router/server",
68
+ () => rscEnv.runner.import("@rangojs/router/server"),
69
+ );
45
70
  let registry: Map<string, any> = serverMod.RouterRegistry;
46
71
 
47
72
  if (!registry || registry.size === 0) {
48
73
  // No RSC routers found directly. Check for host routers with lazy handlers
49
74
  // that need to be resolved to trigger sub-app createRouter() calls.
75
+ //
76
+ // Handler failures are collected rather than swallowed: when the registry
77
+ // is still empty afterwards, these errors (typically a sub-app whose router
78
+ // module failed to import) are the most likely cause and are surfaced in
79
+ // the terminal "No routers found" error below.
80
+ const discoveryErrors: CaughtDiscoveryError[] = [];
50
81
  try {
51
82
  const hostRegistry: Map<string, any> | undefined =
52
83
  serverMod.HostRouterRegistry;
53
84
 
54
85
  if (hostRegistry && hostRegistry.size > 0) {
55
86
  console.log(
56
- `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
87
+ `[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
57
88
  );
58
89
 
59
- for (const [, entry] of hostRegistry) {
60
- for (const route of entry.routes) {
61
- if (typeof route.handler === "function") {
62
- try {
63
- await route.handler();
64
- } catch {
65
- // Lazy handler may fail in temp server context, that's OK
66
- }
67
- }
68
- }
69
- if (entry.fallback && typeof entry.fallback.handler === "function") {
70
- try {
71
- await entry.fallback.handler();
72
- } catch {
73
- // Fallback handler may fail in temp server context
74
- }
75
- }
90
+ const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
91
+ discoveryErrors.push(...handlerErrors);
92
+ for (const { context, error } of handlerErrors) {
93
+ debug?.("caught error while resolving %s: %O", context, error);
76
94
  }
77
95
 
78
96
  // Re-read RouterRegistry - sub-app createRouter() calls should have populated it
@@ -87,22 +105,31 @@ export async function discoverRouters(
87
105
  registry = freshRegistry;
88
106
  }
89
107
  }
90
- } catch {
91
- // Host-router discovery is best-effort; skip if unavailable
108
+ } catch (error) {
109
+ // Host-router discovery is best-effort; record the failure so it can be
110
+ // surfaced if no routers are found.
111
+ discoveryErrors.push({ context: "host-router discovery", error });
92
112
  }
93
113
 
94
114
  // If still no routers after host router resolution, fail
95
115
  if (!registry || registry.size === 0) {
96
- throw new Error(
97
- `[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`,
98
- );
116
+ throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
99
117
  }
100
118
  }
101
119
 
102
- // Import build utilities for manifest generation
103
- const buildMod = await rscEnv.runner.import("@rangojs/router/build");
120
+ // generateManifestFull must run in the RSC runner realm: it invokes the
121
+ // user's urlpatterns.handler() via RangoContext, consuming router instances
122
+ // from the runner. The trie/fallback-ref builders are pure transforms over
123
+ // its output and are imported directly from source above.
124
+ const buildMod = await timed(
125
+ debug,
126
+ "inner: import @rangojs/router/build",
127
+ () => rscEnv.runner.import("@rangojs/router/build"),
128
+ );
104
129
  const generateManifestFull = buildMod.generateManifestFull;
105
130
 
131
+ debug?.("inner: found %d router(s) in registry", registry.size);
132
+
106
133
  const nestedRouterConflict = findNestedRouterConflict(
107
134
  [...registry.values()]
108
135
  .map((router) => router.__sourceFile)
@@ -130,14 +157,55 @@ export async function discoverRouters(
130
157
  // Collect all manifests for trie building (avoid re-running generateManifest)
131
158
  const allManifests: Array<{ id: string; manifest: any }> = [];
132
159
 
160
+ // Built-in clientChunks context (present only when the built-in strategy is
161
+ // active). Collect the production hashes of "use client" error/notFound
162
+ // fallback modules so the strategy can route them into app-fallback.
163
+ const clientChunkCtx = state.opts?.clientChunkCtx;
164
+ const collectClientFallbackRef = clientChunkCtx
165
+ ? (refKey: string) =>
166
+ clientChunkCtx.fallbackRefs.add(
167
+ computeProductionHash(state.projectRoot, refKey),
168
+ )
169
+ : undefined;
170
+ // Router-level boundary defaults (`createRouter({ defaultErrorBoundary, ... })`)
171
+ // are NOT in EntryData, so generateManifestFull's walk misses them. Collect any
172
+ // "use client" default boundary directly off the router instance. The value is
173
+ // commonly a handler function wrapping the client boundary in server providers,
174
+ // so collectFallbackClientRefs invokes + walks the tree. The walker keys on the
175
+ // global-registry Symbol.for("react.client.reference"), so it detects client
176
+ // references in a runner-realm boundary tree even when imported here directly.
177
+ const collectFromBoundaryNode = (node: unknown): void => {
178
+ if (collectClientFallbackRef) {
179
+ collectFallbackClientRefs(node, collectClientFallbackRef);
180
+ }
181
+ };
182
+
183
+ const manifestGenStart = debug ? performance.now() : 0;
133
184
  for (const [id, router] of registry) {
134
185
  if (!router.urlpatterns || !generateManifestFull) {
135
186
  continue;
136
187
  }
137
188
 
138
- const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
189
+ const manifest = generateManifestFull(
190
+ router.urlpatterns,
191
+ routerMountIndex,
192
+ {
193
+ ...(router.__basename ? { urlPrefix: router.__basename } : {}),
194
+ ...(collectClientFallbackRef ? { collectClientFallbackRef } : {}),
195
+ },
196
+ );
139
197
  routerMountIndex++;
140
198
  allManifests.push({ id, manifest });
199
+
200
+ // Router-level "use client" boundary defaults -> app-fallback (the
201
+ // route-tree errorBoundary()/notFoundBoundary() helpers are already
202
+ // collected inside generateManifestFull via collectClientFallbackRef).
203
+ if (collectClientFallbackRef) {
204
+ collectFromBoundaryNode(router.__defaultErrorBoundary);
205
+ collectFromBoundaryNode(router.__defaultNotFoundBoundary);
206
+ collectFromBoundaryNode(router.__notFound);
207
+ }
208
+
141
209
  const routeCount = Object.keys(manifest.routeManifest).length;
142
210
  const staticRoutes = Object.values(manifest.routeManifest).filter(
143
211
  (p: any) => !p.includes(":") && !p.includes("*"),
@@ -189,24 +257,23 @@ export async function discoverRouters(
189
257
  // Flatten prefix tree leaf nodes into precomputed entries.
190
258
  // Leaf nodes (no children) can have their routes used directly by
191
259
  // evaluateLazyEntry() without running the handler at runtime.
260
+ // Walk once into a per-router array, then fold it into the merged array;
261
+ // the merged and per-router entries are identical, so a second walk is
262
+ // redundant. Append order is preserved within and across routers.
263
+ const routerPrecomputed: PrecomputedEntry[] = [];
192
264
  flattenLeafEntries(
193
265
  manifest.prefixTree,
194
266
  manifest.routeManifest,
195
- newMergedPrecomputedEntries,
267
+ routerPrecomputed,
196
268
  );
269
+ newMergedPrecomputedEntries.push(...routerPrecomputed);
197
270
 
198
271
  // Store per-router manifest and precomputed entries for isolated virtual modules.
199
272
  newPerRouterManifestDataMap.set(id, manifest.routeManifest);
200
- const routerPrecomputed: PrecomputedEntry[] = [];
201
- flattenLeafEntries(
202
- manifest.prefixTree,
203
- manifest.routeManifest,
204
- routerPrecomputed,
205
- );
206
273
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
207
274
 
208
275
  console.log(
209
- `[rsc-router] Router "${id}" -> ${routeCount} routes ` +
276
+ `[rango] Router "${id}" -> ${routeCount} routes ` +
210
277
  `(${staticRoutes} static, ${dynamicRoutes} dynamic)`,
211
278
  );
212
279
  }
@@ -222,7 +289,7 @@ export async function discoverRouters(
222
289
  );
223
290
  if (autoIds.length > 1) {
224
291
  console.warn(
225
- `[rsc-router] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
292
+ `[rango] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
226
293
  `In multi-router setups, each createRouter() must have an explicit \`id\` option ` +
227
294
  `to ensure per-router manifest data is matched correctly at runtime. ` +
228
295
  `Example: createRouter({ id: "site", ... })`,
@@ -230,11 +297,17 @@ export async function discoverRouters(
230
297
  }
231
298
  }
232
299
 
300
+ debug?.(
301
+ "inner: generated manifests for %d router(s) (%sms)",
302
+ allManifests.length,
303
+ (performance.now() - manifestGenStart).toFixed(1),
304
+ );
305
+
233
306
  // Build route trie from merged manifest + ancestry
234
307
  let newMergedRouteTrie: any = null;
308
+ const trieStart = debug ? performance.now() : 0;
235
309
  if (Object.keys(newMergedRouteManifest).length > 0) {
236
- const buildRouteTrie = buildMod.buildRouteTrie;
237
- if (buildRouteTrie && mergedRouteAncestry) {
310
+ if (mergedRouteAncestry) {
238
311
  // Build routeToStaticPrefix from saved manifests
239
312
  const routeToStaticPrefix: Record<string, string> = {};
240
313
  for (const { manifest } of allManifests) {
@@ -267,64 +340,36 @@ export async function discoverRouters(
267
340
  }
268
341
  }
269
342
 
343
+ // buildRouteTrie reads these via ?.has / ?.[] — empty is observationally
344
+ // identical to undefined, so no empty->undefined coercion is needed.
270
345
  newMergedRouteTrie = buildRouteTrie(
271
346
  newMergedRouteManifest,
272
347
  mergedRouteAncestry,
273
348
  routeToStaticPrefix,
274
- Object.keys(mergedRouteTrailingSlash).length > 0
275
- ? mergedRouteTrailingSlash
276
- : undefined,
277
- prerenderRouteNames.size > 0 ? prerenderRouteNames : undefined,
278
- passthroughRouteNames.size > 0 ? passthroughRouteNames : undefined,
279
- Object.keys(mergedResponseTypeRoutes).length > 0
280
- ? mergedResponseTypeRoutes
281
- : undefined,
349
+ mergedRouteTrailingSlash,
350
+ prerenderRouteNames,
351
+ passthroughRouteNames,
352
+ mergedResponseTypeRoutes,
282
353
  );
283
354
 
284
- // Build per-router tries for multi-router isolation.
355
+ // Build per-router tries for multi-router isolation. Uses the single
356
+ // shared buildPerRouterTrie so the production serialized trie is built by
357
+ // exactly the same code as the dev/HMR runtime rebuild (manifest-init.ts).
358
+ // Returns null for route-less manifests (route-trie.ts).
285
359
  for (const { id, manifest } of allManifests) {
286
- if (
287
- !manifest._routeAncestry ||
288
- Object.keys(manifest._routeAncestry).length === 0
289
- )
290
- continue;
291
- const perRouterStaticPrefix: Record<string, string> = {};
292
- for (const name of Object.keys(manifest.routeManifest)) {
293
- perRouterStaticPrefix[name] = "";
360
+ const perRouterTrie = buildPerRouterTrie(manifest);
361
+ if (perRouterTrie) {
362
+ newPerRouterTrieMap.set(id, perRouterTrie);
294
363
  }
295
- buildRouteToStaticPrefix(manifest.prefixTree, perRouterStaticPrefix);
296
-
297
- const perRouterPrerenderNames = manifest.prerenderRoutes
298
- ? new Set<string>(manifest.prerenderRoutes)
299
- : undefined;
300
- const perRouterPassthroughNames = manifest.passthroughRoutes
301
- ? new Set<string>(manifest.passthroughRoutes)
302
- : undefined;
303
-
304
- const perRouterTrie = buildRouteTrie(
305
- manifest.routeManifest,
306
- manifest._routeAncestry,
307
- perRouterStaticPrefix,
308
- manifest.routeTrailingSlash &&
309
- Object.keys(manifest.routeTrailingSlash).length > 0
310
- ? manifest.routeTrailingSlash
311
- : undefined,
312
- perRouterPrerenderNames && perRouterPrerenderNames.size > 0
313
- ? perRouterPrerenderNames
314
- : undefined,
315
- perRouterPassthroughNames && perRouterPassthroughNames.size > 0
316
- ? perRouterPassthroughNames
317
- : undefined,
318
- manifest.responseTypeRoutes &&
319
- Object.keys(manifest.responseTypeRoutes).length > 0
320
- ? manifest.responseTypeRoutes
321
- : undefined,
322
- );
323
- newPerRouterTrieMap.set(id, perRouterTrie);
324
364
  }
325
365
  }
326
366
  }
327
367
 
368
+ debug?.(
369
+ "inner: trie build done (%sms)",
370
+ (performance.now() - trieStart).toFixed(1),
371
+ );
372
+
328
373
  // Commit all local state to the shared discovery state atomically.
329
374
  // This ensures a failed re-discovery (e.g. from a transient module
330
375
  // evaluation error) preserves the last known-good state.