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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Bundle Post-Processing
3
+ *
4
+ * Handles handler code eviction from prerender/static chunks and
5
+ * injection of collected prerender/static data into the RSC entry bundle.
6
+ */
7
+
8
+ import { resolve } from "node:path";
9
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
10
+ import { evictHandlerCode } from "../utils/bundle-analysis.js";
11
+ import { copyStagedBuildAssets } from "../utils/prerender-utils.js";
12
+ import type { DiscoveryState } from "./state.js";
13
+
14
+ /**
15
+ * Post-process the RSC bundle: evict handler code and inject
16
+ * prerender/static data as importable asset modules.
17
+ */
18
+ export function postprocessBundle(state: DiscoveryState): void {
19
+ const hasPrerenderData =
20
+ state.prerenderManifestEntries &&
21
+ Object.keys(state.prerenderManifestEntries).length > 0;
22
+ const hasStaticData =
23
+ state.staticManifestEntries &&
24
+ Object.keys(state.staticManifestEntries).length > 0;
25
+ if (!hasPrerenderData && !hasStaticData) return;
26
+
27
+ // Find RSC entry (recorded in generateBundle, fallback to dist/rsc/index.js)
28
+ const rscEntryPath = resolve(
29
+ state.projectRoot,
30
+ "dist/rsc",
31
+ state.rscEntryFileName ?? "index.js",
32
+ );
33
+
34
+ // 1. Evict handler code from whichever chunks contain handler exports.
35
+ // handlerChunkInfoMap/staticHandlerChunkInfoMap are populated by generateBundle
36
+ // after the production RSC build. In Vite 6 multi-environment builds, the
37
+ // RSC build runs twice (analysis + production). The maps are cleared at the
38
+ // start of each generateBundle pass so only production data is used here.
39
+ const evictionTargets: Array<{
40
+ infos: Iterable<import("./state.js").ChunkInfo>;
41
+ fnName: string;
42
+ brand: string;
43
+ label: string;
44
+ }> = [
45
+ {
46
+ infos: state.handlerChunkInfoMap.values(),
47
+ fnName: "Prerender",
48
+ brand: "prerenderHandler",
49
+ label: "handler code from RSC bundle",
50
+ },
51
+ {
52
+ infos: state.staticHandlerChunkInfoMap.values(),
53
+ fnName: "Static",
54
+ brand: "staticHandler",
55
+ label: "static handler code",
56
+ },
57
+ ];
58
+
59
+ for (const target of evictionTargets) {
60
+ for (const info of target.infos) {
61
+ const chunkPath = resolve(state.projectRoot, "dist/rsc", info.fileName);
62
+ try {
63
+ const code = readFileSync(chunkPath, "utf-8");
64
+ const result = evictHandlerCode(
65
+ code,
66
+ info.exports,
67
+ target.fnName,
68
+ target.brand,
69
+ );
70
+ if (result) {
71
+ writeFileSync(chunkPath, result.code);
72
+ const savedKB = (result.savedBytes / 1024).toFixed(1);
73
+ console.log(
74
+ `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
75
+ );
76
+ }
77
+ } catch (replaceErr: any) {
78
+ console.warn(
79
+ `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`,
80
+ );
81
+ }
82
+ }
83
+ }
84
+ state.handlerChunkInfoMap.clear();
85
+ state.staticHandlerChunkInfoMap.clear();
86
+
87
+ // 2. Write prerender data as separate importable asset modules
88
+ // and inject a lazy manifest loader into the RSC entry.
89
+ if (hasPrerenderData && existsSync(rscEntryPath)) {
90
+ const rscCode = readFileSync(rscEntryPath, "utf-8");
91
+ // Check for the specific injection marker to avoid double-injection.
92
+ if (!rscCode.includes("__prerender-manifest.js")) {
93
+ try {
94
+ let totalBytes = copyStagedBuildAssets(
95
+ state.projectRoot,
96
+ Object.values(state.prerenderManifestEntries!),
97
+ );
98
+
99
+ const manifestMap: Record<string, string> = {};
100
+ for (const [key, assetFileName] of Object.entries(
101
+ state.prerenderManifestEntries!,
102
+ )) {
103
+ manifestMap[key] = `./assets/${assetFileName}`;
104
+ }
105
+
106
+ const manifestCode = [
107
+ `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
108
+ `export function loadPrerenderAsset(s){return import(s)}`,
109
+ `export default m;`,
110
+ "",
111
+ ].join("\n");
112
+ const manifestPath = resolve(
113
+ state.projectRoot,
114
+ "dist/rsc/__prerender-manifest.js",
115
+ );
116
+ writeFileSync(manifestPath, manifestCode);
117
+ totalBytes += Buffer.byteLength(manifestCode);
118
+
119
+ const injection = `globalThis.__loadPrerenderManifestModule = () => import("./__prerender-manifest.js");\n`;
120
+ writeFileSync(rscEntryPath, injection + rscCode);
121
+
122
+ const totalKB = (totalBytes / 1024).toFixed(1);
123
+ console.log(
124
+ `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries!).length} entries)`,
125
+ );
126
+ } catch (err: any) {
127
+ throw new Error(
128
+ `[rsc-router] Failed to write prerender assets: ${err.message}`,
129
+ );
130
+ }
131
+ }
132
+ }
133
+
134
+ // 3. Write static handler data as separate importable asset modules
135
+ // and inject a __STATIC_MANIFEST import into the RSC entry.
136
+ if (hasStaticData && existsSync(rscEntryPath)) {
137
+ const rscCode = readFileSync(rscEntryPath, "utf-8");
138
+ if (!rscCode.includes("__static-manifest.js")) {
139
+ try {
140
+ const manifestEntries: string[] = [];
141
+ let totalBytes = copyStagedBuildAssets(
142
+ state.projectRoot,
143
+ Object.values(state.staticManifestEntries!),
144
+ );
145
+
146
+ for (const [handlerId, assetFileName] of Object.entries(
147
+ state.staticManifestEntries!,
148
+ )) {
149
+ manifestEntries.push(
150
+ `${JSON.stringify(handlerId)}:()=>import("./assets/${assetFileName}")`,
151
+ );
152
+ }
153
+
154
+ // Set the global inside the manifest module so it is assigned
155
+ // during module evaluation (before dependent modules like
156
+ // segment-resolution.ts run their top-level initializers).
157
+ const manifestCode = `const m={${manifestEntries.join(",")}};globalThis.__STATIC_MANIFEST=m;export default m;\n`;
158
+ const manifestPath = resolve(
159
+ state.projectRoot,
160
+ "dist/rsc/__static-manifest.js",
161
+ );
162
+ writeFileSync(manifestPath, manifestCode);
163
+ totalBytes += Buffer.byteLength(manifestCode);
164
+
165
+ // The import ensures the manifest module is evaluated early.
166
+ // The global is already set inside the module itself.
167
+ const injection = `import "./__static-manifest.js";\n`;
168
+ writeFileSync(rscEntryPath, injection + rscCode);
169
+
170
+ const totalKB = (totalBytes / 1024).toFixed(1);
171
+ console.log(
172
+ `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries!).length} entries)`,
173
+ );
174
+ } catch (err: any) {
175
+ throw new Error(
176
+ `[rsc-router] Failed to write static assets: ${err.message}`,
177
+ );
178
+ }
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Router Discovery
3
+ *
4
+ * Core discovery logic: imports the user's entry file via the RSC
5
+ * environment's module runner, generates manifests for each discovered
6
+ * router, and builds route tries for O(path_length) matching.
7
+ */
8
+
9
+ import {
10
+ buildCombinedRouteMapForRouterFile,
11
+ formatNestedRouterConflictError,
12
+ findNestedRouterConflict,
13
+ } from "../../build/generate-route-types.js";
14
+ import {
15
+ flattenLeafEntries,
16
+ buildRouteToStaticPrefix,
17
+ } from "../utils/manifest-utils.js";
18
+ import type { DiscoveryState, PrecomputedEntry } from "./state.js";
19
+ import {
20
+ expandPrerenderRoutes,
21
+ renderStaticHandlers,
22
+ } from "./prerender-collection.js";
23
+ import { createRangoDebugger, timed, NS } from "../debug.js";
24
+
25
+ const debug = createRangoDebugger(NS.discovery);
26
+
27
+ /**
28
+ * Import the user's entry via RSC runner, generate manifests for each
29
+ * discovered router, build route tries, and optionally run prerender
30
+ * expansion and static handler rendering (build mode only).
31
+ *
32
+ * Returns the imported `@rangojs/router/server` module so the caller
33
+ * can access the RouterRegistry and manifest setters.
34
+ */
35
+ export async function discoverRouters(
36
+ state: DiscoveryState,
37
+ rscEnv: any,
38
+ ): Promise<any> {
39
+ if (!state.resolvedEntryPath) return;
40
+
41
+ // Import the entry file via RSC environment.
42
+ // For node preset: this is the router file (createRouter() registers in RouterRegistry).
43
+ // For cloudflare preset: this is the worker entry (which imports the router).
44
+ await timed(debug, "inner: import entry", () =>
45
+ rscEnv.runner.import(state.resolvedEntryPath),
46
+ );
47
+
48
+ // Import the router package to access the registry
49
+ const serverMod = await timed(
50
+ debug,
51
+ "inner: import @rangojs/router/server",
52
+ () => rscEnv.runner.import("@rangojs/router/server"),
53
+ );
54
+ let registry: Map<string, any> = serverMod.RouterRegistry;
55
+
56
+ if (!registry || registry.size === 0) {
57
+ // No RSC routers found directly. Check for host routers with lazy handlers
58
+ // that need to be resolved to trigger sub-app createRouter() calls.
59
+ try {
60
+ const hostRegistry: Map<string, any> | undefined =
61
+ serverMod.HostRouterRegistry;
62
+
63
+ if (hostRegistry && hostRegistry.size > 0) {
64
+ console.log(
65
+ `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
66
+ );
67
+
68
+ for (const [, entry] of hostRegistry) {
69
+ for (const route of entry.routes) {
70
+ if (typeof route.handler === "function") {
71
+ try {
72
+ await route.handler();
73
+ } catch {
74
+ // Lazy handler may fail in temp server context, that's OK
75
+ }
76
+ }
77
+ }
78
+ if (entry.fallback && typeof entry.fallback.handler === "function") {
79
+ try {
80
+ await entry.fallback.handler();
81
+ } catch {
82
+ // Fallback handler may fail in temp server context
83
+ }
84
+ }
85
+ }
86
+
87
+ // Re-read RouterRegistry - sub-app createRouter() calls should have populated it
88
+ const freshServerMod = await rscEnv.runner.import(
89
+ "@rangojs/router/server",
90
+ );
91
+ const freshRegistry: Map<string, any> = freshServerMod.RouterRegistry;
92
+
93
+ if (freshRegistry && freshRegistry.size > 0) {
94
+ // Update references so the manifest generation below uses the fresh data
95
+ Object.assign(serverMod, freshServerMod);
96
+ registry = freshRegistry;
97
+ }
98
+ }
99
+ } catch {
100
+ // Host-router discovery is best-effort; skip if unavailable
101
+ }
102
+
103
+ // If still no routers after host router resolution, fail
104
+ if (!registry || registry.size === 0) {
105
+ throw new Error(
106
+ `[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`,
107
+ );
108
+ }
109
+ }
110
+
111
+ // Import build utilities for manifest generation
112
+ const buildMod = await timed(
113
+ debug,
114
+ "inner: import @rangojs/router/build",
115
+ () => rscEnv.runner.import("@rangojs/router/build"),
116
+ );
117
+ const generateManifestFull = buildMod.generateManifestFull;
118
+
119
+ debug?.("inner: found %d router(s) in registry", registry.size);
120
+
121
+ const nestedRouterConflict = findNestedRouterConflict(
122
+ [...registry.values()]
123
+ .map((router) => router.__sourceFile)
124
+ .filter(
125
+ (sourceFile): sourceFile is string => typeof sourceFile === "string",
126
+ ),
127
+ );
128
+ if (nestedRouterConflict) {
129
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
130
+ }
131
+
132
+ // Build into local variables first. Only commit to state after the
133
+ // full pass succeeds, so a failed re-discovery preserves the last
134
+ // known-good state instead of leaving it partially wiped.
135
+ const newMergedRouteManifest: Record<string, string> = {};
136
+ const newMergedPrecomputedEntries: PrecomputedEntry[] = [];
137
+ const newPerRouterManifests: typeof state.perRouterManifests = [];
138
+ const newPerRouterManifestDataMap = new Map<string, any>();
139
+ const newPerRouterPrecomputedMap = new Map<string, PrecomputedEntry[]>();
140
+ const newPerRouterTrieMap = new Map<string, any>();
141
+ let mergedRouteAncestry: Record<string, string[]> = {};
142
+ let mergedRouteTrailingSlash: Record<string, string> = {};
143
+
144
+ let routerMountIndex = 0;
145
+ // Collect all manifests for trie building (avoid re-running generateManifest)
146
+ const allManifests: Array<{ id: string; manifest: any }> = [];
147
+
148
+ const manifestGenStart = debug ? performance.now() : 0;
149
+ for (const [id, router] of registry) {
150
+ if (!router.urlpatterns || !generateManifestFull) {
151
+ continue;
152
+ }
153
+
154
+ const manifest = generateManifestFull(
155
+ router.urlpatterns,
156
+ routerMountIndex,
157
+ router.__basename ? { urlPrefix: router.__basename } : undefined,
158
+ );
159
+ routerMountIndex++;
160
+ allManifests.push({ id, manifest });
161
+ const routeCount = Object.keys(manifest.routeManifest).length;
162
+ const staticRoutes = Object.values(manifest.routeManifest).filter(
163
+ (p: any) => !p.includes(":") && !p.includes("*"),
164
+ ).length;
165
+ const dynamicRoutes = routeCount - staticRoutes;
166
+
167
+ // Merge into the combined manifest
168
+ Object.assign(newMergedRouteManifest, manifest.routeManifest);
169
+
170
+ // Compute factory-only prefixes: dot-prefixed groups in the runtime
171
+ // manifest that the static parser cannot see. These are routes created
172
+ // by factory functions (e.g. createDocsPatterns()) and should always be
173
+ // supplemented on file change since HMR won't re-discover them.
174
+ let factoryOnlyPrefixes: Set<string> | undefined;
175
+ if (router.__sourceFile) {
176
+ const staticParsed = buildCombinedRouteMapForRouterFile(
177
+ router.__sourceFile,
178
+ );
179
+ const staticNames = new Set(Object.keys(staticParsed.routes));
180
+ factoryOnlyPrefixes = new Set<string>();
181
+ for (const name of Object.keys(manifest.routeManifest)) {
182
+ if (staticNames.has(name)) continue;
183
+ const dotIdx = name.indexOf(".");
184
+ if (dotIdx <= 0) continue;
185
+ const prefix = name.substring(0, dotIdx + 1);
186
+ if ([...staticNames].some((n) => n.startsWith(prefix))) continue;
187
+ factoryOnlyPrefixes.add(prefix);
188
+ }
189
+ if (factoryOnlyPrefixes.size === 0) factoryOnlyPrefixes = undefined;
190
+ }
191
+
192
+ newPerRouterManifests.push({
193
+ id,
194
+ routeManifest: manifest.routeManifest,
195
+ routeSearchSchemas: manifest.routeSearchSchemas,
196
+ sourceFile: router.__sourceFile,
197
+ factoryOnlyPrefixes,
198
+ });
199
+
200
+ // Merge ancestry (internal field, used only for trie building)
201
+ if (manifest._routeAncestry) {
202
+ Object.assign(mergedRouteAncestry, manifest._routeAncestry);
203
+ }
204
+ // Merge trailing slash config
205
+ if (manifest.routeTrailingSlash) {
206
+ Object.assign(mergedRouteTrailingSlash, manifest.routeTrailingSlash);
207
+ }
208
+
209
+ // Flatten prefix tree leaf nodes into precomputed entries.
210
+ // Leaf nodes (no children) can have their routes used directly by
211
+ // evaluateLazyEntry() without running the handler at runtime.
212
+ flattenLeafEntries(
213
+ manifest.prefixTree,
214
+ manifest.routeManifest,
215
+ newMergedPrecomputedEntries,
216
+ );
217
+
218
+ // Store per-router manifest and precomputed entries for isolated virtual modules.
219
+ newPerRouterManifestDataMap.set(id, manifest.routeManifest);
220
+ const routerPrecomputed: PrecomputedEntry[] = [];
221
+ flattenLeafEntries(
222
+ manifest.prefixTree,
223
+ manifest.routeManifest,
224
+ routerPrecomputed,
225
+ );
226
+ newPerRouterPrecomputedMap.set(id, routerPrecomputed);
227
+
228
+ console.log(
229
+ `[rsc-router] Router "${id}" -> ${routeCount} routes ` +
230
+ `(${staticRoutes} static, ${dynamicRoutes} dynamic)`,
231
+ );
232
+ }
233
+
234
+ // Warn if multiple routers use auto-generated IDs (router_0, router_1, ...).
235
+ // Auto-IDs are assigned by counter and depend on module evaluation order,
236
+ // which can differ between build time and runtime (especially with dynamic
237
+ // imports in host routers). This causes per-router data to be loaded into
238
+ // the wrong router at runtime.
239
+ if (registry.size > 1) {
240
+ const autoIds = [...registry.keys()].filter((id) =>
241
+ /^router_\d+$/.test(id),
242
+ );
243
+ if (autoIds.length > 1) {
244
+ console.warn(
245
+ `[rsc-router] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
246
+ `In multi-router setups, each createRouter() must have an explicit \`id\` option ` +
247
+ `to ensure per-router manifest data is matched correctly at runtime. ` +
248
+ `Example: createRouter({ id: "site", ... })`,
249
+ );
250
+ }
251
+ }
252
+
253
+ debug?.(
254
+ "inner: generated manifests for %d router(s) (%sms)",
255
+ allManifests.length,
256
+ (performance.now() - manifestGenStart).toFixed(1),
257
+ );
258
+
259
+ // Build route trie from merged manifest + ancestry
260
+ let newMergedRouteTrie: any = null;
261
+ const trieStart = debug ? performance.now() : 0;
262
+ if (Object.keys(newMergedRouteManifest).length > 0) {
263
+ const buildRouteTrie = buildMod.buildRouteTrie;
264
+ if (buildRouteTrie && mergedRouteAncestry) {
265
+ // Build routeToStaticPrefix from saved manifests
266
+ const routeToStaticPrefix: Record<string, string> = {};
267
+ for (const { manifest } of allManifests) {
268
+ // Root-level routes have empty static prefix
269
+ for (const name of Object.keys(manifest.routeManifest)) {
270
+ if (!(name in routeToStaticPrefix)) {
271
+ routeToStaticPrefix[name] = "";
272
+ }
273
+ }
274
+ buildRouteToStaticPrefix(manifest.prefixTree, routeToStaticPrefix);
275
+ }
276
+
277
+ // Collect prerender route names and response type routes from all manifests
278
+ const prerenderRouteNames = new Set<string>();
279
+ const passthroughRouteNames = new Set<string>();
280
+ const mergedResponseTypeRoutes: Record<string, string> = {};
281
+ for (const { manifest } of allManifests) {
282
+ if (manifest.prerenderRoutes) {
283
+ for (const name of manifest.prerenderRoutes) {
284
+ prerenderRouteNames.add(name);
285
+ }
286
+ }
287
+ if (manifest.passthroughRoutes) {
288
+ for (const name of manifest.passthroughRoutes) {
289
+ passthroughRouteNames.add(name);
290
+ }
291
+ }
292
+ if (manifest.responseTypeRoutes) {
293
+ Object.assign(mergedResponseTypeRoutes, manifest.responseTypeRoutes);
294
+ }
295
+ }
296
+
297
+ newMergedRouteTrie = buildRouteTrie(
298
+ newMergedRouteManifest,
299
+ mergedRouteAncestry,
300
+ routeToStaticPrefix,
301
+ Object.keys(mergedRouteTrailingSlash).length > 0
302
+ ? mergedRouteTrailingSlash
303
+ : undefined,
304
+ prerenderRouteNames.size > 0 ? prerenderRouteNames : undefined,
305
+ passthroughRouteNames.size > 0 ? passthroughRouteNames : undefined,
306
+ Object.keys(mergedResponseTypeRoutes).length > 0
307
+ ? mergedResponseTypeRoutes
308
+ : undefined,
309
+ );
310
+
311
+ // Build per-router tries for multi-router isolation.
312
+ for (const { id, manifest } of allManifests) {
313
+ if (
314
+ !manifest._routeAncestry ||
315
+ Object.keys(manifest._routeAncestry).length === 0
316
+ )
317
+ continue;
318
+ const perRouterStaticPrefix: Record<string, string> = {};
319
+ for (const name of Object.keys(manifest.routeManifest)) {
320
+ perRouterStaticPrefix[name] = "";
321
+ }
322
+ buildRouteToStaticPrefix(manifest.prefixTree, perRouterStaticPrefix);
323
+
324
+ const perRouterPrerenderNames = manifest.prerenderRoutes
325
+ ? new Set<string>(manifest.prerenderRoutes)
326
+ : undefined;
327
+ const perRouterPassthroughNames = manifest.passthroughRoutes
328
+ ? new Set<string>(manifest.passthroughRoutes)
329
+ : undefined;
330
+
331
+ const perRouterTrie = buildRouteTrie(
332
+ manifest.routeManifest,
333
+ manifest._routeAncestry,
334
+ perRouterStaticPrefix,
335
+ manifest.routeTrailingSlash &&
336
+ Object.keys(manifest.routeTrailingSlash).length > 0
337
+ ? manifest.routeTrailingSlash
338
+ : undefined,
339
+ perRouterPrerenderNames && perRouterPrerenderNames.size > 0
340
+ ? perRouterPrerenderNames
341
+ : undefined,
342
+ perRouterPassthroughNames && perRouterPassthroughNames.size > 0
343
+ ? perRouterPassthroughNames
344
+ : undefined,
345
+ manifest.responseTypeRoutes &&
346
+ Object.keys(manifest.responseTypeRoutes).length > 0
347
+ ? manifest.responseTypeRoutes
348
+ : undefined,
349
+ );
350
+ newPerRouterTrieMap.set(id, perRouterTrie);
351
+ }
352
+ }
353
+ }
354
+
355
+ debug?.(
356
+ "inner: trie build done (%sms)",
357
+ (performance.now() - trieStart).toFixed(1),
358
+ );
359
+
360
+ // Commit all local state to the shared discovery state atomically.
361
+ // This ensures a failed re-discovery (e.g. from a transient module
362
+ // evaluation error) preserves the last known-good state.
363
+ state.mergedRouteManifest = newMergedRouteManifest;
364
+ state.mergedPrecomputedEntries = newMergedPrecomputedEntries;
365
+ state.perRouterManifests = newPerRouterManifests;
366
+ state.perRouterManifestDataMap = newPerRouterManifestDataMap;
367
+ state.perRouterPrecomputedMap = newPerRouterPrecomputedMap;
368
+ state.perRouterTrieMap = newPerRouterTrieMap;
369
+ state.mergedRouteTrie = newMergedRouteTrie;
370
+
371
+ // Expand prerender routes and render static handlers (build mode only)
372
+ await expandPrerenderRoutes(state, rscEnv, registry, allManifests);
373
+ await renderStaticHandlers(state, rscEnv, registry);
374
+
375
+ return serverMod;
376
+ }