@rangojs/router 0.0.0-experimental.77 → 0.0.0-experimental.77ed8945

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 (239) hide show
  1. package/README.md +120 -25
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/vite/index.js +2103 -861
  4. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  5. package/package.json +13 -8
  6. package/skills/api-client/SKILL.md +211 -0
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/bundle-analysis/SKILL.md +159 -0
  9. package/skills/cache-guide/SKILL.md +220 -30
  10. package/skills/caching/SKILL.md +116 -8
  11. package/skills/composability/SKILL.md +27 -2
  12. package/skills/css/SKILL.md +76 -0
  13. package/skills/document-cache/SKILL.md +78 -55
  14. package/skills/handler-use/SKILL.md +3 -1
  15. package/skills/hooks/SKILL.md +229 -20
  16. package/skills/host-router/SKILL.md +66 -20
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +26 -4
  19. package/skills/layout/SKILL.md +6 -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 +12 -6
  28. package/skills/prerender/SKILL.md +14 -33
  29. package/skills/rango/SKILL.md +238 -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 +33 -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/tailwind/SKILL.md +27 -3
  37. package/skills/typesafety/SKILL.md +319 -27
  38. package/skills/use-cache/SKILL.md +34 -5
  39. package/skills/view-transitions/SKILL.md +294 -0
  40. package/src/__augment-tests__/augment.ts +81 -0
  41. package/src/__augment-tests__/augmented.check.ts +116 -0
  42. package/src/browser/action-coordinator.ts +53 -36
  43. package/src/browser/app-shell.ts +39 -0
  44. package/src/browser/event-controller.ts +86 -70
  45. package/src/browser/history-state.ts +21 -0
  46. package/src/browser/index.ts +3 -3
  47. package/src/browser/navigation-bridge.ts +29 -9
  48. package/src/browser/navigation-client.ts +99 -77
  49. package/src/browser/navigation-store.ts +7 -8
  50. package/src/browser/navigation-transaction.ts +10 -28
  51. package/src/browser/partial-update.ts +60 -40
  52. package/src/browser/prefetch/cache.ts +196 -49
  53. package/src/browser/prefetch/fetch.ts +203 -59
  54. package/src/browser/prefetch/queue.ts +36 -5
  55. package/src/browser/rango-state.ts +37 -13
  56. package/src/browser/react/Link.tsx +18 -13
  57. package/src/browser/react/NavigationProvider.tsx +75 -31
  58. package/src/browser/react/filter-segment-order.ts +51 -7
  59. package/src/browser/react/index.ts +3 -0
  60. package/src/browser/react/location-state-shared.ts +175 -4
  61. package/src/browser/react/location-state.ts +39 -13
  62. package/src/browser/react/use-handle.ts +17 -9
  63. package/src/browser/react/use-navigation.ts +22 -2
  64. package/src/browser/react/use-params.ts +20 -8
  65. package/src/browser/react/use-reverse.ts +106 -0
  66. package/src/browser/react/use-router.ts +23 -2
  67. package/src/browser/react/use-segments.ts +11 -8
  68. package/src/browser/response-adapter.ts +52 -1
  69. package/src/browser/rsc-router.tsx +71 -22
  70. package/src/browser/scroll-restoration.ts +22 -14
  71. package/src/browser/segment-reconciler.ts +10 -14
  72. package/src/browser/segment-structure-assert.ts +2 -2
  73. package/src/browser/server-action-bridge.ts +44 -30
  74. package/src/browser/types.ts +12 -2
  75. package/src/build/collect-fallback-refs.ts +107 -0
  76. package/src/build/generate-manifest.ts +60 -35
  77. package/src/build/generate-route-types.ts +2 -0
  78. package/src/build/index.ts +8 -1
  79. package/src/build/prefix-tree-utils.ts +123 -0
  80. package/src/build/route-trie.ts +45 -1
  81. package/src/build/route-types/codegen.ts +4 -4
  82. package/src/build/route-types/include-resolution.ts +1 -1
  83. package/src/build/route-types/per-module-writer.ts +7 -4
  84. package/src/build/route-types/router-processing.ts +55 -14
  85. package/src/build/route-types/scan-filter.ts +1 -1
  86. package/src/build/route-types/source-scan.ts +118 -0
  87. package/src/build/runtime-discovery.ts +9 -20
  88. package/src/cache/cache-runtime.ts +17 -5
  89. package/src/cache/cache-scope.ts +51 -49
  90. package/src/cache/cf/cf-cache-store.ts +502 -32
  91. package/src/cache/cf/index.ts +3 -0
  92. package/src/cache/handle-snapshot.ts +103 -0
  93. package/src/cache/index.ts +3 -0
  94. package/src/cache/memory-segment-store.ts +3 -2
  95. package/src/cache/types.ts +10 -6
  96. package/src/client.rsc.tsx +3 -0
  97. package/src/client.tsx +96 -205
  98. package/src/context-var.ts +5 -5
  99. package/src/decode-loader-results.ts +36 -0
  100. package/src/errors.ts +30 -4
  101. package/src/handle.ts +4 -6
  102. package/src/host/index.ts +2 -2
  103. package/src/host/router.ts +129 -57
  104. package/src/host/types.ts +31 -2
  105. package/src/host/utils.ts +1 -1
  106. package/src/href-client.ts +140 -21
  107. package/src/index.rsc.ts +10 -6
  108. package/src/index.ts +17 -8
  109. package/src/loader-store.ts +500 -0
  110. package/src/loader.rsc.ts +2 -5
  111. package/src/loader.ts +3 -10
  112. package/src/missing-id-error.ts +68 -0
  113. package/src/outlet-context.ts +1 -1
  114. package/src/prerender/store.ts +9 -7
  115. package/src/prerender.ts +4 -4
  116. package/src/response-utils.ts +37 -0
  117. package/src/reverse.ts +65 -39
  118. package/src/route-content-wrapper.tsx +6 -28
  119. package/src/route-definition/dsl-helpers.ts +253 -265
  120. package/src/route-definition/helper-factories.ts +29 -139
  121. package/src/route-definition/helpers-types.ts +43 -15
  122. package/src/route-definition/resolve-handler-use.ts +6 -0
  123. package/src/route-definition/use-item-types.ts +32 -0
  124. package/src/route-types.ts +26 -41
  125. package/src/router/content-negotiation.ts +15 -2
  126. package/src/router/error-handling.ts +1 -1
  127. package/src/router/find-match.ts +54 -6
  128. package/src/router/handler-context.ts +21 -41
  129. package/src/router/intercept-resolution.ts +4 -18
  130. package/src/router/lazy-includes.ts +41 -22
  131. package/src/router/loader-resolution.ts +82 -36
  132. package/src/router/manifest.ts +41 -19
  133. package/src/router/match-api.ts +4 -3
  134. package/src/router/match-handlers.ts +1 -0
  135. package/src/router/match-middleware/cache-lookup.ts +57 -95
  136. package/src/router/match-middleware/cache-store.ts +3 -2
  137. package/src/router/match-result.ts +53 -32
  138. package/src/router/metrics.ts +1 -1
  139. package/src/router/middleware-types.ts +15 -26
  140. package/src/router/middleware.ts +99 -84
  141. package/src/router/pattern-matching.ts +116 -19
  142. package/src/router/prerender-match.ts +40 -15
  143. package/src/router/preview-match.ts +3 -1
  144. package/src/router/request-classification.ts +40 -37
  145. package/src/router/revalidation.ts +58 -2
  146. package/src/router/router-interfaces.ts +51 -35
  147. package/src/router/router-options.ts +25 -1
  148. package/src/router/router-registry.ts +2 -5
  149. package/src/router/segment-resolution/fresh.ts +27 -6
  150. package/src/router/segment-resolution/revalidation.ts +147 -106
  151. package/src/router/segment-resolution/static-store.ts +19 -5
  152. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  153. package/src/router/substitute-pattern-params.ts +56 -0
  154. package/src/router/trie-matching.ts +40 -16
  155. package/src/router/types.ts +8 -0
  156. package/src/router/url-params.ts +49 -0
  157. package/src/router.ts +37 -25
  158. package/src/rsc/handler-context.ts +2 -2
  159. package/src/rsc/handler.ts +58 -77
  160. package/src/rsc/helpers.ts +72 -43
  161. package/src/rsc/index.ts +1 -1
  162. package/src/rsc/manifest-init.ts +28 -41
  163. package/src/rsc/origin-guard.ts +30 -10
  164. package/src/rsc/progressive-enhancement.ts +4 -0
  165. package/src/rsc/response-error.ts +79 -12
  166. package/src/rsc/response-route-handler.ts +76 -61
  167. package/src/rsc/rsc-rendering.ts +45 -51
  168. package/src/rsc/runtime-warnings.ts +9 -10
  169. package/src/rsc/server-action.ts +33 -39
  170. package/src/rsc/ssr-setup.ts +16 -0
  171. package/src/rsc/types.ts +8 -2
  172. package/src/search-params.ts +4 -4
  173. package/src/segment-content-promise.ts +67 -0
  174. package/src/segment-loader-promise.ts +122 -0
  175. package/src/segment-system.tsx +132 -116
  176. package/src/serialize.ts +243 -0
  177. package/src/server/context.ts +175 -53
  178. package/src/server/cookie-store.ts +28 -4
  179. package/src/server/request-context.ts +57 -51
  180. package/src/ssr/index.tsx +5 -1
  181. package/src/static-handler.ts +1 -1
  182. package/src/types/global-namespace.ts +39 -26
  183. package/src/types/handler-context.ts +68 -50
  184. package/src/types/index.ts +1 -0
  185. package/src/types/loader-types.ts +11 -9
  186. package/src/types/request-scope.ts +126 -0
  187. package/src/types/route-entry.ts +11 -0
  188. package/src/types/segments.ts +35 -2
  189. package/src/urls/include-helper.ts +34 -67
  190. package/src/urls/index.ts +1 -5
  191. package/src/urls/path-helper-types.ts +17 -3
  192. package/src/urls/path-helper.ts +17 -52
  193. package/src/urls/pattern-types.ts +36 -19
  194. package/src/urls/response-types.ts +22 -29
  195. package/src/urls/type-extraction.ts +58 -139
  196. package/src/urls/urls-function.ts +1 -5
  197. package/src/use-loader.tsx +413 -42
  198. package/src/vite/debug.ts +185 -0
  199. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  200. package/src/vite/discovery/discover-routers.ts +106 -75
  201. package/src/vite/discovery/discovery-errors.ts +194 -0
  202. package/src/vite/discovery/gate-state.ts +171 -0
  203. package/src/vite/discovery/prerender-collection.ts +72 -31
  204. package/src/vite/discovery/route-types-writer.ts +40 -84
  205. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  206. package/src/vite/discovery/state.ts +33 -0
  207. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  208. package/src/vite/index.ts +2 -0
  209. package/src/vite/plugin-types.ts +67 -0
  210. package/src/vite/plugins/cjs-to-esm.ts +8 -7
  211. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  212. package/src/vite/plugins/client-ref-hashing.ts +28 -5
  213. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  214. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  215. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  216. package/src/vite/plugins/expose-action-id.ts +54 -30
  217. package/src/vite/plugins/expose-id-utils.ts +12 -8
  218. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  219. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  220. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  221. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  222. package/src/vite/plugins/expose-internal-ids.ts +496 -486
  223. package/src/vite/plugins/performance-tracks.ts +29 -25
  224. package/src/vite/plugins/use-cache-transform.ts +65 -50
  225. package/src/vite/plugins/version-injector.ts +39 -23
  226. package/src/vite/plugins/version-plugin.ts +59 -2
  227. package/src/vite/plugins/virtual-entries.ts +2 -2
  228. package/src/vite/rango.ts +116 -29
  229. package/src/vite/router-discovery.ts +753 -104
  230. package/src/vite/utils/ast-handler-extract.ts +15 -15
  231. package/src/vite/utils/banner.ts +1 -1
  232. package/src/vite/utils/bundle-analysis.ts +4 -2
  233. package/src/vite/utils/client-chunks.ts +190 -0
  234. package/src/vite/utils/forward-user-plugins.ts +193 -0
  235. package/src/vite/utils/manifest-utils.ts +8 -59
  236. package/src/vite/utils/package-resolution.ts +41 -1
  237. package/src/vite/utils/prerender-utils.ts +5 -4
  238. package/src/vite/utils/shared-utils.ts +107 -26
  239. package/src/browser/action-response-classifier.ts +0 -99
@@ -21,8 +21,8 @@ function hashId(filePath, exportName) {
21
21
  function makeStubId(filePath, exportName, isBuild) {
22
22
  return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
23
23
  }
24
- function hashInlineId(filePath, lineNumber, index) {
25
- const input = index !== void 0 && index > 0 ? `${filePath}:${lineNumber}:${index}` : `${filePath}:${lineNumber}`;
24
+ function hashInlineId(filePath, fnName, index) {
25
+ const input = `${filePath}:${fnName}:${index}`;
26
26
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
27
27
  }
28
28
  function buildExportMap(program) {
@@ -191,7 +191,100 @@ function escapeRegExp(input) {
191
191
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
192
192
  }
193
193
 
194
+ // src/vite/debug.ts
195
+ import debugFactory from "debug";
196
+ var NS = {
197
+ config: "rango:config",
198
+ discovery: "rango:discovery",
199
+ routes: "rango:routes",
200
+ prerender: "rango:prerender",
201
+ build: "rango:build",
202
+ dev: "rango:dev",
203
+ transform: "rango:transform",
204
+ chunks: "rango:chunks"
205
+ };
206
+ if (process.env.INTERNAL_RANGO_DEBUG) {
207
+ const existing = debugFactory.disable();
208
+ debugFactory.enable(existing ? `${existing},rango:*` : "rango:*");
209
+ }
210
+ function createRangoDebugger(namespace) {
211
+ const primary = debugFactory(namespace);
212
+ const shadow = debugFactory(`vite:${namespace}`);
213
+ if (primary.enabled) return primary;
214
+ if (shadow.enabled) return shadow;
215
+ return void 0;
216
+ }
217
+ async function timed(debug11, label, fn) {
218
+ if (!debug11) return await fn();
219
+ const start = performance.now();
220
+ try {
221
+ return await fn();
222
+ } finally {
223
+ debug11("%s (%sms)", label, (performance.now() - start).toFixed(1));
224
+ }
225
+ }
226
+ function timedSync(debug11, label, fn) {
227
+ if (!debug11) return fn();
228
+ const start = performance.now();
229
+ try {
230
+ return fn();
231
+ } finally {
232
+ debug11("%s (%sms)", label, (performance.now() - start).toFixed(1));
233
+ }
234
+ }
235
+ function createCounter(debug11, label) {
236
+ if (!debug11) return void 0;
237
+ let n = 0;
238
+ let totalMs = 0;
239
+ let slowestMs = 0;
240
+ let slowestFile = "";
241
+ const record = (file, ms) => {
242
+ n++;
243
+ totalMs += ms;
244
+ if (ms > slowestMs) {
245
+ slowestMs = ms;
246
+ slowestFile = file;
247
+ }
248
+ };
249
+ return {
250
+ record,
251
+ time(file, fn) {
252
+ const start = performance.now();
253
+ let out;
254
+ try {
255
+ out = fn();
256
+ } catch (err) {
257
+ record(file, performance.now() - start);
258
+ throw err;
259
+ }
260
+ if (out && typeof out.then === "function") {
261
+ return out.finally(
262
+ () => record(file, performance.now() - start)
263
+ );
264
+ }
265
+ record(file, performance.now() - start);
266
+ return out;
267
+ },
268
+ flush() {
269
+ if (n === 0) return;
270
+ debug11(
271
+ "%s: %d files, %sms total, slowest %sms %s",
272
+ label,
273
+ n,
274
+ totalMs.toFixed(1),
275
+ slowestMs.toFixed(1),
276
+ slowestFile
277
+ );
278
+ n = 0;
279
+ totalMs = 0;
280
+ slowestMs = 0;
281
+ slowestFile = "";
282
+ }
283
+ };
284
+ }
285
+
194
286
  // src/vite/plugins/expose-action-id.ts
287
+ var debug = createRangoDebugger(NS.transform);
195
288
  function getRscPluginApi(config) {
196
289
  let plugin = config.plugins.find((p) => p.name === "rsc:minimal");
197
290
  if (!plugin) {
@@ -200,7 +293,7 @@ function getRscPluginApi(config) {
200
293
  );
201
294
  if (plugin) {
202
295
  console.warn(
203
- `[rsc-router:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). Consider updating the name lookup if the plugin was renamed.`
296
+ `[rango:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). Consider updating the name lookup if the plugin was renamed.`
204
297
  );
205
298
  }
206
299
  }
@@ -280,6 +373,8 @@ function exposeActionId() {
280
373
  let isBuild = false;
281
374
  let hashToFileMap;
282
375
  let rscPluginApi;
376
+ const counterTransform = createCounter(debug, "expose-action-id transform");
377
+ const counterRender = createCounter(debug, "expose-action-id renderChunk");
283
378
  return {
284
379
  name: "@rangojs/router:expose-action-id",
285
380
  // Run after all other plugins (including RSC plugin's transforms)
@@ -289,13 +384,17 @@ function exposeActionId() {
289
384
  isBuild = config.command === "build";
290
385
  rscPluginApi = getRscPluginApi(config);
291
386
  },
387
+ buildEnd() {
388
+ counterTransform?.flush();
389
+ counterRender?.flush();
390
+ },
292
391
  buildStart() {
293
392
  if (!rscPluginApi) {
294
393
  rscPluginApi = getRscPluginApi(config);
295
394
  }
296
395
  if (!rscPluginApi) {
297
396
  throw new Error(
298
- "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
397
+ "[rango] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
299
398
  );
300
399
  }
301
400
  if (!isBuild) return;
@@ -325,35 +424,49 @@ function exposeActionId() {
325
424
  if (id.includes("/node_modules/")) {
326
425
  return;
327
426
  }
328
- return transformServerReferences(code, id);
427
+ const start = counterTransform ? performance.now() : 0;
428
+ try {
429
+ return transformServerReferences(code, id);
430
+ } finally {
431
+ counterTransform?.record(id, performance.now() - start);
432
+ }
329
433
  },
330
434
  // Build mode: renderChunk runs after all transforms and bundling complete
331
435
  renderChunk(code, chunk) {
332
- const isRscEnv = this.environment?.name === "rsc";
333
- const effectiveMap = isRscEnv ? hashToFileMap : void 0;
334
- if (isRscEnv && hashToFileMap) {
335
- const s = new MagicString(code);
336
- const changed1 = applyServerReferenceWrapping(code, s, effectiveMap);
337
- const changed2 = applyRegisterReferenceWrapping(code, s, hashToFileMap);
338
- if (changed1 || changed2) {
339
- return {
340
- code: s.toString(),
341
- map: s.generateMap({
342
- source: chunk.fileName,
343
- includeContent: true
344
- })
345
- };
436
+ const start = counterRender ? performance.now() : 0;
437
+ try {
438
+ const isRscEnv = this.environment?.name === "rsc";
439
+ const effectiveMap = isRscEnv ? hashToFileMap : void 0;
440
+ if (isRscEnv && hashToFileMap) {
441
+ const s = new MagicString(code);
442
+ const changed1 = applyServerReferenceWrapping(code, s, effectiveMap);
443
+ const changed2 = applyRegisterReferenceWrapping(
444
+ code,
445
+ s,
446
+ hashToFileMap
447
+ );
448
+ if (changed1 || changed2) {
449
+ return {
450
+ code: s.toString(),
451
+ map: s.generateMap({
452
+ source: chunk.fileName,
453
+ includeContent: true
454
+ })
455
+ };
456
+ }
457
+ return null;
346
458
  }
347
- return null;
459
+ return transformServerReferences(code, chunk.fileName, effectiveMap);
460
+ } finally {
461
+ counterRender?.record(chunk.fileName, performance.now() - start);
348
462
  }
349
- return transformServerReferences(code, chunk.fileName, effectiveMap);
350
463
  }
351
464
  };
352
465
  }
353
466
 
354
467
  // src/vite/plugins/expose-internal-ids.ts
355
468
  import { parseAst as parseAst2 } from "vite";
356
- import MagicString4 from "magic-string";
469
+ import MagicString3 from "magic-string";
357
470
  import path4 from "node:path";
358
471
 
359
472
  // src/vite/utils/ast-handler-extract.ts
@@ -363,7 +476,7 @@ function isDirectivePrologueStatement(node) {
363
476
  function findImportInsertionPos(code, parseAst4) {
364
477
  let program;
365
478
  try {
366
- program = parseAst4(code, { jsx: true });
479
+ program = parseAst4(code, { lang: "tsx" });
367
480
  } catch {
368
481
  return 0;
369
482
  }
@@ -403,7 +516,7 @@ function walkNode(node, parent, ancestors, enter) {
403
516
  function findHandlerCalls(code, fnName, parseAst4) {
404
517
  let program;
405
518
  try {
406
- program = parseAst4(code, { jsx: true });
519
+ program = parseAst4(code, { lang: "tsx" });
407
520
  } catch {
408
521
  return [];
409
522
  }
@@ -477,7 +590,7 @@ function getImportedLocalNamesFromProgram(program, importedName) {
477
590
  }
478
591
  function getImportedLocalNames(code, importedName, parseAst4) {
479
592
  try {
480
- const program = parseAst4(code, { jsx: true });
593
+ const program = parseAst4(code, { lang: "tsx" });
481
594
  return getImportedLocalNamesFromProgram(program, importedName);
482
595
  } catch {
483
596
  return /* @__PURE__ */ new Set();
@@ -486,7 +599,7 @@ function getImportedLocalNames(code, importedName, parseAst4) {
486
599
  function extractImportDeclarations(code, parseAst4) {
487
600
  let program;
488
601
  try {
489
- program = parseAst4(code, { jsx: true });
602
+ program = parseAst4(code, { lang: "tsx" });
490
603
  } catch {
491
604
  return [];
492
605
  }
@@ -541,7 +654,7 @@ function isSafeVariableDeclaration(node, handlerNames) {
541
654
  function extractModuleLevelDeclarations(code, parseAst4, handlerNames) {
542
655
  let program;
543
656
  try {
544
- program = parseAst4(code, { jsx: true });
657
+ program = parseAst4(code, { lang: "tsx" });
545
658
  } catch {
546
659
  return [];
547
660
  }
@@ -585,14 +698,12 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
585
698
  parseAst4,
586
699
  handlerNames
587
700
  );
588
- const lineCounts = /* @__PURE__ */ new Map();
589
701
  const importStatements = [];
590
- for (const site of inlineSites) {
591
- const lineCount = lineCounts.get(site.lineNumber) ?? 0;
592
- lineCounts.set(site.lineNumber, lineCount + 1);
593
- const hash = hashInlineId(filePath, site.lineNumber, lineCount);
702
+ for (const [siteIndex, site] of inlineSites.entries()) {
703
+ const hash = hashInlineId(filePath, fnName, siteIndex);
594
704
  const exportName = `__sh_${hash}`;
595
- const virtualId = `\0${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
705
+ const idSuffix = `${filePath}:${fnName}:${siteIndex}`;
706
+ const virtualId = `\0${virtualPrefix}${idSuffix}`;
596
707
  const handlerCode = code.slice(site.callStart, site.callEnd);
597
708
  virtualRegistry.set(virtualId, {
598
709
  originalModuleId: moduleId,
@@ -602,7 +713,7 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
602
713
  exportName
603
714
  });
604
715
  s.overwrite(site.callStart, site.callEnd, exportName);
605
- const importId = `${virtualPrefix}${filePath}:${site.lineNumber}${lineCount > 0 ? `:${lineCount}` : ""}`;
716
+ const importId = `${virtualPrefix}${idSuffix}`;
606
717
  importStatements.push(`import { ${exportName} } from "${importId}";`);
607
718
  }
608
719
  if (importStatements.length > 0) {
@@ -634,6 +745,83 @@ var STRICT_CREATE_CONFIGS = [
634
745
 
635
746
  // src/vite/plugins/expose-ids/export-analysis.ts
636
747
  import { parseAst } from "vite";
748
+
749
+ // src/build/route-types/source-scan.ts
750
+ function isLineTerminator(ch) {
751
+ const c = ch.charCodeAt(0);
752
+ return c === 10 || c === 13 || c === 8232 || c === 8233;
753
+ }
754
+ function makeCodeClassifier(code) {
755
+ const n = code.length;
756
+ let i = 0;
757
+ let skipStart = -1;
758
+ let skipEnd = -1;
759
+ return (q) => {
760
+ if (q >= skipStart && q < skipEnd) return false;
761
+ while (i < n && i <= q) {
762
+ const c = code[i];
763
+ const d = i + 1 < n ? code[i + 1] : "";
764
+ let end = -1;
765
+ if (c === "/" && d === "/") {
766
+ let j = i + 2;
767
+ while (j < n && !isLineTerminator(code[j])) j++;
768
+ end = j;
769
+ } else if (c === "/" && d === "*") {
770
+ let j = i + 2;
771
+ while (j < n && !(code[j] === "*" && code[j + 1] === "/")) j++;
772
+ end = Math.min(n, j + 2);
773
+ } else if (c === '"' || c === "'" || c === "`") {
774
+ let j = i + 1;
775
+ while (j < n) {
776
+ if (code[j] === "\\") {
777
+ j += 2;
778
+ continue;
779
+ }
780
+ if (code[j] === c) {
781
+ j++;
782
+ break;
783
+ }
784
+ j++;
785
+ }
786
+ end = j;
787
+ }
788
+ if (end >= 0) {
789
+ if (q < end) {
790
+ skipStart = i;
791
+ skipEnd = end;
792
+ return false;
793
+ }
794
+ i = end;
795
+ } else {
796
+ i++;
797
+ }
798
+ }
799
+ return true;
800
+ };
801
+ }
802
+ function firstCodeMatchIndex(code, pattern) {
803
+ const inCode = makeCodeClassifier(code);
804
+ pattern.lastIndex = 0;
805
+ let m;
806
+ while ((m = pattern.exec(code)) !== null) {
807
+ if (inCode(m.index)) return m.index;
808
+ if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
809
+ }
810
+ return -1;
811
+ }
812
+ function codeMatchIndices(code, pattern) {
813
+ const inCode = makeCodeClassifier(code);
814
+ const indices = [];
815
+ pattern.lastIndex = 0;
816
+ let m;
817
+ while ((m = pattern.exec(code)) !== null) {
818
+ if (inCode(m.index)) indices.push(m.index);
819
+ if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
820
+ }
821
+ return indices;
822
+ }
823
+
824
+ // src/vite/plugins/expose-ids/export-analysis.ts
637
825
  function isExportOnlyFile(code, bindings) {
638
826
  if (bindings.length === 0) return false;
639
827
  const knownLocals = /* @__PURE__ */ new Set();
@@ -662,12 +850,30 @@ function isExportOnlyFile(code, bindings) {
662
850
  }
663
851
  return true;
664
852
  }
665
- function countCreateCallsForNames(code, fnNames) {
666
- const pattern = new RegExp(
853
+ function createCallPattern(fnNames) {
854
+ return new RegExp(
667
855
  `\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
668
856
  "g"
669
857
  );
670
- return (code.match(pattern) || []).length;
858
+ }
859
+ function countCreateCallsForNames(code, fnNames) {
860
+ return codeMatchIndices(code, createCallPattern(fnNames)).length;
861
+ }
862
+ function offsetToLineColumn(code, index) {
863
+ let line = 1;
864
+ let lineStart = 0;
865
+ const end = Math.min(index, code.length);
866
+ for (let i = 0; i < end; i++) {
867
+ if (code[i] === "\n") {
868
+ line++;
869
+ lineStart = i + 1;
870
+ }
871
+ }
872
+ return { line, column: index - lineStart + 1 };
873
+ }
874
+ function findUnsupportedCreateCallSites(code, fnNames, supportedBindings) {
875
+ const supported = new Set(supportedBindings.map((b) => b.callExprStart));
876
+ return codeMatchIndices(code, createCallPattern(fnNames)).filter((index) => !supported.has(index)).map((index) => offsetToLineColumn(code, index));
671
877
  }
672
878
  function getImportedFnNames(code, importedName) {
673
879
  const importPattern = /import\s*\{([^}]*)\}\s*from\s*["']@rangojs\/router(?:\/[^"']*)?["']/g;
@@ -698,6 +904,17 @@ function getCalledIdentifierFromCall(callExpr) {
698
904
  }
699
905
  return null;
700
906
  }
907
+ function unwrapSignatureWrappedCall(init, fnNameSet) {
908
+ if (init?.type !== "CallExpression") return init;
909
+ const directId = getCalledIdentifierFromCall(init);
910
+ if (directId && fnNameSet.has(directId)) return init;
911
+ const firstArg = init.arguments?.[0];
912
+ if (firstArg?.type === "CallExpression") {
913
+ const innerId = getCalledIdentifierFromCall(firstArg);
914
+ if (innerId && fnNameSet.has(innerId)) return firstArg;
915
+ }
916
+ return init;
917
+ }
701
918
  function collectCreateExportBindingsFallback(code, fnNames) {
702
919
  const alternation = fnNames.map(escapeRegExp).join("|");
703
920
  const exportConstPattern = new RegExp(
@@ -757,7 +974,7 @@ function collectCreateExportBindingsFallback(code, fnNames) {
757
974
  function collectCreateExportBindings(code, fnNames, program) {
758
975
  if (!program) {
759
976
  try {
760
- program = parseAst(code, { jsx: true });
977
+ program = parseAst(code, { lang: "tsx" });
761
978
  } catch {
762
979
  return collectCreateExportBindingsFallback(code, fnNames);
763
980
  }
@@ -770,16 +987,16 @@ function collectCreateExportBindings(code, fnNames, program) {
770
987
  return;
771
988
  }
772
989
  for (const decl of varDecl.declarations ?? []) {
773
- const calledIdentifier = getCalledIdentifierFromCall(decl?.init);
774
- if (decl?.id?.type !== "Identifier" || decl?.init?.type !== "CallExpression" || !calledIdentifier || !fnNameSet.has(calledIdentifier)) {
990
+ const callExpr = unwrapSignatureWrappedCall(decl?.init, fnNameSet);
991
+ const calledIdentifier = getCalledIdentifierFromCall(callExpr);
992
+ if (decl?.id?.type !== "Identifier" || callExpr?.type !== "CallExpression" || !calledIdentifier || !fnNameSet.has(calledIdentifier)) {
775
993
  continue;
776
994
  }
777
995
  const localName = decl.id.name;
778
996
  const exportNames = exportMap.get(localName) ?? [];
779
997
  if (exportNames.length === 0) continue;
780
- const callStart = decl.init.start;
781
- const callEnd = decl.init.end;
782
- const calleeEnd = decl.init.callee.end;
998
+ const callEnd = callExpr.end;
999
+ const calleeEnd = callExpr.callee.end;
783
1000
  let openParenPos = -1;
784
1001
  for (let i = calleeEnd; i < callEnd; i++) {
785
1002
  if (code[i] === "(") {
@@ -793,10 +1010,10 @@ function collectCreateExportBindings(code, fnNames, program) {
793
1010
  bindings.push({
794
1011
  localName,
795
1012
  exportNames,
796
- callExprStart: decl.init.start,
1013
+ callExprStart: callExpr.start,
797
1014
  callOpenParenPos: openParenPos,
798
1015
  callCloseParenPos: closeParenPos,
799
- argCount: decl.init.arguments?.length ?? 0,
1016
+ argCount: callExpr.arguments?.length ?? 0,
800
1017
  statementEnd
801
1018
  });
802
1019
  }
@@ -815,9 +1032,20 @@ function collectCreateExportBindings(code, fnNames, program) {
815
1032
  }
816
1033
  return bindings;
817
1034
  }
818
- function buildUnsupportedShapeWarning(filePath, fnName) {
819
- return [
820
- `[rsc-router] Unsupported ${fnName} shape in "${filePath}".`,
1035
+ function buildUnsupportedShapeWarning(filePath, fnName, sites = []) {
1036
+ const lines = [`[rango] Unsupported ${fnName} shape in "${filePath}".`];
1037
+ if (sites.length === 1) {
1038
+ const s = sites[0];
1039
+ lines.push(
1040
+ `The ${fnName}(...) call at ${filePath}:${s.line}:${s.column} has no stable $$id injected \u2014 it is not in a supported shape.`
1041
+ );
1042
+ } else if (sites.length > 1) {
1043
+ lines.push(
1044
+ `These ${fnName}(...) calls have no stable $$id injected \u2014 they are not in a supported shape:`
1045
+ );
1046
+ for (const s of sites) lines.push(` - ${filePath}:${s.line}:${s.column}`);
1047
+ }
1048
+ lines.push(
821
1049
  `Supported shapes are:`,
822
1050
  ` - export const X = ${fnName}(...)`,
823
1051
  ` - const X = ${fnName}(...); export { X }`,
@@ -825,7 +1053,8 @@ function buildUnsupportedShapeWarning(filePath, fnName) {
825
1053
  `Potentially unsupported forms include:`,
826
1054
  ` - export let/var X = ${fnName}(...)`,
827
1055
  ` - inline ${fnName}(...) calls`
828
- ].join("\n");
1056
+ );
1057
+ return lines.join("\n");
829
1058
  }
830
1059
 
831
1060
  // src/vite/plugins/expose-ids/loader-transform.ts
@@ -839,7 +1068,7 @@ function generateClientLoaderStubs(bindings, code, filePath, isBuild) {
839
1068
  const lines = [];
840
1069
  for (const binding of bindings) {
841
1070
  for (const name of binding.exportNames) {
842
- const loaderId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1071
+ const loaderId = makeStubId(filePath, name, isBuild);
843
1072
  lines.push(
844
1073
  `export const ${name} = { __brand: "loader", $$id: "${loaderId}" };`
845
1074
  );
@@ -851,7 +1080,7 @@ function transformLoaders(bindings, s, filePath, isBuild) {
851
1080
  let hasChanges = false;
852
1081
  for (const binding of bindings) {
853
1082
  const exportName = binding.exportNames[0];
854
- const loaderId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1083
+ const loaderId = makeStubId(filePath, exportName, isBuild);
855
1084
  const paramInjection = binding.argCount === 1 ? `, undefined, "${loaderId}"` : `, "${loaderId}"`;
856
1085
  s.appendLeft(binding.callCloseParenPos, paramInjection);
857
1086
  const propInjection = `
@@ -863,7 +1092,6 @@ ${binding.localName}.$$id = "${loaderId}";`;
863
1092
  }
864
1093
 
865
1094
  // src/vite/plugins/expose-ids/handler-transform.ts
866
- import MagicString2 from "magic-string";
867
1095
  function analyzeCreateHandleArgs(code, startPos, endPos) {
868
1096
  const content = code.slice(startPos, endPos).trim();
869
1097
  return { hasArgs: content.length > 0 };
@@ -877,7 +1105,7 @@ function transformHandles(bindings, s, code, filePath, isBuild) {
877
1105
  binding.callOpenParenPos + 1,
878
1106
  binding.callCloseParenPos
879
1107
  );
880
- const handleId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1108
+ const handleId = makeStubId(filePath, exportName, isBuild);
881
1109
  let paramInjection;
882
1110
  if (!args.hasArgs) {
883
1111
  paramInjection = `undefined, "${handleId}"`;
@@ -896,7 +1124,7 @@ function transformLocationState(bindings, s, filePath, isBuild) {
896
1124
  let hasChanges = false;
897
1125
  for (const binding of bindings) {
898
1126
  const exportName = binding.exportNames[0];
899
- const stateKey = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1127
+ const stateKey = makeStubId(filePath, exportName, isBuild);
900
1128
  const propInjection = `
901
1129
  ${binding.localName}.__rsc_ls_key = "__rsc_ls_${stateKey}";`;
902
1130
  s.appendRight(binding.statementEnd, propInjection);
@@ -908,7 +1136,7 @@ function generateWholeFileStubs(cfg, bindings, code, filePath, isBuild) {
908
1136
  if (!isExportOnlyFile(code, bindings)) return null;
909
1137
  const exportNames = bindings.flatMap((b) => b.exportNames);
910
1138
  const stubs = exportNames.map((name) => {
911
- const handlerId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1139
+ const handlerId = makeStubId(filePath, name, isBuild);
912
1140
  return `export const ${name} = { __brand: "${cfg.brand}", $$id: "${handlerId}" };`;
913
1141
  });
914
1142
  return { code: stubs.join("\n") + "\n", map: null };
@@ -917,7 +1145,7 @@ function stubHandlerExprs(cfg, bindings, s, filePath, isBuild) {
917
1145
  let hasChanges = false;
918
1146
  for (const binding of bindings) {
919
1147
  const exportName = binding.exportNames[0];
920
- const handlerId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1148
+ const handlerId = makeStubId(filePath, exportName, isBuild);
921
1149
  s.overwrite(
922
1150
  binding.callExprStart,
923
1151
  binding.callCloseParenPos + 1,
@@ -931,7 +1159,7 @@ function transformHandlerIds(cfg, bindings, s, filePath, isBuild) {
931
1159
  let hasChanges = false;
932
1160
  for (const binding of bindings) {
933
1161
  const exportName = binding.exportNames[0];
934
- const handlerId = isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
1162
+ const handlerId = makeStubId(filePath, exportName, isBuild);
935
1163
  let paramInjection;
936
1164
  if (binding.argCount === 0) {
937
1165
  paramInjection = `undefined, "${handlerId}"`;
@@ -950,19 +1178,20 @@ ${binding.localName}.$$id = "${handlerId}";`;
950
1178
  }
951
1179
 
952
1180
  // src/vite/plugins/expose-ids/router-transform.ts
953
- import MagicString3 from "magic-string";
1181
+ import MagicString2 from "magic-string";
954
1182
  import path3 from "node:path";
955
1183
  import { createHash } from "node:crypto";
1184
+ var debug2 = createRangoDebugger(NS.transform);
956
1185
  function transformRouter(code, filePath, routerFnNames, absolutePath) {
957
1186
  const pat = new RegExp(
958
1187
  `\\b(?:${routerFnNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\s*(?:<[^>]*>)?\\s*\\(`,
959
1188
  "g"
960
1189
  );
961
1190
  let match;
962
- const s = new MagicString3(code);
1191
+ const s = new MagicString2(code);
963
1192
  let changed = false;
964
- const basename3 = path3.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
965
- const routeNamesImport = `./${basename3}.named-routes.gen.js`;
1193
+ const basename2 = path3.basename(filePath).replace(/\.(tsx?|jsx?)$/, "");
1194
+ const routeNamesImport = `./${basename2}.named-routes.gen.js`;
966
1195
  const routeNamesVar = `__rsc_rn`;
967
1196
  while ((match = pat.exec(code)) !== null) {
968
1197
  const callStart = match.index;
@@ -995,11 +1224,15 @@ function transformRouter(code, filePath, routerFnNames, absolutePath) {
995
1224
  }
996
1225
  function exposeRouterId() {
997
1226
  let projectRoot = "";
1227
+ const counter = createCounter(debug2, "expose-router-id");
998
1228
  return {
999
1229
  name: "@rangojs/router:expose-router-id",
1000
1230
  configResolved(config) {
1001
1231
  projectRoot = config.root;
1002
1232
  },
1233
+ buildEnd() {
1234
+ counter?.flush();
1235
+ },
1003
1236
  transform(code, id) {
1004
1237
  if (!code.includes("createRouter")) return null;
1005
1238
  if (!/import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
@@ -1008,14 +1241,25 @@ function exposeRouterId() {
1008
1241
  return null;
1009
1242
  }
1010
1243
  if (id.includes("node_modules")) return null;
1011
- const filePath = normalizePath(path3.relative(projectRoot, id));
1012
- const routerFnNames = getImportedFnNames(code, "createRouter");
1013
- return transformRouter(code, filePath, routerFnNames, normalizePath(id));
1244
+ const start = counter ? performance.now() : 0;
1245
+ try {
1246
+ const filePath = normalizePath(path3.relative(projectRoot, id));
1247
+ const routerFnNames = getImportedFnNames(code, "createRouter");
1248
+ return transformRouter(
1249
+ code,
1250
+ filePath,
1251
+ routerFnNames,
1252
+ normalizePath(id)
1253
+ );
1254
+ } finally {
1255
+ counter?.record(id, performance.now() - start);
1256
+ }
1014
1257
  }
1015
1258
  };
1016
1259
  }
1017
1260
 
1018
1261
  // src/vite/plugins/expose-internal-ids.ts
1262
+ var debug3 = createRangoDebugger(NS.transform);
1019
1263
  var VIRTUAL_LOADER_MANIFEST = "virtual:rsc-router/loader-manifest";
1020
1264
  var RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
1021
1265
  var VIRTUAL_HANDLER_PREFIX = "virtual:handler-extract:";
@@ -1028,9 +1272,13 @@ function exposeInternalIds(options) {
1028
1272
  const staticHandlerModules = /* @__PURE__ */ new Map();
1029
1273
  const virtualHandlers = /* @__PURE__ */ new Map();
1030
1274
  const unsupportedShapeWarnings = /* @__PURE__ */ new Set();
1275
+ const counter = createCounter(debug3, "expose-internal-ids");
1031
1276
  return {
1032
1277
  name: "@rangojs/router:expose-internal-ids",
1033
1278
  enforce: "post",
1279
+ buildEnd() {
1280
+ counter?.flush();
1281
+ },
1034
1282
  api: {
1035
1283
  prerenderHandlerModules,
1036
1284
  staticHandlerModules
@@ -1144,11 +1392,13 @@ ${lazyImports.join(",\n")}
1144
1392
  // --------------- Unified transform ---------------
1145
1393
  transform(code, id) {
1146
1394
  if (id.includes("/node_modules/")) return;
1147
- const filePath = normalizePath(path4.relative(projectRoot, id));
1148
- const isRscEnv = this.environment?.name === "rsc";
1149
- if (id.includes(".named-routes.gen.") && !isRscEnv && this.environment?.name === "client") {
1150
- this.warn(
1151
- `
1395
+ const __t0 = counter ? performance.now() : 0;
1396
+ try {
1397
+ const filePath = normalizePath(path4.relative(projectRoot, id));
1398
+ const isRscEnv = this.environment?.name === "rsc";
1399
+ if (id.includes(".named-routes.gen.") && !isRscEnv && this.environment?.name === "client") {
1400
+ this.warn(
1401
+ `
1152
1402
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1153
1403
  !! !!
1154
1404
  !! WARNING: NamedRoutes imported in a CLIENT component! !!
@@ -1168,367 +1418,363 @@ ${lazyImports.join(",\n")}
1168
1418
  !! !!
1169
1419
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1170
1420
  `
1171
- );
1172
- }
1173
- if (!code.includes("@rangojs/router")) return;
1174
- const has = detectImports(code);
1175
- const hasLoaderCode = has.loader && code.includes("createLoader");
1176
- const hasHandleCode = has.handle && code.includes("createHandle");
1177
- const hasLocationStateCode = has.locationState && code.includes("createLocationState");
1178
- const hasPrerenderHandlerCode = has.prerenderHandler && code.includes("Prerender");
1179
- const hasStaticHandlerCode = has.staticHandler && code.includes("Static");
1180
- if (!hasLoaderCode && !hasHandleCode && !hasLocationStateCode && !hasPrerenderHandlerCode && !hasStaticHandlerCode) {
1181
- return;
1182
- }
1183
- const _fnNamesCache = /* @__PURE__ */ new Map();
1184
- const _bindingsCache = /* @__PURE__ */ new Map();
1185
- let _cachedAst;
1186
- let _astParseFailed = false;
1187
- let _astCodeRef = code;
1188
- const getFnNames = (canonicalName) => {
1189
- let result = _fnNamesCache.get(canonicalName);
1190
- if (!result) {
1191
- result = getImportedFnNames(code, canonicalName);
1192
- _fnNamesCache.set(canonicalName, result);
1421
+ );
1193
1422
  }
1194
- return result;
1195
- };
1196
- const lazyAst = () => {
1197
- if (code !== _astCodeRef) {
1198
- _cachedAst = void 0;
1199
- _astParseFailed = false;
1200
- _astCodeRef = code;
1423
+ if (!code.includes("@rangojs/router")) return;
1424
+ const has = detectImports(code);
1425
+ const hasLoaderCode = has.loader && code.includes("createLoader");
1426
+ const hasHandleCode = has.handle && code.includes("createHandle");
1427
+ const hasLocationStateCode = has.locationState && code.includes("createLocationState");
1428
+ const hasPrerenderHandlerCode = has.prerenderHandler && code.includes("Prerender");
1429
+ const hasStaticHandlerCode = has.staticHandler && code.includes("Static");
1430
+ if (!hasLoaderCode && !hasHandleCode && !hasLocationStateCode && !hasPrerenderHandlerCode && !hasStaticHandlerCode) {
1431
+ return;
1201
1432
  }
1202
- if (_cachedAst !== void 0 || _astParseFailed) return _cachedAst;
1203
- try {
1204
- _cachedAst = parseAst2(code, { jsx: true });
1205
- } catch {
1206
- _astParseFailed = true;
1433
+ const _fnNamesCache = /* @__PURE__ */ new Map();
1434
+ const _bindingsCache = /* @__PURE__ */ new Map();
1435
+ let _cachedAst;
1436
+ let _astParseFailed = false;
1437
+ let _astCodeRef = code;
1438
+ const getFnNames = (canonicalName) => {
1439
+ let result = _fnNamesCache.get(canonicalName);
1440
+ if (!result) {
1441
+ result = getImportedFnNames(code, canonicalName);
1442
+ _fnNamesCache.set(canonicalName, result);
1443
+ }
1444
+ return result;
1445
+ };
1446
+ const lazyAst = () => {
1447
+ if (code !== _astCodeRef) {
1448
+ _cachedAst = void 0;
1449
+ _astParseFailed = false;
1450
+ _astCodeRef = code;
1451
+ }
1452
+ if (_cachedAst !== void 0 || _astParseFailed) return _cachedAst;
1453
+ try {
1454
+ _cachedAst = parseAst2(code, { lang: "tsx" });
1455
+ } catch {
1456
+ _astParseFailed = true;
1457
+ }
1458
+ return _cachedAst;
1459
+ };
1460
+ const getBindings = (currentCode, fnNames) => {
1461
+ const key = fnNames.join("\0");
1462
+ let result = _bindingsCache.get(key);
1463
+ if (!result) {
1464
+ result = collectCreateExportBindings(
1465
+ currentCode,
1466
+ fnNames,
1467
+ lazyAst()
1468
+ );
1469
+ _bindingsCache.set(key, result);
1470
+ }
1471
+ return result;
1472
+ };
1473
+ for (const cfg of STRICT_CREATE_CONFIGS) {
1474
+ const hasCode = cfg.fnName === "createLoader" ? hasLoaderCode : cfg.fnName === "createHandle" ? hasHandleCode : hasLocationStateCode;
1475
+ if (!hasCode) continue;
1476
+ const fnNames = getFnNames(cfg.fnName);
1477
+ const sites = findUnsupportedCreateCallSites(
1478
+ code,
1479
+ fnNames,
1480
+ getBindings(code, fnNames)
1481
+ );
1482
+ if (sites.length === 0) continue;
1483
+ const warnKey = `${id}::${cfg.fnName}`;
1484
+ if (unsupportedShapeWarnings.has(warnKey)) continue;
1485
+ unsupportedShapeWarnings.add(warnKey);
1486
+ this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName, sites));
1207
1487
  }
1208
- return _cachedAst;
1209
- };
1210
- const getBindings = (currentCode, fnNames) => {
1211
- const key = fnNames.join("\0");
1212
- let result = _bindingsCache.get(key);
1213
- if (!result) {
1214
- result = collectCreateExportBindings(currentCode, fnNames, lazyAst());
1215
- _bindingsCache.set(key, result);
1488
+ if (hasLoaderCode && isRscEnv) {
1489
+ const fnNames = getFnNames("createLoader");
1490
+ const bindings = getBindings(code, fnNames);
1491
+ for (const binding of bindings) {
1492
+ const exportName = binding.exportNames[0];
1493
+ const hashedId = hashId(filePath, exportName);
1494
+ loaderRegistry.set(hashedId, {
1495
+ filePath,
1496
+ exportName
1497
+ });
1498
+ }
1216
1499
  }
1217
- return result;
1218
- };
1219
- for (const cfg of STRICT_CREATE_CONFIGS) {
1220
- const hasCode = cfg.fnName === "createLoader" ? hasLoaderCode : cfg.fnName === "createHandle" ? hasHandleCode : hasLocationStateCode;
1221
- if (!hasCode) continue;
1222
- const fnNames = getFnNames(cfg.fnName);
1223
- const totalCalls = countCreateCallsForNames(code, fnNames);
1224
- const supportedBindings = getBindings(code, fnNames).length;
1225
- if (totalCalls <= supportedBindings) continue;
1226
- const warnKey = `${id}::${cfg.fnName}`;
1227
- if (unsupportedShapeWarnings.has(warnKey)) continue;
1228
- unsupportedShapeWarnings.add(warnKey);
1229
- this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
1230
- }
1231
- if (hasLoaderCode && isRscEnv) {
1232
- const fnNames = getFnNames("createLoader");
1233
- const bindings = getBindings(code, fnNames);
1234
- for (const binding of bindings) {
1235
- const exportName = binding.exportNames[0];
1236
- const hashedId = hashId(filePath, exportName);
1237
- loaderRegistry.set(hashedId, {
1500
+ if (hasLoaderCode && !isRscEnv) {
1501
+ const fnNames = getFnNames("createLoader");
1502
+ const bindings = getBindings(code, fnNames);
1503
+ const stubResult = generateClientLoaderStubs(
1504
+ bindings,
1505
+ code,
1238
1506
  filePath,
1239
- exportName
1240
- });
1241
- }
1242
- }
1243
- if (hasLoaderCode && !isRscEnv) {
1244
- const fnNames = getFnNames("createLoader");
1245
- const bindings = getBindings(code, fnNames);
1246
- const stubResult = generateClientLoaderStubs(
1247
- bindings,
1248
- code,
1249
- filePath,
1250
- isBuild
1251
- );
1252
- if (stubResult) return stubResult;
1253
- }
1254
- if (hasPrerenderHandlerCode && !isRscEnv) {
1255
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1256
- const bindings = getBindings(code, fnNames);
1257
- const wholeFile = generateWholeFileStubs(
1258
- PRERENDER_CONFIG,
1259
- bindings,
1260
- code,
1261
- filePath,
1262
- isBuild
1263
- );
1264
- if (wholeFile) return wholeFile;
1265
- }
1266
- if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1267
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1268
- const exportNames = getBindings(code, fnNames).map(
1269
- (b) => b.exportNames[0]
1270
- );
1271
- if (exportNames.length > 0) {
1272
- prerenderHandlerModules.set(id, exportNames);
1507
+ isBuild
1508
+ );
1509
+ if (stubResult) return stubResult;
1273
1510
  }
1274
- }
1275
- let changed = false;
1276
- const handlerConfigs = [
1277
- hasStaticHandlerCode && STATIC_CONFIG,
1278
- hasPrerenderHandlerCode && PRERENDER_CONFIG
1279
- ].filter((c) => !!c).map((cfg) => {
1280
- const fnNames = getFnNames(cfg.fnName);
1281
- return { cfg, fnNames };
1282
- });
1283
- for (const { cfg, fnNames } of handlerConfigs) {
1284
- const totalCalls = countCreateCallsForNames(code, fnNames);
1285
- const supportedBindings = getBindings(code, fnNames).length;
1286
- if (totalCalls > supportedBindings) {
1287
- const iterS = new MagicString4(code);
1288
- const result = transformInlineHandlers(
1289
- cfg.fnName,
1290
- VIRTUAL_HANDLER_PREFIX,
1291
- iterS,
1511
+ if (hasPrerenderHandlerCode && !isRscEnv) {
1512
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1513
+ const bindings = getBindings(code, fnNames);
1514
+ const wholeFile = generateWholeFileStubs(
1515
+ PRERENDER_CONFIG,
1516
+ bindings,
1292
1517
  code,
1293
1518
  filePath,
1294
- virtualHandlers,
1295
- id,
1296
- parseAst2
1519
+ isBuild
1297
1520
  );
1298
- if (result) {
1299
- changed = true;
1300
- code = iterS.toString();
1301
- _bindingsCache.clear();
1302
- }
1521
+ if (wholeFile) return wholeFile;
1303
1522
  }
1304
- }
1305
- if (hasStaticHandlerCode && !isRscEnv) {
1306
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1307
- const bindings = getBindings(code, fnNames);
1308
- const wholeFile = generateWholeFileStubs(
1309
- STATIC_CONFIG,
1310
- bindings,
1311
- code,
1312
- filePath,
1313
- isBuild
1314
- );
1315
- if (wholeFile) return wholeFile;
1316
- }
1317
- if (!isRscEnv && (hasPrerenderHandlerCode || hasStaticHandlerCode)) {
1318
- const prerenderFnNames = hasPrerenderHandlerCode ? getFnNames(PRERENDER_CONFIG.fnName) : [];
1319
- const staticFnNames = hasStaticHandlerCode ? getFnNames(STATIC_CONFIG.fnName) : [];
1320
- const loaderFnNames = hasLoaderCode ? getFnNames("createLoader") : [];
1321
- const handleFnNames = hasHandleCode ? getFnNames("createHandle") : [];
1322
- const lsFnNames = hasLocationStateCode ? getFnNames("createLocationState") : [];
1323
- const allBindings = [];
1324
- for (const fnNames of [
1325
- prerenderFnNames,
1326
- staticFnNames,
1327
- loaderFnNames,
1328
- handleFnNames,
1329
- lsFnNames
1330
- ]) {
1331
- if (fnNames.length > 0) {
1332
- allBindings.push(...getBindings(code, fnNames));
1523
+ let changed = false;
1524
+ const handlerConfigs = [
1525
+ hasStaticHandlerCode && STATIC_CONFIG,
1526
+ hasPrerenderHandlerCode && PRERENDER_CONFIG
1527
+ ].filter((c) => !!c).map((cfg) => {
1528
+ const fnNames = getFnNames(cfg.fnName);
1529
+ return { cfg, fnNames };
1530
+ });
1531
+ for (const { cfg, fnNames } of handlerConfigs) {
1532
+ const totalCalls = countCreateCallsForNames(code, fnNames);
1533
+ const supportedBindings = getBindings(code, fnNames).length;
1534
+ if (totalCalls > supportedBindings) {
1535
+ const iterS = new MagicString3(code);
1536
+ const result = transformInlineHandlers(
1537
+ cfg.fnName,
1538
+ VIRTUAL_HANDLER_PREFIX,
1539
+ iterS,
1540
+ code,
1541
+ filePath,
1542
+ virtualHandlers,
1543
+ id,
1544
+ parseAst2
1545
+ );
1546
+ if (result) {
1547
+ changed = true;
1548
+ code = iterS.toString();
1549
+ _bindingsCache.clear();
1550
+ }
1333
1551
  }
1334
1552
  }
1335
- let canStubWholeFile = allBindings.length > 0 && isExportOnlyFile(code, allBindings);
1336
- if (canStubWholeFile && (handleFnNames.length > 0 || lsFnNames.length > 0)) {
1337
- const exportedLocals = new Set(allBindings.map((b) => b.localName));
1338
- const strippedBindings = [];
1339
- const localDeclPattern = /(?:^|;|\n)\s*(?:const|let|var|function)\s+(\w+)/g;
1340
- let declMatch;
1341
- while ((declMatch = localDeclPattern.exec(code)) !== null) {
1342
- const name = declMatch[1];
1343
- if (!exportedLocals.has(name) && !/^_c\d*$/.test(name)) {
1344
- strippedBindings.push(name);
1553
+ if (hasStaticHandlerCode && !isRscEnv) {
1554
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
1555
+ const bindings = getBindings(code, fnNames);
1556
+ const wholeFile = generateWholeFileStubs(
1557
+ STATIC_CONFIG,
1558
+ bindings,
1559
+ code,
1560
+ filePath,
1561
+ isBuild
1562
+ );
1563
+ if (wholeFile) return wholeFile;
1564
+ }
1565
+ if (!isRscEnv && (hasPrerenderHandlerCode || hasStaticHandlerCode)) {
1566
+ const prerenderFnNames = hasPrerenderHandlerCode ? getFnNames(PRERENDER_CONFIG.fnName) : [];
1567
+ const staticFnNames = hasStaticHandlerCode ? getFnNames(STATIC_CONFIG.fnName) : [];
1568
+ const loaderFnNames = hasLoaderCode ? getFnNames("createLoader") : [];
1569
+ const handleFnNames = hasHandleCode ? getFnNames("createHandle") : [];
1570
+ const lsFnNames = hasLocationStateCode ? getFnNames("createLocationState") : [];
1571
+ const allBindings = [];
1572
+ for (const fnNames of [
1573
+ prerenderFnNames,
1574
+ staticFnNames,
1575
+ loaderFnNames,
1576
+ handleFnNames,
1577
+ lsFnNames
1578
+ ]) {
1579
+ if (fnNames.length > 0) {
1580
+ allBindings.push(...getBindings(code, fnNames));
1345
1581
  }
1346
1582
  }
1347
- const importPattern = /import\s*\{([^}]*)\}\s*from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1348
- let importMatch;
1349
- while ((importMatch = importPattern.exec(code)) !== null) {
1350
- for (const spec of importMatch[1].split(",")) {
1351
- const m = spec.trim().match(/^[A-Za-z_$][\w$]*(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
1352
- if (m) strippedBindings.push(m[1] || m[0].trim().split(/\s/)[0]);
1583
+ let canStubWholeFile = allBindings.length > 0 && isExportOnlyFile(code, allBindings);
1584
+ if (canStubWholeFile && (handleFnNames.length > 0 || lsFnNames.length > 0)) {
1585
+ const exportedLocals = new Set(allBindings.map((b) => b.localName));
1586
+ const strippedBindings = [];
1587
+ const localDeclPattern = /(?:^|;|\n)\s*(?:const|let|var|function)\s+(\w+)/g;
1588
+ let declMatch;
1589
+ while ((declMatch = localDeclPattern.exec(code)) !== null) {
1590
+ const name = declMatch[1];
1591
+ if (!exportedLocals.has(name) && !/^_c\d*$/.test(name)) {
1592
+ strippedBindings.push(name);
1593
+ }
1594
+ }
1595
+ const importPattern = /import\s*\{([^}]*)\}\s*from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1596
+ let importMatch;
1597
+ while ((importMatch = importPattern.exec(code)) !== null) {
1598
+ for (const spec of importMatch[1].split(",")) {
1599
+ const m = spec.trim().match(/^[A-Za-z_$][\w$]*(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
1600
+ if (m)
1601
+ strippedBindings.push(m[1] || m[0].trim().split(/\s/)[0]);
1602
+ }
1603
+ }
1604
+ const defaultImportPattern = /import\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1605
+ while ((importMatch = defaultImportPattern.exec(code)) !== null) {
1606
+ strippedBindings.push(importMatch[1]);
1607
+ }
1608
+ const nsImportPattern = /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1609
+ while ((importMatch = nsImportPattern.exec(code)) !== null) {
1610
+ strippedBindings.push(importMatch[1]);
1611
+ }
1612
+ if (strippedBindings.length > 0) {
1613
+ const preservedBindings = allBindings.filter((b) => {
1614
+ const fc = code.slice(b.callExprStart, b.callOpenParenPos + 1);
1615
+ return handleFnNames.some((n) => fc.includes(n)) || lsFnNames.some((n) => fc.includes(n));
1616
+ });
1617
+ const strippedRe = new RegExp(
1618
+ `\\b(?:${strippedBindings.join("|")})\\b`
1619
+ );
1620
+ canStubWholeFile = !preservedBindings.some((b) => {
1621
+ const expr = code.slice(
1622
+ b.callExprStart,
1623
+ b.callCloseParenPos + 1
1624
+ );
1625
+ return strippedRe.test(expr);
1626
+ });
1353
1627
  }
1354
1628
  }
1355
- const defaultImportPattern = /import\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1356
- while ((importMatch = defaultImportPattern.exec(code)) !== null) {
1357
- strippedBindings.push(importMatch[1]);
1358
- }
1359
- const nsImportPattern = /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1360
- while ((importMatch = nsImportPattern.exec(code)) !== null) {
1361
- strippedBindings.push(importMatch[1]);
1362
- }
1363
- if (strippedBindings.length > 0) {
1364
- const preservedBindings = allBindings.filter((b) => {
1365
- const fc = code.slice(b.callExprStart, b.callOpenParenPos + 1);
1366
- return handleFnNames.some((n) => fc.includes(n)) || lsFnNames.some((n) => fc.includes(n));
1367
- });
1368
- const strippedRe = new RegExp(
1369
- `\\b(?:${strippedBindings.join("|")})\\b`
1370
- );
1371
- canStubWholeFile = !preservedBindings.some((b) => {
1372
- const expr = code.slice(b.callExprStart, b.callCloseParenPos + 1);
1373
- return strippedRe.test(expr);
1374
- });
1375
- }
1376
- }
1377
- if (canStubWholeFile) {
1378
- const lines = [];
1379
- const neededImports = [];
1380
- if (handleFnNames.length > 0) neededImports.push("createHandle");
1381
- if (lsFnNames.length > 0) neededImports.push("createLocationState");
1382
- if (neededImports.length > 0) {
1383
- lines.push(
1384
- `import { ${neededImports.join(", ")} } from "@rangojs/router";`
1385
- );
1386
- }
1387
- for (const binding of allBindings) {
1388
- const fnCall = code.slice(
1389
- binding.callExprStart,
1390
- binding.callOpenParenPos + 1
1391
- );
1392
- const isHandle = handleFnNames.some((n) => fnCall.includes(n));
1393
- const isLocationState = lsFnNames.some((n) => fnCall.includes(n));
1394
- const primaryName = binding.exportNames[0];
1395
- const stubId = makeStubId(filePath, primaryName, isBuild);
1396
- if (isHandle || isLocationState) {
1397
- const rawArgs = code.slice(binding.callOpenParenPos + 1, binding.callCloseParenPos).replace(/\b_c\d*\s*=\s*/g, "");
1398
- const canonicalName = isHandle ? "createHandle" : "createLocationState";
1399
- const activeFnNames = isHandle ? handleFnNames : lsFnNames;
1400
- let rawCallee = code.slice(
1629
+ if (canStubWholeFile) {
1630
+ const lines = [];
1631
+ const neededImports = [];
1632
+ if (handleFnNames.length > 0) neededImports.push("createHandle");
1633
+ if (lsFnNames.length > 0) neededImports.push("createLocationState");
1634
+ if (neededImports.length > 0) {
1635
+ lines.push(
1636
+ `import { ${neededImports.join(", ")} } from "@rangojs/router";`
1637
+ );
1638
+ }
1639
+ for (const binding of allBindings) {
1640
+ const fnCall = code.slice(
1401
1641
  binding.callExprStart,
1402
- binding.callOpenParenPos
1642
+ binding.callOpenParenPos + 1
1403
1643
  );
1404
- for (const alias of activeFnNames) {
1405
- if (alias !== canonicalName && rawCallee.startsWith(alias)) {
1406
- rawCallee = canonicalName + rawCallee.slice(alias.length);
1407
- break;
1408
- }
1409
- }
1410
- if (isHandle) {
1411
- const idParam = binding.argCount === 0 ? `undefined, "${stubId}"` : `, "${stubId}"`;
1412
- lines.push(
1413
- `export const ${primaryName} = ${rawCallee}(${rawArgs}${idParam});`
1644
+ const isHandle = handleFnNames.some((n) => fnCall.includes(n));
1645
+ const isLocationState = lsFnNames.some((n) => fnCall.includes(n));
1646
+ const primaryName = binding.exportNames[0];
1647
+ const stubId = makeStubId(filePath, primaryName, isBuild);
1648
+ if (isHandle || isLocationState) {
1649
+ const rawArgs = code.slice(
1650
+ binding.callOpenParenPos + 1,
1651
+ binding.callCloseParenPos
1652
+ ).replace(/\b_c\d*\s*=\s*/g, "");
1653
+ const canonicalName = isHandle ? "createHandle" : "createLocationState";
1654
+ const activeFnNames = isHandle ? handleFnNames : lsFnNames;
1655
+ let rawCallee = code.slice(
1656
+ binding.callExprStart,
1657
+ binding.callOpenParenPos
1414
1658
  );
1415
- lines.push(`${primaryName}.$$id = "${stubId}";`);
1659
+ for (const alias of activeFnNames) {
1660
+ if (alias !== canonicalName && rawCallee.startsWith(alias)) {
1661
+ rawCallee = canonicalName + rawCallee.slice(alias.length);
1662
+ break;
1663
+ }
1664
+ }
1665
+ if (isHandle) {
1666
+ const idParam = binding.argCount === 0 ? `undefined, "${stubId}"` : `, "${stubId}"`;
1667
+ lines.push(
1668
+ `export const ${primaryName} = ${rawCallee}(${rawArgs}${idParam});`
1669
+ );
1670
+ lines.push(`${primaryName}.$$id = "${stubId}";`);
1671
+ } else {
1672
+ lines.push(
1673
+ `export const ${primaryName} = ${rawCallee}(${rawArgs});`
1674
+ );
1675
+ lines.push(
1676
+ `${primaryName}.__rsc_ls_key = "__rsc_ls_${stubId}";`
1677
+ );
1678
+ }
1679
+ for (const name of binding.exportNames.slice(1)) {
1680
+ lines.push(`export const ${name} = ${primaryName};`);
1681
+ }
1416
1682
  } else {
1683
+ let brand = "loader";
1684
+ if (prerenderFnNames.some((n) => fnCall.includes(n))) {
1685
+ brand = PRERENDER_CONFIG.brand;
1686
+ } else if (staticFnNames.some((n) => fnCall.includes(n))) {
1687
+ brand = STATIC_CONFIG.brand;
1688
+ }
1417
1689
  lines.push(
1418
- `export const ${primaryName} = ${rawCallee}(${rawArgs});`
1419
- );
1420
- lines.push(
1421
- `${primaryName}.__rsc_ls_key = "__rsc_ls_${stubId}";`
1690
+ `export const ${primaryName} = { __brand: "${brand}", $$id: "${stubId}" };`
1422
1691
  );
1423
- }
1424
- for (const name of binding.exportNames.slice(1)) {
1425
- lines.push(`export const ${name} = ${primaryName};`);
1426
- }
1427
- } else {
1428
- let brand = "loader";
1429
- if (prerenderFnNames.some((n) => fnCall.includes(n))) {
1430
- brand = PRERENDER_CONFIG.brand;
1431
- } else if (staticFnNames.some((n) => fnCall.includes(n))) {
1432
- brand = STATIC_CONFIG.brand;
1433
- }
1434
- lines.push(
1435
- `export const ${primaryName} = { __brand: "${brand}", $$id: "${stubId}" };`
1436
- );
1437
- for (const name of binding.exportNames.slice(1)) {
1438
- lines.push(`export const ${name} = ${primaryName};`);
1692
+ for (const name of binding.exportNames.slice(1)) {
1693
+ lines.push(`export const ${name} = ${primaryName};`);
1694
+ }
1439
1695
  }
1440
1696
  }
1697
+ return { code: lines.join("\n") + "\n", map: null };
1441
1698
  }
1442
- return { code: lines.join("\n") + "\n", map: null };
1443
1699
  }
1444
- }
1445
- if (hasStaticHandlerCode && isRscEnv && isBuild) {
1446
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1447
- const exportNames = getBindings(code, fnNames).map(
1448
- (b) => b.exportNames[0]
1449
- );
1450
- if (exportNames.length > 0) {
1451
- staticHandlerModules.set(id, exportNames);
1700
+ if (isRscEnv && isBuild) {
1701
+ const trackTypes = [
1702
+ [
1703
+ hasPrerenderHandlerCode,
1704
+ PRERENDER_CONFIG,
1705
+ prerenderHandlerModules
1706
+ ],
1707
+ [hasStaticHandlerCode, STATIC_CONFIG, staticHandlerModules]
1708
+ ];
1709
+ for (const [has2, cfg, trackMap] of trackTypes) {
1710
+ if (!has2) continue;
1711
+ const exportNames = getBindings(code, getFnNames(cfg.fnName)).map(
1712
+ (b) => b.exportNames[0]
1713
+ );
1714
+ if (exportNames.length > 0) trackMap.set(id, exportNames);
1715
+ }
1452
1716
  }
1453
- }
1454
- const s = new MagicString4(code);
1455
- if (hasLoaderCode) {
1456
- const fnNames = getFnNames("createLoader");
1457
- changed = transformLoaders(getBindings(code, fnNames), s, filePath, isBuild) || changed;
1458
- }
1459
- if (hasHandleCode) {
1460
- const fnNames = getFnNames("createHandle");
1461
- changed = transformHandles(
1462
- getBindings(code, fnNames),
1463
- s,
1464
- code,
1465
- filePath,
1466
- isBuild
1467
- ) || changed;
1468
- }
1469
- if (hasLocationStateCode) {
1470
- const fnNames = getFnNames("createLocationState");
1471
- changed = transformLocationState(
1472
- getBindings(code, fnNames),
1473
- s,
1474
- filePath,
1475
- isBuild
1476
- ) || changed;
1477
- }
1478
- if (hasPrerenderHandlerCode) {
1479
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1480
- const bindings = getBindings(code, fnNames);
1481
- if (isRscEnv) {
1482
- changed = transformHandlerIds(
1483
- PRERENDER_CONFIG,
1484
- bindings,
1717
+ const s = new MagicString3(code);
1718
+ if (hasLoaderCode) {
1719
+ const fnNames = getFnNames("createLoader");
1720
+ changed = transformLoaders(
1721
+ getBindings(code, fnNames),
1485
1722
  s,
1486
1723
  filePath,
1487
1724
  isBuild
1488
1725
  ) || changed;
1489
- } else {
1490
- changed = stubHandlerExprs(
1491
- PRERENDER_CONFIG,
1492
- bindings,
1726
+ }
1727
+ if (hasHandleCode) {
1728
+ const fnNames = getFnNames("createHandle");
1729
+ changed = transformHandles(
1730
+ getBindings(code, fnNames),
1493
1731
  s,
1732
+ code,
1494
1733
  filePath,
1495
1734
  isBuild
1496
1735
  ) || changed;
1497
1736
  }
1498
- }
1499
- if (hasStaticHandlerCode) {
1500
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1501
- const bindings = getBindings(code, fnNames);
1502
- if (isRscEnv) {
1503
- changed = transformHandlerIds(
1504
- STATIC_CONFIG,
1505
- bindings,
1737
+ if (hasLocationStateCode) {
1738
+ const fnNames = getFnNames("createLocationState");
1739
+ changed = transformLocationState(
1740
+ getBindings(code, fnNames),
1506
1741
  s,
1507
1742
  filePath,
1508
1743
  isBuild
1509
1744
  ) || changed;
1510
- } else {
1511
- changed = stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) || changed;
1512
1745
  }
1746
+ const finalHandlerConfigs = [
1747
+ hasPrerenderHandlerCode && PRERENDER_CONFIG,
1748
+ hasStaticHandlerCode && STATIC_CONFIG
1749
+ ].filter((c) => !!c);
1750
+ for (const cfg of finalHandlerConfigs) {
1751
+ const bindings = getBindings(code, getFnNames(cfg.fnName));
1752
+ changed = (isRscEnv ? transformHandlerIds(cfg, bindings, s, filePath, isBuild) : stubHandlerExprs(cfg, bindings, s, filePath, isBuild)) || changed;
1753
+ }
1754
+ if (!changed) return;
1755
+ return {
1756
+ code: s.toString(),
1757
+ map: s.generateMap({ source: id, includeContent: true })
1758
+ };
1759
+ } finally {
1760
+ counter?.record(id, performance.now() - __t0);
1513
1761
  }
1514
- if (!changed) return;
1515
- return {
1516
- code: s.toString(),
1517
- map: s.generateMap({ source: id, includeContent: true })
1518
- };
1519
1762
  }
1520
1763
  };
1521
1764
  }
1522
1765
 
1523
1766
  // src/vite/plugins/use-cache-transform.ts
1524
1767
  import path5 from "node:path";
1525
- import MagicString5 from "magic-string";
1768
+ import MagicString4 from "magic-string";
1769
+ var debug4 = createRangoDebugger(NS.transform);
1526
1770
  var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
1527
1771
  var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
1772
+ var USE_CACHE_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
1528
1773
  function useCacheTransform() {
1529
1774
  let projectRoot = "";
1530
1775
  let isBuild = false;
1531
1776
  let rscTransforms = null;
1777
+ const counter = createCounter(debug4, "use-cache");
1532
1778
  return {
1533
1779
  name: "@rangojs/router:use-cache",
1534
1780
  enforce: "post",
@@ -1536,53 +1782,61 @@ function useCacheTransform() {
1536
1782
  projectRoot = config.root;
1537
1783
  isBuild = config.command === "build";
1538
1784
  },
1785
+ buildEnd() {
1786
+ counter?.flush();
1787
+ },
1539
1788
  async transform(code, id) {
1540
1789
  if (this.environment?.name !== "rsc") return;
1541
1790
  if (!code.includes("use cache")) return;
1542
1791
  if (id.includes("/node_modules/") || id.startsWith("\0")) return;
1543
1792
  if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
1544
- if (!rscTransforms) {
1793
+ const start = counter ? performance.now() : 0;
1794
+ try {
1795
+ if (!rscTransforms) {
1796
+ try {
1797
+ rscTransforms = await import("@vitejs/plugin-rsc/transforms");
1798
+ } catch {
1799
+ return;
1800
+ }
1801
+ }
1802
+ const {
1803
+ hasDirective,
1804
+ transformWrapExport,
1805
+ transformHoistInlineDirective
1806
+ } = rscTransforms;
1807
+ let ast;
1545
1808
  try {
1546
- rscTransforms = await import("@vitejs/plugin-rsc/transforms");
1809
+ const { parseAst: parseAst4 } = await import("vite");
1810
+ ast = parseAst4(code, { lang: "tsx" });
1547
1811
  } catch {
1548
1812
  return;
1549
1813
  }
1550
- }
1551
- const {
1552
- hasDirective,
1553
- transformWrapExport,
1554
- transformHoistInlineDirective
1555
- } = rscTransforms;
1556
- let ast;
1557
- try {
1558
- const { parseAst: parseAst4 } = await import("vite");
1559
- ast = parseAst4(code);
1560
- } catch {
1561
- return;
1562
- }
1563
- const filePath = normalizePath(path5.relative(projectRoot, id));
1564
- const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
1565
- if (hasDirective(ast.body, "use cache")) {
1566
- return transformFileLevelUseCache(
1814
+ const filePath = normalizePath(path5.relative(projectRoot, id));
1815
+ const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
1816
+ if (hasDirective(ast.body, "use cache")) {
1817
+ return transformFileLevelUseCache(
1818
+ code,
1819
+ ast,
1820
+ filePath,
1821
+ id,
1822
+ isBuild,
1823
+ isLayoutOrTemplate,
1824
+ transformWrapExport
1825
+ );
1826
+ }
1827
+ const functionResult = transformFunctionLevelUseCache(
1567
1828
  code,
1568
1829
  ast,
1569
1830
  filePath,
1570
1831
  id,
1571
1832
  isBuild,
1572
- isLayoutOrTemplate,
1573
- transformWrapExport
1833
+ transformHoistInlineDirective
1574
1834
  );
1835
+ warnOnNearMissDirectives(ast, id, this.warn.bind(this));
1836
+ if (functionResult) return functionResult;
1837
+ } finally {
1838
+ counter?.record(id, performance.now() - start);
1575
1839
  }
1576
- const functionResult = transformFunctionLevelUseCache(
1577
- code,
1578
- ast,
1579
- filePath,
1580
- id,
1581
- isBuild,
1582
- transformHoistInlineDirective
1583
- );
1584
- warnOnNearMissDirectives(ast, id, this.warn.bind(this));
1585
- if (functionResult) return functionResult;
1586
1840
  }
1587
1841
  };
1588
1842
  }
@@ -1609,7 +1863,7 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
1609
1863
  );
1610
1864
  }
1611
1865
  if (exportNames.length === 0) {
1612
- const s = new MagicString5(code);
1866
+ const s = new MagicString4(code);
1613
1867
  const directive2 = findFileLevelDirective(ast);
1614
1868
  if (directive2) {
1615
1869
  s.overwrite(
@@ -1644,7 +1898,7 @@ function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild, isLa
1644
1898
  function transformFunctionLevelUseCache(code, ast, filePath, sourceId, isBuild, transformHoistInlineDirective) {
1645
1899
  try {
1646
1900
  const { output, names } = transformHoistInlineDirective(code, ast, {
1647
- directive: /^use cache(:\s*[\w-]+)?$/,
1901
+ directive: USE_CACHE_DIRECTIVE_RE,
1648
1902
  runtime: (value, name, meta) => {
1649
1903
  const funcId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1650
1904
  const profileMatch = meta.directiveMatch[1];
@@ -1674,14 +1928,13 @@ function findFileLevelDirective(ast) {
1674
1928
  }
1675
1929
  return null;
1676
1930
  }
1677
- var VALID_DIRECTIVE_RE = /^use cache(:\s*[\w-]+)?$/;
1678
1931
  var NEAR_MISS_RE = /^use cache:\s*.+$/;
1679
1932
  function warnOnNearMissDirectives(ast, fileId, warn) {
1680
1933
  const visit = (node) => {
1681
1934
  if (!node || typeof node !== "object") return;
1682
1935
  if (node.type === "ExpressionStatement" && node.expression?.type === "Literal" && typeof node.expression.value === "string") {
1683
1936
  const value = node.expression.value;
1684
- if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !VALID_DIRECTIVE_RE.test(value)) {
1937
+ if (value.startsWith("use cache") && NEAR_MISS_RE.test(value) && !USE_CACHE_DIRECTIVE_RE.test(value)) {
1685
1938
  const profilePart = value.slice("use cache:".length).trim();
1686
1939
  warn(
1687
1940
  `[rango:use-cache] "${value}" in ${fileId} has an invalid profile name "${profilePart}". Profile names must match [a-zA-Z0-9_-]+. This directive will be ignored.`
@@ -1705,6 +1958,7 @@ function warnOnNearMissDirectives(ast, fileId, warn) {
1705
1958
  }
1706
1959
 
1707
1960
  // src/vite/plugins/client-ref-dedup.ts
1961
+ var debug5 = createRangoDebugger(NS.transform);
1708
1962
  var CLIENT_IN_SERVER_PROXY_PREFIX = "virtual:vite-rsc/client-in-server-package-proxy/";
1709
1963
  function extractPackageName(absolutePath) {
1710
1964
  const marker = "/node_modules/";
@@ -1721,6 +1975,7 @@ function extractPackageName(absolutePath) {
1721
1975
  }
1722
1976
  function clientRefDedup() {
1723
1977
  let clientExclude = [];
1978
+ const dedupedPackages = /* @__PURE__ */ new Set();
1724
1979
  return {
1725
1980
  name: "@rangojs/router:client-ref-dedup",
1726
1981
  enforce: "pre",
@@ -1729,6 +1984,15 @@ function clientRefDedup() {
1729
1984
  const clientEnv = config.environments?.["client"];
1730
1985
  clientExclude = clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
1731
1986
  },
1987
+ buildEnd() {
1988
+ if (debug5 && dedupedPackages.size > 0) {
1989
+ debug5(
1990
+ "client-ref-dedup: redirected %d package(s) (%s)",
1991
+ dedupedPackages.size,
1992
+ [...dedupedPackages].join(",")
1993
+ );
1994
+ }
1995
+ },
1732
1996
  resolveId(source, importer, options) {
1733
1997
  if (this.environment?.name !== "client") return;
1734
1998
  if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
@@ -1737,6 +2001,7 @@ function clientRefDedup() {
1737
2001
  const packageName = extractPackageName(source);
1738
2002
  if (!packageName) return;
1739
2003
  if (clientExclude.includes(packageName)) return;
2004
+ if (debug5) dedupedPackages.add(packageName);
1740
2005
  return `\0rango:dedup/${packageName}`;
1741
2006
  },
1742
2007
  load(id) {
@@ -1763,7 +2028,7 @@ import {
1763
2028
  import { createElement, StrictMode } from "react";
1764
2029
  import { hydrateRoot } from "react-dom/client";
1765
2030
  import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
1766
- import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
2031
+ import { initBrowserApp, Rango } from "@rangojs/router/browser";
1767
2032
 
1768
2033
  async function initializeApp() {
1769
2034
  const deps = {
@@ -1778,7 +2043,7 @@ async function initializeApp() {
1778
2043
 
1779
2044
  hydrateRoot(
1780
2045
  document,
1781
- createElement(StrictMode, null, createElement(RSCRouter))
2046
+ createElement(StrictMode, null, createElement(Rango))
1782
2047
  );
1783
2048
  }
1784
2049
 
@@ -1859,12 +2124,13 @@ function getVirtualVersionContent(version) {
1859
2124
 
1860
2125
  // src/vite/utils/package-resolution.ts
1861
2126
  import { existsSync } from "node:fs";
2127
+ import { createRequire } from "node:module";
1862
2128
  import { resolve } from "node:path";
1863
2129
 
1864
2130
  // package.json
1865
2131
  var package_default = {
1866
2132
  name: "@rangojs/router",
1867
- version: "0.0.0-experimental.77",
2133
+ version: "0.0.0-experimental.77ed8945",
1868
2134
  description: "Django-inspired RSC router with composable URL patterns",
1869
2135
  keywords: [
1870
2136
  "react",
@@ -1997,22 +2263,26 @@ var package_default = {
1997
2263
  tag: "experimental"
1998
2264
  },
1999
2265
  scripts: {
2000
- build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2266
+ build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2001
2267
  prepublishOnly: "pnpm build",
2002
- typecheck: "tsc --noEmit",
2268
+ typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
2003
2269
  test: "playwright test",
2004
2270
  "test:ui": "playwright test --ui",
2271
+ "test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
2005
2272
  "test:unit": "vitest run",
2006
2273
  "test:unit:watch": "vitest"
2007
2274
  },
2008
2275
  dependencies: {
2009
- "@vitejs/plugin-rsc": "^0.5.23",
2276
+ "@types/debug": "^4.1.12",
2277
+ "@vitejs/plugin-rsc": "^0.5.26",
2278
+ debug: "^4.4.1",
2010
2279
  "magic-string": "^0.30.17",
2011
2280
  picomatch: "^4.0.3",
2012
2281
  "rsc-html-stream": "^0.0.7"
2013
2282
  },
2014
2283
  devDependencies: {
2015
2284
  "@playwright/test": "^1.49.1",
2285
+ "@shared/e2e": "workspace:*",
2016
2286
  "@types/node": "^24.10.1",
2017
2287
  "@types/react": "catalog:",
2018
2288
  "@types/react-dom": "catalog:",
@@ -2025,10 +2295,11 @@ var package_default = {
2025
2295
  vitest: "^4.0.0"
2026
2296
  },
2027
2297
  peerDependencies: {
2028
- "@cloudflare/vite-plugin": "^1.25.0",
2029
- "@vitejs/plugin-rsc": "^0.5.23",
2030
- react: "^18.0.0 || ^19.0.0",
2031
- vite: "^7.3.0"
2298
+ "@cloudflare/vite-plugin": "^1.38.0",
2299
+ "@vitejs/plugin-rsc": "^0.5.26",
2300
+ react: ">=19.2.6 <20",
2301
+ "react-dom": ">=19.2.6 <20",
2302
+ vite: "^8.0.0"
2032
2303
  },
2033
2304
  peerDependenciesMeta: {
2034
2305
  "@cloudflare/vite-plugin": {
@@ -2041,6 +2312,7 @@ var package_default = {
2041
2312
  };
2042
2313
 
2043
2314
  // src/vite/utils/package-resolution.ts
2315
+ var require2 = createRequire(import.meta.url);
2044
2316
  var VIRTUAL_PACKAGE_NAME = "@rangojs/router";
2045
2317
  function getPublishedPackageName() {
2046
2318
  return package_default.name;
@@ -2081,6 +2353,20 @@ function getPackageAliases() {
2081
2353
  }
2082
2354
  return aliases;
2083
2355
  }
2356
+ function getVendorAliases() {
2357
+ const specs = [
2358
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
2359
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
2360
+ ];
2361
+ const aliases = {};
2362
+ for (const spec of specs) {
2363
+ try {
2364
+ aliases[spec] = require2.resolve(spec);
2365
+ } catch {
2366
+ }
2367
+ }
2368
+ return aliases;
2369
+ }
2084
2370
 
2085
2371
  // src/build/route-types/param-extraction.ts
2086
2372
  function extractParamsFromPattern(pattern) {
@@ -2206,7 +2492,7 @@ ${objectBody}
2206
2492
  } as const;
2207
2493
 
2208
2494
  declare global {
2209
- namespace RSCRouter {
2495
+ namespace Rango {
2210
2496
  interface GeneratedRouteMap extends Readonly<typeof NamedRoutes> {}
2211
2497
  }
2212
2498
  }
@@ -2441,7 +2727,7 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2441
2727
  const realPath = resolve2(filePath);
2442
2728
  const key = variableName ? `${realPath}:${variableName}` : realPath;
2443
2729
  if (visited.has(key)) {
2444
- console.warn(`[rsc-router] Circular include detected, skipping: ${key}`);
2730
+ console.warn(`[rango] Circular include detected, skipping: ${key}`);
2445
2731
  return { routes: {}, searchSchemas: {} };
2446
2732
  }
2447
2733
  visited.add(key);
@@ -2502,6 +2788,7 @@ function countPublicRouteEntries(source) {
2502
2788
  return count;
2503
2789
  }
2504
2790
  var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
2791
+ var ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
2505
2792
  function isRoutableSourceFile(name) {
2506
2793
  return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
2507
2794
  }
@@ -2511,7 +2798,7 @@ function findRouterFilesRecursive(dir, filter, results) {
2511
2798
  entries = readdirSync(dir, { withFileTypes: true });
2512
2799
  } catch (err) {
2513
2800
  console.warn(
2514
- `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
2801
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
2515
2802
  );
2516
2803
  return;
2517
2804
  }
@@ -2529,7 +2816,7 @@ function findRouterFilesRecursive(dir, filter, results) {
2529
2816
  if (filter && !filter(fullPath)) continue;
2530
2817
  try {
2531
2818
  const source = readFileSync2(fullPath, "utf-8");
2532
- if (ROUTER_CALL_PATTERN.test(source)) {
2819
+ if (ROUTER_CALL_PATTERN.test(source) && firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0) {
2533
2820
  routerFilesInDir.push(fullPath);
2534
2821
  }
2535
2822
  } catch {
@@ -2567,7 +2854,7 @@ function findNestedRouterConflict(routerFiles) {
2567
2854
  }
2568
2855
  return null;
2569
2856
  }
2570
- function formatNestedRouterConflictError(conflict, prefix = "[rsc-router]") {
2857
+ function formatNestedRouterConflictError(conflict, prefix = "[rango]") {
2571
2858
  return `${prefix} Nested router roots are not supported.
2572
2859
  Router root: ${conflict.ancestor}
2573
2860
  Nested router: ${conflict.nested}
@@ -2663,19 +2950,38 @@ function extractBasenameFromRouter(code) {
2663
2950
  visit(sourceFile);
2664
2951
  return result;
2665
2952
  }
2666
- function applyBasenameToRoutes(result, basename3) {
2953
+ function applyBasenameToRoutes(result, basename2) {
2667
2954
  const prefixed = {};
2668
2955
  for (const [name, pattern] of Object.entries(result.routes)) {
2669
2956
  if (pattern === "/") {
2670
- prefixed[name] = basename3;
2671
- } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2672
- prefixed[name] = basename3 + pattern.slice(1);
2957
+ prefixed[name] = basename2;
2958
+ } else if (basename2.endsWith("/") && pattern.startsWith("/")) {
2959
+ prefixed[name] = basename2 + pattern.slice(1);
2673
2960
  } else {
2674
- prefixed[name] = basename3 + pattern;
2961
+ prefixed[name] = basename2 + pattern;
2675
2962
  }
2676
2963
  }
2677
2964
  return { routes: prefixed, searchSchemas: result.searchSchemas };
2678
2965
  }
2966
+ function genFileTsPath(sourceFile) {
2967
+ const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
2968
+ return join(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
2969
+ }
2970
+ function resolveSearchSchemas(publicRouteNames, runtimeSchemas, sourceFile) {
2971
+ if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
2972
+ return runtimeSchemas;
2973
+ }
2974
+ const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
2975
+ if (Object.keys(staticParsed.searchSchemas).length === 0) {
2976
+ return runtimeSchemas;
2977
+ }
2978
+ const filtered = {};
2979
+ for (const name of publicRouteNames) {
2980
+ const schema = staticParsed.searchSchemas[name];
2981
+ if (schema) filtered[name] = schema;
2982
+ }
2983
+ return Object.keys(filtered).length > 0 ? filtered : runtimeSchemas;
2984
+ }
2679
2985
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2680
2986
  let routerSource;
2681
2987
  try {
@@ -2688,7 +2994,7 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2688
2994
  return { routes: {}, searchSchemas: {} };
2689
2995
  }
2690
2996
  const rawBasename = extractBasenameFromRouter(routerSource);
2691
- const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2997
+ const basename2 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2692
2998
  let result;
2693
2999
  if (extraction.kind === "inline") {
2694
3000
  result = buildCombinedRouteMapWithSearch(
@@ -2713,8 +3019,8 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2713
3019
  result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2714
3020
  }
2715
3021
  }
2716
- if (basename3) {
2717
- result = applyBasenameToRoutes(result, basename3);
3022
+ if (basename2) {
3023
+ result = applyBasenameToRoutes(result, basename2);
2718
3024
  }
2719
3025
  return result;
2720
3026
  }
@@ -2729,7 +3035,7 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2729
3035
  if (existsSync3(oldCombinedPath)) {
2730
3036
  unlinkSync(oldCombinedPath);
2731
3037
  console.log(
2732
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
3038
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
2733
3039
  );
2734
3040
  }
2735
3041
  } catch {
@@ -2751,18 +3057,12 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2751
3057
  }
2752
3058
  if (!extractUrlsFromRouter(routerSource)) continue;
2753
3059
  }
2754
- const routerBasename = pathBasename(routerFilePath).replace(
2755
- /\.(tsx?|jsx?)$/,
2756
- ""
2757
- );
2758
- const outPath = join(
2759
- dirname2(routerFilePath),
2760
- `${routerBasename}.named-routes.gen.ts`
2761
- );
3060
+ const outPath = genFileTsPath(routerFilePath);
2762
3061
  const existing = existsSync3(outPath) ? readFileSync2(outPath, "utf-8") : null;
2763
3062
  if (Object.keys(result.routes).length === 0) {
2764
3063
  if (!existing) {
2765
3064
  const emptySource = generateRouteTypesSource({});
3065
+ opts?.onWrite?.(outPath, emptySource);
2766
3066
  writeFileSync(outPath, emptySource);
2767
3067
  }
2768
3068
  continue;
@@ -2782,9 +3082,10 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2782
3082
  continue;
2783
3083
  }
2784
3084
  }
3085
+ opts?.onWrite?.(outPath, source);
2785
3086
  writeFileSync(outPath, source);
2786
3087
  console.log(
2787
- `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
3088
+ `[rango] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
2788
3089
  );
2789
3090
  }
2790
3091
  }
@@ -2801,7 +3102,7 @@ function normalizeModuleId(id) {
2801
3102
  function getClientModuleSignature(source) {
2802
3103
  let program;
2803
3104
  try {
2804
- program = parseAst3(source, { jsx: true });
3105
+ program = parseAst3(source, { lang: "tsx" });
2805
3106
  } catch {
2806
3107
  return void 0;
2807
3108
  }
@@ -2884,11 +3185,12 @@ function createVersionPlugin() {
2884
3185
  let currentVersion = buildVersion;
2885
3186
  let isDev = false;
2886
3187
  let server = null;
3188
+ let resolvedCacheDir;
2887
3189
  const clientModuleSignatures = /* @__PURE__ */ new Map();
2888
3190
  let versionCounter = 0;
2889
3191
  const bumpVersion = (reason) => {
2890
3192
  currentVersion = Date.now().toString(16) + String(++versionCounter);
2891
- console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
3193
+ console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
2892
3194
  const rscEnv = server?.environments?.rsc;
2893
3195
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
2894
3196
  "\0" + VIRTUAL_IDS.version
@@ -2902,6 +3204,7 @@ function createVersionPlugin() {
2902
3204
  enforce: "pre",
2903
3205
  configResolved(config) {
2904
3206
  isDev = config.command === "serve";
3207
+ resolvedCacheDir = config.cacheDir ? String(config.cacheDir).replace(/\\/g, "/") : void 0;
2905
3208
  },
2906
3209
  configureServer(devServer) {
2907
3210
  server = devServer;
@@ -2943,6 +3246,7 @@ function createVersionPlugin() {
2943
3246
  if (!isDev) return;
2944
3247
  const isRscModule = this.environment?.name === "rsc";
2945
3248
  if (!isRscModule) return;
3249
+ if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
2946
3250
  if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
2947
3251
  return;
2948
3252
  }
@@ -2972,12 +3276,24 @@ function createVersionPlugin() {
2972
3276
  }
2973
3277
  };
2974
3278
  }
3279
+ function isViteDepCachePath(filePath, cacheDir) {
3280
+ if (!filePath) return false;
3281
+ const normalized = filePath.replace(/\\/g, "/");
3282
+ if (cacheDir) {
3283
+ const normalizedCacheDir = cacheDir.replace(/\\/g, "/").replace(/\/+$/, "");
3284
+ if (normalized === normalizedCacheDir || normalized.startsWith(normalizedCacheDir + "/")) {
3285
+ return true;
3286
+ }
3287
+ }
3288
+ return /\/node_modules\/\.vite[^/]*\//.test(normalized) || normalized.includes("/.vite-isolated/");
3289
+ }
2975
3290
 
2976
3291
  // src/vite/utils/shared-utils.ts
2977
3292
  import * as Vite from "vite";
2978
3293
 
2979
3294
  // src/vite/plugins/performance-tracks.ts
2980
3295
  import { readFile } from "node:fs/promises";
3296
+ var debug6 = createRangoDebugger(NS.transform);
2981
3297
  var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2982
3298
  function buildPatchReplacement(match, debugInfoVar) {
2983
3299
  return `${match}
@@ -2999,62 +3315,65 @@ function patchRsdwClientDebugInfoRecovery(code) {
2999
3315
  };
3000
3316
  }
3001
3317
  function performanceTracksOptimizeDepsPlugin() {
3318
+ const RSDW_CLIENT_RE = /react-server-dom-webpack-client\.browser\.(development|production)\.js$/;
3002
3319
  return {
3003
3320
  name: "@rangojs/router:performance-tracks-optimize-deps",
3004
- setup(build) {
3005
- build.onLoad(
3006
- {
3007
- filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
3008
- },
3009
- async (args) => {
3010
- const code = await readFile(args.path, "utf8");
3011
- const patched = patchRsdwClientDebugInfoRecovery(code);
3012
- return {
3013
- contents: patched.code,
3014
- loader: "js"
3015
- };
3016
- }
3017
- );
3321
+ // Vite 8 optimizes deps with Rolldown (Rollup-style plugin pipeline), so the
3322
+ // pre-bundled RSDW client is patched via load() rather than esbuild's onLoad.
3323
+ // Returning code overrides Rolldown's default filesystem read for the module.
3324
+ async load(id) {
3325
+ const cleanId = id.split("?")[0] ?? id;
3326
+ if (!RSDW_CLIENT_RE.test(cleanId)) return null;
3327
+ const code = await readFile(cleanId, "utf8");
3328
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3329
+ return { code: patched.code };
3018
3330
  }
3019
3331
  };
3020
3332
  }
3021
3333
  function performanceTracksPlugin() {
3334
+ const counter = createCounter(debug6, "performance-tracks");
3022
3335
  return {
3023
3336
  name: "@rangojs/router:performance-tracks",
3337
+ buildEnd() {
3338
+ counter?.flush();
3339
+ },
3024
3340
  transform(code, id) {
3025
3341
  if (!id.includes("react-server-dom") || !id.includes("client")) return;
3026
- const patched = patchRsdwClientDebugInfoRecovery(code);
3027
- if (!patched.debugInfoVar) return;
3028
- if (process.env.INTERNAL_RANGO_DEBUG)
3029
- console.log(
3030
- "[perf-tracks] patched RSDW client (var:",
3031
- patched.debugInfoVar,
3032
- ")"
3033
- );
3034
- return patched.code;
3342
+ const start = counter ? performance.now() : 0;
3343
+ try {
3344
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3345
+ if (!patched.debugInfoVar) return;
3346
+ debug6?.("patched RSDW client (var: %s)", patched.debugInfoVar);
3347
+ return patched.code;
3348
+ } finally {
3349
+ counter?.record(id, performance.now() - start);
3350
+ }
3035
3351
  }
3036
3352
  };
3037
3353
  }
3038
3354
 
3039
3355
  // src/vite/utils/shared-utils.ts
3040
- var versionEsbuildPlugin = {
3356
+ function resolveRscEntryFromConfig(config) {
3357
+ const entries = config.environments?.["rsc"]?.optimizeDeps?.entries;
3358
+ if (typeof entries === "string") return entries;
3359
+ if (Array.isArray(entries) && entries.length > 0) return entries[0];
3360
+ return void 0;
3361
+ }
3362
+ var versionRolldownPlugin = {
3041
3363
  name: "@rangojs/router-version",
3042
- setup(build) {
3043
- build.onResolve({ filter: /^rsc-router:version$/ }, (args) => ({
3044
- path: args.path,
3045
- namespace: "@rangojs/router-virtual"
3046
- }));
3047
- build.onLoad(
3048
- { filter: /.*/, namespace: "@rangojs/router-virtual" },
3049
- () => ({
3050
- contents: `export const VERSION = "dev";`,
3051
- loader: "js"
3052
- })
3053
- );
3364
+ resolveId(id) {
3365
+ if (id === VIRTUAL_IDS.version) return "\0" + VIRTUAL_IDS.version;
3366
+ return void 0;
3367
+ },
3368
+ load(id) {
3369
+ if (id === "\0" + VIRTUAL_IDS.version) {
3370
+ return getVirtualVersionContent("dev");
3371
+ }
3372
+ return void 0;
3054
3373
  }
3055
3374
  };
3056
- var sharedEsbuildOptions = {
3057
- plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
3375
+ var sharedRolldownOptions = {
3376
+ plugins: [versionRolldownPlugin, performanceTracksOptimizeDepsPlugin()]
3058
3377
  };
3059
3378
  function createVirtualEntriesPlugin(entries, routerPathRef) {
3060
3379
  const virtualModules = {};
@@ -3096,8 +3415,29 @@ function createVirtualEntriesPlugin(entries, routerPathRef) {
3096
3415
  }
3097
3416
  };
3098
3417
  }
3418
+ function isContentHashedAssetConflict(message) {
3419
+ if (!message) return false;
3420
+ const match = /The emitted file "?([^"\s]+)"? overwrites a previously emitted file/.exec(
3421
+ message
3422
+ );
3423
+ if (!match) return false;
3424
+ const fileName = match[1];
3425
+ const base = fileName.slice(fileName.lastIndexOf("/") + 1);
3426
+ const dot = base.lastIndexOf(".");
3427
+ if (dot <= 0) return false;
3428
+ const stem = base.slice(0, dot);
3429
+ const HASH_LEN = 8;
3430
+ if (stem.length < HASH_LEN + 1 || stem[stem.length - HASH_LEN - 1] !== "-") {
3431
+ return false;
3432
+ }
3433
+ const hash = stem.slice(-HASH_LEN);
3434
+ return /^[A-Za-z0-9_-]+$/.test(hash) && /[A-Z0-9]/.test(hash);
3435
+ }
3099
3436
  function onwarn(warning, defaultHandler) {
3100
- if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.code === "SOURCEMAP_ERROR" || warning.code === "EMPTY_BUNDLE") {
3437
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" || warning.code === "SOURCEMAP_ERROR" || warning.code === "EMPTY_BUNDLE" || warning.code === "INEFFECTIVE_DYNAMIC_IMPORT") {
3438
+ return;
3439
+ }
3440
+ if (warning.code === "FILE_NAME_CONFLICT" && isContentHashedAssetConflict(warning.message)) {
3101
3441
  return;
3102
3442
  }
3103
3443
  if (warning.message?.includes("Sourcemap is likely to be incorrect")) {
@@ -3116,12 +3456,138 @@ function getManualChunks(id) {
3116
3456
  return "react";
3117
3457
  }
3118
3458
  const packageName = getPublishedPackageName();
3119
- if (normalized.includes(`node_modules/${packageName}/`) || normalized.includes("packages/rsc-router/") || normalized.includes("packages/rangojs-router/")) {
3459
+ if (normalized.includes(`node_modules/${packageName}/`) || /\/packages\/(rsc-router|rangojs-router)\/(src|dist)\//.test(normalized)) {
3120
3460
  return "router";
3121
3461
  }
3122
3462
  return void 0;
3123
3463
  }
3124
3464
 
3465
+ // src/vite/plugins/client-ref-hashing.ts
3466
+ import { relative } from "node:path";
3467
+ import { createHash as createHash2 } from "node:crypto";
3468
+ var debug7 = createRangoDebugger(NS.transform);
3469
+ var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3470
+ var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3471
+ var FS_PREFIX = "/@fs/";
3472
+ function hashRefKey(relativeId) {
3473
+ return createHash2("sha256").update(relativeId).digest("hex").slice(0, 12);
3474
+ }
3475
+ function computeProductionHash(projectRoot, refKey) {
3476
+ let toHash;
3477
+ if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
3478
+ toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
3479
+ } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
3480
+ const absPath = decodeURIComponent(
3481
+ refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3482
+ );
3483
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3484
+ } else if (refKey.startsWith(FS_PREFIX)) {
3485
+ const absPath = refKey.slice(FS_PREFIX.length - 1);
3486
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3487
+ } else if (refKey.startsWith("/")) {
3488
+ toHash = refKey.slice(1);
3489
+ } else {
3490
+ return refKey;
3491
+ }
3492
+ return hashRefKey(toHash);
3493
+ }
3494
+ var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
3495
+ function transformClientRefs(code, projectRoot) {
3496
+ if (!code.includes("registerClientReference")) return null;
3497
+ let hasReplacement = false;
3498
+ const result = code.replace(
3499
+ REGISTER_CLIENT_REF_RE,
3500
+ (match, refKey) => {
3501
+ const hash = computeProductionHash(projectRoot, refKey);
3502
+ if (hash === refKey) return match;
3503
+ hasReplacement = true;
3504
+ return match.replace(`"${refKey}"`, `"${hash}"`);
3505
+ }
3506
+ );
3507
+ return hasReplacement ? result : null;
3508
+ }
3509
+ function hashClientRefs(projectRoot) {
3510
+ const counter = createCounter(debug7, "hash-client-refs");
3511
+ return {
3512
+ name: "@rangojs/router:hash-client-refs",
3513
+ // Run after the RSC plugin's transform (default enforce is normal)
3514
+ enforce: "post",
3515
+ applyToEnvironment(env) {
3516
+ return env.name === "rsc";
3517
+ },
3518
+ buildEnd() {
3519
+ counter?.flush();
3520
+ },
3521
+ transform(code, id) {
3522
+ const start = counter ? performance.now() : 0;
3523
+ try {
3524
+ const result = transformClientRefs(code, projectRoot);
3525
+ if (result === null) return;
3526
+ return { code: result, map: null };
3527
+ } finally {
3528
+ counter?.record(id, performance.now() - start);
3529
+ }
3530
+ }
3531
+ };
3532
+ }
3533
+
3534
+ // src/vite/utils/client-chunks.ts
3535
+ var debugChunks = createRangoDebugger(NS.chunks);
3536
+ function isSharedRuntime(meta) {
3537
+ return [meta.id, meta.normalizedId].some(
3538
+ (path6) => path6.includes("/node_modules/") || /\/@rangojs\/router\//.test(path6) || /\/packages\/(rangojs-router|rsc-router)\/(src|dist)\//.test(path6)
3539
+ );
3540
+ }
3541
+ function sanitizeGroup(name) {
3542
+ return name.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "app";
3543
+ }
3544
+ var ROUTE_ROOT_DIRS = /* @__PURE__ */ new Set([
3545
+ "routes",
3546
+ "route",
3547
+ "pages",
3548
+ "page",
3549
+ "app",
3550
+ "features",
3551
+ "feature",
3552
+ "views",
3553
+ "view",
3554
+ "handlers",
3555
+ "urls",
3556
+ "modules",
3557
+ "screens",
3558
+ "sections"
3559
+ ]);
3560
+ function directoryClientChunks(meta, ctx) {
3561
+ if (isSharedRuntime(meta)) {
3562
+ return void 0;
3563
+ }
3564
+ if (ctx?.fallbackRefs.size && ctx.fallbackRefs.has(hashRefKey(meta.normalizedId))) {
3565
+ debugChunks?.("fallback %s -> app-fallback", meta.normalizedId);
3566
+ return "app-fallback";
3567
+ }
3568
+ const segments = meta.normalizedId.split("/").filter(Boolean);
3569
+ const dirCount = segments.length - 1;
3570
+ if (dirCount >= 1) {
3571
+ for (let i = 0; i < dirCount - 1; i++) {
3572
+ if (ROUTE_ROOT_DIRS.has(segments[i].toLowerCase())) {
3573
+ const group = `app-${sanitizeGroup(segments[i + 1])}`;
3574
+ debugChunks?.("split %s -> %s", meta.normalizedId, group);
3575
+ return group;
3576
+ }
3577
+ }
3578
+ }
3579
+ debugChunks?.(
3580
+ "shared %s (no route-root marker; inherits default grouping)",
3581
+ meta.normalizedId
3582
+ );
3583
+ return void 0;
3584
+ }
3585
+ function resolveClientChunks(option, ctx) {
3586
+ if (!option) return void 0;
3587
+ if (option === true) return (meta) => directoryClientChunks(meta, ctx);
3588
+ return option;
3589
+ }
3590
+
3125
3591
  // src/vite/utils/banner.ts
3126
3592
  var rangoVersion = package_default.version;
3127
3593
  var _bannerPrinted = false;
@@ -3158,15 +3624,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
3158
3624
  enforce: "pre",
3159
3625
  configResolved(config) {
3160
3626
  let entryPath = rscEntryPath;
3161
- if (!entryPath) {
3162
- const rscEnvConfig = config.environments?.["rsc"];
3163
- const entries = rscEnvConfig?.optimizeDeps?.entries;
3164
- if (typeof entries === "string") {
3165
- entryPath = entries;
3166
- } else if (Array.isArray(entries) && entries.length > 0) {
3167
- entryPath = entries[0];
3168
- }
3169
- }
3627
+ if (!entryPath) entryPath = resolveRscEntryFromConfig(config);
3170
3628
  if (entryPath) {
3171
3629
  resolvedEntryPath = resolve4(config.root, entryPath);
3172
3630
  }
@@ -3178,11 +3636,10 @@ function createVersionInjectorPlugin(rscEntryPath) {
3178
3636
  if (normalizedId !== normalizedEntry) {
3179
3637
  return null;
3180
3638
  }
3181
- const prepend = [];
3639
+ const prepend = [
3640
+ `import "virtual:rsc-router/routes-manifest";`
3641
+ ];
3182
3642
  let newCode = code;
3183
- if (!code.includes("virtual:rsc-router/routes-manifest")) {
3184
- prepend.push(`import "virtual:rsc-router/routes-manifest";`);
3185
- }
3186
3643
  const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
3187
3644
  if (needsVersion) {
3188
3645
  prepend.push(`import { VERSION } from "@rangojs/router:version";`);
@@ -3191,8 +3648,21 @@ function createVersionInjectorPlugin(rscEntryPath) {
3191
3648
  "createRSCHandler({\n version: VERSION,"
3192
3649
  );
3193
3650
  }
3194
- if (prepend.length === 0 && newCode === code) return null;
3195
- newCode = prepend.join("\n") + (prepend.length > 0 ? "\n" : "") + newCode;
3651
+ const lines = newCode.split("\n");
3652
+ let insertAt = 0;
3653
+ while (insertAt < lines.length) {
3654
+ const trimmed = lines[insertAt].trim();
3655
+ if (trimmed === "" || /^\/\/\/\s*<reference\b/.test(trimmed)) {
3656
+ insertAt++;
3657
+ } else {
3658
+ break;
3659
+ }
3660
+ }
3661
+ newCode = [
3662
+ ...lines.slice(0, insertAt),
3663
+ ...prepend,
3664
+ ...lines.slice(insertAt)
3665
+ ].join("\n");
3196
3666
  return {
3197
3667
  code: newCode,
3198
3668
  map: null
@@ -3202,21 +3672,23 @@ function createVersionInjectorPlugin(rscEntryPath) {
3202
3672
  }
3203
3673
 
3204
3674
  // src/vite/plugins/cjs-to-esm.ts
3675
+ var debug8 = createRangoDebugger(NS.transform);
3205
3676
  function createCjsToEsmPlugin() {
3206
3677
  return {
3207
3678
  name: "@rangojs/router:cjs-to-esm",
3208
3679
  enforce: "pre",
3209
3680
  transform(code, id) {
3210
- const cleanId = id.split("?")[0];
3211
- if (cleanId.includes("vendor/react-server-dom/client.browser.js") || cleanId.includes("vendor\\react-server-dom\\client.browser.js")) {
3681
+ const cleanId = id.split("?")[0].replaceAll("\\", "/");
3682
+ if (cleanId.includes("vendor/react-server-dom/client.browser.js")) {
3212
3683
  const isProd = process.env.NODE_ENV === "production";
3213
3684
  const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
3685
+ debug8?.("cjs-to-esm entry redirect %s", id);
3214
3686
  return {
3215
3687
  code: `export * from "${cjsFile}";`,
3216
3688
  map: null
3217
3689
  };
3218
3690
  }
3219
- if ((cleanId.includes("vendor/react-server-dom/cjs/") || cleanId.includes("vendor\\react-server-dom\\cjs\\")) && cleanId.includes("client.browser")) {
3691
+ if (cleanId.includes("vendor/react-server-dom/cjs/") && cleanId.includes("client.browser")) {
3220
3692
  let transformed = code;
3221
3693
  const licenseMatch = transformed.match(/^\/\*\*[\s\S]*?\*\//);
3222
3694
  const license = licenseMatch ? licenseMatch[0] : "";
@@ -3246,6 +3718,7 @@ function createCjsToEsmPlugin() {
3246
3718
  "export const $1 ="
3247
3719
  );
3248
3720
  transformed = license + "\n" + transformed;
3721
+ debug8?.("cjs-to-esm body rewrite %s", id);
3249
3722
  return {
3250
3723
  code: transformed,
3251
3724
  map: null
@@ -3260,7 +3733,7 @@ function createCjsToEsmPlugin() {
3260
3733
  import { createServer as createViteServer } from "vite";
3261
3734
  import { resolve as resolve8 } from "node:path";
3262
3735
  import { readFileSync as readFileSync6 } from "node:fs";
3263
- import { createRequire } from "node:module";
3736
+ import { createRequire as createRequire2, register } from "node:module";
3264
3737
  import { pathToFileURL } from "node:url";
3265
3738
 
3266
3739
  // src/vite/plugins/virtual-stub-plugin.ts
@@ -3287,61 +3760,112 @@ function createVirtualStubPlugin() {
3287
3760
  };
3288
3761
  }
3289
3762
 
3290
- // src/vite/plugins/client-ref-hashing.ts
3291
- import { relative } from "node:path";
3292
- import { createHash as createHash2 } from "node:crypto";
3293
- var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3294
- var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3295
- var FS_PREFIX = "/@fs/";
3296
- function computeProductionHash(projectRoot, refKey) {
3297
- let toHash;
3298
- if (refKey.startsWith(CLIENT_PKG_PROXY_PREFIX)) {
3299
- toHash = refKey.slice(CLIENT_PKG_PROXY_PREFIX.length);
3300
- } else if (refKey.startsWith(CLIENT_IN_SERVER_PKG_PROXY_PREFIX)) {
3301
- const absPath = decodeURIComponent(
3302
- refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3303
- );
3304
- toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3305
- } else if (refKey.startsWith(FS_PREFIX)) {
3306
- const absPath = refKey.slice(FS_PREFIX.length - 1);
3307
- toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3308
- } else if (refKey.startsWith("/")) {
3309
- toHash = refKey.slice(1);
3310
- } else {
3311
- return refKey;
3312
- }
3313
- return createHash2("sha256").update(toHash).digest("hex").slice(0, 12);
3314
- }
3315
- var REGISTER_CLIENT_REF_RE = /registerClientReference\(\s*(?:(?:\([^)]*\))|(?:\(\)[\s\S]*?\}))\s*,\s*"([^"]+)"\s*,\s*"[^"]+"\s*\)/g;
3316
- function transformClientRefs(code, projectRoot) {
3317
- if (!code.includes("registerClientReference")) return null;
3318
- let hasReplacement = false;
3319
- const result = code.replace(
3320
- REGISTER_CLIENT_REF_RE,
3321
- (match, refKey) => {
3322
- const hash = computeProductionHash(projectRoot, refKey);
3323
- if (hash === refKey) return match;
3324
- hasReplacement = true;
3325
- return match.replace(`"${refKey}"`, `"${hash}"`);
3326
- }
3327
- );
3328
- return hasReplacement ? result : null;
3329
- }
3330
- function hashClientRefs(projectRoot) {
3763
+ // src/vite/plugins/cloudflare-protocol-stub.ts
3764
+ var VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
3765
+ var NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
3766
+ var CF_PREFIX = "cloudflare:";
3767
+ var BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
3768
+ var SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
3769
+ var IMPORT_NODE_TYPES = /* @__PURE__ */ new Set([
3770
+ "ImportDeclaration",
3771
+ "ImportExpression",
3772
+ "ExportNamedDeclaration",
3773
+ "ExportAllDeclaration"
3774
+ ]);
3775
+ var STUBS = {
3776
+ "cloudflare:workers": `
3777
+ export class DurableObject { constructor(_ctx, _env) {} }
3778
+ export class WorkerEntrypoint { constructor(_ctx, _env) {} }
3779
+ export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
3780
+ export class RpcTarget {}
3781
+ export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
3782
+ export default {};
3783
+ `,
3784
+ "cloudflare:email": `
3785
+ export class EmailMessage { constructor(_from, _to, _raw) {} }
3786
+ export default {};
3787
+ `,
3788
+ "cloudflare:sockets": `
3789
+ export function connect() { return {}; }
3790
+ export default {};
3791
+ `,
3792
+ "cloudflare:workflows": `
3793
+ export class NonRetryableError extends Error {
3794
+ constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
3795
+ }
3796
+ export default {};
3797
+ `
3798
+ };
3799
+ var FALLBACK_STUB = `export default {};
3800
+ `;
3801
+ function createCloudflareProtocolStubPlugin() {
3331
3802
  return {
3332
- name: "@rangojs/router:hash-client-refs",
3333
- // Run after the RSC plugin's transform (default enforce is normal)
3334
- enforce: "post",
3335
- applyToEnvironment(env) {
3336
- return env.name === "rsc";
3803
+ name: "@rangojs/router:cloudflare-protocol-stub",
3804
+ transform(code, id) {
3805
+ const cleanId = id.split("?")[0] ?? id;
3806
+ if (!SOURCE_EXT_RE.test(cleanId)) return null;
3807
+ if (!code.includes(CF_PREFIX)) return null;
3808
+ let ast;
3809
+ try {
3810
+ ast = this.parse(code, { lang: "tsx" });
3811
+ } catch {
3812
+ return null;
3813
+ }
3814
+ const hits = [];
3815
+ walk(ast, (node) => {
3816
+ if (!IMPORT_NODE_TYPES.has(node.type)) return;
3817
+ const source = node.source;
3818
+ if (!source || source.type !== "Literal") return;
3819
+ if (typeof source.value !== "string") return;
3820
+ if (!source.value.startsWith(CF_PREFIX)) return;
3821
+ if (typeof source.start !== "number" || typeof source.end !== "number")
3822
+ return;
3823
+ hits.push({
3824
+ start: source.start,
3825
+ end: source.end,
3826
+ value: source.value
3827
+ });
3828
+ });
3829
+ if (hits.length === 0) return null;
3830
+ hits.sort((a, b) => b.start - a.start);
3831
+ let out = code;
3832
+ for (const hit of hits) {
3833
+ const submodule = hit.value.slice(CF_PREFIX.length);
3834
+ const quote = code[hit.start] === "'" ? "'" : '"';
3835
+ out = out.slice(0, hit.start) + quote + VIRTUAL_PREFIX + submodule + quote + out.slice(hit.end);
3836
+ }
3837
+ return { code: out, map: null };
3337
3838
  },
3338
- transform(code, _id) {
3339
- const result = transformClientRefs(code, projectRoot);
3340
- if (result === null) return;
3341
- return { code: result, map: null };
3839
+ resolveId(id) {
3840
+ if (id.startsWith(VIRTUAL_PREFIX)) {
3841
+ return "\0" + id;
3842
+ }
3843
+ return null;
3844
+ },
3845
+ load(id) {
3846
+ if (!id.startsWith(NULL_PREFIX)) return null;
3847
+ const submodule = id.slice(NULL_PREFIX.length);
3848
+ const specifier = CF_PREFIX + submodule;
3849
+ return STUBS[specifier] ?? FALLBACK_STUB;
3342
3850
  }
3343
3851
  };
3344
3852
  }
3853
+ function walk(node, visit) {
3854
+ if (!node || typeof node !== "object") return;
3855
+ if (Array.isArray(node)) {
3856
+ for (const child of node) walk(child, visit);
3857
+ return;
3858
+ }
3859
+ const n = node;
3860
+ if (typeof n.type !== "string") return;
3861
+ visit(n);
3862
+ for (const key in n) {
3863
+ if (key === "loc" || key === "start" || key === "end" || key === "range") {
3864
+ continue;
3865
+ }
3866
+ walk(n[key], visit);
3867
+ }
3868
+ }
3345
3869
 
3346
3870
  // src/vite/utils/bundle-analysis.ts
3347
3871
  function findMatchingParenInBundle(code, openParenPos) {
@@ -3373,7 +3897,7 @@ function extractHandlerExportsFromChunk(chunkCode, handlerModules, fnName, detec
3373
3897
  if (detectPassthrough) {
3374
3898
  const eFnName = escapeRegExp(fnName);
3375
3899
  const callStartRe = new RegExp(
3376
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3900
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3377
3901
  );
3378
3902
  const callStart = callStartRe.exec(chunkCode);
3379
3903
  if (callStart) {
@@ -3398,7 +3922,7 @@ function evictHandlerCode(code, exports, fnName, brand) {
3398
3922
  if (passthrough) continue;
3399
3923
  const eName = escapeRegExp(name);
3400
3924
  const callStartRe = new RegExp(
3401
- `const\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3925
+ `(?:const|let|var)\\s+${eName}\\s*=\\s*${eFnName}\\s*(?:<[^>]*>)?\\s*\\(`
3402
3926
  );
3403
3927
  const startMatch = callStartRe.exec(modified);
3404
3928
  if (!startMatch) continue;
@@ -3433,6 +3957,8 @@ function createDiscoveryState(entryPath, opts) {
3433
3957
  projectRoot: "",
3434
3958
  isBuildMode: false,
3435
3959
  userResolveAlias: void 0,
3960
+ userRunnerConfig: void 0,
3961
+ userResolvePlugins: [],
3436
3962
  scanFilter: void 0,
3437
3963
  cachedRouterFiles: void 0,
3438
3964
  opts,
@@ -3454,7 +3980,8 @@ function createDiscoveryState(entryPath, opts) {
3454
3980
  devServerOrigin: null,
3455
3981
  devServer: null,
3456
3982
  selfWrittenGenFiles: /* @__PURE__ */ new Map(),
3457
- SELF_WRITE_WINDOW_MS: 5e3
3983
+ SELF_WRITE_WINDOW_MS: 5e3,
3984
+ lastDiscoveryError: null
3458
3985
  };
3459
3986
  }
3460
3987
 
@@ -3466,6 +3993,12 @@ function markSelfGenWrite(state, filePath, content) {
3466
3993
  state.selfWrittenGenFiles.set(filePath, { at: Date.now(), hash });
3467
3994
  }
3468
3995
  function consumeSelfGenWrite(state, filePath) {
3996
+ return checkSelfGenWrite(state, filePath, true);
3997
+ }
3998
+ function peekSelfGenWrite(state, filePath) {
3999
+ return checkSelfGenWrite(state, filePath, false);
4000
+ }
4001
+ function checkSelfGenWrite(state, filePath, consume) {
3469
4002
  const info = state.selfWrittenGenFiles.get(filePath);
3470
4003
  if (!info) return false;
3471
4004
  if (Date.now() - info.at > state.SELF_WRITE_WINDOW_MS) {
@@ -3476,7 +4009,7 @@ function consumeSelfGenWrite(state, filePath) {
3476
4009
  const current = readFileSync3(filePath, "utf-8");
3477
4010
  const currentHash = createHash3("sha256").update(current).digest("hex");
3478
4011
  if (currentHash === info.hash) {
3479
- state.selfWrittenGenFiles.delete(filePath);
4012
+ if (consume) state.selfWrittenGenFiles.delete(filePath);
3480
4013
  return true;
3481
4014
  }
3482
4015
  return false;
@@ -3486,11 +4019,14 @@ function consumeSelfGenWrite(state, filePath) {
3486
4019
  }
3487
4020
  }
3488
4021
 
3489
- // src/vite/utils/manifest-utils.ts
4022
+ // src/build/prefix-tree-utils.ts
3490
4023
  function flattenLeafEntries(prefixTree, routeManifest, result) {
3491
- function visit(node) {
4024
+ function visit(node, ancestorStaticPrefixes) {
3492
4025
  const children = node.children || {};
3493
4026
  if (Object.keys(children).length === 0 && node.routes && node.routes.length > 0) {
4027
+ if (ancestorStaticPrefixes.has(node.staticPrefix)) {
4028
+ return;
4029
+ }
3494
4030
  const routes = {};
3495
4031
  for (const name of node.routes) {
3496
4032
  if (name in routeManifest) {
@@ -3499,13 +4035,15 @@ function flattenLeafEntries(prefixTree, routeManifest, result) {
3499
4035
  }
3500
4036
  result.push({ staticPrefix: node.staticPrefix, routes });
3501
4037
  } else {
4038
+ const nextAncestors = new Set(ancestorStaticPrefixes);
4039
+ nextAncestors.add(node.staticPrefix);
3502
4040
  for (const child of Object.values(children)) {
3503
- visit(child);
4041
+ visit(child, nextAncestors);
3504
4042
  }
3505
4043
  }
3506
4044
  }
3507
4045
  for (const node of Object.values(prefixTree)) {
3508
- visit(node);
4046
+ visit(node, /* @__PURE__ */ new Set());
3509
4047
  }
3510
4048
  }
3511
4049
  function buildRouteToStaticPrefix(prefixTree, result) {
@@ -3522,6 +4060,8 @@ function buildRouteToStaticPrefix(prefixTree, result) {
3522
4060
  visit(node);
3523
4061
  }
3524
4062
  }
4063
+
4064
+ // src/vite/utils/manifest-utils.ts
3525
4065
  function jsonParseExpression(value) {
3526
4066
  const json = JSON.stringify(value);
3527
4067
  const escaped = json.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
@@ -3698,8 +4238,14 @@ function copyStagedBuildAssets(projectRoot, fileNames) {
3698
4238
  }
3699
4239
 
3700
4240
  // src/vite/discovery/prerender-collection.ts
4241
+ var debug9 = createRangoDebugger(NS.prerender);
3701
4242
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3702
4243
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode) return;
4244
+ const overallStart = debug9 ? performance.now() : 0;
4245
+ debug9?.(
4246
+ "expandPrerenderRoutes: start (%d router manifest(s))",
4247
+ allManifests.length
4248
+ );
3703
4249
  const entries = [];
3704
4250
  const allRoutes = {};
3705
4251
  for (const { manifest: m } of allManifests) {
@@ -3726,7 +4272,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3726
4272
  const progressInterval = totalDynamic > 0 ? setInterval(() => {
3727
4273
  const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3728
4274
  console.log(
3729
- `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4275
+ `[rango] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3730
4276
  );
3731
4277
  }, 5e3) : void 0;
3732
4278
  try {
@@ -3749,6 +4295,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3749
4295
  });
3750
4296
  } else {
3751
4297
  if (def?.getParams) {
4298
+ const getParamsStart = debug9 ? performance.now() : 0;
3752
4299
  try {
3753
4300
  const buildVars = {};
3754
4301
  const buildEnv = state.resolvedBuildEnv;
@@ -3762,11 +4309,17 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3762
4309
  get env() {
3763
4310
  if (buildEnv !== void 0) return buildEnv;
3764
4311
  throw new Error(
3765
- "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
4312
+ "[rango] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3766
4313
  );
3767
4314
  }
3768
4315
  };
3769
4316
  const paramsList = await def.getParams(getParamsCtx);
4317
+ debug9?.(
4318
+ "getParams %s -> %d params (%sms)",
4319
+ routeName,
4320
+ paramsList.length,
4321
+ (performance.now() - getParamsStart).toFixed(1)
4322
+ );
3770
4323
  const concurrency = def.options?.concurrency ?? 1;
3771
4324
  const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3772
4325
  for (const params of paramsList) {
@@ -3797,7 +4350,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3797
4350
  resolvedRoutes++;
3798
4351
  if (err.name === "Skip") {
3799
4352
  console.log(
3800
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
4353
+ `[rango] SKIP route "${routeName}" - ${err.message}`
3801
4354
  );
3802
4355
  notifyOnError(
3803
4356
  registry,
@@ -3810,14 +4363,14 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3810
4363
  continue;
3811
4364
  }
3812
4365
  console.error(
3813
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
4366
+ `[rango] Failed to get params for prerender route "${routeName}": ${err.message}`
3814
4367
  );
3815
4368
  notifyOnError(registry, err, "prerender", routeName);
3816
4369
  throw err;
3817
4370
  }
3818
4371
  } else {
3819
4372
  console.warn(
3820
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
4373
+ `[rango] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3821
4374
  );
3822
4375
  }
3823
4376
  }
@@ -3828,15 +4381,26 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3828
4381
  clearInterval(progressInterval);
3829
4382
  const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3830
4383
  console.log(
3831
- `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4384
+ `[rango] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3832
4385
  );
3833
4386
  }
3834
4387
  }
3835
- if (entries.length === 0) return;
4388
+ if (entries.length === 0) {
4389
+ debug9?.(
4390
+ "no prerender entries (done in %sms)",
4391
+ (performance.now() - overallStart).toFixed(1)
4392
+ );
4393
+ return;
4394
+ }
3836
4395
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
3837
4396
  const concurrencyNote = maxConcurrency > 1 ? ` (concurrency: ${maxConcurrency})` : "";
3838
4397
  console.log(
3839
- `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
4398
+ `[rango] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
4399
+ );
4400
+ debug9?.(
4401
+ "prerender loop: %d entries, max concurrency %d",
4402
+ entries.length,
4403
+ maxConcurrency
3840
4404
  );
3841
4405
  const { hashParams } = await rscEnv.runner.import("@rangojs/router/build");
3842
4406
  const manifestEntries = {};
@@ -3864,7 +4428,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3864
4428
  if (result.passthrough) {
3865
4429
  const elapsed2 = (performance.now() - startUrl).toFixed(0);
3866
4430
  console.log(
3867
- `[rsc-router] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
4431
+ `[rango] PASS ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - live fallback`
3868
4432
  );
3869
4433
  doneCount++;
3870
4434
  break;
@@ -3884,10 +4448,9 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3884
4448
  const interceptKey = `${result.routeName}/${paramHash}/i`;
3885
4449
  const interceptValue = JSON.stringify({
3886
4450
  segments: [...result.segments, ...result.interceptSegments],
3887
- handles: {
3888
- ...result.handles,
3889
- ...result.interceptHandles || {}
3890
- }
4451
+ // interceptHandles is the pre-encoded MERGED (main + intercept)
4452
+ // handle string (the producer merged before encoding).
4453
+ handles: result.interceptHandles ?? ""
3891
4454
  });
3892
4455
  manifestEntries[interceptKey] = stageBuildAssetModule(
3893
4456
  state.projectRoot,
@@ -3897,7 +4460,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3897
4460
  }
3898
4461
  const elapsed = (performance.now() - startUrl).toFixed(0);
3899
4462
  console.log(
3900
- `[rsc-router] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`
4463
+ `[rango] OK ${entry.urlPath.padEnd(40)} (${elapsed}ms)`
3901
4464
  );
3902
4465
  doneCount++;
3903
4466
  break;
@@ -3905,7 +4468,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3905
4468
  if (err.name === "Skip") {
3906
4469
  const elapsed2 = (performance.now() - startUrl).toFixed(0);
3907
4470
  console.log(
3908
- `[rsc-router] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4471
+ `[rango] SKIP ${entry.urlPath.padEnd(40)} (${elapsed2}ms) - ${err.message}`
3909
4472
  );
3910
4473
  skipCount++;
3911
4474
  notifyOnError(
@@ -3920,7 +4483,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3920
4483
  }
3921
4484
  const elapsed = (performance.now() - startUrl).toFixed(0);
3922
4485
  console.error(
3923
- `[rsc-router] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
4486
+ `[rango] FAIL ${entry.urlPath.padEnd(40)} (${elapsed}ms) - ${err.message}`
3924
4487
  );
3925
4488
  notifyOnError(
3926
4489
  registry,
@@ -3942,12 +4505,24 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3942
4505
  const parts = [`${doneCount} done`];
3943
4506
  if (skipCount > 0) parts.push(`${skipCount} skipped`);
3944
4507
  console.log(
3945
- `[rsc-router] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
4508
+ `[rango] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
4509
+ );
4510
+ debug9?.(
4511
+ "expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
4512
+ doneCount,
4513
+ skipCount,
4514
+ totalElapsed,
4515
+ (performance.now() - overallStart).toFixed(1)
3946
4516
  );
3947
4517
  }
3948
4518
  async function renderStaticHandlers(state, rscEnv, registry) {
3949
4519
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode || !state.resolvedStaticModules?.size)
3950
4520
  return;
4521
+ const overallStart = debug9 ? performance.now() : 0;
4522
+ debug9?.(
4523
+ "renderStaticHandlers: start (%d static module(s))",
4524
+ state.resolvedStaticModules.size
4525
+ );
3951
4526
  const manifestEntries = {};
3952
4527
  let staticDone = 0;
3953
4528
  let staticSkip = 0;
@@ -3956,16 +4531,14 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3956
4531
  totalStaticCount += exportNames.length;
3957
4532
  }
3958
4533
  const startStatic = performance.now();
3959
- console.log(
3960
- `[rsc-router] Rendering ${totalStaticCount} static handler(s)...`
3961
- );
4534
+ console.log(`[rango] Rendering ${totalStaticCount} static handler(s)...`);
3962
4535
  for (const [moduleId, exportNames] of state.resolvedStaticModules) {
3963
4536
  let mod;
3964
4537
  try {
3965
4538
  mod = await rscEnv.runner.import(moduleId);
3966
4539
  } catch (err) {
3967
4540
  console.error(
3968
- `[rsc-router] Failed to import static module ${moduleId}: ${err.message}`
4541
+ `[rango] Failed to import static module ${moduleId}: ${err.message}`
3969
4542
  );
3970
4543
  notifyOnError(registry, err, "static");
3971
4544
  throw err;
@@ -3987,7 +4560,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3987
4560
  !state.isBuildMode
3988
4561
  );
3989
4562
  if (result) {
3990
- const hasHandles = Object.keys(result.handles).length > 0;
4563
+ const hasHandles = result.handles !== "";
3991
4564
  const exportValue = hasHandles ? JSON.stringify(result) : JSON.stringify(result.encoded);
3992
4565
  manifestEntries[def.$$id] = stageBuildAssetModule(
3993
4566
  state.projectRoot,
@@ -3995,9 +4568,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3995
4568
  exportValue
3996
4569
  );
3997
4570
  const elapsed = (performance.now() - startHandler).toFixed(0);
3998
- console.log(
3999
- `[rsc-router] OK ${name.padEnd(40)} (${elapsed}ms)`
4000
- );
4571
+ console.log(`[rango] OK ${name.padEnd(40)} (${elapsed}ms)`);
4001
4572
  staticDone++;
4002
4573
  handled = true;
4003
4574
  break;
@@ -4006,7 +4577,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4006
4577
  if (err.name === "Skip") {
4007
4578
  const elapsed2 = (performance.now() - startHandler).toFixed(0);
4008
4579
  console.log(
4009
- `[rsc-router] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4580
+ `[rango] SKIP ${name.padEnd(40)} (${elapsed2}ms) - ${err.message}`
4010
4581
  );
4011
4582
  staticSkip++;
4012
4583
  notifyOnError(registry, err, "static", void 0, void 0, true);
@@ -4015,16 +4586,14 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4015
4586
  }
4016
4587
  const elapsed = (performance.now() - startHandler).toFixed(0);
4017
4588
  console.error(
4018
- `[rsc-router] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
4589
+ `[rango] FAIL ${name.padEnd(40)} (${elapsed}ms) - ${err.message}`
4019
4590
  );
4020
4591
  notifyOnError(registry, err, "static");
4021
4592
  throw err;
4022
4593
  }
4023
4594
  }
4024
4595
  if (!handled) {
4025
- console.warn(
4026
- `[rsc-router] No router could render static handler "${name}"`
4027
- );
4596
+ console.warn(`[rango] No router could render static handler "${name}"`);
4028
4597
  }
4029
4598
  }
4030
4599
  }
@@ -4035,38 +4604,118 @@ async function renderStaticHandlers(state, rscEnv, registry) {
4035
4604
  const staticParts = [`${staticDone} done`];
4036
4605
  if (staticSkip > 0) staticParts.push(`${staticSkip} skipped`);
4037
4606
  console.log(
4038
- `[rsc-router] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
4607
+ `[rango] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
4608
+ );
4609
+ debug9?.(
4610
+ "renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
4611
+ staticDone,
4612
+ staticSkip,
4613
+ totalStaticElapsed,
4614
+ (performance.now() - overallStart).toFixed(1)
4039
4615
  );
4040
4616
  }
4041
4617
 
4618
+ // src/vite/discovery/discovery-errors.ts
4619
+ function indent(text, pad) {
4620
+ return text.split("\n").map((line) => line.length > 0 ? pad + line : line).join("\n");
4621
+ }
4622
+ async function invokeLazyMount(loader, context, errors) {
4623
+ try {
4624
+ await loader();
4625
+ } catch (error) {
4626
+ errors.push({ context, error });
4627
+ }
4628
+ }
4629
+ function isLazyMount(route) {
4630
+ return !!route && route.kind === "lazy" && typeof route.handler === "function";
4631
+ }
4632
+ async function resolveHostRouterHandlers(hostRegistry) {
4633
+ const errors = [];
4634
+ for (const [hostId, entry] of hostRegistry) {
4635
+ for (const route of entry.routes) {
4636
+ if (isLazyMount(route)) {
4637
+ await invokeLazyMount(
4638
+ route.handler,
4639
+ `host "${hostId}" route handler`,
4640
+ errors
4641
+ );
4642
+ }
4643
+ }
4644
+ if (isLazyMount(entry.fallback)) {
4645
+ await invokeLazyMount(
4646
+ entry.fallback.handler,
4647
+ `host "${hostId}" fallback handler`,
4648
+ errors
4649
+ );
4650
+ }
4651
+ }
4652
+ return errors;
4653
+ }
4654
+ function formatNoRoutersError(entryPath, errors) {
4655
+ const base = `[rango] No routers found in registry after importing ${entryPath}`;
4656
+ if (errors.length === 0) {
4657
+ return base;
4658
+ }
4659
+ const formatted = errors.map(({ context, error }) => {
4660
+ const err = error instanceof Error ? error : new Error(String(error));
4661
+ const detail = err.stack ?? err.message;
4662
+ return ` - while resolving ${context}:
4663
+ ${indent(detail, " ")}`;
4664
+ }).join("\n");
4665
+ return `${base}
4666
+
4667
+ ${errors.length} error(s) were caught during host-router discovery and likely explain why no routers were registered:
4668
+ ${formatted}`;
4669
+ }
4670
+ function toCause(errors) {
4671
+ if (errors.length === 0) return void 0;
4672
+ if (errors.length === 1) return errors[0].error;
4673
+ return new AggregateError(
4674
+ errors.map((e) => e.error),
4675
+ "Multiple host-router handlers failed during discovery"
4676
+ );
4677
+ }
4678
+ var DiscoveryError = class _DiscoveryError extends Error {
4679
+ constructor(entryPath, caught) {
4680
+ super(formatNoRoutersError(entryPath, caught));
4681
+ const cause = toCause(caught);
4682
+ if (cause !== void 0) {
4683
+ this.cause = cause;
4684
+ }
4685
+ this.name = "DiscoveryError";
4686
+ this.entryPath = entryPath;
4687
+ this.caught = caught;
4688
+ Object.setPrototypeOf(this, _DiscoveryError.prototype);
4689
+ }
4690
+ };
4691
+
4042
4692
  // src/vite/discovery/discover-routers.ts
4693
+ var debug10 = createRangoDebugger(NS.discovery);
4043
4694
  async function discoverRouters(state, rscEnv) {
4044
4695
  if (!state.resolvedEntryPath) return;
4045
- await rscEnv.runner.import(state.resolvedEntryPath);
4046
- const serverMod = await rscEnv.runner.import("@rangojs/router/server");
4696
+ await timed(
4697
+ debug10,
4698
+ "inner: import entry",
4699
+ () => rscEnv.runner.import(state.resolvedEntryPath)
4700
+ );
4701
+ const serverMod = await timed(
4702
+ debug10,
4703
+ "inner: import @rangojs/router/server",
4704
+ () => rscEnv.runner.import("@rangojs/router/server")
4705
+ );
4047
4706
  let registry = serverMod.RouterRegistry;
4048
4707
  if (!registry || registry.size === 0) {
4708
+ const discoveryErrors = [];
4049
4709
  try {
4050
4710
  const hostRegistry = serverMod.HostRouterRegistry;
4051
4711
  if (hostRegistry && hostRegistry.size > 0) {
4052
4712
  console.log(
4053
- `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
4713
+ `[rango] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
4054
4714
  );
4055
- for (const [, entry] of hostRegistry) {
4056
- for (const route of entry.routes) {
4057
- if (typeof route.handler === "function") {
4058
- try {
4059
- await route.handler();
4060
- } catch {
4061
- }
4062
- }
4063
- }
4064
- if (entry.fallback && typeof entry.fallback.handler === "function") {
4065
- try {
4066
- await entry.fallback.handler();
4067
- } catch {
4068
- }
4069
- }
4715
+ const handlerErrors = await resolveHostRouterHandlers(hostRegistry);
4716
+ discoveryErrors.push(...handlerErrors);
4717
+ for (const { context, error } of handlerErrors) {
4718
+ debug10?.("caught error while resolving %s: %O", context, error);
4070
4719
  }
4071
4720
  const freshServerMod = await rscEnv.runner.import(
4072
4721
  "@rangojs/router/server"
@@ -4077,16 +4726,20 @@ async function discoverRouters(state, rscEnv) {
4077
4726
  registry = freshRegistry;
4078
4727
  }
4079
4728
  }
4080
- } catch {
4729
+ } catch (error) {
4730
+ discoveryErrors.push({ context: "host-router discovery", error });
4081
4731
  }
4082
4732
  if (!registry || registry.size === 0) {
4083
- throw new Error(
4084
- `[rsc-router] No routers found in registry after importing ${state.resolvedEntryPath}`
4085
- );
4733
+ throw new DiscoveryError(state.resolvedEntryPath, discoveryErrors);
4086
4734
  }
4087
4735
  }
4088
- const buildMod = await rscEnv.runner.import("@rangojs/router/build");
4736
+ const buildMod = await timed(
4737
+ debug10,
4738
+ "inner: import @rangojs/router/build",
4739
+ () => rscEnv.runner.import("@rangojs/router/build")
4740
+ );
4089
4741
  const generateManifestFull = buildMod.generateManifestFull;
4742
+ debug10?.("inner: found %d router(s) in registry", registry.size);
4090
4743
  const nestedRouterConflict = findNestedRouterConflict(
4091
4744
  [...registry.values()].map((router) => router.__sourceFile).filter(
4092
4745
  (sourceFile) => typeof sourceFile === "string"
@@ -4105,6 +4758,16 @@ async function discoverRouters(state, rscEnv) {
4105
4758
  let mergedRouteTrailingSlash = {};
4106
4759
  let routerMountIndex = 0;
4107
4760
  const allManifests = [];
4761
+ const clientChunkCtx = state.opts?.clientChunkCtx;
4762
+ const collectClientFallbackRef = clientChunkCtx ? (refKey) => clientChunkCtx.fallbackRefs.add(
4763
+ computeProductionHash(state.projectRoot, refKey)
4764
+ ) : void 0;
4765
+ const collectFromBoundaryNode = (node) => {
4766
+ if (collectClientFallbackRef && buildMod.collectFallbackClientRefs) {
4767
+ buildMod.collectFallbackClientRefs(node, collectClientFallbackRef);
4768
+ }
4769
+ };
4770
+ const manifestGenStart = debug10 ? performance.now() : 0;
4108
4771
  for (const [id, router] of registry) {
4109
4772
  if (!router.urlpatterns || !generateManifestFull) {
4110
4773
  continue;
@@ -4112,10 +4775,18 @@ async function discoverRouters(state, rscEnv) {
4112
4775
  const manifest = generateManifestFull(
4113
4776
  router.urlpatterns,
4114
4777
  routerMountIndex,
4115
- router.__basename ? { urlPrefix: router.__basename } : void 0
4778
+ {
4779
+ ...router.__basename ? { urlPrefix: router.__basename } : {},
4780
+ ...collectClientFallbackRef ? { collectClientFallbackRef } : {}
4781
+ }
4116
4782
  );
4117
4783
  routerMountIndex++;
4118
4784
  allManifests.push({ id, manifest });
4785
+ if (collectClientFallbackRef) {
4786
+ collectFromBoundaryNode(router.__defaultErrorBoundary);
4787
+ collectFromBoundaryNode(router.__defaultNotFoundBoundary);
4788
+ collectFromBoundaryNode(router.__notFound);
4789
+ }
4119
4790
  const routeCount = Object.keys(manifest.routeManifest).length;
4120
4791
  const staticRoutes = Object.values(manifest.routeManifest).filter(
4121
4792
  (p) => !p.includes(":") && !p.includes("*")
@@ -4166,7 +4837,7 @@ async function discoverRouters(state, rscEnv) {
4166
4837
  );
4167
4838
  newPerRouterPrecomputedMap.set(id, routerPrecomputed);
4168
4839
  console.log(
4169
- `[rsc-router] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
4840
+ `[rango] Router "${id}" -> ${routeCount} routes (${staticRoutes} static, ${dynamicRoutes} dynamic)`
4170
4841
  );
4171
4842
  }
4172
4843
  if (registry.size > 1) {
@@ -4175,11 +4846,17 @@ async function discoverRouters(state, rscEnv) {
4175
4846
  );
4176
4847
  if (autoIds.length > 1) {
4177
4848
  console.warn(
4178
- `[rsc-router] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). In multi-router setups, each createRouter() must have an explicit \`id\` option to ensure per-router manifest data is matched correctly at runtime. Example: createRouter({ id: "site", ... })`
4849
+ `[rango] WARNING: ${autoIds.length} routers use auto-generated IDs (${autoIds.join(", ")}). In multi-router setups, each createRouter() must have an explicit \`id\` option to ensure per-router manifest data is matched correctly at runtime. Example: createRouter({ id: "site", ... })`
4179
4850
  );
4180
4851
  }
4181
4852
  }
4853
+ debug10?.(
4854
+ "inner: generated manifests for %d router(s) (%sms)",
4855
+ allManifests.length,
4856
+ (performance.now() - manifestGenStart).toFixed(1)
4857
+ );
4182
4858
  let newMergedRouteTrie = null;
4859
+ const trieStart = debug10 ? performance.now() : 0;
4183
4860
  if (Object.keys(newMergedRouteManifest).length > 0) {
4184
4861
  const buildRouteTrie = buildMod.buildRouteTrie;
4185
4862
  if (buildRouteTrie && mergedRouteAncestry) {
@@ -4214,34 +4891,24 @@ async function discoverRouters(state, rscEnv) {
4214
4891
  newMergedRouteManifest,
4215
4892
  mergedRouteAncestry,
4216
4893
  routeToStaticPrefix,
4217
- Object.keys(mergedRouteTrailingSlash).length > 0 ? mergedRouteTrailingSlash : void 0,
4218
- prerenderRouteNames.size > 0 ? prerenderRouteNames : void 0,
4219
- passthroughRouteNames.size > 0 ? passthroughRouteNames : void 0,
4220
- Object.keys(mergedResponseTypeRoutes).length > 0 ? mergedResponseTypeRoutes : void 0
4894
+ mergedRouteTrailingSlash,
4895
+ prerenderRouteNames,
4896
+ passthroughRouteNames,
4897
+ mergedResponseTypeRoutes
4221
4898
  );
4899
+ const buildPerRouterTrie = buildMod.buildPerRouterTrie;
4222
4900
  for (const { id, manifest } of allManifests) {
4223
- if (!manifest._routeAncestry || Object.keys(manifest._routeAncestry).length === 0)
4224
- continue;
4225
- const perRouterStaticPrefix = {};
4226
- for (const name of Object.keys(manifest.routeManifest)) {
4227
- perRouterStaticPrefix[name] = "";
4901
+ const perRouterTrie = buildPerRouterTrie ? buildPerRouterTrie(manifest) : null;
4902
+ if (perRouterTrie) {
4903
+ newPerRouterTrieMap.set(id, perRouterTrie);
4228
4904
  }
4229
- buildRouteToStaticPrefix(manifest.prefixTree, perRouterStaticPrefix);
4230
- const perRouterPrerenderNames = manifest.prerenderRoutes ? new Set(manifest.prerenderRoutes) : void 0;
4231
- const perRouterPassthroughNames = manifest.passthroughRoutes ? new Set(manifest.passthroughRoutes) : void 0;
4232
- const perRouterTrie = buildRouteTrie(
4233
- manifest.routeManifest,
4234
- manifest._routeAncestry,
4235
- perRouterStaticPrefix,
4236
- manifest.routeTrailingSlash && Object.keys(manifest.routeTrailingSlash).length > 0 ? manifest.routeTrailingSlash : void 0,
4237
- perRouterPrerenderNames && perRouterPrerenderNames.size > 0 ? perRouterPrerenderNames : void 0,
4238
- perRouterPassthroughNames && perRouterPassthroughNames.size > 0 ? perRouterPassthroughNames : void 0,
4239
- manifest.responseTypeRoutes && Object.keys(manifest.responseTypeRoutes).length > 0 ? manifest.responseTypeRoutes : void 0
4240
- );
4241
- newPerRouterTrieMap.set(id, perRouterTrie);
4242
4905
  }
4243
4906
  }
4244
4907
  }
4908
+ debug10?.(
4909
+ "inner: trie build done (%sms)",
4910
+ (performance.now() - trieStart).toFixed(1)
4911
+ );
4245
4912
  state.mergedRouteManifest = newMergedRouteManifest;
4246
4913
  state.mergedPrecomputedEntries = newMergedPrecomputedEntries;
4247
4914
  state.perRouterManifests = newPerRouterManifests;
@@ -4255,7 +4922,7 @@ async function discoverRouters(state, rscEnv) {
4255
4922
  }
4256
4923
 
4257
4924
  // src/vite/discovery/route-types-writer.ts
4258
- import { dirname as dirname3, basename, join as join2, resolve as resolve6 } from "node:path";
4925
+ import { dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
4259
4926
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
4260
4927
  function filterUserNamedRoutes(manifest) {
4261
4928
  const filtered = {};
@@ -4266,39 +4933,20 @@ function filterUserNamedRoutes(manifest) {
4266
4933
  }
4267
4934
  return filtered;
4268
4935
  }
4936
+ function writeGenFileIfChanged(state, outPath, source, opts) {
4937
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4938
+ if (existing === source) return;
4939
+ markSelfGenWrite(state, outPath, source);
4940
+ writeFileSync3(outPath, source);
4941
+ if (opts?.log) console.log(`[rango] Generated route types -> ${outPath}`);
4942
+ }
4269
4943
  function writeCombinedRouteTypesWithTracking(state, opts) {
4270
4944
  const routerFiles = state.cachedRouterFiles ?? findRouterFiles(state.projectRoot, state.scanFilter);
4271
4945
  state.cachedRouterFiles = routerFiles;
4272
- const preContent = /* @__PURE__ */ new Map();
4273
- for (const routerFilePath of routerFiles) {
4274
- const routerDir = dirname3(routerFilePath);
4275
- const routerBasename = basename(routerFilePath).replace(
4276
- /\.(tsx?|jsx?)$/,
4277
- ""
4278
- );
4279
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4280
- try {
4281
- preContent.set(outPath, readFileSync4(outPath, "utf-8"));
4282
- } catch {
4283
- }
4284
- }
4285
- writeCombinedRouteTypes(state.projectRoot, routerFiles, opts);
4286
- for (const routerFilePath of routerFiles) {
4287
- const routerDir = dirname3(routerFilePath);
4288
- const routerBasename = basename(routerFilePath).replace(
4289
- /\.(tsx?|jsx?)$/,
4290
- ""
4291
- );
4292
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4293
- if (!existsSync5(outPath)) continue;
4294
- try {
4295
- const content = readFileSync4(outPath, "utf-8");
4296
- if (content !== preContent.get(outPath)) {
4297
- markSelfGenWrite(state, outPath, content);
4298
- }
4299
- } catch {
4300
- }
4301
- }
4946
+ writeCombinedRouteTypes(state.projectRoot, routerFiles, {
4947
+ ...opts,
4948
+ onWrite: (outPath, content) => markSelfGenWrite(state, outPath, content)
4949
+ });
4302
4950
  }
4303
4951
  function writeRouteTypesFiles(state) {
4304
4952
  if (state.perRouterManifests.length === 0) return;
@@ -4310,7 +4958,7 @@ function writeRouteTypesFiles(state) {
4310
4958
  if (existsSync5(oldCombinedPath)) {
4311
4959
  unlinkSync2(oldCombinedPath);
4312
4960
  console.log(
4313
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
4961
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
4314
4962
  );
4315
4963
  }
4316
4964
  } catch {
@@ -4324,39 +4972,23 @@ function writeRouteTypesFiles(state) {
4324
4972
  if (!sourceFile) continue;
4325
4973
  if (sourceFile.includes("node_modules")) {
4326
4974
  throw new Error(
4327
- `[rsc-router] Router "${id}" has sourceFile inside node_modules: ${sourceFile}
4975
+ `[rango] Router "${id}" has sourceFile inside node_modules: ${sourceFile}
4328
4976
  This means createRouter() stack trace parsing matched a Vite internal frame.
4329
4977
  Set an explicit \`id\` on createRouter() or check the call site.`
4330
4978
  );
4331
4979
  }
4332
- const routerDir = dirname3(sourceFile);
4333
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4334
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4980
+ const outPath = genFileTsPath(sourceFile);
4335
4981
  const userRoutes = filterUserNamedRoutes(routeManifest);
4336
- let effectiveSearchSchemas = routeSearchSchemas;
4337
- if ((!effectiveSearchSchemas || Object.keys(effectiveSearchSchemas).length === 0) && sourceFile) {
4338
- const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
4339
- if (Object.keys(staticParsed.searchSchemas).length > 0) {
4340
- const filtered = {};
4341
- for (const name of Object.keys(userRoutes)) {
4342
- const schema = staticParsed.searchSchemas[name];
4343
- if (schema) filtered[name] = schema;
4344
- }
4345
- if (Object.keys(filtered).length > 0) {
4346
- effectiveSearchSchemas = filtered;
4347
- }
4348
- }
4349
- }
4982
+ const effectiveSearchSchemas = resolveSearchSchemas(
4983
+ Object.keys(userRoutes),
4984
+ routeSearchSchemas,
4985
+ sourceFile
4986
+ );
4350
4987
  const source = generateRouteTypesSource(
4351
4988
  userRoutes,
4352
4989
  effectiveSearchSchemas && Object.keys(effectiveSearchSchemas).length > 0 ? effectiveSearchSchemas : void 0
4353
4990
  );
4354
- const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4355
- if (existing !== source) {
4356
- markSelfGenWrite(state, outPath, source);
4357
- writeFileSync3(outPath, source);
4358
- console.log(`[rsc-router] Generated route types -> ${outPath}`);
4359
- }
4991
+ writeGenFileIfChanged(state, outPath, source, { log: true });
4360
4992
  }
4361
4993
  }
4362
4994
  function supplementGenFilesWithRuntimeRoutes(state) {
@@ -4394,23 +5026,17 @@ function supplementGenFilesWithRuntimeRoutes(state) {
4394
5026
  }
4395
5027
  }
4396
5028
  }
4397
- const routerDir = dirname3(sourceFile);
4398
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4399
- const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
5029
+ const outPath = genFileTsPath(sourceFile);
4400
5030
  const source = generateRouteTypesSource(
4401
5031
  mergedRoutes,
4402
5032
  Object.keys(mergedSearchSchemas).length > 0 ? mergedSearchSchemas : void 0
4403
5033
  );
4404
- const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4405
- if (existing !== source) {
4406
- markSelfGenWrite(state, outPath, source);
4407
- writeFileSync3(outPath, source);
4408
- }
5034
+ writeGenFileIfChanged(state, outPath, source);
4409
5035
  }
4410
5036
  }
4411
5037
 
4412
5038
  // src/vite/discovery/virtual-module-codegen.ts
4413
- import { dirname as dirname4, basename as basename2, join as join3 } from "node:path";
5039
+ import { dirname as dirname4, basename, join as join3 } from "node:path";
4414
5040
  function generateRoutesManifestModule(state) {
4415
5041
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
4416
5042
  if (hasManifest) {
@@ -4421,7 +5047,7 @@ function generateRoutesManifestModule(state) {
4421
5047
  for (const entry of state.perRouterManifests) {
4422
5048
  if (entry.sourceFile) {
4423
5049
  const routerDir = dirname4(entry.sourceFile);
4424
- const routerBasename = basename2(entry.sourceFile).replace(
5050
+ const routerBasename = basename(entry.sourceFile).replace(
4425
5051
  /\.(tsx?|jsx?)$/,
4426
5052
  ""
4427
5053
  );
@@ -4442,7 +5068,7 @@ function generateRoutesManifestModule(state) {
4442
5068
  }
4443
5069
  }
4444
5070
  const lines = [
4445
- `import { setCachedManifest, setPrecomputedEntries, setRouteTrie, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
5071
+ `import { setCachedManifest, setRouterManifest, registerRouterManifestLoader, clearAllRouterData } from "@rangojs/router/server";`,
4446
5072
  ...genFileImports,
4447
5073
  // Clear stale per-router cached data (manifest, trie, precomputed entries)
4448
5074
  // before re-populating. In Cloudflare dev mode, program reloads re-evaluate
@@ -4478,18 +5104,6 @@ function generateRoutesManifestModule(state) {
4478
5104
  );
4479
5105
  }
4480
5106
  }
4481
- if (state.isBuildMode) {
4482
- if (state.mergedPrecomputedEntries && state.mergedPrecomputedEntries.length > 0) {
4483
- lines.push(
4484
- `setPrecomputedEntries(${jsonParseExpression(state.mergedPrecomputedEntries)});`
4485
- );
4486
- }
4487
- if (state.mergedRouteTrie) {
4488
- lines.push(
4489
- `setRouteTrie(${jsonParseExpression(state.mergedRouteTrie)});`
4490
- );
4491
- }
4492
- }
4493
5107
  for (const routerId of state.perRouterManifestDataMap.keys()) {
4494
5108
  lines.push(
4495
5109
  `registerRouterManifestLoader(${JSON.stringify(routerId)}, () => import(${JSON.stringify(VIRTUAL_ROUTES_MANIFEST_ID + "/" + routerId)}));`
@@ -4518,7 +5132,7 @@ function generatePerRouterModule(state, routerId) {
4518
5132
  const lines = [];
4519
5133
  if (routerEntry?.sourceFile) {
4520
5134
  const routerDir = dirname4(routerEntry.sourceFile);
4521
- const routerBasename = basename2(routerEntry.sourceFile).replace(
5135
+ const routerBasename = basename(routerEntry.sourceFile).replace(
4522
5136
  /\.(tsx?|jsx?)$/,
4523
5137
  ""
4524
5138
  );
@@ -4589,12 +5203,12 @@ function postprocessBundle(state) {
4589
5203
  writeFileSync4(chunkPath, result.code);
4590
5204
  const savedKB = (result.savedBytes / 1024).toFixed(1);
4591
5205
  console.log(
4592
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
5206
+ `[rango] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4593
5207
  );
4594
5208
  }
4595
5209
  } catch (replaceErr) {
4596
5210
  console.warn(
4597
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
5211
+ `[rango] Failed to evict ${target.label}: ${replaceErr.message}`
4598
5212
  );
4599
5213
  }
4600
5214
  }
@@ -4632,11 +5246,11 @@ function postprocessBundle(state) {
4632
5246
  writeFileSync4(rscEntryPath, injection + rscCode);
4633
5247
  const totalKB = (totalBytes / 1024).toFixed(1);
4634
5248
  console.log(
4635
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
5249
+ `[rango] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
4636
5250
  );
4637
5251
  } catch (err) {
4638
5252
  throw new Error(
4639
- `[rsc-router] Failed to write prerender assets: ${err.message}`
5253
+ `[rango] Failed to write prerender assets: ${err.message}`
4640
5254
  );
4641
5255
  }
4642
5256
  }
@@ -4670,28 +5284,180 @@ function postprocessBundle(state) {
4670
5284
  writeFileSync4(rscEntryPath, injection + rscCode);
4671
5285
  const totalKB = (totalBytes / 1024).toFixed(1);
4672
5286
  console.log(
4673
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
5287
+ `[rango] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
4674
5288
  );
4675
5289
  } catch (err) {
4676
5290
  throw new Error(
4677
- `[rsc-router] Failed to write static assets: ${err.message}`
5291
+ `[rango] Failed to write static assets: ${err.message}`
4678
5292
  );
4679
5293
  }
4680
5294
  }
4681
5295
  }
4682
5296
  }
4683
5297
 
5298
+ // src/vite/discovery/gate-state.ts
5299
+ function createDiscoveryGate(s, debug11) {
5300
+ let gatePending = false;
5301
+ let gateResolver = () => {
5302
+ };
5303
+ let inProgress = false;
5304
+ let queued = false;
5305
+ let pendingEvents = false;
5306
+ const beginGate = () => {
5307
+ if (gatePending) return;
5308
+ s.discoveryDone = new Promise((resolve10) => {
5309
+ gateResolver = resolve10;
5310
+ });
5311
+ gatePending = true;
5312
+ };
5313
+ const resolveGate = () => {
5314
+ if (!gatePending) return;
5315
+ if (inProgress || queued || pendingEvents) {
5316
+ debug11?.(
5317
+ "hmr: resolveGate deferred \u2014 work in flight (inProgress=%s queued=%s pendingEvents=%s)",
5318
+ inProgress,
5319
+ queued,
5320
+ pendingEvents
5321
+ );
5322
+ return;
5323
+ }
5324
+ gatePending = false;
5325
+ debug11?.("hmr: discoveryDone resolved");
5326
+ gateResolver();
5327
+ };
5328
+ const noteRouteEvent = () => {
5329
+ pendingEvents = true;
5330
+ beginGate();
5331
+ };
5332
+ const runRefreshCycle = async (work) => {
5333
+ if (inProgress) {
5334
+ queued = true;
5335
+ debug11?.("hmr: rediscovery in flight \u2014 queued for a follow-up cycle");
5336
+ return;
5337
+ }
5338
+ pendingEvents = false;
5339
+ inProgress = true;
5340
+ try {
5341
+ await work();
5342
+ } finally {
5343
+ inProgress = false;
5344
+ if (queued) {
5345
+ queued = false;
5346
+ debug11?.("hmr: consuming queued rediscovery");
5347
+ runRefreshCycle(work).catch((err) => {
5348
+ debug11?.(
5349
+ "hmr: queued cycle rejected \u2014 releasing gate (%s)",
5350
+ err instanceof Error ? err.message : String(err)
5351
+ );
5352
+ resolveGate();
5353
+ });
5354
+ } else if (pendingEvents) {
5355
+ debug11?.(
5356
+ "hmr: holding gate for pending events (debounce not yet fired)"
5357
+ );
5358
+ } else {
5359
+ resolveGate();
5360
+ }
5361
+ }
5362
+ };
5363
+ return {
5364
+ beginGate,
5365
+ resolveGate,
5366
+ noteRouteEvent,
5367
+ runRefreshCycle,
5368
+ state: () => ({ gatePending, inProgress, queued, pendingEvents })
5369
+ };
5370
+ }
5371
+
5372
+ // src/vite/utils/forward-user-plugins.ts
5373
+ function isDenied(name) {
5374
+ return name.startsWith("vite:") || name === "rsc" || name.startsWith("rsc:") || name.startsWith("@rangojs/router") || name.startsWith("@cloudflare/vite-plugin") || name.startsWith("vite-plugin-cloudflare");
5375
+ }
5376
+ function hasResolutionHooks(p) {
5377
+ return Boolean(p.resolveId || p.load);
5378
+ }
5379
+ function stripToResolutionHooks(p) {
5380
+ const stripped = { name: p.name };
5381
+ if (p.enforce) stripped.enforce = p.enforce;
5382
+ if (p.applyToEnvironment)
5383
+ stripped.applyToEnvironment = p.applyToEnvironment;
5384
+ if (p.resolveId) stripped.resolveId = p.resolveId;
5385
+ if (p.load) stripped.load = p.load;
5386
+ return stripped;
5387
+ }
5388
+ function selectForwardableResolvePlugins(plugins) {
5389
+ if (!plugins) return [];
5390
+ const forwarded = [];
5391
+ for (const p of plugins) {
5392
+ const name = p?.name;
5393
+ if (!name || isDenied(name)) continue;
5394
+ if (!hasResolutionHooks(p)) continue;
5395
+ forwarded.push(stripToResolutionHooks(p));
5396
+ }
5397
+ return forwarded;
5398
+ }
5399
+ function pickForwardedRunnerConfig(config) {
5400
+ const r = config.resolve ?? {};
5401
+ const resolve10 = {};
5402
+ if (r.alias !== void 0) resolve10.alias = r.alias;
5403
+ if (r.dedupe !== void 0) resolve10.dedupe = r.dedupe;
5404
+ if (r.conditions !== void 0) resolve10.conditions = r.conditions;
5405
+ if (r.mainFields !== void 0) resolve10.mainFields = r.mainFields;
5406
+ if (r.extensions !== void 0) resolve10.extensions = r.extensions;
5407
+ if (r.preserveSymlinks !== void 0)
5408
+ resolve10.preserveSymlinks = r.preserveSymlinks;
5409
+ if (r.tsconfigPaths !== void 0) resolve10.tsconfigPaths = r.tsconfigPaths;
5410
+ const userOxc = config.oxc;
5411
+ const userJsx = userOxc && typeof userOxc === "object" && typeof userOxc.jsx === "object" && userOxc.jsx !== null ? userOxc.jsx : {};
5412
+ const oxc = userOxc && typeof userOxc === "object" ? {
5413
+ ...userOxc,
5414
+ jsx: { ...userJsx, runtime: "automatic", importSource: "react" }
5415
+ } : { jsx: { runtime: "automatic", importSource: "react" } };
5416
+ return {
5417
+ resolve: resolve10,
5418
+ define: config.define,
5419
+ oxc
5420
+ };
5421
+ }
5422
+
4684
5423
  // src/vite/router-discovery.ts
5424
+ var debugDiscovery = createRangoDebugger(NS.discovery);
5425
+ var debugRoutes = createRangoDebugger(NS.routes);
5426
+ var debugBuild = createRangoDebugger(NS.build);
5427
+ var debugDev = createRangoDebugger(NS.dev);
5428
+ var loaderHookRegistered = false;
5429
+ function ensureCloudflareProtocolLoaderRegistered() {
5430
+ if (loaderHookRegistered) return;
5431
+ loaderHookRegistered = true;
5432
+ try {
5433
+ register(
5434
+ new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url)
5435
+ );
5436
+ } catch (err) {
5437
+ console.warn(
5438
+ `[rango] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5439
+ );
5440
+ }
5441
+ }
4685
5442
  async function createTempRscServer(state, options = {}) {
5443
+ ensureCloudflareProtocolLoaderRegistered();
4686
5444
  const { default: rsc } = await import("@vitejs/plugin-rsc");
5445
+ const runnerConfig = state.userRunnerConfig;
5446
+ const resolveConfig = runnerConfig?.resolve ?? {
5447
+ alias: state.userResolveAlias
5448
+ };
5449
+ const oxcConfig = runnerConfig?.oxc ?? {
5450
+ jsx: { runtime: "automatic", importSource: "react" }
5451
+ };
4687
5452
  return createViteServer({
4688
5453
  root: state.projectRoot,
4689
5454
  configFile: false,
4690
5455
  server: { middlewareMode: true },
4691
5456
  appType: "custom",
4692
5457
  logLevel: "silent",
4693
- resolve: { alias: state.userResolveAlias },
4694
- esbuild: { jsx: "automatic", jsxImportSource: "react" },
5458
+ resolve: resolveConfig,
5459
+ ...runnerConfig?.define ? { define: runnerConfig.define } : {},
5460
+ oxc: oxcConfig,
4695
5461
  ...options.cacheDir && { cacheDir: options.cacheDir },
4696
5462
  plugins: [
4697
5463
  rsc({
@@ -4705,10 +5471,15 @@ async function createTempRscServer(state, options = {}) {
4705
5471
  ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
4706
5472
  createVersionPlugin(),
4707
5473
  createVirtualStubPlugin(),
5474
+ createCloudflareProtocolStubPlugin(),
4708
5475
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
4709
5476
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
4710
5477
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
4711
- exposeRouterId()
5478
+ exposeRouterId(),
5479
+ // Forwarded user resolution plugins (e.g. vite-tsconfig-paths). Stripped
5480
+ // to resolveId/load and placed last so framework resolution runs first;
5481
+ // Vite re-sorts by `enforce`, so `enforce: "pre"` resolvers still lead.
5482
+ ...state.userResolvePlugins
4712
5483
  ]
4713
5484
  });
4714
5485
  }
@@ -4717,11 +5488,11 @@ async function resolveBuildEnv(option, factoryCtx) {
4717
5488
  if (option === "auto") {
4718
5489
  if (factoryCtx.preset !== "cloudflare") {
4719
5490
  throw new Error(
4720
- '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
5491
+ '[rango] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
4721
5492
  );
4722
5493
  }
4723
5494
  try {
4724
- const userRequire = createRequire(
5495
+ const userRequire = createRequire2(
4725
5496
  resolve8(factoryCtx.root, "package.json")
4726
5497
  );
4727
5498
  const wranglerPath = userRequire.resolve("wrangler");
@@ -4733,7 +5504,7 @@ async function resolveBuildEnv(option, factoryCtx) {
4733
5504
  };
4734
5505
  } catch (err) {
4735
5506
  throw new Error(
4736
- `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
5507
+ `[rango] buildEnv: "auto" requires wrangler to be installed.
4737
5508
  Install it with: pnpm add -D wrangler
4738
5509
  ${err.message}`
4739
5510
  );
@@ -4756,6 +5527,7 @@ async function acquireBuildEnv(s, command, mode) {
4756
5527
  if (!result) return false;
4757
5528
  s.resolvedBuildEnv = result.env;
4758
5529
  s.buildEnvDispose = result.dispose ?? null;
5530
+ globalThis[BUILD_ENV_GLOBAL_KEY] = result.env;
4759
5531
  return true;
4760
5532
  }
4761
5533
  async function releaseBuildEnv(s) {
@@ -4763,11 +5535,12 @@ async function releaseBuildEnv(s) {
4763
5535
  try {
4764
5536
  await s.buildEnvDispose();
4765
5537
  } catch (err) {
4766
- console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
5538
+ console.warn(`[rango] buildEnv dispose failed: ${err.message}`);
4767
5539
  }
4768
5540
  s.buildEnvDispose = null;
4769
5541
  }
4770
5542
  s.resolvedBuildEnv = void 0;
5543
+ delete globalThis[BUILD_ENV_GLOBAL_KEY];
4771
5544
  }
4772
5545
  function createRouterDiscoveryPlugin(entryPath, opts) {
4773
5546
  const s = createDiscoveryState(entryPath, opts);
@@ -4789,17 +5562,16 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4789
5562
  viteCommand = config.command;
4790
5563
  viteMode = config.mode;
4791
5564
  s.userResolveAlias = config.resolve.alias;
5565
+ s.userRunnerConfig = pickForwardedRunnerConfig(config);
5566
+ s.userResolvePlugins = selectForwardableResolvePlugins(
5567
+ config.plugins
5568
+ );
4792
5569
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
4793
5570
  s.resolvedEntryPath = opts.routerPathRef.path;
4794
5571
  }
4795
5572
  if (!s.resolvedEntryPath) {
4796
- const rscEnvConfig = config.environments?.["rsc"];
4797
- const entries = rscEnvConfig?.optimizeDeps?.entries;
4798
- if (typeof entries === "string") {
4799
- s.resolvedEntryPath = entries;
4800
- } else if (Array.isArray(entries) && entries.length > 0) {
4801
- s.resolvedEntryPath = entries[0];
4802
- }
5573
+ const entry = resolveRscEntryFromConfig(config);
5574
+ if (entry) s.resolvedEntryPath = entry;
4803
5575
  }
4804
5576
  if (opts?.staticRouteTypesGeneration !== false) {
4805
5577
  s.cachedRouterFiles = findRouterFiles(s.projectRoot, s.scanFilter);
@@ -4823,6 +5595,9 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4823
5595
  const discoveryPromise = new Promise((resolve10) => {
4824
5596
  resolveDiscovery = resolve10;
4825
5597
  });
5598
+ const gate = createDiscoveryGate(s, debugDiscovery);
5599
+ const beginDiscoveryGate = gate.beginGate;
5600
+ const resolveDiscoveryGate = gate.resolveGate;
4826
5601
  const getDevServerOrigin = () => server.resolvedUrls?.local?.[0]?.replace(/\/$/, "") || `http://localhost:${server.config.server.port || 5173}`;
4827
5602
  let prerenderTempServer = null;
4828
5603
  let prerenderNodeRegistry = null;
@@ -4835,74 +5610,241 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4835
5610
  releaseBuildEnv(s).catch(() => {
4836
5611
  });
4837
5612
  });
5613
+ async function importEntryAndRegistry(tempRscEnv) {
5614
+ const flagAlreadySet = !!globalThis.__rscRouterDiscoveryActive;
5615
+ if (!flagAlreadySet) {
5616
+ globalThis.__rscRouterDiscoveryActive = true;
5617
+ }
5618
+ try {
5619
+ debugDiscovery?.(
5620
+ "importEntryAndRegistry: importing entry (flag=%s)",
5621
+ globalThis.__rscRouterDiscoveryActive ?? false
5622
+ );
5623
+ await tempRscEnv.runner.import(s.resolvedEntryPath);
5624
+ debugDiscovery?.(
5625
+ "importEntryAndRegistry: entry import OK, fetching RouterRegistry"
5626
+ );
5627
+ const serverMod = await tempRscEnv.runner.import(
5628
+ "@rangojs/router/server"
5629
+ );
5630
+ prerenderNodeRegistry = serverMod.RouterRegistry;
5631
+ debugDiscovery?.(
5632
+ "importEntryAndRegistry: registry size=%d",
5633
+ prerenderNodeRegistry?.size ?? 0
5634
+ );
5635
+ } finally {
5636
+ if (!flagAlreadySet) {
5637
+ delete globalThis.__rscRouterDiscoveryActive;
5638
+ debugDiscovery?.(
5639
+ "importEntryAndRegistry: cleared __rscRouterDiscoveryActive"
5640
+ );
5641
+ }
5642
+ }
5643
+ }
4838
5644
  async function getOrCreateTempServer() {
4839
- if (prerenderNodeRegistry) {
4840
- return prerenderTempServer.environments?.rsc ?? null;
5645
+ if (prerenderTempServer) {
5646
+ const existingEnv = prerenderTempServer.environments?.rsc;
5647
+ if (existingEnv?.runner) {
5648
+ if (prerenderNodeRegistry) {
5649
+ debugDiscovery?.(
5650
+ "getOrCreateTempServer: cached temp runner reused"
5651
+ );
5652
+ return existingEnv;
5653
+ }
5654
+ debugDiscovery?.(
5655
+ "getOrCreateTempServer: server alive but registry missing \u2014 re-importing"
5656
+ );
5657
+ try {
5658
+ await importEntryAndRegistry(existingEnv);
5659
+ return existingEnv;
5660
+ } catch (err) {
5661
+ debugDiscovery?.(
5662
+ "getOrCreateTempServer: reuse import failed (%s) \u2014 closing orphan and creating fresh",
5663
+ err?.message ?? String(err)
5664
+ );
5665
+ await prerenderTempServer.close().catch(() => {
5666
+ });
5667
+ prerenderTempServer = null;
5668
+ prerenderNodeRegistry = null;
5669
+ }
5670
+ } else {
5671
+ debugDiscovery?.(
5672
+ "getOrCreateTempServer: existing server has no rsc.runner \u2014 closing and recreating"
5673
+ );
5674
+ await prerenderTempServer.close().catch(() => {
5675
+ });
5676
+ prerenderTempServer = null;
5677
+ prerenderNodeRegistry = null;
5678
+ }
4841
5679
  }
5680
+ debugDiscovery?.(
5681
+ "getOrCreateTempServer: creating new temp server, entry=%s",
5682
+ s.resolvedEntryPath ?? "(unset)"
5683
+ );
4842
5684
  try {
4843
5685
  prerenderTempServer = await createTempRscServer(s, {
4844
5686
  cacheDir: "node_modules/.vite_prerender"
4845
5687
  });
4846
5688
  const tempRscEnv = prerenderTempServer.environments?.rsc;
4847
5689
  if (tempRscEnv?.runner) {
4848
- await tempRscEnv.runner.import(s.resolvedEntryPath);
4849
- const serverMod = await tempRscEnv.runner.import(
4850
- "@rangojs/router/server"
4851
- );
4852
- prerenderNodeRegistry = serverMod.RouterRegistry;
5690
+ await importEntryAndRegistry(tempRscEnv);
4853
5691
  return tempRscEnv;
4854
5692
  }
5693
+ debugDiscovery?.(
5694
+ "getOrCreateTempServer: tempRscEnv.runner unavailable"
5695
+ );
4855
5696
  } catch (err) {
4856
- console.warn(
4857
- `[rsc-router] Failed to create temp runner: ${err.message}`
5697
+ debugDiscovery?.(
5698
+ "getOrCreateTempServer: FAILED message=%s",
5699
+ err.message
4858
5700
  );
5701
+ console.warn(`[rango] Failed to create temp runner: ${err.message}`);
4859
5702
  }
4860
5703
  return null;
4861
5704
  }
5705
+ async function clearTempRegistries(tempRscEnv) {
5706
+ try {
5707
+ const serverMod = await tempRscEnv.runner.import(
5708
+ "@rangojs/router/server"
5709
+ );
5710
+ if (typeof serverMod?.RouterRegistry?.clear === "function") {
5711
+ serverMod.RouterRegistry.clear();
5712
+ }
5713
+ if (typeof serverMod?.HostRouterRegistry?.clear === "function") {
5714
+ serverMod.HostRouterRegistry.clear();
5715
+ }
5716
+ debugDiscovery?.(
5717
+ "clearTempRegistries: cleared RouterRegistry + HostRouterRegistry"
5718
+ );
5719
+ } catch (err) {
5720
+ debugDiscovery?.(
5721
+ "clearTempRegistries: import @rangojs/router/server failed (%s)",
5722
+ err?.message ?? String(err)
5723
+ );
5724
+ }
5725
+ }
5726
+ async function refreshTempRscEnv() {
5727
+ let tempRscEnv = await getOrCreateTempServer();
5728
+ if (!tempRscEnv) return null;
5729
+ const envGraph = tempRscEnv.moduleGraph;
5730
+ const serverGraph = prerenderTempServer?.moduleGraph;
5731
+ const target = envGraph?.invalidateAll ? envGraph : serverGraph?.invalidateAll ? serverGraph : null;
5732
+ if (!target) {
5733
+ debugDiscovery?.(
5734
+ "refreshTempRscEnv: invalidateAll unavailable on env+server graphs, falling back to close+recreate"
5735
+ );
5736
+ if (prerenderTempServer) {
5737
+ await prerenderTempServer.close().catch(() => {
5738
+ });
5739
+ prerenderTempServer = null;
5740
+ prerenderNodeRegistry = null;
5741
+ }
5742
+ return await getOrCreateTempServer();
5743
+ }
5744
+ debugDiscovery?.(
5745
+ "refreshTempRscEnv: invalidating module graph (%s)",
5746
+ envGraph?.invalidateAll ? "env" : "server"
5747
+ );
5748
+ target.invalidateAll();
5749
+ prerenderNodeRegistry = null;
5750
+ await clearTempRegistries(tempRscEnv);
5751
+ await importEntryAndRegistry(tempRscEnv);
5752
+ return tempRscEnv;
5753
+ }
4862
5754
  const discover = async () => {
5755
+ const discoverStart = performance.now();
4863
5756
  const rscEnv = server.environments?.rsc;
4864
5757
  if (!rscEnv?.runner) {
5758
+ debugDiscovery?.(
5759
+ "dev: cloudflare path start, __rscRouterDiscoveryActive=%s",
5760
+ globalThis.__rscRouterDiscoveryActive ?? false
5761
+ );
4865
5762
  s.devServerOrigin = getDevServerOrigin();
4866
5763
  try {
4867
- await acquireBuildEnv(s, viteCommand, viteMode);
4868
- const tempRscEnv = await getOrCreateTempServer();
5764
+ await timed(
5765
+ debugDiscovery,
5766
+ "acquireBuildEnv",
5767
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5768
+ );
5769
+ const tempRscEnv = await timed(
5770
+ debugDiscovery,
5771
+ "getOrCreateTempServer",
5772
+ () => getOrCreateTempServer()
5773
+ );
4869
5774
  if (tempRscEnv) {
4870
- await discoverRouters(s, tempRscEnv);
4871
- writeRouteTypesFiles(s);
5775
+ await timed(
5776
+ debugDiscovery,
5777
+ "discoverRouters (cloudflare)",
5778
+ () => discoverRouters(s, tempRscEnv)
5779
+ );
5780
+ timedSync(
5781
+ debugDiscovery,
5782
+ "writeRouteTypesFiles",
5783
+ () => writeRouteTypesFiles(s)
5784
+ );
4872
5785
  }
4873
5786
  } catch (err) {
4874
5787
  console.warn(
4875
- `[rsc-router] Cloudflare dev discovery failed: ${err.message}
5788
+ `[rango] Cloudflare dev discovery failed: ${err.message}
4876
5789
  ${err.stack}`
4877
5790
  );
4878
5791
  }
5792
+ debugDiscovery?.(
5793
+ "dev discovery done (%sms)",
5794
+ (performance.now() - discoverStart).toFixed(1)
5795
+ );
4879
5796
  resolveDiscovery();
4880
5797
  return;
4881
5798
  }
4882
5799
  try {
4883
- await acquireBuildEnv(s, viteCommand, viteMode);
4884
- const serverMod = await rscEnv.runner.import(
4885
- "@rangojs/router/server"
5800
+ debugDiscovery?.("dev: node path start");
5801
+ await timed(
5802
+ debugDiscovery,
5803
+ "acquireBuildEnv",
5804
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5805
+ );
5806
+ const serverMod = await timed(
5807
+ debugDiscovery,
5808
+ "import @rangojs/router/server",
5809
+ () => rscEnv.runner.import("@rangojs/router/server")
4886
5810
  );
4887
5811
  if (serverMod?.setManifestReadyPromise) {
4888
5812
  serverMod.setManifestReadyPromise(discoveryPromise);
4889
5813
  }
4890
- await discoverRouters(s, rscEnv);
5814
+ await timed(
5815
+ debugDiscovery,
5816
+ "discoverRouters",
5817
+ () => discoverRouters(s, rscEnv)
5818
+ );
4891
5819
  s.devServerOrigin = getDevServerOrigin();
4892
- writeRouteTypesFiles(s);
4893
- await propagateDiscoveryState(rscEnv);
5820
+ timedSync(
5821
+ debugDiscovery,
5822
+ "writeRouteTypesFiles",
5823
+ () => writeRouteTypesFiles(s)
5824
+ );
5825
+ await timed(
5826
+ debugDiscovery,
5827
+ "propagateDiscoveryState",
5828
+ () => propagateDiscoveryState(rscEnv)
5829
+ );
4894
5830
  } catch (err) {
4895
5831
  console.warn(
4896
- `[rsc-router] Router discovery failed: ${err.message}
5832
+ `[rango] Router discovery failed: ${err.message}
4897
5833
  ${err.stack}`
4898
5834
  );
4899
5835
  } finally {
5836
+ debugDiscovery?.(
5837
+ "dev discovery done (%sms)",
5838
+ (performance.now() - discoverStart).toFixed(1)
5839
+ );
4900
5840
  resolveDiscovery();
4901
5841
  }
4902
5842
  };
4903
- s.discoveryDone = new Promise((resolve10) => {
4904
- setTimeout(() => discover().then(resolve10, resolve10), 0);
4905
- });
5843
+ beginDiscoveryGate();
5844
+ setTimeout(
5845
+ () => discover().then(resolveDiscoveryGate, resolveDiscoveryGate),
5846
+ 0
5847
+ );
4906
5848
  let mainRegistry = null;
4907
5849
  const propagateDiscoveryState = async (rscEnv) => {
4908
5850
  const serverMod = await rscEnv.runner.import("@rangojs/router/server");
@@ -4920,29 +5862,35 @@ ${err.stack}`
4920
5862
  if (s.mergedRouteTrie && serverMod.setRouteTrie) {
4921
5863
  serverMod.setRouteTrie(s.mergedRouteTrie);
4922
5864
  }
4923
- if (serverMod.setRouterManifest) {
4924
- for (const [routerId, manifest] of s.perRouterManifestDataMap) {
4925
- serverMod.setRouterManifest(routerId, manifest);
4926
- }
4927
- }
4928
- if (serverMod.setRouterTrie) {
4929
- for (const [routerId, trie] of s.perRouterTrieMap) {
4930
- serverMod.setRouterTrie(routerId, trie);
4931
- }
4932
- }
4933
- if (serverMod.setRouterPrecomputedEntries) {
4934
- for (const [routerId, entries] of s.perRouterPrecomputedMap) {
4935
- serverMod.setRouterPrecomputedEntries(routerId, entries);
4936
- }
5865
+ const perRouterSetters = [
5866
+ [s.perRouterManifestDataMap, "setRouterManifest"],
5867
+ [s.perRouterTrieMap, "setRouterTrie"],
5868
+ [s.perRouterPrecomputedMap, "setRouterPrecomputedEntries"]
5869
+ ];
5870
+ for (const [map, fn] of perRouterSetters) {
5871
+ const setter = serverMod[fn];
5872
+ if (typeof setter !== "function") continue;
5873
+ for (const [routerId, value] of map) setter(routerId, value);
4937
5874
  }
4938
5875
  };
4939
5876
  server.middlewares.use("/__rsc_prerender", async (req, res) => {
5877
+ const reqStart = debugDev ? performance.now() : 0;
5878
+ const logResult = (status, note) => {
5879
+ debugDev?.(
5880
+ "/__rsc_prerender %s -> %d %s (%sms)",
5881
+ req.url,
5882
+ status,
5883
+ note,
5884
+ (performance.now() - reqStart).toFixed(1)
5885
+ );
5886
+ };
4940
5887
  if (s.discoveryDone) await s.discoveryDone;
4941
5888
  const url = new URL(req.url || "/", "http://localhost");
4942
5889
  const pathname = url.searchParams.get("pathname");
4943
5890
  if (!pathname) {
4944
5891
  res.statusCode = 400;
4945
5892
  res.end("Missing pathname");
5893
+ logResult(400, "missing pathname");
4946
5894
  return;
4947
5895
  }
4948
5896
  const rscEnv = server.environments?.rsc;
@@ -4956,10 +5904,11 @@ ${err.stack}`
4956
5904
  registry = serverMod.RouterRegistry ?? null;
4957
5905
  } catch (err) {
4958
5906
  console.warn(
4959
- `[rsc-router] Dev prerender module refresh failed: ${err.message}`
5907
+ `[rango] Dev prerender module refresh failed: ${err.message}`
4960
5908
  );
4961
5909
  res.statusCode = 500;
4962
5910
  res.end(`Prerender handler error: ${err.message}`);
5911
+ logResult(500, "module refresh failed");
4963
5912
  return;
4964
5913
  }
4965
5914
  } else {
@@ -4974,6 +5923,7 @@ ${err.stack}`
4974
5923
  if (!registry || registry.size === 0) {
4975
5924
  res.statusCode = 503;
4976
5925
  res.end("Prerender runner not available");
5926
+ logResult(503, "no registry");
4977
5927
  return;
4978
5928
  }
4979
5929
  const wantIntercept = url.searchParams.get("intercept") === "1";
@@ -4999,24 +5949,25 @@ ${err.stack}`
4999
5949
  if (wantIntercept && result.interceptSegments?.length) {
5000
5950
  payload = {
5001
5951
  segments: [...result.segments, ...result.interceptSegments],
5002
- handles: {
5003
- ...result.handles,
5004
- ...result.interceptHandles || {}
5005
- }
5952
+ // Pre-encoded MERGED handle string from the producer (handles are
5953
+ // Flight-encoded so Promise/ReactNode values survive the wire).
5954
+ handles: result.interceptHandles ?? ""
5006
5955
  };
5007
5956
  } else {
5008
5957
  payload = { segments: result.segments, handles: result.handles };
5009
5958
  }
5010
5959
  res.end(JSON.stringify(payload));
5960
+ logResult(200, `match ${result.routeName}`);
5011
5961
  return;
5012
5962
  } catch (err) {
5013
5963
  console.warn(
5014
- `[rsc-router] Dev prerender failed for ${pathname}: ${err.message}`
5964
+ `[rango] Dev prerender failed for ${pathname}: ${err.message}`
5015
5965
  );
5016
5966
  }
5017
5967
  }
5018
5968
  res.statusCode = 404;
5019
5969
  res.end("No prerender match");
5970
+ logResult(404, "no match");
5020
5971
  });
5021
5972
  if (opts?.staticRouteTypesGeneration !== false) {
5022
5973
  const isGeneratedRouteFile = (filePath) => filePath.endsWith(".gen.ts") && (filePath.includes("named-routes.gen.ts") || filePath.includes("urls.gen.ts"));
@@ -5036,59 +5987,185 @@ ${err.stack}`
5036
5987
  return true;
5037
5988
  };
5038
5989
  let routeChangeTimer;
5039
- let runtimeRediscoveryInProgress = false;
5040
5990
  const refreshRuntimeDiscovery = async () => {
5041
5991
  const rscEnv = server.environments?.rsc;
5042
- if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
5043
- runtimeRediscoveryInProgress = true;
5992
+ const hasMainRunner = !!rscEnv?.runner;
5993
+ if (!hasMainRunner && s.perRouterManifests.length === 0) return;
5994
+ await gate.runRefreshCycle(async () => {
5995
+ const hmrStart = performance.now();
5996
+ try {
5997
+ if (hasMainRunner) {
5998
+ await timed(
5999
+ debugDiscovery,
6000
+ "hmr discoverRouters",
6001
+ () => discoverRouters(s, rscEnv)
6002
+ );
6003
+ timedSync(
6004
+ debugDiscovery,
6005
+ "hmr writeRouteTypesFiles",
6006
+ () => writeRouteTypesFiles(s)
6007
+ );
6008
+ await timed(
6009
+ debugDiscovery,
6010
+ "hmr propagateDiscoveryState",
6011
+ () => propagateDiscoveryState(rscEnv)
6012
+ );
6013
+ } else {
6014
+ const tempRscEnv = await timed(
6015
+ debugDiscovery,
6016
+ "hmr refreshTempRscEnv (cloudflare)",
6017
+ () => refreshTempRscEnv()
6018
+ );
6019
+ if (!tempRscEnv) {
6020
+ throw new Error(
6021
+ "temp runner unavailable for cloudflare HMR rediscovery"
6022
+ );
6023
+ }
6024
+ await timed(
6025
+ debugDiscovery,
6026
+ "hmr discoverRouters (cloudflare)",
6027
+ () => discoverRouters(s, tempRscEnv)
6028
+ );
6029
+ timedSync(
6030
+ debugDiscovery,
6031
+ "hmr writeRouteTypesFiles",
6032
+ () => writeRouteTypesFiles(s)
6033
+ );
6034
+ }
6035
+ if (s.lastDiscoveryError) {
6036
+ debugDiscovery?.(
6037
+ "hmr: cleared lastDiscoveryError (%s) after successful rediscovery",
6038
+ s.lastDiscoveryError.message
6039
+ );
6040
+ s.lastDiscoveryError = null;
6041
+ }
6042
+ if (rscEnv && !rscEnv.runner) forceCloudflareWorkerReload(rscEnv);
6043
+ } catch (err) {
6044
+ s.lastDiscoveryError = {
6045
+ message: err?.message ?? String(err),
6046
+ at: Date.now()
6047
+ };
6048
+ console.warn(
6049
+ `[rango] Runtime re-discovery failed: ${err.message}`
6050
+ );
6051
+ debugDiscovery?.(
6052
+ "hmr: lastDiscoveryError set (%s) \u2014 manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
6053
+ err?.message
6054
+ );
6055
+ } finally {
6056
+ debugDiscovery?.(
6057
+ "hmr re-discovery done (%sms)",
6058
+ (performance.now() - hmrStart).toFixed(1)
6059
+ );
6060
+ }
6061
+ });
6062
+ };
6063
+ const forceCloudflareWorkerReload = (rscEnv) => {
6064
+ if (!rscEnv?.hot) return;
5044
6065
  try {
5045
- await discoverRouters(s, rscEnv);
5046
- writeRouteTypesFiles(s);
5047
- await propagateDiscoveryState(rscEnv);
6066
+ const graph = rscEnv.moduleGraph;
6067
+ if (graph?.invalidateAll) {
6068
+ graph.invalidateAll();
6069
+ debugDiscovery?.("hmr: invalidated workerd rsc module graph");
6070
+ }
6071
+ rscEnv.hot.send({ type: "full-reload" });
6072
+ debugDiscovery?.(
6073
+ "hmr: forced workerd rsc env reload (full-reload)"
6074
+ );
5048
6075
  } catch (err) {
5049
- console.warn(
5050
- `[rsc-router] Runtime re-discovery failed: ${err.message}`
6076
+ debugDiscovery?.(
6077
+ "hmr: workerd reload failed: %s",
6078
+ err?.message ?? err
5051
6079
  );
5052
- } finally {
5053
- runtimeRediscoveryInProgress = false;
5054
6080
  }
5055
6081
  };
5056
6082
  const scheduleRouteRegeneration = () => {
5057
6083
  clearTimeout(routeChangeTimer);
5058
6084
  routeChangeTimer = setTimeout(() => {
5059
6085
  routeChangeTimer = void 0;
6086
+ const regenStart = debugDiscovery ? performance.now() : 0;
6087
+ const rscEnv = server.environments?.rsc;
6088
+ const skipStaticWrite = !rscEnv?.runner && s.perRouterManifests.length > 0;
5060
6089
  try {
5061
- writeCombinedRouteTypesWithTracking(s);
5062
- if (s.perRouterManifests.length > 0) {
5063
- supplementGenFilesWithRuntimeRoutes(s);
6090
+ if (skipStaticWrite) {
6091
+ debugDiscovery?.(
6092
+ "watcher: skipping static write (cloudflare HMR \u2014 runtime rediscovery owns gen file)"
6093
+ );
6094
+ } else {
6095
+ writeCombinedRouteTypesWithTracking(s);
6096
+ if (s.perRouterManifests.length > 0) {
6097
+ supplementGenFilesWithRuntimeRoutes(s);
6098
+ }
5064
6099
  }
5065
6100
  } catch (err) {
5066
- console.error(
5067
- `[rsc-router] Route regeneration error: ${err.message}`
5068
- );
6101
+ console.error(`[rango] Route regeneration error: ${err.message}`);
5069
6102
  }
6103
+ debugDiscovery?.(
6104
+ "watcher: regenerated gen files (%sms)",
6105
+ (performance.now() - regenStart).toFixed(1)
6106
+ );
5070
6107
  if (s.perRouterManifests.length > 0) {
5071
6108
  refreshRuntimeDiscovery().catch((err) => {
5072
6109
  console.warn(
5073
- `[rsc-router] Runtime re-discovery error: ${err.message}`
6110
+ `[rango] Runtime re-discovery error: ${err.message}`
5074
6111
  );
6112
+ resolveDiscoveryGate();
5075
6113
  });
5076
6114
  }
5077
6115
  }, 100);
5078
6116
  };
5079
6117
  const handleRouteFileChange = (filePath) => {
5080
6118
  if (maybeHandleGeneratedRouteFileMutation(filePath)) return;
5081
- if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx"))
6119
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx")) {
6120
+ if (s.lastDiscoveryError) {
6121
+ debugDiscovery?.(
6122
+ "watcher: skip non-source %s [LASTERR %s]",
6123
+ filePath,
6124
+ s.lastDiscoveryError.message
6125
+ );
6126
+ }
6127
+ return;
6128
+ }
6129
+ if (s.scanFilter && !s.scanFilter(filePath)) {
6130
+ if (s.lastDiscoveryError) {
6131
+ debugDiscovery?.(
6132
+ "watcher: skip scan-filter %s [LASTERR %s]",
6133
+ filePath,
6134
+ s.lastDiscoveryError.message
6135
+ );
6136
+ }
5082
6137
  return;
5083
- if (s.scanFilter && !s.scanFilter(filePath)) return;
6138
+ }
6139
+ const inRecoveryMode = !!s.lastDiscoveryError;
5084
6140
  try {
5085
6141
  const source = readFileSync6(filePath, "utf-8");
5086
6142
  const trimmed = source.trimStart();
5087
- if (trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'"))
5088
- return;
5089
- const hasUrls = source.includes("urls(");
5090
- const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
5091
- if (!hasUrls && !hasCreateRouter) return;
6143
+ const isUseClient = trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'");
6144
+ if (!inRecoveryMode && isUseClient) return;
6145
+ let hasUrls = source.includes("urls(");
6146
+ let hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
6147
+ if (hasUrls) hasUrls = firstCodeMatchIndex(source, /urls\(/g) >= 0;
6148
+ if (hasCreateRouter) {
6149
+ hasCreateRouter = firstCodeMatchIndex(source, /\bcreateRouter\s*[<(]/g) >= 0;
6150
+ }
6151
+ if (!inRecoveryMode && !hasUrls && !hasCreateRouter) return;
6152
+ if (inRecoveryMode) {
6153
+ debugDiscovery?.(
6154
+ "watcher: recovery rediscovery for %s (urls=%s, router=%s, useClient=%s) [LASTERR %s]",
6155
+ filePath,
6156
+ hasUrls,
6157
+ hasCreateRouter,
6158
+ isUseClient,
6159
+ s.lastDiscoveryError.message
6160
+ );
6161
+ } else {
6162
+ debugDiscovery?.(
6163
+ "watcher: %s matches (urls=%s, router=%s)",
6164
+ filePath,
6165
+ hasUrls,
6166
+ hasCreateRouter
6167
+ );
6168
+ }
5092
6169
  if (hasCreateRouter) {
5093
6170
  const nestedRouterConflict = findNestedRouterConflict([
5094
6171
  ...s.cachedRouterFiles ?? [],
@@ -5102,8 +6179,19 @@ ${err.stack}`
5102
6179
  }
5103
6180
  s.cachedRouterFiles = void 0;
5104
6181
  }
6182
+ if (s.perRouterManifests.length > 0) {
6183
+ gate.noteRouteEvent();
6184
+ }
5105
6185
  scheduleRouteRegeneration();
5106
- } catch {
6186
+ } catch (readErr) {
6187
+ if (s.lastDiscoveryError) {
6188
+ debugDiscovery?.(
6189
+ "watcher: read error %s: %s [LASTERR %s]",
6190
+ filePath,
6191
+ readErr?.message,
6192
+ s.lastDiscoveryError.message
6193
+ );
6194
+ }
5107
6195
  }
5108
6196
  };
5109
6197
  server.watcher.on("add", handleRouteFileChange);
@@ -5121,19 +6209,35 @@ ${err.stack}`
5121
6209
  // The manifest data is stored for the virtual module's load hook.
5122
6210
  async buildStart() {
5123
6211
  if (!s.isBuildMode) return;
5124
- if (s.mergedRouteManifest !== null) return;
6212
+ if (s.mergedRouteManifest !== null) {
6213
+ debugDiscovery?.(
6214
+ "build: skip (already discovered, env=%s)",
6215
+ this.environment?.name ?? "?"
6216
+ );
6217
+ return;
6218
+ }
6219
+ const buildStartTime = performance.now();
6220
+ debugDiscovery?.("build: start (env=%s)", this.environment?.name ?? "?");
5125
6221
  resetStagedBuildAssets(s.projectRoot);
5126
6222
  s.prerenderManifestEntries = null;
5127
6223
  s.staticManifestEntries = null;
5128
- await acquireBuildEnv(s, viteCommand, viteMode);
6224
+ await timed(
6225
+ debugDiscovery,
6226
+ "build acquireBuildEnv",
6227
+ () => acquireBuildEnv(s, viteCommand, viteMode)
6228
+ );
5129
6229
  let tempServer = null;
5130
6230
  globalThis.__rscRouterDiscoveryActive = true;
5131
6231
  try {
5132
- tempServer = await createTempRscServer(s, { forceBuild: true });
6232
+ tempServer = await timed(
6233
+ debugDiscovery,
6234
+ "build createTempRscServer",
6235
+ () => createTempRscServer(s, { forceBuild: true })
6236
+ );
5133
6237
  const rscEnv = tempServer.environments?.rsc;
5134
6238
  if (!rscEnv?.runner) {
5135
6239
  console.warn(
5136
- "[rsc-router] RSC environment runner not available during build, skipping manifest generation"
6240
+ "[rango] RSC environment runner not available during build, skipping manifest generation"
5137
6241
  );
5138
6242
  return;
5139
6243
  }
@@ -5143,8 +6247,16 @@ ${err.stack}`
5143
6247
  if (tempIdsPlugin?.api?.staticHandlerModules) {
5144
6248
  s.resolvedStaticModules = tempIdsPlugin.api.staticHandlerModules;
5145
6249
  }
5146
- await discoverRouters(s, rscEnv);
5147
- writeRouteTypesFiles(s);
6250
+ await timed(
6251
+ debugDiscovery,
6252
+ "build discoverRouters",
6253
+ () => discoverRouters(s, rscEnv)
6254
+ );
6255
+ timedSync(
6256
+ debugDiscovery,
6257
+ "build writeRouteTypesFiles",
6258
+ () => writeRouteTypesFiles(s)
6259
+ );
5148
6260
  } catch (err) {
5149
6261
  const sourceFile = err.stack?.split("\n").find(
5150
6262
  (line) => line.includes(s.projectRoot) && !line.includes("node_modules")
@@ -5157,15 +6269,52 @@ ${err.stack}`
5157
6269
  ${err.stack}` : null
5158
6270
  ].filter(Boolean).join("\n");
5159
6271
  throw new Error(
5160
- `[rsc-router] Build-time router discovery failed:
5161
- ${details}`
6272
+ `[rango] Build-time router discovery failed:
6273
+ ${details}`,
6274
+ { cause: err }
5162
6275
  );
5163
6276
  } finally {
5164
6277
  delete globalThis.__rscRouterDiscoveryActive;
5165
6278
  if (tempServer) {
5166
- await tempServer.close();
6279
+ await timed(
6280
+ debugDiscovery,
6281
+ "build tempServer.close",
6282
+ () => tempServer.close()
6283
+ );
5167
6284
  }
5168
6285
  await releaseBuildEnv(s);
6286
+ debugDiscovery?.(
6287
+ "build discovery done (%sms)",
6288
+ (performance.now() - buildStartTime).toFixed(1)
6289
+ );
6290
+ }
6291
+ },
6292
+ // Suppress vite's HMR cascade for our own gen-file writes.
6293
+ //
6294
+ // After every cf HMR cycle, refreshTempRscEnv → writeRouteTypesFiles
6295
+ // writes the configured gen files (default `router.named-routes.gen.ts`,
6296
+ // but the source filenames and gen suffix are user-configurable). The
6297
+ // chokidar watcher then fires twice independently: our
6298
+ // `handleRouteFileChange` (already short-circuited by
6299
+ // `consumeSelfGenWrite` inside `maybeHandleGeneratedRouteFileMutation`),
6300
+ // AND vite's own HMR pipeline (which invalidates the gen file's
6301
+ // importers and triggers a second workerd full reload — visible to the
6302
+ // user as a duplicate "[Rango] HMR: version changed" on the client).
6303
+ //
6304
+ // `peekSelfGenWrite` is the authoritative filter: its map only contains
6305
+ // paths that `markSelfGenWrite` has registered, so it natively works
6306
+ // for any configured gen-file name. It is non-consuming so the chokidar
6307
+ // handler that fires later can still consume the same entry. Returning
6308
+ // [] tells vite "no modules invalidated by this change" — safe because
6309
+ // `s.perRouterManifests` is already up-to-date (the write that just
6310
+ // happened is the consequence of our just-completed rediscovery).
6311
+ handleHotUpdate(ctx) {
6312
+ if (peekSelfGenWrite(s, ctx.file)) {
6313
+ debugDiscovery?.(
6314
+ "handleHotUpdate: suppressing self-write HMR cascade for %s",
6315
+ ctx.file
6316
+ );
6317
+ return [];
5169
6318
  }
5170
6319
  },
5171
6320
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -5182,17 +6331,36 @@ ${details}`
5182
6331
  async load(id) {
5183
6332
  if (id === "\0" + VIRTUAL_ROUTES_MANIFEST_ID) {
5184
6333
  if (s.discoveryDone) {
5185
- await s.discoveryDone;
6334
+ await timed(
6335
+ debugRoutes,
6336
+ "await discoveryDone (manifest)",
6337
+ () => s.discoveryDone
6338
+ );
5186
6339
  }
5187
- return generateRoutesManifestModule(s);
6340
+ const code = await timed(
6341
+ debugRoutes,
6342
+ "generateRoutesManifestModule",
6343
+ () => generateRoutesManifestModule(s)
6344
+ );
6345
+ debugRoutes?.("manifest module emitted (%d bytes)", code?.length ?? 0);
6346
+ return code;
5188
6347
  }
5189
6348
  const perRouterPrefix = "\0" + VIRTUAL_ROUTES_MANIFEST_ID + "/";
5190
6349
  if (id.startsWith(perRouterPrefix)) {
5191
6350
  if (s.discoveryDone) {
5192
- await s.discoveryDone;
6351
+ await timed(
6352
+ debugRoutes,
6353
+ "await discoveryDone (per-router)",
6354
+ () => s.discoveryDone
6355
+ );
5193
6356
  }
5194
6357
  const routerId = id.slice(perRouterPrefix.length);
5195
- return generatePerRouterModule(s, routerId);
6358
+ const code = await timed(
6359
+ debugRoutes,
6360
+ `generatePerRouterModule ${routerId}`,
6361
+ () => generatePerRouterModule(s, routerId)
6362
+ );
6363
+ return code;
5196
6364
  }
5197
6365
  return null;
5198
6366
  },
@@ -5200,14 +6368,20 @@ ${details}`
5200
6368
  // Used by closeBundle for handler code eviction and prerender data injection.
5201
6369
  generateBundle(_options, bundle) {
5202
6370
  if (this.environment?.name !== "rsc") return;
6371
+ const genStart = debugBuild ? performance.now() : 0;
5203
6372
  for (const [fileName, chunk] of Object.entries(bundle)) {
5204
6373
  if (chunk.type === "chunk" && chunk.isEntry) {
5205
6374
  s.rscEntryFileName = fileName;
5206
6375
  break;
5207
6376
  }
5208
6377
  }
5209
- if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
6378
+ if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size) {
6379
+ debugBuild?.(
6380
+ "generateBundle (rsc): no handlers to scan (%sms)",
6381
+ (performance.now() - genStart).toFixed(1)
6382
+ );
5210
6383
  return;
6384
+ }
5211
6385
  s.handlerChunkInfoMap.clear();
5212
6386
  s.staticHandlerChunkInfoMap.clear();
5213
6387
  for (const [fileName, chunk] of Object.entries(bundle)) {
@@ -5251,6 +6425,13 @@ ${details}`
5251
6425
  }
5252
6426
  }
5253
6427
  }
6428
+ debugBuild?.(
6429
+ "generateBundle (rsc): scanned %d chunks, %d prerender chunk(s), %d static chunk(s) (%sms)",
6430
+ Object.keys(bundle).length,
6431
+ s.handlerChunkInfoMap.size,
6432
+ s.staticHandlerChunkInfoMap.size,
6433
+ (performance.now() - genStart).toFixed(1)
6434
+ );
5254
6435
  },
5255
6436
  // Build-time pre-rendering: evict handler code and inject collected prerender data.
5256
6437
  // Collection now happens in-process during discoverRouters() via RSC runner.
@@ -5261,29 +6442,45 @@ ${details}`
5261
6442
  async handler() {
5262
6443
  if (!s.isBuildMode) return;
5263
6444
  if (this.environment && this.environment.name !== "rsc") return;
5264
- postprocessBundle(s);
6445
+ timedSync(
6446
+ debugBuild,
6447
+ "closeBundle postprocessBundle",
6448
+ () => postprocessBundle(s)
6449
+ );
5265
6450
  }
5266
6451
  }
5267
6452
  };
5268
6453
  }
5269
6454
 
5270
6455
  // src/vite/rango.ts
6456
+ var debugConfig = createRangoDebugger(NS.config);
5271
6457
  async function rango(options) {
6458
+ const rangoStart = performance.now();
5272
6459
  const resolvedOptions = options ?? { preset: "node" };
5273
6460
  const preset = resolvedOptions.preset ?? "node";
5274
6461
  const showBanner = resolvedOptions.banner ?? true;
6462
+ const clientChunksOption = resolvedOptions.clientChunks ?? true;
6463
+ const useBuiltInClientChunks = clientChunksOption === true;
6464
+ const clientChunkCtx = useBuiltInClientChunks ? { fallbackRefs: /* @__PURE__ */ new Set() } : void 0;
6465
+ const clientChunks = resolveClientChunks(clientChunksOption, clientChunkCtx);
6466
+ debugConfig?.("rango(%s) setup start", preset);
5275
6467
  const plugins = [];
5276
- const rangoAliases = getPackageAliases();
6468
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
5277
6469
  const excludeDeps = [
5278
6470
  ...getExcludeDeps(),
5279
- // The public browser entry re-exports the RSDW browser client.
5280
- // Excluding both keeps Vite from freezing the unpatched bundle into
5281
- // .vite/deps before our source transforms run.
6471
+ // plugin-rsc itself injects these into the client env's
6472
+ // optimizeDeps.include, which overrides exclude for the dep's own
6473
+ // pre-bundle entry. What exclude still controls is how *other*
6474
+ // pre-bundled deps treat imports of these specs (external vs inlined)
6475
+ // via esbuildCjsExternalPlugin. The cjs-to-esm transform in
6476
+ // plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
6477
+ // where client.browser's bare include fails to resolve and Vite ends up
6478
+ // serving the raw CJS file at dev-serve time.
5282
6479
  "@vitejs/plugin-rsc/browser",
5283
- // Keep the browser RSDW client out of Vite's dep optimizer so our
5284
- // cjs-to-esm transform can patch the real file.
5285
6480
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5286
6481
  ];
6482
+ const pkg = getPublishedPackageName();
6483
+ const nested = (spec) => `${pkg} > ${spec}`;
5287
6484
  const routerRef = { path: void 0 };
5288
6485
  const prerenderEnabled = true;
5289
6486
  if (preset === "cloudflare") {
@@ -5301,10 +6498,18 @@ async function rango(options) {
5301
6498
  // This ensures the same Context instance is used by both browser entry and RSC proxy modules
5302
6499
  optimizeDeps: {
5303
6500
  exclude: excludeDeps,
5304
- esbuildOptions: sharedEsbuildOptions
6501
+ rolldownOptions: sharedRolldownOptions
5305
6502
  },
5306
6503
  resolve: {
5307
- alias: rangoAliases
6504
+ alias: rangoAliases,
6505
+ // Force a single React/React-DOM copy across all three RSC
6506
+ // environments. RSC requires exactly one react/react-dom instance
6507
+ // per environment runtime; consumer install topologies (pnpm
6508
+ // strict layout, experimental React pins, third-party "use client"
6509
+ // packages) can otherwise resolve duplicate copies, causing
6510
+ // "Invalid hook call" / lost context. Child environments inherit
6511
+ // this root dedupe, and Vite merges it with any consumer dedupe.
6512
+ dedupe: ["react", "react-dom"]
5308
6513
  },
5309
6514
  build: {
5310
6515
  rollupOptions: { onwarn }
@@ -5313,6 +6518,14 @@ async function rango(options) {
5313
6518
  client: {
5314
6519
  build: {
5315
6520
  rollupOptions: {
6521
+ // FILE_NAME_CONFLICT (and any other client-build warning) is
6522
+ // emitted by the CLIENT environment build, which consults THIS
6523
+ // env's onwarn -- Vite 8's environment builds do NOT propagate
6524
+ // the top-level build.rollupOptions.onwarn into the client env.
6525
+ // Wire it here so the suppression runs where the conflicts
6526
+ // originate (the top-level handler is invoked 0x for these; the
6527
+ // client-env handler is invoked for all of them).
6528
+ onwarn,
5316
6529
  output: {
5317
6530
  manualChunks: getManualChunks
5318
6531
  }
@@ -5321,9 +6534,9 @@ async function rango(options) {
5321
6534
  // Pre-bundle rsc-html-stream to prevent discovery during first request
5322
6535
  // Exclude rsc-router modules to ensure same Context instance
5323
6536
  optimizeDeps: {
5324
- include: ["rsc-html-stream/client"],
6537
+ include: [nested("rsc-html-stream/client")],
5325
6538
  exclude: excludeDeps,
5326
- esbuildOptions: sharedEsbuildOptions
6539
+ rolldownOptions: sharedRolldownOptions
5327
6540
  }
5328
6541
  },
5329
6542
  ssr: {
@@ -5331,10 +6544,6 @@ async function rango(options) {
5331
6544
  build: {
5332
6545
  outDir: "./dist/rsc/ssr"
5333
6546
  },
5334
- resolve: {
5335
- // Ensure single React instance in SSR child environment
5336
- dedupe: ["react", "react-dom"]
5337
- },
5338
6547
  // Pre-bundle SSR entry and React for proper module linking with childEnvironments
5339
6548
  // All deps must be listed to avoid late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
5340
6549
  optimizeDeps: {
@@ -5346,11 +6555,13 @@ async function rango(options) {
5346
6555
  "react-dom/static.edge",
5347
6556
  "react/jsx-runtime",
5348
6557
  "react/jsx-dev-runtime",
5349
- "rsc-html-stream/server",
5350
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6558
+ nested("rsc-html-stream/server"),
6559
+ nested(
6560
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6561
+ )
5351
6562
  ],
5352
6563
  exclude: excludeDeps,
5353
- esbuildOptions: sharedEsbuildOptions
6564
+ rolldownOptions: sharedRolldownOptions
5354
6565
  }
5355
6566
  },
5356
6567
  rsc: {
@@ -5362,10 +6573,12 @@ async function rango(options) {
5362
6573
  "react",
5363
6574
  "react/jsx-runtime",
5364
6575
  "react/jsx-dev-runtime",
5365
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6576
+ nested(
6577
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6578
+ )
5366
6579
  ],
5367
6580
  exclude: excludeDeps,
5368
- esbuildOptions: sharedEsbuildOptions
6581
+ rolldownOptions: sharedRolldownOptions
5369
6582
  }
5370
6583
  }
5371
6584
  }
@@ -5383,7 +6596,8 @@ async function rango(options) {
5383
6596
  plugins.push(
5384
6597
  rsc({
5385
6598
  entries: finalEntries,
5386
- serverHandler: false
6599
+ serverHandler: false,
6600
+ clientChunks
5387
6601
  })
5388
6602
  );
5389
6603
  plugins.push(clientRefDedup());
@@ -5401,7 +6615,7 @@ async function rango(options) {
5401
6615
  const list = candidates.map(
5402
6616
  (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
5403
6617
  ).join("\n");
5404
- throw new Error(`[rsc-router] Multiple routers found:
6618
+ throw new Error(`[rango] Multiple routers found:
5405
6619
  ${list}`);
5406
6620
  }
5407
6621
  }
@@ -5420,18 +6634,34 @@ ${list}`);
5420
6634
  return {
5421
6635
  optimizeDeps: {
5422
6636
  exclude: excludeDeps,
5423
- esbuildOptions: sharedEsbuildOptions
6637
+ rolldownOptions: sharedRolldownOptions
5424
6638
  },
5425
6639
  build: {
5426
6640
  rollupOptions: { onwarn }
5427
6641
  },
5428
6642
  resolve: {
5429
- alias: rangoAliases
6643
+ alias: rangoAliases,
6644
+ // Force a single React/React-DOM copy across all three RSC
6645
+ // environments. RSC requires exactly one react/react-dom instance
6646
+ // per environment runtime; consumer install topologies (pnpm
6647
+ // strict layout, experimental React pins, third-party "use client"
6648
+ // packages) can otherwise resolve duplicate copies, causing
6649
+ // "Invalid hook call" / lost context. Child environments inherit
6650
+ // this root dedupe, and Vite merges it with any consumer dedupe.
6651
+ dedupe: ["react", "react-dom"]
5430
6652
  },
5431
6653
  environments: {
5432
6654
  client: {
5433
6655
  build: {
5434
6656
  rollupOptions: {
6657
+ // FILE_NAME_CONFLICT (and any other client-build warning) is
6658
+ // emitted by the CLIENT environment build, which consults THIS
6659
+ // env's onwarn -- Vite 8's environment builds do NOT propagate
6660
+ // the top-level build.rollupOptions.onwarn into the client env.
6661
+ // Wire it here so the suppression runs where the conflicts
6662
+ // originate (the top-level handler is invoked 0x for these; the
6663
+ // client-env handler is invoked for all of them).
6664
+ onwarn,
5435
6665
  output: {
5436
6666
  manualChunks: getManualChunks
5437
6667
  }
@@ -5443,10 +6673,10 @@ ${list}`);
5443
6673
  "react-dom",
5444
6674
  "react/jsx-runtime",
5445
6675
  "react/jsx-dev-runtime",
5446
- "rsc-html-stream/client"
6676
+ nested("rsc-html-stream/client")
5447
6677
  ],
5448
6678
  exclude: excludeDeps,
5449
- esbuildOptions: sharedEsbuildOptions,
6679
+ rolldownOptions: sharedRolldownOptions,
5450
6680
  entries: [VIRTUAL_IDS.browser]
5451
6681
  }
5452
6682
  },
@@ -5460,10 +6690,12 @@ ${list}`);
5460
6690
  "react-dom/static.edge",
5461
6691
  "react/jsx-runtime",
5462
6692
  "react/jsx-dev-runtime",
5463
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6693
+ nested(
6694
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6695
+ )
5464
6696
  ],
5465
6697
  exclude: excludeDeps,
5466
- esbuildOptions: sharedEsbuildOptions
6698
+ rolldownOptions: sharedRolldownOptions
5467
6699
  }
5468
6700
  },
5469
6701
  rsc: {
@@ -5473,9 +6705,11 @@ ${list}`);
5473
6705
  "react",
5474
6706
  "react/jsx-runtime",
5475
6707
  "react/jsx-dev-runtime",
5476
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6708
+ nested(
6709
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6710
+ )
5477
6711
  ],
5478
- esbuildOptions: sharedEsbuildOptions
6712
+ rolldownOptions: sharedRolldownOptions
5479
6713
  }
5480
6714
  }
5481
6715
  }
@@ -5492,7 +6726,7 @@ ${list}`);
5492
6726
  if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
5493
6727
  hasWarnedDuplicate = true;
5494
6728
  console.warn(
5495
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
6729
+ "[rango] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
5496
6730
  );
5497
6731
  }
5498
6732
  }
@@ -5501,7 +6735,8 @@ ${list}`);
5501
6735
  plugins.push(performanceTracksPlugin());
5502
6736
  plugins.push(
5503
6737
  rsc({
5504
- entries: finalEntries
6738
+ entries: finalEntries,
6739
+ clientChunks
5505
6740
  })
5506
6741
  );
5507
6742
  plugins.push(clientRefDedup());
@@ -5540,9 +6775,16 @@ ${list}`);
5540
6775
  routerPathRef: discoveryRouterRef,
5541
6776
  enableBuildPrerender: prerenderEnabled,
5542
6777
  buildEnv: options?.buildEnv,
5543
- preset
6778
+ preset,
6779
+ clientChunkCtx
5544
6780
  })
5545
6781
  );
6782
+ debugConfig?.(
6783
+ "rango(%s) setup done: %d plugin(s) (%sms)",
6784
+ preset,
6785
+ plugins.length,
6786
+ (performance.now() - rangoStart).toFixed(1)
6787
+ );
5546
6788
  return plugins;
5547
6789
  }
5548
6790
 
@@ -5553,7 +6795,7 @@ function poke() {
5553
6795
  apply: "serve",
5554
6796
  configureServer(server) {
5555
6797
  const stdin = process.stdin;
5556
- const debug = process.env.RANGO_POKE_DEBUG === "1";
6798
+ const debug11 = process.env.RANGO_POKE_DEBUG === "1";
5557
6799
  const triggerReload = (source) => {
5558
6800
  server.hot.send({ type: "full-reload", path: "*" });
5559
6801
  server.config.logger.info(` browser reload (${source})`, {
@@ -5586,7 +6828,7 @@ function poke() {
5586
6828
  lines.pop();
5587
6829
  return lines;
5588
6830
  };
5589
- if (debug) {
6831
+ if (debug11) {
5590
6832
  server.config.logger.info(
5591
6833
  ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
5592
6834
  { timestamp: true }
@@ -5599,7 +6841,7 @@ function poke() {
5599
6841
  );
5600
6842
  }
5601
6843
  const onData = (data) => {
5602
- if (debug) {
6844
+ if (debug11) {
5603
6845
  server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5604
6846
  timestamp: true
5605
6847
  });