@rangojs/router 0.0.0-experimental.d7eeaa75 → 0.0.0-experimental.d98a8e9d

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 (278) hide show
  1. package/README.md +120 -25
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/testing/vitest.js +82 -0
  4. package/dist/vite/index.js +2154 -861
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +57 -11
  7. package/skills/api-client/SKILL.md +211 -0
  8. package/skills/breadcrumbs/SKILL.md +3 -1
  9. package/skills/bundle-analysis/SKILL.md +159 -0
  10. package/skills/cache-guide/SKILL.md +220 -30
  11. package/skills/caching/SKILL.md +116 -8
  12. package/skills/composability/SKILL.md +27 -2
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +229 -20
  16. package/skills/host-router/SKILL.md +45 -20
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +46 -4
  19. package/skills/layout/SKILL.md +28 -7
  20. package/skills/links/SKILL.md +247 -17
  21. package/skills/loader/SKILL.md +219 -9
  22. package/skills/middleware/SKILL.md +47 -12
  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 +27 -0
  26. package/skills/observability/SKILL.md +137 -0
  27. package/skills/parallel/SKILL.md +71 -6
  28. package/skills/prerender/SKILL.md +14 -33
  29. package/skills/rango/SKILL.md +243 -22
  30. package/skills/react-compiler/SKILL.md +168 -0
  31. package/skills/response-routes/SKILL.md +122 -47
  32. package/skills/route/SKILL.md +57 -4
  33. package/skills/router-setup/SKILL.md +3 -3
  34. package/skills/server-actions/SKILL.md +751 -0
  35. package/skills/streams-and-websockets/SKILL.md +283 -0
  36. package/skills/testing/SKILL.md +128 -0
  37. package/skills/testing/bindings.md +89 -0
  38. package/skills/testing/cache-prerender.md +98 -0
  39. package/skills/testing/client-components.md +121 -0
  40. package/skills/testing/e2e-parity.md +124 -0
  41. package/skills/testing/flight.md +89 -0
  42. package/skills/testing/handles.md +127 -0
  43. package/skills/testing/loader.md +108 -0
  44. package/skills/testing/middleware.md +97 -0
  45. package/skills/testing/render-handler.md +102 -0
  46. package/skills/testing/response-routes.md +94 -0
  47. package/skills/testing/reverse-and-types.md +83 -0
  48. package/skills/testing/server-actions.md +89 -0
  49. package/skills/testing/server-tree.md +128 -0
  50. package/skills/testing/setup.md +120 -0
  51. package/skills/typesafety/SKILL.md +319 -27
  52. package/skills/use-cache/SKILL.md +34 -5
  53. package/skills/view-transitions/SKILL.md +294 -0
  54. package/src/__augment-tests__/augment.ts +81 -0
  55. package/src/__augment-tests__/augmented.check.ts +116 -0
  56. package/src/browser/action-coordinator.ts +53 -36
  57. package/src/browser/app-shell.ts +52 -0
  58. package/src/browser/event-controller.ts +86 -70
  59. package/src/browser/history-state.ts +21 -0
  60. package/src/browser/index.ts +3 -3
  61. package/src/browser/navigation-bridge.ts +84 -11
  62. package/src/browser/navigation-client.ts +104 -68
  63. package/src/browser/navigation-store.ts +32 -9
  64. package/src/browser/navigation-transaction.ts +10 -28
  65. package/src/browser/partial-update.ts +64 -26
  66. package/src/browser/prefetch/cache.ts +183 -44
  67. package/src/browser/prefetch/fetch.ts +228 -37
  68. package/src/browser/prefetch/queue.ts +36 -5
  69. package/src/browser/rango-state.ts +53 -13
  70. package/src/browser/react/Link.tsx +30 -2
  71. package/src/browser/react/NavigationProvider.tsx +72 -31
  72. package/src/browser/react/filter-segment-order.ts +51 -7
  73. package/src/browser/react/index.ts +3 -0
  74. package/src/browser/react/location-state-shared.ts +175 -4
  75. package/src/browser/react/location-state.ts +39 -13
  76. package/src/browser/react/use-handle.ts +17 -9
  77. package/src/browser/react/use-navigation.ts +22 -2
  78. package/src/browser/react/use-params.ts +20 -8
  79. package/src/browser/react/use-reverse.ts +106 -0
  80. package/src/browser/react/use-router.ts +22 -2
  81. package/src/browser/react/use-segments.ts +11 -8
  82. package/src/browser/response-adapter.ts +32 -1
  83. package/src/browser/rsc-router.tsx +69 -22
  84. package/src/browser/scroll-restoration.ts +22 -14
  85. package/src/browser/segment-reconciler.ts +36 -14
  86. package/src/browser/segment-structure-assert.ts +2 -2
  87. package/src/browser/server-action-bridge.ts +23 -30
  88. package/src/browser/types.ts +21 -0
  89. package/src/build/collect-fallback-refs.ts +107 -0
  90. package/src/build/generate-manifest.ts +60 -35
  91. package/src/build/generate-route-types.ts +2 -0
  92. package/src/build/index.ts +8 -1
  93. package/src/build/prefix-tree-utils.ts +123 -0
  94. package/src/build/route-trie.ts +95 -25
  95. package/src/build/route-types/codegen.ts +4 -4
  96. package/src/build/route-types/include-resolution.ts +1 -1
  97. package/src/build/route-types/per-module-writer.ts +7 -4
  98. package/src/build/route-types/router-processing.ts +55 -14
  99. package/src/build/route-types/scan-filter.ts +1 -1
  100. package/src/build/route-types/source-scan.ts +118 -0
  101. package/src/build/runtime-discovery.ts +9 -20
  102. package/src/cache/cache-scope.ts +28 -42
  103. package/src/cache/cf/cf-cache-store.ts +54 -13
  104. package/src/client.rsc.tsx +3 -0
  105. package/src/client.tsx +96 -205
  106. package/src/context-var.ts +5 -5
  107. package/src/decode-loader-results.ts +36 -0
  108. package/src/errors.ts +30 -4
  109. package/src/handle.ts +32 -14
  110. package/src/host/index.ts +2 -2
  111. package/src/host/router.ts +129 -57
  112. package/src/host/types.ts +31 -2
  113. package/src/host/utils.ts +1 -1
  114. package/src/href-client.ts +140 -21
  115. package/src/index.rsc.ts +10 -6
  116. package/src/index.ts +54 -17
  117. package/src/loader-store.ts +500 -0
  118. package/src/loader.rsc.ts +25 -7
  119. package/src/loader.ts +16 -9
  120. package/src/missing-id-error.ts +68 -0
  121. package/src/outlet-context.ts +1 -1
  122. package/src/prerender.ts +27 -6
  123. package/src/response-utils.ts +37 -0
  124. package/src/reverse.ts +65 -36
  125. package/src/route-content-wrapper.tsx +6 -28
  126. package/src/route-definition/dsl-helpers.ts +384 -257
  127. package/src/route-definition/helper-factories.ts +29 -139
  128. package/src/route-definition/helpers-types.ts +100 -28
  129. package/src/route-definition/resolve-handler-use.ts +6 -0
  130. package/src/route-definition/use-item-types.ts +32 -0
  131. package/src/route-types.ts +26 -41
  132. package/src/router/basename.ts +14 -0
  133. package/src/router/content-negotiation.ts +15 -2
  134. package/src/router/error-handling.ts +1 -1
  135. package/src/router/find-match.ts +54 -6
  136. package/src/router/handler-context.ts +21 -38
  137. package/src/router/intercept-resolution.ts +4 -18
  138. package/src/router/lazy-includes.ts +41 -22
  139. package/src/router/loader-resolution.ts +82 -36
  140. package/src/router/manifest.ts +41 -19
  141. package/src/router/match-api.ts +4 -3
  142. package/src/router/match-handlers.ts +63 -20
  143. package/src/router/match-middleware/cache-lookup.ts +44 -91
  144. package/src/router/match-middleware/cache-store.ts +3 -2
  145. package/src/router/match-result.ts +53 -32
  146. package/src/router/metrics.ts +1 -1
  147. package/src/router/middleware-types.ts +15 -26
  148. package/src/router/middleware.ts +99 -84
  149. package/src/router/pattern-matching.ts +116 -19
  150. package/src/router/prerender-match.ts +1 -1
  151. package/src/router/preview-match.ts +3 -1
  152. package/src/router/request-classification.ts +4 -28
  153. package/src/router/revalidation.ts +58 -2
  154. package/src/router/router-interfaces.ts +45 -28
  155. package/src/router/router-options.ts +40 -1
  156. package/src/router/router-registry.ts +2 -5
  157. package/src/router/segment-resolution/fresh.ts +27 -6
  158. package/src/router/segment-resolution/revalidation.ts +147 -106
  159. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  160. package/src/router/substitute-pattern-params.ts +56 -0
  161. package/src/router/telemetry.ts +99 -0
  162. package/src/router/trie-matching.ts +40 -16
  163. package/src/router/types.ts +8 -0
  164. package/src/router/url-params.ts +49 -0
  165. package/src/router.ts +52 -30
  166. package/src/rsc/handler-context.ts +2 -2
  167. package/src/rsc/handler.ts +28 -69
  168. package/src/rsc/helpers.ts +91 -43
  169. package/src/rsc/index.ts +1 -1
  170. package/src/rsc/manifest-init.ts +28 -41
  171. package/src/rsc/origin-guard.ts +28 -10
  172. package/src/rsc/progressive-enhancement.ts +4 -0
  173. package/src/rsc/response-error.ts +79 -12
  174. package/src/rsc/response-route-handler.ts +57 -61
  175. package/src/rsc/rsc-rendering.ts +35 -51
  176. package/src/rsc/runtime-warnings.ts +9 -10
  177. package/src/rsc/server-action.ts +17 -37
  178. package/src/rsc/ssr-setup.ts +16 -0
  179. package/src/rsc/types.ts +8 -2
  180. package/src/runtime-env.ts +18 -0
  181. package/src/search-params.ts +4 -4
  182. package/src/segment-content-promise.ts +67 -0
  183. package/src/segment-loader-promise.ts +122 -0
  184. package/src/segment-system.tsx +132 -116
  185. package/src/serialize.ts +243 -0
  186. package/src/server/context.ts +175 -53
  187. package/src/server/cookie-store.ts +28 -4
  188. package/src/server/request-context.ts +67 -51
  189. package/src/ssr/index.tsx +5 -1
  190. package/src/static-handler.ts +25 -3
  191. package/src/testing/cache-status.ts +166 -0
  192. package/src/testing/collect-handle.ts +63 -0
  193. package/src/testing/dispatch.ts +581 -0
  194. package/src/testing/dom.entry.ts +22 -0
  195. package/src/testing/e2e/fixture.ts +188 -0
  196. package/src/testing/e2e/index.ts +149 -0
  197. package/src/testing/e2e/matchers.ts +51 -0
  198. package/src/testing/e2e/page-helpers.ts +272 -0
  199. package/src/testing/e2e/parity.ts +326 -0
  200. package/src/testing/e2e/server.ts +195 -0
  201. package/src/testing/flight-matchers.ts +110 -0
  202. package/src/testing/flight-normalize.ts +38 -0
  203. package/src/testing/flight-runtime.d.ts +57 -0
  204. package/src/testing/flight-tree.ts +682 -0
  205. package/src/testing/flight.entry.ts +51 -0
  206. package/src/testing/flight.ts +234 -0
  207. package/src/testing/generated-routes.ts +223 -0
  208. package/src/testing/index.ts +106 -0
  209. package/src/testing/internal/context.ts +304 -0
  210. package/src/testing/internal/flight-client-globals.ts +30 -0
  211. package/src/testing/internal/seed-vars.ts +42 -0
  212. package/src/testing/render-handler.ts +323 -0
  213. package/src/testing/render-route.tsx +590 -0
  214. package/src/testing/run-loader.ts +363 -0
  215. package/src/testing/run-middleware.ts +205 -0
  216. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  217. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  218. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  219. package/src/testing/vitest-stubs/version.ts +5 -0
  220. package/src/testing/vitest.ts +285 -0
  221. package/src/types/global-namespace.ts +39 -26
  222. package/src/types/handler-context.ts +68 -50
  223. package/src/types/index.ts +1 -0
  224. package/src/types/loader-types.ts +11 -9
  225. package/src/types/request-scope.ts +126 -0
  226. package/src/types/route-entry.ts +11 -0
  227. package/src/types/segments.ts +35 -2
  228. package/src/urls/include-helper.ts +34 -67
  229. package/src/urls/index.ts +1 -5
  230. package/src/urls/path-helper-types.ts +41 -7
  231. package/src/urls/path-helper.ts +17 -52
  232. package/src/urls/pattern-types.ts +36 -19
  233. package/src/urls/response-types.ts +22 -29
  234. package/src/urls/type-extraction.ts +58 -139
  235. package/src/urls/urls-function.ts +1 -5
  236. package/src/use-loader.tsx +413 -42
  237. package/src/vite/debug.ts +185 -0
  238. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  239. package/src/vite/discovery/discover-routers.ts +106 -75
  240. package/src/vite/discovery/discovery-errors.ts +194 -0
  241. package/src/vite/discovery/gate-state.ts +171 -0
  242. package/src/vite/discovery/prerender-collection.ts +67 -26
  243. package/src/vite/discovery/route-types-writer.ts +40 -84
  244. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  245. package/src/vite/discovery/state.ts +33 -0
  246. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  247. package/src/vite/index.ts +2 -0
  248. package/src/vite/plugin-types.ts +67 -0
  249. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  250. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  251. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  252. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  253. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  254. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  255. package/src/vite/plugins/expose-action-id.ts +54 -30
  256. package/src/vite/plugins/expose-id-utils.ts +12 -8
  257. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  258. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  259. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  260. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  261. package/src/vite/plugins/expose-internal-ids.ts +496 -486
  262. package/src/vite/plugins/performance-tracks.ts +29 -25
  263. package/src/vite/plugins/use-cache-transform.ts +65 -50
  264. package/src/vite/plugins/version-injector.ts +39 -23
  265. package/src/vite/plugins/version-plugin.ts +59 -2
  266. package/src/vite/plugins/virtual-entries.ts +2 -2
  267. package/src/vite/rango.ts +116 -29
  268. package/src/vite/router-discovery.ts +750 -100
  269. package/src/vite/utils/ast-handler-extract.ts +15 -15
  270. package/src/vite/utils/banner.ts +1 -1
  271. package/src/vite/utils/bundle-analysis.ts +4 -2
  272. package/src/vite/utils/client-chunks.ts +190 -0
  273. package/src/vite/utils/forward-user-plugins.ts +193 -0
  274. package/src/vite/utils/manifest-utils.ts +8 -59
  275. package/src/vite/utils/package-resolution.ts +41 -1
  276. package/src/vite/utils/prerender-utils.ts +21 -6
  277. package/src/vite/utils/shared-utils.ts +107 -26
  278. package/src/browser/action-response-classifier.ts +0 -99
@@ -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
+ }
@@ -71,12 +71,12 @@ export function postprocessBundle(state: DiscoveryState): void {
71
71
  writeFileSync(chunkPath, result.code);
72
72
  const savedKB = (result.savedBytes / 1024).toFixed(1);
73
73
  console.log(
74
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
74
+ `[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`,
75
75
  );
76
76
  }
77
77
  } catch (replaceErr: any) {
78
78
  console.warn(
79
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`,
79
+ `[rango] Failed to evict ${target.label}: ${replaceErr.message}`,
80
80
  );
81
81
  }
82
82
  }
@@ -121,11 +121,11 @@ export function postprocessBundle(state: DiscoveryState): void {
121
121
 
122
122
  const totalKB = (totalBytes / 1024).toFixed(1);
123
123
  console.log(
124
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries!).length} entries)`,
124
+ `[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries!).length} entries)`,
125
125
  );
126
126
  } catch (err: any) {
127
127
  throw new Error(
128
- `[rsc-router] Failed to write prerender assets: ${err.message}`,
128
+ `[rango] Failed to write prerender assets: ${err.message}`,
129
129
  );
130
130
  }
131
131
  }
@@ -169,11 +169,11 @@ export function postprocessBundle(state: DiscoveryState): void {
169
169
 
170
170
  const totalKB = (totalBytes / 1024).toFixed(1);
171
171
  console.log(
172
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries!).length} entries)`,
172
+ `[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries!).length} entries)`,
173
173
  );
174
174
  } catch (err: any) {
175
175
  throw new Error(
176
- `[rsc-router] Failed to write static assets: ${err.message}`,
176
+ `[rango] Failed to write static assets: ${err.message}`,
177
177
  );
178
178
  }
179
179
  }
@@ -20,6 +20,15 @@ import {
20
20
  expandPrerenderRoutes,
21
21
  renderStaticHandlers,
22
22
  } from "./prerender-collection.js";
23
+ import {
24
+ resolveHostRouterHandlers,
25
+ DiscoveryError,
26
+ type CaughtDiscoveryError,
27
+ } from "./discovery-errors.js";
28
+ import { createRangoDebugger, timed, NS } from "../debug.js";
29
+ import { computeProductionHash } from "../plugins/client-ref-hashing.js";
30
+
31
+ const debug = createRangoDebugger(NS.discovery);
23
32
 
24
33
  /**
25
34
  * Import the user's entry via RSC runner, generate manifests for each
@@ -38,41 +47,40 @@ export async function discoverRouters(
38
47
  // Import the entry file via RSC environment.
39
48
  // For node preset: this is the router file (createRouter() registers in RouterRegistry).
40
49
  // For cloudflare preset: this is the worker entry (which imports the router).
41
- await rscEnv.runner.import(state.resolvedEntryPath);
50
+ await timed(debug, "inner: import entry", () =>
51
+ rscEnv.runner.import(state.resolvedEntryPath),
52
+ );
42
53
 
43
54
  // Import the router package to access the registry
44
- const serverMod = await rscEnv.runner.import("@rangojs/router/server");
55
+ const serverMod = await timed(
56
+ debug,
57
+ "inner: import @rangojs/router/server",
58
+ () => rscEnv.runner.import("@rangojs/router/server"),
59
+ );
45
60
  let registry: Map<string, any> = serverMod.RouterRegistry;
46
61
 
47
62
  if (!registry || registry.size === 0) {
48
63
  // No RSC routers found directly. Check for host routers with lazy handlers
49
64
  // that need to be resolved to trigger sub-app createRouter() calls.
65
+ //
66
+ // Handler failures are collected rather than swallowed: when the registry
67
+ // is still empty afterwards, these errors (typically a sub-app whose router
68
+ // module failed to import) are the most likely cause and are surfaced in
69
+ // the terminal "No routers found" error below.
70
+ const discoveryErrors: CaughtDiscoveryError[] = [];
50
71
  try {
51
72
  const hostRegistry: Map<string, any> | undefined =
52
73
  serverMod.HostRouterRegistry;
53
74
 
54
75
  if (hostRegistry && hostRegistry.size > 0) {
55
76
  console.log(
56
- `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
77
+ `[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`,
57
78
  );
58
79
 
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
- }
80
+ const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
81
+ discoveryErrors.push(...handlerErrors);
82
+ for (const { context, error } of handlerErrors) {
83
+ debug?.("caught error while resolving %s: %O", context, error);
76
84
  }
77
85
 
78
86
  // Re-read RouterRegistry - sub-app createRouter() calls should have populated it
@@ -87,22 +95,28 @@ export async function discoverRouters(
87
95
  registry = freshRegistry;
88
96
  }
89
97
  }
90
- } catch {
91
- // Host-router discovery is best-effort; skip if unavailable
98
+ } catch (error) {
99
+ // Host-router discovery is best-effort; record the failure so it can be
100
+ // surfaced if no routers are found.
101
+ discoveryErrors.push({ context: "host-router discovery", error });
92
102
  }
93
103
 
94
104
  // If still no routers after host router resolution, fail
95
105
  if (!registry || registry.size === 0) {
96
- throw new Error(
97
- `[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`,
98
- );
106
+ throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
99
107
  }
100
108
  }
101
109
 
102
110
  // Import build utilities for manifest generation
103
- const buildMod = await rscEnv.runner.import("@rangojs/router/build");
111
+ const buildMod = await timed(
112
+ debug,
113
+ "inner: import @rangojs/router/build",
114
+ () => rscEnv.runner.import("@rangojs/router/build"),
115
+ );
104
116
  const generateManifestFull = buildMod.generateManifestFull;
105
117
 
118
+ debug?.("inner: found %d router(s) in registry", registry.size);
119
+
106
120
  const nestedRouterConflict = findNestedRouterConflict(
107
121
  [...registry.values()]
108
122
  .map((router) => router.__sourceFile)
@@ -130,6 +144,29 @@ export async function discoverRouters(
130
144
  // Collect all manifests for trie building (avoid re-running generateManifest)
131
145
  const allManifests: Array<{ id: string; manifest: any }> = [];
132
146
 
147
+ // Built-in clientChunks context (present only when the built-in strategy is
148
+ // active). Collect the production hashes of "use client" error/notFound
149
+ // fallback modules so the strategy can route them into app-fallback.
150
+ const clientChunkCtx = state.opts?.clientChunkCtx;
151
+ const collectClientFallbackRef = clientChunkCtx
152
+ ? (refKey: string) =>
153
+ clientChunkCtx.fallbackRefs.add(
154
+ computeProductionHash(state.projectRoot, refKey),
155
+ )
156
+ : undefined;
157
+ // Router-level boundary defaults (`createRouter({ defaultErrorBoundary, ... })`)
158
+ // are NOT in EntryData, so generateManifestFull's walk misses them. Collect any
159
+ // "use client" default boundary directly off the router instance. The value is
160
+ // commonly a handler function wrapping the client boundary in server providers,
161
+ // so collectFallbackClientRefs invokes + walks the tree. Routed through buildMod
162
+ // so it runs in the same RSC runner realm the boundary value came from.
163
+ const collectFromBoundaryNode = (node: unknown): void => {
164
+ if (collectClientFallbackRef && buildMod.collectFallbackClientRefs) {
165
+ buildMod.collectFallbackClientRefs(node, collectClientFallbackRef);
166
+ }
167
+ };
168
+
169
+ const manifestGenStart = debug ? performance.now() : 0;
133
170
  for (const [id, router] of registry) {
134
171
  if (!router.urlpatterns || !generateManifestFull) {
135
172
  continue;
@@ -138,10 +175,23 @@ export async function discoverRouters(
138
175
  const manifest = generateManifestFull(
139
176
  router.urlpatterns,
140
177
  routerMountIndex,
141
- router.__basename ? { urlPrefix: router.__basename } : undefined,
178
+ {
179
+ ...(router.__basename ? { urlPrefix: router.__basename } : {}),
180
+ ...(collectClientFallbackRef ? { collectClientFallbackRef } : {}),
181
+ },
142
182
  );
143
183
  routerMountIndex++;
144
184
  allManifests.push({ id, manifest });
185
+
186
+ // Router-level "use client" boundary defaults -> app-fallback (the
187
+ // route-tree errorBoundary()/notFoundBoundary() helpers are already
188
+ // collected inside generateManifestFull via collectClientFallbackRef).
189
+ if (collectClientFallbackRef) {
190
+ collectFromBoundaryNode(router.__defaultErrorBoundary);
191
+ collectFromBoundaryNode(router.__defaultNotFoundBoundary);
192
+ collectFromBoundaryNode(router.__notFound);
193
+ }
194
+
145
195
  const routeCount = Object.keys(manifest.routeManifest).length;
146
196
  const staticRoutes = Object.values(manifest.routeManifest).filter(
147
197
  (p: any) => !p.includes(":") && !p.includes("*"),
@@ -210,7 +260,7 @@ export async function discoverRouters(
210
260
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
211
261
 
212
262
  console.log(
213
- `[rsc-router] Router "${id}" -> ${routeCount} routes ` +
263
+ `[rango] Router "${id}" -> ${routeCount} routes ` +
214
264
  `(${staticRoutes} static, ${dynamicRoutes} dynamic)`,
215
265
  );
216
266
  }
@@ -226,7 +276,7 @@ export async function discoverRouters(
226
276
  );
227
277
  if (autoIds.length > 1) {
228
278
  console.warn(
229
- `[rsc-router] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
279
+ `[rango] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). ` +
230
280
  `In multi-router setups, each createRouter() must have an explicit \`id\` option ` +
231
281
  `to ensure per-router manifest data is matched correctly at runtime. ` +
232
282
  `Example: createRouter({ id: "site", ... })`,
@@ -234,8 +284,15 @@ export async function discoverRouters(
234
284
  }
235
285
  }
236
286
 
287
+ debug?.(
288
+ "inner: generated manifests for %d router(s) (%sms)",
289
+ allManifests.length,
290
+ (performance.now() - manifestGenStart).toFixed(1),
291
+ );
292
+
237
293
  // Build route trie from merged manifest + ancestry
238
294
  let newMergedRouteTrie: any = null;
295
+ const trieStart = debug ? performance.now() : 0;
239
296
  if (Object.keys(newMergedRouteManifest).length > 0) {
240
297
  const buildRouteTrie = buildMod.buildRouteTrie;
241
298
  if (buildRouteTrie && mergedRouteAncestry) {
@@ -271,64 +328,38 @@ export async function discoverRouters(
271
328
  }
272
329
  }
273
330
 
331
+ // buildRouteTrie reads these via ?.has / ?.[] — empty is observationally
332
+ // identical to undefined, so no empty->undefined coercion is needed.
274
333
  newMergedRouteTrie = buildRouteTrie(
275
334
  newMergedRouteManifest,
276
335
  mergedRouteAncestry,
277
336
  routeToStaticPrefix,
278
- Object.keys(mergedRouteTrailingSlash).length > 0
279
- ? mergedRouteTrailingSlash
280
- : undefined,
281
- prerenderRouteNames.size > 0 ? prerenderRouteNames : undefined,
282
- passthroughRouteNames.size > 0 ? passthroughRouteNames : undefined,
283
- Object.keys(mergedResponseTypeRoutes).length > 0
284
- ? mergedResponseTypeRoutes
285
- : undefined,
337
+ mergedRouteTrailingSlash,
338
+ prerenderRouteNames,
339
+ passthroughRouteNames,
340
+ mergedResponseTypeRoutes,
286
341
  );
287
342
 
288
- // Build per-router tries for multi-router isolation.
343
+ // Build per-router tries for multi-router isolation. Uses the single
344
+ // shared buildPerRouterTrie so the production serialized trie is built by
345
+ // exactly the same code as the dev/HMR runtime rebuild (manifest-init.ts).
346
+ const buildPerRouterTrie = buildMod.buildPerRouterTrie;
289
347
  for (const { id, manifest } of allManifests) {
290
- if (
291
- !manifest._routeAncestry ||
292
- Object.keys(manifest._routeAncestry).length === 0
293
- )
294
- continue;
295
- const perRouterStaticPrefix: Record<string, string> = {};
296
- for (const name of Object.keys(manifest.routeManifest)) {
297
- perRouterStaticPrefix[name] = "";
348
+ const perRouterTrie = buildPerRouterTrie
349
+ ? buildPerRouterTrie(manifest)
350
+ : null;
351
+ if (perRouterTrie) {
352
+ newPerRouterTrieMap.set(id, perRouterTrie);
298
353
  }
299
- buildRouteToStaticPrefix(manifest.prefixTree, perRouterStaticPrefix);
300
-
301
- const perRouterPrerenderNames = manifest.prerenderRoutes
302
- ? new Set<string>(manifest.prerenderRoutes)
303
- : undefined;
304
- const perRouterPassthroughNames = manifest.passthroughRoutes
305
- ? new Set<string>(manifest.passthroughRoutes)
306
- : undefined;
307
-
308
- const perRouterTrie = buildRouteTrie(
309
- manifest.routeManifest,
310
- manifest._routeAncestry,
311
- perRouterStaticPrefix,
312
- manifest.routeTrailingSlash &&
313
- Object.keys(manifest.routeTrailingSlash).length > 0
314
- ? manifest.routeTrailingSlash
315
- : undefined,
316
- perRouterPrerenderNames && perRouterPrerenderNames.size > 0
317
- ? perRouterPrerenderNames
318
- : undefined,
319
- perRouterPassthroughNames && perRouterPassthroughNames.size > 0
320
- ? perRouterPassthroughNames
321
- : undefined,
322
- manifest.responseTypeRoutes &&
323
- Object.keys(manifest.responseTypeRoutes).length > 0
324
- ? manifest.responseTypeRoutes
325
- : undefined,
326
- );
327
- newPerRouterTrieMap.set(id, perRouterTrie);
328
354
  }
329
355
  }
330
356
  }
331
357
 
358
+ debug?.(
359
+ "inner: trie build done (%sms)",
360
+ (performance.now() - trieStart).toFixed(1),
361
+ );
362
+
332
363
  // Commit all local state to the shared discovery state atomically.
333
364
  // This ensures a failed re-discovery (e.g. from a transient module
334
365
  // evaluation error) preserves the last known-good state.