@rangojs/router 0.0.0-experimental.8874d8d2 → 0.0.0-experimental.89

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 (178) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +126 -38
  3. package/dist/bin/rango.js +138 -50
  4. package/dist/vite/index.js +1705 -701
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +19 -16
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/cache-guide/SKILL.md +32 -0
  9. package/skills/caching/SKILL.md +45 -4
  10. package/skills/handler-use/SKILL.md +362 -0
  11. package/skills/hooks/SKILL.md +28 -20
  12. package/skills/intercept/SKILL.md +20 -0
  13. package/skills/layout/SKILL.md +22 -0
  14. package/skills/links/SKILL.md +91 -17
  15. package/skills/loader/SKILL.md +88 -45
  16. package/skills/middleware/SKILL.md +34 -3
  17. package/skills/migrate-nextjs/SKILL.md +560 -0
  18. package/skills/migrate-react-router/SKILL.md +765 -0
  19. package/skills/parallel/SKILL.md +185 -0
  20. package/skills/prerender/SKILL.md +110 -68
  21. package/skills/rango/SKILL.md +24 -22
  22. package/skills/response-routes/SKILL.md +8 -0
  23. package/skills/route/SKILL.md +55 -0
  24. package/skills/router-setup/SKILL.md +87 -2
  25. package/skills/streams-and-websockets/SKILL.md +283 -0
  26. package/skills/typesafety/SKILL.md +13 -1
  27. package/src/__internal.ts +1 -1
  28. package/src/browser/app-shell.ts +52 -0
  29. package/src/browser/app-version.ts +14 -0
  30. package/src/browser/event-controller.ts +5 -0
  31. package/src/browser/navigation-bridge.ts +90 -16
  32. package/src/browser/navigation-client.ts +167 -59
  33. package/src/browser/navigation-store.ts +68 -9
  34. package/src/browser/navigation-transaction.ts +11 -9
  35. package/src/browser/partial-update.ts +113 -17
  36. package/src/browser/prefetch/cache.ts +175 -15
  37. package/src/browser/prefetch/fetch.ts +180 -33
  38. package/src/browser/prefetch/queue.ts +123 -20
  39. package/src/browser/prefetch/resource-ready.ts +77 -0
  40. package/src/browser/rango-state.ts +53 -13
  41. package/src/browser/react/Link.tsx +81 -9
  42. package/src/browser/react/NavigationProvider.tsx +89 -14
  43. package/src/browser/react/context.ts +7 -2
  44. package/src/browser/react/use-handle.ts +9 -58
  45. package/src/browser/react/use-navigation.ts +22 -2
  46. package/src/browser/react/use-params.ts +11 -1
  47. package/src/browser/react/use-router.ts +29 -9
  48. package/src/browser/rsc-router.tsx +168 -65
  49. package/src/browser/scroll-restoration.ts +41 -42
  50. package/src/browser/segment-reconciler.ts +36 -9
  51. package/src/browser/server-action-bridge.ts +8 -6
  52. package/src/browser/types.ts +49 -5
  53. package/src/build/generate-manifest.ts +6 -6
  54. package/src/build/generate-route-types.ts +3 -0
  55. package/src/build/route-trie.ts +50 -24
  56. package/src/build/route-types/include-resolution.ts +8 -1
  57. package/src/build/route-types/router-processing.ts +223 -74
  58. package/src/build/route-types/scan-filter.ts +8 -1
  59. package/src/cache/cache-runtime.ts +15 -11
  60. package/src/cache/cache-scope.ts +48 -7
  61. package/src/cache/cf/cf-cache-store.ts +455 -15
  62. package/src/cache/cf/index.ts +5 -1
  63. package/src/cache/document-cache.ts +17 -7
  64. package/src/cache/index.ts +1 -0
  65. package/src/cache/taint.ts +55 -0
  66. package/src/client.tsx +84 -230
  67. package/src/context-var.ts +72 -2
  68. package/src/debug.ts +2 -2
  69. package/src/handle.ts +40 -0
  70. package/src/index.rsc.ts +6 -1
  71. package/src/index.ts +49 -6
  72. package/src/outlet-context.ts +1 -1
  73. package/src/prerender/store.ts +5 -4
  74. package/src/prerender.ts +138 -77
  75. package/src/response-utils.ts +28 -0
  76. package/src/reverse.ts +27 -2
  77. package/src/route-definition/dsl-helpers.ts +240 -40
  78. package/src/route-definition/helpers-types.ts +67 -19
  79. package/src/route-definition/index.ts +3 -0
  80. package/src/route-definition/redirect.ts +11 -3
  81. package/src/route-definition/resolve-handler-use.ts +155 -0
  82. package/src/route-map-builder.ts +7 -1
  83. package/src/route-types.ts +18 -0
  84. package/src/router/content-negotiation.ts +100 -1
  85. package/src/router/find-match.ts +4 -2
  86. package/src/router/handler-context.ts +101 -25
  87. package/src/router/intercept-resolution.ts +11 -4
  88. package/src/router/lazy-includes.ts +10 -7
  89. package/src/router/loader-resolution.ts +159 -21
  90. package/src/router/logging.ts +5 -2
  91. package/src/router/manifest.ts +31 -16
  92. package/src/router/match-api.ts +127 -192
  93. package/src/router/match-middleware/background-revalidation.ts +30 -2
  94. package/src/router/match-middleware/cache-lookup.ts +94 -17
  95. package/src/router/match-middleware/cache-store.ts +53 -10
  96. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  97. package/src/router/match-middleware/segment-resolution.ts +61 -5
  98. package/src/router/match-result.ts +104 -10
  99. package/src/router/metrics.ts +6 -1
  100. package/src/router/middleware-types.ts +8 -30
  101. package/src/router/middleware.ts +36 -10
  102. package/src/router/navigation-snapshot.ts +182 -0
  103. package/src/router/pattern-matching.ts +60 -9
  104. package/src/router/prerender-match.ts +110 -10
  105. package/src/router/preview-match.ts +30 -102
  106. package/src/router/request-classification.ts +310 -0
  107. package/src/router/route-snapshot.ts +245 -0
  108. package/src/router/router-context.ts +6 -1
  109. package/src/router/router-interfaces.ts +36 -4
  110. package/src/router/router-options.ts +37 -11
  111. package/src/router/segment-resolution/fresh.ts +198 -20
  112. package/src/router/segment-resolution/helpers.ts +29 -24
  113. package/src/router/segment-resolution/loader-cache.ts +1 -0
  114. package/src/router/segment-resolution/revalidation.ts +437 -297
  115. package/src/router/segment-wrappers.ts +2 -0
  116. package/src/router/trie-matching.ts +10 -4
  117. package/src/router/types.ts +1 -0
  118. package/src/router/url-params.ts +49 -0
  119. package/src/router.ts +60 -8
  120. package/src/rsc/handler.ts +478 -374
  121. package/src/rsc/helpers.ts +69 -41
  122. package/src/rsc/loader-fetch.ts +23 -3
  123. package/src/rsc/manifest-init.ts +5 -1
  124. package/src/rsc/progressive-enhancement.ts +16 -2
  125. package/src/rsc/response-route-handler.ts +14 -1
  126. package/src/rsc/rsc-rendering.ts +17 -1
  127. package/src/rsc/server-action.ts +10 -0
  128. package/src/rsc/ssr-setup.ts +2 -2
  129. package/src/rsc/types.ts +9 -1
  130. package/src/segment-content-promise.ts +67 -0
  131. package/src/segment-loader-promise.ts +122 -0
  132. package/src/segment-system.tsx +109 -23
  133. package/src/server/context.ts +166 -17
  134. package/src/server/handle-store.ts +19 -0
  135. package/src/server/loader-registry.ts +9 -8
  136. package/src/server/request-context.ts +194 -60
  137. package/src/ssr/index.tsx +4 -0
  138. package/src/static-handler.ts +18 -6
  139. package/src/types/cache-types.ts +4 -4
  140. package/src/types/handler-context.ts +137 -65
  141. package/src/types/loader-types.ts +41 -15
  142. package/src/types/request-scope.ts +126 -0
  143. package/src/types/route-entry.ts +19 -1
  144. package/src/types/segments.ts +2 -0
  145. package/src/urls/include-helper.ts +24 -14
  146. package/src/urls/path-helper-types.ts +39 -6
  147. package/src/urls/path-helper.ts +48 -13
  148. package/src/urls/pattern-types.ts +12 -0
  149. package/src/urls/response-types.ts +18 -16
  150. package/src/use-loader.tsx +77 -5
  151. package/src/vite/debug.ts +184 -0
  152. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  153. package/src/vite/discovery/discover-routers.ts +36 -4
  154. package/src/vite/discovery/prerender-collection.ts +175 -74
  155. package/src/vite/discovery/state.ts +13 -6
  156. package/src/vite/index.ts +4 -0
  157. package/src/vite/plugin-types.ts +51 -79
  158. package/src/vite/plugins/cjs-to-esm.ts +5 -0
  159. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  160. package/src/vite/plugins/client-ref-hashing.ts +16 -4
  161. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  162. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  163. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  164. package/src/vite/plugins/expose-action-id.ts +53 -31
  165. package/src/vite/plugins/expose-id-utils.ts +12 -0
  166. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  167. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  168. package/src/vite/plugins/expose-internal-ids.ts +563 -316
  169. package/src/vite/plugins/performance-tracks.ts +96 -0
  170. package/src/vite/plugins/refresh-cmd.ts +88 -26
  171. package/src/vite/plugins/use-cache-transform.ts +56 -43
  172. package/src/vite/plugins/version-plugin.ts +13 -1
  173. package/src/vite/rango.ts +204 -217
  174. package/src/vite/router-discovery.ts +393 -67
  175. package/src/vite/utils/banner.ts +4 -4
  176. package/src/vite/utils/package-resolution.ts +41 -1
  177. package/src/vite/utils/prerender-utils.ts +37 -5
  178. package/src/vite/utils/shared-utils.ts +3 -2
@@ -18,6 +18,9 @@ function hashId(filePath, exportName) {
18
18
  const hash = crypto.createHash("sha256").update(input).digest("hex");
19
19
  return `${hash.slice(0, 8)}#${exportName}`;
20
20
  }
21
+ function makeStubId(filePath, exportName, isBuild) {
22
+ return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
23
+ }
21
24
  function hashInlineId(filePath, lineNumber, index) {
22
25
  const input = index !== void 0 && index > 0 ? `${filePath}:${lineNumber}:${index}` : `${filePath}:${lineNumber}`;
23
26
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
@@ -188,7 +191,99 @@ function escapeRegExp(input) {
188
191
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
189
192
  }
190
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
+ };
205
+ if (process.env.INTERNAL_RANGO_DEBUG) {
206
+ const existing = debugFactory.disable();
207
+ debugFactory.enable(existing ? `${existing},rango:*` : "rango:*");
208
+ }
209
+ function createRangoDebugger(namespace) {
210
+ const primary = debugFactory(namespace);
211
+ const shadow = debugFactory(`vite:${namespace}`);
212
+ if (primary.enabled) return primary;
213
+ if (shadow.enabled) return shadow;
214
+ return void 0;
215
+ }
216
+ async function timed(debug11, label, fn) {
217
+ if (!debug11) return await fn();
218
+ const start = performance.now();
219
+ try {
220
+ return await fn();
221
+ } finally {
222
+ debug11("%s (%sms)", label, (performance.now() - start).toFixed(1));
223
+ }
224
+ }
225
+ function timedSync(debug11, label, fn) {
226
+ if (!debug11) return fn();
227
+ const start = performance.now();
228
+ try {
229
+ return fn();
230
+ } finally {
231
+ debug11("%s (%sms)", label, (performance.now() - start).toFixed(1));
232
+ }
233
+ }
234
+ function createCounter(debug11, label) {
235
+ if (!debug11) return void 0;
236
+ let n = 0;
237
+ let totalMs = 0;
238
+ let slowestMs = 0;
239
+ let slowestFile = "";
240
+ const record = (file, ms) => {
241
+ n++;
242
+ totalMs += ms;
243
+ if (ms > slowestMs) {
244
+ slowestMs = ms;
245
+ slowestFile = file;
246
+ }
247
+ };
248
+ return {
249
+ record,
250
+ time(file, fn) {
251
+ const start = performance.now();
252
+ let out;
253
+ try {
254
+ out = fn();
255
+ } catch (err) {
256
+ record(file, performance.now() - start);
257
+ throw err;
258
+ }
259
+ if (out && typeof out.then === "function") {
260
+ return out.finally(
261
+ () => record(file, performance.now() - start)
262
+ );
263
+ }
264
+ record(file, performance.now() - start);
265
+ return out;
266
+ },
267
+ flush() {
268
+ if (n === 0) return;
269
+ debug11(
270
+ "%s: %d files, %sms total, slowest %sms %s",
271
+ label,
272
+ n,
273
+ totalMs.toFixed(1),
274
+ slowestMs.toFixed(1),
275
+ slowestFile
276
+ );
277
+ n = 0;
278
+ totalMs = 0;
279
+ slowestMs = 0;
280
+ slowestFile = "";
281
+ }
282
+ };
283
+ }
284
+
191
285
  // src/vite/plugins/expose-action-id.ts
286
+ var debug = createRangoDebugger(NS.transform);
192
287
  function getRscPluginApi(config) {
193
288
  let plugin = config.plugins.find((p) => p.name === "rsc:minimal");
194
289
  if (!plugin) {
@@ -277,6 +372,8 @@ function exposeActionId() {
277
372
  let isBuild = false;
278
373
  let hashToFileMap;
279
374
  let rscPluginApi;
375
+ const counterTransform = createCounter(debug, "expose-action-id transform");
376
+ const counterRender = createCounter(debug, "expose-action-id renderChunk");
280
377
  return {
281
378
  name: "@rangojs/router:expose-action-id",
282
379
  // Run after all other plugins (including RSC plugin's transforms)
@@ -286,13 +383,17 @@ function exposeActionId() {
286
383
  isBuild = config.command === "build";
287
384
  rscPluginApi = getRscPluginApi(config);
288
385
  },
386
+ buildEnd() {
387
+ counterTransform?.flush();
388
+ counterRender?.flush();
389
+ },
289
390
  buildStart() {
290
391
  if (!rscPluginApi) {
291
392
  rscPluginApi = getRscPluginApi(config);
292
393
  }
293
394
  if (!rscPluginApi) {
294
395
  throw new Error(
295
- "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin.\nThe RSC plugin should be included automatically. If you disabled it with\nrango({ rsc: false }), add rsc() before rango() in your config."
396
+ "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
296
397
  );
297
398
  }
298
399
  if (!isBuild) return;
@@ -322,28 +423,42 @@ function exposeActionId() {
322
423
  if (id.includes("/node_modules/")) {
323
424
  return;
324
425
  }
325
- return transformServerReferences(code, id);
426
+ const start = counterTransform ? performance.now() : 0;
427
+ try {
428
+ return transformServerReferences(code, id);
429
+ } finally {
430
+ counterTransform?.record(id, performance.now() - start);
431
+ }
326
432
  },
327
433
  // Build mode: renderChunk runs after all transforms and bundling complete
328
434
  renderChunk(code, chunk) {
329
- const isRscEnv = this.environment?.name === "rsc";
330
- const effectiveMap = isRscEnv ? hashToFileMap : void 0;
331
- if (isRscEnv && hashToFileMap) {
332
- const s = new MagicString(code);
333
- const changed1 = applyServerReferenceWrapping(code, s, effectiveMap);
334
- const changed2 = applyRegisterReferenceWrapping(code, s, hashToFileMap);
335
- if (changed1 || changed2) {
336
- return {
337
- code: s.toString(),
338
- map: s.generateMap({
339
- source: chunk.fileName,
340
- includeContent: true
341
- })
342
- };
435
+ const start = counterRender ? performance.now() : 0;
436
+ try {
437
+ const isRscEnv = this.environment?.name === "rsc";
438
+ const effectiveMap = isRscEnv ? hashToFileMap : void 0;
439
+ if (isRscEnv && hashToFileMap) {
440
+ const s = new MagicString(code);
441
+ const changed1 = applyServerReferenceWrapping(code, s, effectiveMap);
442
+ const changed2 = applyRegisterReferenceWrapping(
443
+ code,
444
+ s,
445
+ hashToFileMap
446
+ );
447
+ if (changed1 || changed2) {
448
+ return {
449
+ code: s.toString(),
450
+ map: s.generateMap({
451
+ source: chunk.fileName,
452
+ includeContent: true
453
+ })
454
+ };
455
+ }
456
+ return null;
343
457
  }
344
- return null;
458
+ return transformServerReferences(code, chunk.fileName, effectiveMap);
459
+ } finally {
460
+ counterRender?.record(chunk.fileName, performance.now() - start);
345
461
  }
346
- return transformServerReferences(code, chunk.fileName, effectiveMap);
347
462
  }
348
463
  };
349
464
  }
@@ -910,9 +1025,7 @@ function generateWholeFileStubs(cfg, bindings, code, filePath, isBuild) {
910
1025
  });
911
1026
  return { code: stubs.join("\n") + "\n", map: null };
912
1027
  }
913
- function generateExprStubs(cfg, bindings, code, filePath, sourceId, isBuild) {
914
- if (bindings.length === 0) return null;
915
- const s = new MagicString2(code);
1028
+ function stubHandlerExprs(cfg, bindings, s, filePath, isBuild) {
916
1029
  let hasChanges = false;
917
1030
  for (const binding of bindings) {
918
1031
  const exportName = binding.exportNames[0];
@@ -924,15 +1037,7 @@ function generateExprStubs(cfg, bindings, code, filePath, sourceId, isBuild) {
924
1037
  );
925
1038
  hasChanges = true;
926
1039
  }
927
- if (!hasChanges) return null;
928
- return {
929
- code: s.toString(),
930
- map: s.generateMap({
931
- source: sourceId,
932
- includeContent: true,
933
- hires: "boundary"
934
- })
935
- };
1040
+ return hasChanges;
936
1041
  }
937
1042
  function transformHandlerIds(cfg, bindings, s, filePath, isBuild) {
938
1043
  let hasChanges = false;
@@ -960,6 +1065,7 @@ ${binding.localName}.$$id = "${handlerId}";`;
960
1065
  import MagicString3 from "magic-string";
961
1066
  import path3 from "node:path";
962
1067
  import { createHash } from "node:crypto";
1068
+ var debug2 = createRangoDebugger(NS.transform);
963
1069
  function transformRouter(code, filePath, routerFnNames, absolutePath) {
964
1070
  const pat = new RegExp(
965
1071
  `\\b(?:${routerFnNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\s*(?:<[^>]*>)?\\s*\\(`,
@@ -1002,11 +1108,15 @@ function transformRouter(code, filePath, routerFnNames, absolutePath) {
1002
1108
  }
1003
1109
  function exposeRouterId() {
1004
1110
  let projectRoot = "";
1111
+ const counter = createCounter(debug2, "expose-router-id");
1005
1112
  return {
1006
1113
  name: "@rangojs/router:expose-router-id",
1007
1114
  configResolved(config) {
1008
1115
  projectRoot = config.root;
1009
1116
  },
1117
+ buildEnd() {
1118
+ counter?.flush();
1119
+ },
1010
1120
  transform(code, id) {
1011
1121
  if (!code.includes("createRouter")) return null;
1012
1122
  if (!/import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
@@ -1015,14 +1125,25 @@ function exposeRouterId() {
1015
1125
  return null;
1016
1126
  }
1017
1127
  if (id.includes("node_modules")) return null;
1018
- const filePath = normalizePath(path3.relative(projectRoot, id));
1019
- const routerFnNames = getImportedFnNames(code, "createRouter");
1020
- return transformRouter(code, filePath, routerFnNames, normalizePath(id));
1128
+ const start = counter ? performance.now() : 0;
1129
+ try {
1130
+ const filePath = normalizePath(path3.relative(projectRoot, id));
1131
+ const routerFnNames = getImportedFnNames(code, "createRouter");
1132
+ return transformRouter(
1133
+ code,
1134
+ filePath,
1135
+ routerFnNames,
1136
+ normalizePath(id)
1137
+ );
1138
+ } finally {
1139
+ counter?.record(id, performance.now() - start);
1140
+ }
1021
1141
  }
1022
1142
  };
1023
1143
  }
1024
1144
 
1025
1145
  // src/vite/plugins/expose-internal-ids.ts
1146
+ var debug3 = createRangoDebugger(NS.transform);
1026
1147
  var VIRTUAL_LOADER_MANIFEST = "virtual:rsc-router/loader-manifest";
1027
1148
  var RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
1028
1149
  var VIRTUAL_HANDLER_PREFIX = "virtual:handler-extract:";
@@ -1035,9 +1156,13 @@ function exposeInternalIds(options) {
1035
1156
  const staticHandlerModules = /* @__PURE__ */ new Map();
1036
1157
  const virtualHandlers = /* @__PURE__ */ new Map();
1037
1158
  const unsupportedShapeWarnings = /* @__PURE__ */ new Set();
1159
+ const counter = createCounter(debug3, "expose-internal-ids");
1038
1160
  return {
1039
1161
  name: "@rangojs/router:expose-internal-ids",
1040
1162
  enforce: "post",
1163
+ buildEnd() {
1164
+ counter?.flush();
1165
+ },
1041
1166
  api: {
1042
1167
  prerenderHandlerModules,
1043
1168
  staticHandlerModules
@@ -1151,11 +1276,13 @@ ${lazyImports.join(",\n")}
1151
1276
  // --------------- Unified transform ---------------
1152
1277
  transform(code, id) {
1153
1278
  if (id.includes("/node_modules/")) return;
1154
- const filePath = normalizePath(path4.relative(projectRoot, id));
1155
- const isRscEnv = this.environment?.name === "rsc";
1156
- if (id.includes(".named-routes.gen.") && !isRscEnv && this.environment?.name === "client") {
1157
- this.warn(
1158
- `
1279
+ const __t0 = counter ? performance.now() : 0;
1280
+ try {
1281
+ const filePath = normalizePath(path4.relative(projectRoot, id));
1282
+ const isRscEnv = this.environment?.name === "rsc";
1283
+ if (id.includes(".named-routes.gen.") && !isRscEnv && this.environment?.name === "client") {
1284
+ this.warn(
1285
+ `
1159
1286
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1160
1287
  !! !!
1161
1288
  !! WARNING: NamedRoutes imported in a CLIENT component! !!
@@ -1175,228 +1302,373 @@ ${lazyImports.join(",\n")}
1175
1302
  !! !!
1176
1303
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1177
1304
  `
1178
- );
1179
- }
1180
- if (!code.includes("@rangojs/router")) return;
1181
- const has = detectImports(code);
1182
- const hasLoaderCode = has.loader && code.includes("createLoader");
1183
- const hasHandleCode = has.handle && code.includes("createHandle");
1184
- const hasLocationStateCode = has.locationState && code.includes("createLocationState");
1185
- const hasPrerenderHandlerCode = has.prerenderHandler && code.includes("Prerender");
1186
- const hasStaticHandlerCode = has.staticHandler && code.includes("Static");
1187
- if (!hasLoaderCode && !hasHandleCode && !hasLocationStateCode && !hasPrerenderHandlerCode && !hasStaticHandlerCode) {
1188
- return;
1189
- }
1190
- const _fnNamesCache = /* @__PURE__ */ new Map();
1191
- const _bindingsCache = /* @__PURE__ */ new Map();
1192
- let _cachedAst;
1193
- let _astParseFailed = false;
1194
- let _astCodeRef = code;
1195
- const getFnNames = (canonicalName) => {
1196
- let result = _fnNamesCache.get(canonicalName);
1197
- if (!result) {
1198
- result = getImportedFnNames(code, canonicalName);
1199
- _fnNamesCache.set(canonicalName, result);
1305
+ );
1200
1306
  }
1201
- return result;
1202
- };
1203
- const lazyAst = () => {
1204
- if (code !== _astCodeRef) {
1205
- _cachedAst = void 0;
1206
- _astParseFailed = false;
1207
- _astCodeRef = code;
1307
+ if (!code.includes("@rangojs/router")) return;
1308
+ const has = detectImports(code);
1309
+ const hasLoaderCode = has.loader && code.includes("createLoader");
1310
+ const hasHandleCode = has.handle && code.includes("createHandle");
1311
+ const hasLocationStateCode = has.locationState && code.includes("createLocationState");
1312
+ const hasPrerenderHandlerCode = has.prerenderHandler && code.includes("Prerender");
1313
+ const hasStaticHandlerCode = has.staticHandler && code.includes("Static");
1314
+ if (!hasLoaderCode && !hasHandleCode && !hasLocationStateCode && !hasPrerenderHandlerCode && !hasStaticHandlerCode) {
1315
+ return;
1208
1316
  }
1209
- if (_cachedAst !== void 0 || _astParseFailed) return _cachedAst;
1210
- try {
1211
- _cachedAst = parseAst2(code, { jsx: true });
1212
- } catch {
1213
- _astParseFailed = true;
1317
+ const _fnNamesCache = /* @__PURE__ */ new Map();
1318
+ const _bindingsCache = /* @__PURE__ */ new Map();
1319
+ let _cachedAst;
1320
+ let _astParseFailed = false;
1321
+ let _astCodeRef = code;
1322
+ const getFnNames = (canonicalName) => {
1323
+ let result = _fnNamesCache.get(canonicalName);
1324
+ if (!result) {
1325
+ result = getImportedFnNames(code, canonicalName);
1326
+ _fnNamesCache.set(canonicalName, result);
1327
+ }
1328
+ return result;
1329
+ };
1330
+ const lazyAst = () => {
1331
+ if (code !== _astCodeRef) {
1332
+ _cachedAst = void 0;
1333
+ _astParseFailed = false;
1334
+ _astCodeRef = code;
1335
+ }
1336
+ if (_cachedAst !== void 0 || _astParseFailed) return _cachedAst;
1337
+ try {
1338
+ _cachedAst = parseAst2(code, { jsx: true });
1339
+ } catch {
1340
+ _astParseFailed = true;
1341
+ }
1342
+ return _cachedAst;
1343
+ };
1344
+ const getBindings = (currentCode, fnNames) => {
1345
+ const key = fnNames.join("\0");
1346
+ let result = _bindingsCache.get(key);
1347
+ if (!result) {
1348
+ result = collectCreateExportBindings(
1349
+ currentCode,
1350
+ fnNames,
1351
+ lazyAst()
1352
+ );
1353
+ _bindingsCache.set(key, result);
1354
+ }
1355
+ return result;
1356
+ };
1357
+ for (const cfg of STRICT_CREATE_CONFIGS) {
1358
+ const hasCode = cfg.fnName === "createLoader" ? hasLoaderCode : cfg.fnName === "createHandle" ? hasHandleCode : hasLocationStateCode;
1359
+ if (!hasCode) continue;
1360
+ const fnNames = getFnNames(cfg.fnName);
1361
+ const totalCalls = countCreateCallsForNames(code, fnNames);
1362
+ const supportedBindings = getBindings(code, fnNames).length;
1363
+ if (totalCalls <= supportedBindings) continue;
1364
+ const warnKey = `${id}::${cfg.fnName}`;
1365
+ if (unsupportedShapeWarnings.has(warnKey)) continue;
1366
+ unsupportedShapeWarnings.add(warnKey);
1367
+ this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
1214
1368
  }
1215
- return _cachedAst;
1216
- };
1217
- const getBindings = (currentCode, fnNames) => {
1218
- const key = fnNames.join("\0");
1219
- let result = _bindingsCache.get(key);
1220
- if (!result) {
1221
- result = collectCreateExportBindings(currentCode, fnNames, lazyAst());
1222
- _bindingsCache.set(key, result);
1369
+ if (hasLoaderCode && isRscEnv) {
1370
+ const fnNames = getFnNames("createLoader");
1371
+ const bindings = getBindings(code, fnNames);
1372
+ for (const binding of bindings) {
1373
+ const exportName = binding.exportNames[0];
1374
+ const hashedId = hashId(filePath, exportName);
1375
+ loaderRegistry.set(hashedId, {
1376
+ filePath,
1377
+ exportName
1378
+ });
1379
+ }
1223
1380
  }
1224
- return result;
1225
- };
1226
- for (const cfg of STRICT_CREATE_CONFIGS) {
1227
- const hasCode = cfg.fnName === "createLoader" ? hasLoaderCode : cfg.fnName === "createHandle" ? hasHandleCode : hasLocationStateCode;
1228
- if (!hasCode) continue;
1229
- const fnNames = getFnNames(cfg.fnName);
1230
- const totalCalls = countCreateCallsForNames(code, fnNames);
1231
- const supportedBindings = getBindings(code, fnNames).length;
1232
- if (totalCalls <= supportedBindings) continue;
1233
- const warnKey = `${id}::${cfg.fnName}`;
1234
- if (unsupportedShapeWarnings.has(warnKey)) continue;
1235
- unsupportedShapeWarnings.add(warnKey);
1236
- this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
1237
- }
1238
- if (hasLoaderCode && isRscEnv) {
1239
- const fnNames = getFnNames("createLoader");
1240
- const bindings = getBindings(code, fnNames);
1241
- for (const binding of bindings) {
1242
- const exportName = binding.exportNames[0];
1243
- const hashedId = hashId(filePath, exportName);
1244
- loaderRegistry.set(hashedId, {
1381
+ if (hasLoaderCode && !isRscEnv) {
1382
+ const fnNames = getFnNames("createLoader");
1383
+ const bindings = getBindings(code, fnNames);
1384
+ const stubResult = generateClientLoaderStubs(
1385
+ bindings,
1386
+ code,
1245
1387
  filePath,
1246
- exportName
1247
- });
1388
+ isBuild
1389
+ );
1390
+ if (stubResult) return stubResult;
1248
1391
  }
1249
- }
1250
- if (hasLoaderCode && !isRscEnv) {
1251
- const fnNames = getFnNames("createLoader");
1252
- const bindings = getBindings(code, fnNames);
1253
- const stubResult = generateClientLoaderStubs(
1254
- bindings,
1255
- code,
1256
- filePath,
1257
- isBuild
1258
- );
1259
- if (stubResult) return stubResult;
1260
- }
1261
- if (hasPrerenderHandlerCode && !isRscEnv) {
1262
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1263
- const bindings = getBindings(code, fnNames);
1264
- const wholeFile = generateWholeFileStubs(
1265
- PRERENDER_CONFIG,
1266
- bindings,
1267
- code,
1268
- filePath,
1269
- isBuild
1270
- );
1271
- if (wholeFile) return wholeFile;
1272
- const exprStubs = generateExprStubs(
1273
- PRERENDER_CONFIG,
1274
- bindings,
1275
- code,
1276
- filePath,
1277
- id,
1278
- isBuild
1279
- );
1280
- if (exprStubs) return exprStubs;
1281
- }
1282
- if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1283
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1284
- const exportNames = getBindings(code, fnNames).map(
1285
- (b) => b.exportNames[0]
1286
- );
1287
- if (exportNames.length > 0) {
1288
- prerenderHandlerModules.set(id, exportNames);
1392
+ if (hasPrerenderHandlerCode && !isRscEnv) {
1393
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1394
+ const bindings = getBindings(code, fnNames);
1395
+ const wholeFile = generateWholeFileStubs(
1396
+ PRERENDER_CONFIG,
1397
+ bindings,
1398
+ code,
1399
+ filePath,
1400
+ isBuild
1401
+ );
1402
+ if (wholeFile) return wholeFile;
1289
1403
  }
1290
- }
1291
- let changed = false;
1292
- const handlerConfigs = [
1293
- hasStaticHandlerCode && STATIC_CONFIG,
1294
- hasPrerenderHandlerCode && PRERENDER_CONFIG
1295
- ].filter((c) => !!c).map((cfg) => {
1296
- const fnNames = getFnNames(cfg.fnName);
1297
- return { cfg, fnNames };
1298
- });
1299
- for (const { cfg, fnNames } of handlerConfigs) {
1300
- const totalCalls = countCreateCallsForNames(code, fnNames);
1301
- const supportedBindings = getBindings(code, fnNames).length;
1302
- if (totalCalls > supportedBindings) {
1303
- const iterS = new MagicString4(code);
1304
- const result = transformInlineHandlers(
1305
- cfg.fnName,
1306
- VIRTUAL_HANDLER_PREFIX,
1307
- iterS,
1404
+ if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1405
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1406
+ const exportNames = getBindings(code, fnNames).map(
1407
+ (b) => b.exportNames[0]
1408
+ );
1409
+ if (exportNames.length > 0) {
1410
+ prerenderHandlerModules.set(id, exportNames);
1411
+ }
1412
+ }
1413
+ let changed = false;
1414
+ const handlerConfigs = [
1415
+ hasStaticHandlerCode && STATIC_CONFIG,
1416
+ hasPrerenderHandlerCode && PRERENDER_CONFIG
1417
+ ].filter((c) => !!c).map((cfg) => {
1418
+ const fnNames = getFnNames(cfg.fnName);
1419
+ return { cfg, fnNames };
1420
+ });
1421
+ for (const { cfg, fnNames } of handlerConfigs) {
1422
+ const totalCalls = countCreateCallsForNames(code, fnNames);
1423
+ const supportedBindings = getBindings(code, fnNames).length;
1424
+ if (totalCalls > supportedBindings) {
1425
+ const iterS = new MagicString4(code);
1426
+ const result = transformInlineHandlers(
1427
+ cfg.fnName,
1428
+ VIRTUAL_HANDLER_PREFIX,
1429
+ iterS,
1430
+ code,
1431
+ filePath,
1432
+ virtualHandlers,
1433
+ id,
1434
+ parseAst2
1435
+ );
1436
+ if (result) {
1437
+ changed = true;
1438
+ code = iterS.toString();
1439
+ _bindingsCache.clear();
1440
+ }
1441
+ }
1442
+ }
1443
+ if (hasStaticHandlerCode && !isRscEnv) {
1444
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
1445
+ const bindings = getBindings(code, fnNames);
1446
+ const wholeFile = generateWholeFileStubs(
1447
+ STATIC_CONFIG,
1448
+ bindings,
1308
1449
  code,
1309
1450
  filePath,
1310
- virtualHandlers,
1311
- id,
1312
- parseAst2
1451
+ isBuild
1313
1452
  );
1314
- if (result) {
1315
- changed = true;
1316
- code = iterS.toString();
1317
- _bindingsCache.clear();
1453
+ if (wholeFile) return wholeFile;
1454
+ }
1455
+ if (!isRscEnv && (hasPrerenderHandlerCode || hasStaticHandlerCode)) {
1456
+ const prerenderFnNames = hasPrerenderHandlerCode ? getFnNames(PRERENDER_CONFIG.fnName) : [];
1457
+ const staticFnNames = hasStaticHandlerCode ? getFnNames(STATIC_CONFIG.fnName) : [];
1458
+ const loaderFnNames = hasLoaderCode ? getFnNames("createLoader") : [];
1459
+ const handleFnNames = hasHandleCode ? getFnNames("createHandle") : [];
1460
+ const lsFnNames = hasLocationStateCode ? getFnNames("createLocationState") : [];
1461
+ const allBindings = [];
1462
+ for (const fnNames of [
1463
+ prerenderFnNames,
1464
+ staticFnNames,
1465
+ loaderFnNames,
1466
+ handleFnNames,
1467
+ lsFnNames
1468
+ ]) {
1469
+ if (fnNames.length > 0) {
1470
+ allBindings.push(...getBindings(code, fnNames));
1471
+ }
1472
+ }
1473
+ let canStubWholeFile = allBindings.length > 0 && isExportOnlyFile(code, allBindings);
1474
+ if (canStubWholeFile && (handleFnNames.length > 0 || lsFnNames.length > 0)) {
1475
+ const exportedLocals = new Set(allBindings.map((b) => b.localName));
1476
+ const strippedBindings = [];
1477
+ const localDeclPattern = /(?:^|;|\n)\s*(?:const|let|var|function)\s+(\w+)/g;
1478
+ let declMatch;
1479
+ while ((declMatch = localDeclPattern.exec(code)) !== null) {
1480
+ const name = declMatch[1];
1481
+ if (!exportedLocals.has(name) && !/^_c\d*$/.test(name)) {
1482
+ strippedBindings.push(name);
1483
+ }
1484
+ }
1485
+ const importPattern = /import\s*\{([^}]*)\}\s*from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1486
+ let importMatch;
1487
+ while ((importMatch = importPattern.exec(code)) !== null) {
1488
+ for (const spec of importMatch[1].split(",")) {
1489
+ const m = spec.trim().match(/^[A-Za-z_$][\w$]*(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
1490
+ if (m)
1491
+ strippedBindings.push(m[1] || m[0].trim().split(/\s/)[0]);
1492
+ }
1493
+ }
1494
+ const defaultImportPattern = /import\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1495
+ while ((importMatch = defaultImportPattern.exec(code)) !== null) {
1496
+ strippedBindings.push(importMatch[1]);
1497
+ }
1498
+ const nsImportPattern = /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1499
+ while ((importMatch = nsImportPattern.exec(code)) !== null) {
1500
+ strippedBindings.push(importMatch[1]);
1501
+ }
1502
+ if (strippedBindings.length > 0) {
1503
+ const preservedBindings = allBindings.filter((b) => {
1504
+ const fc = code.slice(b.callExprStart, b.callOpenParenPos + 1);
1505
+ return handleFnNames.some((n) => fc.includes(n)) || lsFnNames.some((n) => fc.includes(n));
1506
+ });
1507
+ const strippedRe = new RegExp(
1508
+ `\\b(?:${strippedBindings.join("|")})\\b`
1509
+ );
1510
+ canStubWholeFile = !preservedBindings.some((b) => {
1511
+ const expr = code.slice(
1512
+ b.callExprStart,
1513
+ b.callCloseParenPos + 1
1514
+ );
1515
+ return strippedRe.test(expr);
1516
+ });
1517
+ }
1518
+ }
1519
+ if (canStubWholeFile) {
1520
+ const lines = [];
1521
+ const neededImports = [];
1522
+ if (handleFnNames.length > 0) neededImports.push("createHandle");
1523
+ if (lsFnNames.length > 0) neededImports.push("createLocationState");
1524
+ if (neededImports.length > 0) {
1525
+ lines.push(
1526
+ `import { ${neededImports.join(", ")} } from "@rangojs/router";`
1527
+ );
1528
+ }
1529
+ for (const binding of allBindings) {
1530
+ const fnCall = code.slice(
1531
+ binding.callExprStart,
1532
+ binding.callOpenParenPos + 1
1533
+ );
1534
+ const isHandle = handleFnNames.some((n) => fnCall.includes(n));
1535
+ const isLocationState = lsFnNames.some((n) => fnCall.includes(n));
1536
+ const primaryName = binding.exportNames[0];
1537
+ const stubId = makeStubId(filePath, primaryName, isBuild);
1538
+ if (isHandle || isLocationState) {
1539
+ const rawArgs = code.slice(
1540
+ binding.callOpenParenPos + 1,
1541
+ binding.callCloseParenPos
1542
+ ).replace(/\b_c\d*\s*=\s*/g, "");
1543
+ const canonicalName = isHandle ? "createHandle" : "createLocationState";
1544
+ const activeFnNames = isHandle ? handleFnNames : lsFnNames;
1545
+ let rawCallee = code.slice(
1546
+ binding.callExprStart,
1547
+ binding.callOpenParenPos
1548
+ );
1549
+ for (const alias of activeFnNames) {
1550
+ if (alias !== canonicalName && rawCallee.startsWith(alias)) {
1551
+ rawCallee = canonicalName + rawCallee.slice(alias.length);
1552
+ break;
1553
+ }
1554
+ }
1555
+ if (isHandle) {
1556
+ const idParam = binding.argCount === 0 ? `undefined, "${stubId}"` : `, "${stubId}"`;
1557
+ lines.push(
1558
+ `export const ${primaryName} = ${rawCallee}(${rawArgs}${idParam});`
1559
+ );
1560
+ lines.push(`${primaryName}.$$id = "${stubId}";`);
1561
+ } else {
1562
+ lines.push(
1563
+ `export const ${primaryName} = ${rawCallee}(${rawArgs});`
1564
+ );
1565
+ lines.push(
1566
+ `${primaryName}.__rsc_ls_key = "__rsc_ls_${stubId}";`
1567
+ );
1568
+ }
1569
+ for (const name of binding.exportNames.slice(1)) {
1570
+ lines.push(`export const ${name} = ${primaryName};`);
1571
+ }
1572
+ } else {
1573
+ let brand = "loader";
1574
+ if (prerenderFnNames.some((n) => fnCall.includes(n))) {
1575
+ brand = PRERENDER_CONFIG.brand;
1576
+ } else if (staticFnNames.some((n) => fnCall.includes(n))) {
1577
+ brand = STATIC_CONFIG.brand;
1578
+ }
1579
+ lines.push(
1580
+ `export const ${primaryName} = { __brand: "${brand}", $$id: "${stubId}" };`
1581
+ );
1582
+ for (const name of binding.exportNames.slice(1)) {
1583
+ lines.push(`export const ${name} = ${primaryName};`);
1584
+ }
1585
+ }
1586
+ }
1587
+ return { code: lines.join("\n") + "\n", map: null };
1318
1588
  }
1319
1589
  }
1320
- }
1321
- if (hasStaticHandlerCode && !isRscEnv) {
1322
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1323
- const bindings = getBindings(code, fnNames);
1324
- const wholeFile = generateWholeFileStubs(
1325
- STATIC_CONFIG,
1326
- bindings,
1327
- code,
1328
- filePath,
1329
- isBuild
1330
- );
1331
- if (wholeFile) return wholeFile;
1332
- const exprStubs = generateExprStubs(
1333
- STATIC_CONFIG,
1334
- bindings,
1335
- code,
1336
- filePath,
1337
- id,
1338
- isBuild
1339
- );
1340
- if (exprStubs) return exprStubs;
1341
- }
1342
- if (hasStaticHandlerCode && isRscEnv && isBuild) {
1343
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1344
- const exportNames = getBindings(code, fnNames).map(
1345
- (b) => b.exportNames[0]
1346
- );
1347
- if (exportNames.length > 0) {
1348
- staticHandlerModules.set(id, exportNames);
1590
+ if (hasStaticHandlerCode && isRscEnv && isBuild) {
1591
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
1592
+ const exportNames = getBindings(code, fnNames).map(
1593
+ (b) => b.exportNames[0]
1594
+ );
1595
+ if (exportNames.length > 0) {
1596
+ staticHandlerModules.set(id, exportNames);
1597
+ }
1349
1598
  }
1599
+ const s = new MagicString4(code);
1600
+ if (hasLoaderCode) {
1601
+ const fnNames = getFnNames("createLoader");
1602
+ changed = transformLoaders(
1603
+ getBindings(code, fnNames),
1604
+ s,
1605
+ filePath,
1606
+ isBuild
1607
+ ) || changed;
1608
+ }
1609
+ if (hasHandleCode) {
1610
+ const fnNames = getFnNames("createHandle");
1611
+ changed = transformHandles(
1612
+ getBindings(code, fnNames),
1613
+ s,
1614
+ code,
1615
+ filePath,
1616
+ isBuild
1617
+ ) || changed;
1618
+ }
1619
+ if (hasLocationStateCode) {
1620
+ const fnNames = getFnNames("createLocationState");
1621
+ changed = transformLocationState(
1622
+ getBindings(code, fnNames),
1623
+ s,
1624
+ filePath,
1625
+ isBuild
1626
+ ) || changed;
1627
+ }
1628
+ if (hasPrerenderHandlerCode) {
1629
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1630
+ const bindings = getBindings(code, fnNames);
1631
+ if (isRscEnv) {
1632
+ changed = transformHandlerIds(
1633
+ PRERENDER_CONFIG,
1634
+ bindings,
1635
+ s,
1636
+ filePath,
1637
+ isBuild
1638
+ ) || changed;
1639
+ } else {
1640
+ changed = stubHandlerExprs(
1641
+ PRERENDER_CONFIG,
1642
+ bindings,
1643
+ s,
1644
+ filePath,
1645
+ isBuild
1646
+ ) || changed;
1647
+ }
1648
+ }
1649
+ if (hasStaticHandlerCode) {
1650
+ const fnNames = getFnNames(STATIC_CONFIG.fnName);
1651
+ const bindings = getBindings(code, fnNames);
1652
+ if (isRscEnv) {
1653
+ changed = transformHandlerIds(
1654
+ STATIC_CONFIG,
1655
+ bindings,
1656
+ s,
1657
+ filePath,
1658
+ isBuild
1659
+ ) || changed;
1660
+ } else {
1661
+ changed = stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) || changed;
1662
+ }
1663
+ }
1664
+ if (!changed) return;
1665
+ return {
1666
+ code: s.toString(),
1667
+ map: s.generateMap({ source: id, includeContent: true })
1668
+ };
1669
+ } finally {
1670
+ counter?.record(id, performance.now() - __t0);
1350
1671
  }
1351
- const s = new MagicString4(code);
1352
- if (hasLoaderCode) {
1353
- const fnNames = getFnNames("createLoader");
1354
- changed = transformLoaders(getBindings(code, fnNames), s, filePath, isBuild) || changed;
1355
- }
1356
- if (hasHandleCode) {
1357
- const fnNames = getFnNames("createHandle");
1358
- changed = transformHandles(
1359
- getBindings(code, fnNames),
1360
- s,
1361
- code,
1362
- filePath,
1363
- isBuild
1364
- ) || changed;
1365
- }
1366
- if (hasLocationStateCode) {
1367
- const fnNames = getFnNames("createLocationState");
1368
- changed = transformLocationState(
1369
- getBindings(code, fnNames),
1370
- s,
1371
- filePath,
1372
- isBuild
1373
- ) || changed;
1374
- }
1375
- if (hasPrerenderHandlerCode && isRscEnv) {
1376
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1377
- changed = transformHandlerIds(
1378
- PRERENDER_CONFIG,
1379
- getBindings(code, fnNames),
1380
- s,
1381
- filePath,
1382
- isBuild
1383
- ) || changed;
1384
- }
1385
- if (hasStaticHandlerCode && isRscEnv) {
1386
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1387
- changed = transformHandlerIds(
1388
- STATIC_CONFIG,
1389
- getBindings(code, fnNames),
1390
- s,
1391
- filePath,
1392
- isBuild
1393
- ) || changed;
1394
- }
1395
- if (!changed) return;
1396
- return {
1397
- code: s.toString(),
1398
- map: s.generateMap({ source: id, includeContent: true })
1399
- };
1400
1672
  }
1401
1673
  };
1402
1674
  }
@@ -1404,12 +1676,14 @@ ${lazyImports.join(",\n")}
1404
1676
  // src/vite/plugins/use-cache-transform.ts
1405
1677
  import path5 from "node:path";
1406
1678
  import MagicString5 from "magic-string";
1679
+ var debug4 = createRangoDebugger(NS.transform);
1407
1680
  var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
1408
1681
  var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
1409
1682
  function useCacheTransform() {
1410
1683
  let projectRoot = "";
1411
1684
  let isBuild = false;
1412
1685
  let rscTransforms = null;
1686
+ const counter = createCounter(debug4, "use-cache");
1413
1687
  return {
1414
1688
  name: "@rangojs/router:use-cache",
1415
1689
  enforce: "post",
@@ -1417,53 +1691,61 @@ function useCacheTransform() {
1417
1691
  projectRoot = config.root;
1418
1692
  isBuild = config.command === "build";
1419
1693
  },
1694
+ buildEnd() {
1695
+ counter?.flush();
1696
+ },
1420
1697
  async transform(code, id) {
1421
1698
  if (this.environment?.name !== "rsc") return;
1422
1699
  if (!code.includes("use cache")) return;
1423
1700
  if (id.includes("/node_modules/") || id.startsWith("\0")) return;
1424
1701
  if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
1425
- if (!rscTransforms) {
1702
+ const start = counter ? performance.now() : 0;
1703
+ try {
1704
+ if (!rscTransforms) {
1705
+ try {
1706
+ rscTransforms = await import("@vitejs/plugin-rsc/transforms");
1707
+ } catch {
1708
+ return;
1709
+ }
1710
+ }
1711
+ const {
1712
+ hasDirective,
1713
+ transformWrapExport,
1714
+ transformHoistInlineDirective
1715
+ } = rscTransforms;
1716
+ let ast;
1426
1717
  try {
1427
- rscTransforms = await import("@vitejs/plugin-rsc/transforms");
1718
+ const { parseAst: parseAst4 } = await import("vite");
1719
+ ast = parseAst4(code);
1428
1720
  } catch {
1429
1721
  return;
1430
1722
  }
1431
- }
1432
- const {
1433
- hasDirective,
1434
- transformWrapExport,
1435
- transformHoistInlineDirective
1436
- } = rscTransforms;
1437
- let ast;
1438
- try {
1439
- const { parseAst: parseAst4 } = await import("vite");
1440
- ast = parseAst4(code);
1441
- } catch {
1442
- return;
1443
- }
1444
- const filePath = normalizePath(path5.relative(projectRoot, id));
1445
- const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
1446
- if (hasDirective(ast.body, "use cache")) {
1447
- return transformFileLevelUseCache(
1723
+ const filePath = normalizePath(path5.relative(projectRoot, id));
1724
+ const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
1725
+ if (hasDirective(ast.body, "use cache")) {
1726
+ return transformFileLevelUseCache(
1727
+ code,
1728
+ ast,
1729
+ filePath,
1730
+ id,
1731
+ isBuild,
1732
+ isLayoutOrTemplate,
1733
+ transformWrapExport
1734
+ );
1735
+ }
1736
+ const functionResult = transformFunctionLevelUseCache(
1448
1737
  code,
1449
1738
  ast,
1450
1739
  filePath,
1451
1740
  id,
1452
1741
  isBuild,
1453
- isLayoutOrTemplate,
1454
- transformWrapExport
1742
+ transformHoistInlineDirective
1455
1743
  );
1744
+ warnOnNearMissDirectives(ast, id, this.warn.bind(this));
1745
+ if (functionResult) return functionResult;
1746
+ } finally {
1747
+ counter?.record(id, performance.now() - start);
1456
1748
  }
1457
- const functionResult = transformFunctionLevelUseCache(
1458
- code,
1459
- ast,
1460
- filePath,
1461
- id,
1462
- isBuild,
1463
- transformHoistInlineDirective
1464
- );
1465
- warnOnNearMissDirectives(ast, id, this.warn.bind(this));
1466
- if (functionResult) return functionResult;
1467
1749
  }
1468
1750
  };
1469
1751
  }
@@ -1586,6 +1868,7 @@ function warnOnNearMissDirectives(ast, fileId, warn) {
1586
1868
  }
1587
1869
 
1588
1870
  // src/vite/plugins/client-ref-dedup.ts
1871
+ var debug5 = createRangoDebugger(NS.transform);
1589
1872
  var CLIENT_IN_SERVER_PROXY_PREFIX = "virtual:vite-rsc/client-in-server-package-proxy/";
1590
1873
  function extractPackageName(absolutePath) {
1591
1874
  const marker = "/node_modules/";
@@ -1602,6 +1885,7 @@ function extractPackageName(absolutePath) {
1602
1885
  }
1603
1886
  function clientRefDedup() {
1604
1887
  let clientExclude = [];
1888
+ const dedupedPackages = /* @__PURE__ */ new Set();
1605
1889
  return {
1606
1890
  name: "@rangojs/router:client-ref-dedup",
1607
1891
  enforce: "pre",
@@ -1610,6 +1894,15 @@ function clientRefDedup() {
1610
1894
  const clientEnv = config.environments?.["client"];
1611
1895
  clientExclude = clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
1612
1896
  },
1897
+ buildEnd() {
1898
+ if (debug5 && dedupedPackages.size > 0) {
1899
+ debug5(
1900
+ "client-ref-dedup: redirected %d package(s) (%s)",
1901
+ dedupedPackages.size,
1902
+ [...dedupedPackages].join(",")
1903
+ );
1904
+ }
1905
+ },
1613
1906
  resolveId(source, importer, options) {
1614
1907
  if (this.environment?.name !== "client") return;
1615
1908
  if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
@@ -1618,6 +1911,7 @@ function clientRefDedup() {
1618
1911
  const packageName = extractPackageName(source);
1619
1912
  if (!packageName) return;
1620
1913
  if (clientExclude.includes(packageName)) return;
1914
+ if (debug5) dedupedPackages.add(packageName);
1621
1915
  return `\0rango:dedup/${packageName}`;
1622
1916
  },
1623
1917
  load(id) {
@@ -1740,12 +2034,13 @@ function getVirtualVersionContent(version) {
1740
2034
 
1741
2035
  // src/vite/utils/package-resolution.ts
1742
2036
  import { existsSync } from "node:fs";
2037
+ import { createRequire } from "node:module";
1743
2038
  import { resolve } from "node:path";
1744
2039
 
1745
2040
  // package.json
1746
2041
  var package_default = {
1747
2042
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.8874d8d2",
2043
+ version: "0.0.0-experimental.89",
1749
2044
  description: "Django-inspired RSC router with composable URL patterns",
1750
2045
  keywords: [
1751
2046
  "react",
@@ -1878,22 +2173,24 @@ var package_default = {
1878
2173
  tag: "experimental"
1879
2174
  },
1880
2175
  scripts: {
1881
- 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",
2176
+ 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",
1882
2177
  prepublishOnly: "pnpm build",
1883
- typecheck: "tsc --noEmit",
2178
+ typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit",
1884
2179
  test: "playwright test",
1885
2180
  "test:ui": "playwright test --ui",
1886
2181
  "test:unit": "vitest run",
1887
2182
  "test:unit:watch": "vitest"
1888
2183
  },
1889
2184
  dependencies: {
1890
- "@vitejs/plugin-rsc": "^0.5.14",
2185
+ "@vitejs/plugin-rsc": "^0.5.23",
2186
+ debug: "^4.4.1",
1891
2187
  "magic-string": "^0.30.17",
1892
2188
  picomatch: "^4.0.3",
1893
2189
  "rsc-html-stream": "^0.0.7"
1894
2190
  },
1895
2191
  devDependencies: {
1896
2192
  "@playwright/test": "^1.49.1",
2193
+ "@types/debug": "^4.1.12",
1897
2194
  "@types/node": "^24.10.1",
1898
2195
  "@types/react": "catalog:",
1899
2196
  "@types/react-dom": "catalog:",
@@ -1907,7 +2204,7 @@ var package_default = {
1907
2204
  },
1908
2205
  peerDependencies: {
1909
2206
  "@cloudflare/vite-plugin": "^1.25.0",
1910
- "@vitejs/plugin-rsc": "^0.5.14",
2207
+ "@vitejs/plugin-rsc": "^0.5.23",
1911
2208
  react: "^18.0.0 || ^19.0.0",
1912
2209
  vite: "^7.3.0"
1913
2210
  },
@@ -1922,6 +2219,7 @@ var package_default = {
1922
2219
  };
1923
2220
 
1924
2221
  // src/vite/utils/package-resolution.ts
2222
+ var require2 = createRequire(import.meta.url);
1925
2223
  var VIRTUAL_PACKAGE_NAME = "@rangojs/router";
1926
2224
  function getPublishedPackageName() {
1927
2225
  return package_default.name;
@@ -1962,6 +2260,20 @@ function getPackageAliases() {
1962
2260
  }
1963
2261
  return aliases;
1964
2262
  }
2263
+ function getVendorAliases() {
2264
+ const specs = [
2265
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
2266
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
2267
+ ];
2268
+ const aliases = {};
2269
+ for (const spec of specs) {
2270
+ try {
2271
+ aliases[spec] = require2.resolve(spec);
2272
+ } catch {
2273
+ }
2274
+ }
2275
+ return aliases;
2276
+ }
1965
2277
 
1966
2278
  // src/build/route-types/param-extraction.ts
1967
2279
  function extractParamsFromPattern(pattern) {
@@ -2095,31 +2407,7 @@ declare global {
2095
2407
  }
2096
2408
 
2097
2409
  // src/build/route-types/scan-filter.ts
2098
- import { join, relative } from "node:path";
2099
2410
  import picomatch from "picomatch";
2100
- var DEFAULT_EXCLUDE_PATTERNS = [
2101
- "**/__tests__/**",
2102
- "**/__mocks__/**",
2103
- "**/dist/**",
2104
- "**/coverage/**",
2105
- "**/*.test.{ts,tsx,js,jsx}",
2106
- "**/*.spec.{ts,tsx,js,jsx}"
2107
- ];
2108
- function createScanFilter(root, opts) {
2109
- const { include, exclude } = opts;
2110
- const hasInclude = include && include.length > 0;
2111
- const hasCustomExclude = exclude !== void 0;
2112
- if (!hasInclude && !hasCustomExclude) return void 0;
2113
- const effectiveExclude = exclude ?? DEFAULT_EXCLUDE_PATTERNS;
2114
- const includeMatcher = hasInclude ? picomatch(include) : null;
2115
- const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
2116
- return (absolutePath) => {
2117
- const rel = relative(root, absolutePath);
2118
- if (excludeMatcher && excludeMatcher(rel)) return false;
2119
- if (includeMatcher) return includeMatcher(rel);
2120
- return true;
2121
- };
2122
- }
2123
2411
 
2124
2412
  // src/build/route-types/per-module-writer.ts
2125
2413
  import ts4 from "typescript";
@@ -2341,7 +2629,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2341
2629
  }
2342
2630
  return routeMap;
2343
2631
  }
2344
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
2632
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
2345
2633
  visited = visited ?? /* @__PURE__ */ new Set();
2346
2634
  const realPath = resolve2(filePath);
2347
2635
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -2357,7 +2645,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2357
2645
  return { routes: {}, searchSchemas: {} };
2358
2646
  }
2359
2647
  let block;
2360
- if (variableName) {
2648
+ if (inlineBlock) {
2649
+ block = inlineBlock;
2650
+ } else if (variableName) {
2361
2651
  const extracted = extractUrlsBlockForVariable(source, variableName);
2362
2652
  if (!extracted) return { routes: {}, searchSchemas: {} };
2363
2653
  block = extracted;
@@ -2386,7 +2676,7 @@ import {
2386
2676
  readdirSync
2387
2677
  } from "node:fs";
2388
2678
  import {
2389
- join as join2,
2679
+ join,
2390
2680
  dirname as dirname2,
2391
2681
  resolve as resolve3,
2392
2682
  sep,
@@ -2406,7 +2696,7 @@ function countPublicRouteEntries(source) {
2406
2696
  }
2407
2697
  var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
2408
2698
  function isRoutableSourceFile(name) {
2409
- return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.");
2699
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
2410
2700
  }
2411
2701
  function findRouterFilesRecursive(dir, filter, results) {
2412
2702
  let entries;
@@ -2421,9 +2711,10 @@ function findRouterFilesRecursive(dir, filter, results) {
2421
2711
  const childDirs = [];
2422
2712
  const routerFilesInDir = [];
2423
2713
  for (const entry of entries) {
2424
- const fullPath = join2(dir, entry.name);
2714
+ const fullPath = join(dir, entry.name);
2425
2715
  if (entry.isDirectory()) {
2426
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2716
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === "__tests__" || entry.name === "__mocks__" || entry.name.startsWith("."))
2717
+ continue;
2427
2718
  childDirs.push(fullPath);
2428
2719
  continue;
2429
2720
  }
@@ -2475,7 +2766,7 @@ Router root: ${conflict.ancestor}
2475
2766
  Nested router: ${conflict.nested}
2476
2767
  Move the nested router into a sibling directory or configure it as a separate app root.`;
2477
2768
  }
2478
- function extractUrlsVariableFromRouter(code) {
2769
+ function extractUrlsFromRouter(code) {
2479
2770
  const sourceFile = ts5.createSourceFile(
2480
2771
  "router.tsx",
2481
2772
  code,
@@ -2489,24 +2780,70 @@ function extractUrlsVariableFromRouter(code) {
2489
2780
  const callee = node.expression;
2490
2781
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
2491
2782
  }
2783
+ function isInlineBuilder(node) {
2784
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
2785
+ }
2786
+ function isRoutesOnCreateRouter(node) {
2787
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
2788
+ return false;
2789
+ let inner = node.expression.expression;
2790
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2791
+ inner = inner.expression.expression;
2792
+ }
2793
+ return isCreateRouterCall(inner);
2794
+ }
2492
2795
  function visit(node) {
2493
2796
  if (result) return;
2494
- if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
2495
- let inner = node.expression.expression;
2496
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2497
- inner = inner.expression.expression;
2498
- }
2499
- if (isCreateRouterCall(inner)) {
2500
- result = node.arguments[0].text;
2501
- return;
2797
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
2798
+ const arg = node.arguments[0];
2799
+ if (ts5.isIdentifier(arg)) {
2800
+ result = { kind: "variable", name: arg.text };
2801
+ } else if (isInlineBuilder(arg)) {
2802
+ result = { kind: "inline", block: arg.getText(sourceFile) };
2502
2803
  }
2804
+ return;
2503
2805
  }
2504
2806
  if (isCreateRouterCall(node)) {
2505
2807
  const callExpr = node;
2506
- for (const arg of callExpr.arguments) {
2808
+ for (const callArg of callExpr.arguments) {
2809
+ if (ts5.isObjectLiteralExpression(callArg)) {
2810
+ for (const prop of callArg.properties) {
2811
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
2812
+ if (ts5.isIdentifier(prop.initializer)) {
2813
+ result = { kind: "variable", name: prop.initializer.text };
2814
+ } else if (isInlineBuilder(prop.initializer)) {
2815
+ result = {
2816
+ kind: "inline",
2817
+ block: prop.initializer.getText(sourceFile)
2818
+ };
2819
+ }
2820
+ return;
2821
+ }
2822
+ }
2823
+ }
2824
+ }
2825
+ }
2826
+ ts5.forEachChild(node, visit);
2827
+ }
2828
+ visit(sourceFile);
2829
+ return result;
2830
+ }
2831
+ function extractBasenameFromRouter(code) {
2832
+ const sourceFile = ts5.createSourceFile(
2833
+ "router.tsx",
2834
+ code,
2835
+ ts5.ScriptTarget.Latest,
2836
+ true,
2837
+ ts5.ScriptKind.TSX
2838
+ );
2839
+ let result;
2840
+ function visit(node) {
2841
+ if (result !== void 0) return;
2842
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
2843
+ for (const arg of node.arguments) {
2507
2844
  if (ts5.isObjectLiteralExpression(arg)) {
2508
2845
  for (const prop of arg.properties) {
2509
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
2846
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
2510
2847
  result = prop.initializer.text;
2511
2848
  return;
2512
2849
  }
@@ -2519,6 +2856,19 @@ function extractUrlsVariableFromRouter(code) {
2519
2856
  visit(sourceFile);
2520
2857
  return result;
2521
2858
  }
2859
+ function applyBasenameToRoutes(result, basename3) {
2860
+ const prefixed = {};
2861
+ for (const [name, pattern] of Object.entries(result.routes)) {
2862
+ if (pattern === "/") {
2863
+ prefixed[name] = basename3;
2864
+ } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2865
+ prefixed[name] = basename3 + pattern.slice(1);
2866
+ } else {
2867
+ prefixed[name] = basename3 + pattern;
2868
+ }
2869
+ }
2870
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
2871
+ }
2522
2872
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2523
2873
  let routerSource;
2524
2874
  try {
@@ -2526,19 +2876,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2526
2876
  } catch {
2527
2877
  return { routes: {}, searchSchemas: {} };
2528
2878
  }
2529
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2530
- if (!urlsVarName) {
2879
+ const extraction = extractUrlsFromRouter(routerSource);
2880
+ if (!extraction) {
2531
2881
  return { routes: {}, searchSchemas: {} };
2532
2882
  }
2533
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2534
- if (imported) {
2535
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2536
- if (!targetFile) {
2537
- return { routes: {}, searchSchemas: {} };
2883
+ const rawBasename = extractBasenameFromRouter(routerSource);
2884
+ const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2885
+ let result;
2886
+ if (extraction.kind === "inline") {
2887
+ result = buildCombinedRouteMapWithSearch(
2888
+ routerFilePath,
2889
+ void 0,
2890
+ void 0,
2891
+ void 0,
2892
+ extraction.block
2893
+ );
2894
+ } else {
2895
+ const imported = resolveImportedVariable(routerSource, extraction.name);
2896
+ if (imported) {
2897
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2898
+ if (!targetFile) {
2899
+ return { routes: {}, searchSchemas: {} };
2900
+ }
2901
+ result = buildCombinedRouteMapWithSearch(
2902
+ targetFile,
2903
+ imported.exportedName
2904
+ );
2905
+ } else {
2906
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2538
2907
  }
2539
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2540
2908
  }
2541
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2909
+ if (basename3) {
2910
+ result = applyBasenameToRoutes(result, basename3);
2911
+ }
2912
+ return result;
2542
2913
  }
2543
2914
  function findRouterFiles(root, filter) {
2544
2915
  const result = [];
@@ -2547,7 +2918,7 @@ function findRouterFiles(root, filter) {
2547
2918
  }
2548
2919
  function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2549
2920
  try {
2550
- const oldCombinedPath = join2(root, "src", "named-routes.gen.ts");
2921
+ const oldCombinedPath = join(root, "src", "named-routes.gen.ts");
2551
2922
  if (existsSync3(oldCombinedPath)) {
2552
2923
  unlinkSync(oldCombinedPath);
2553
2924
  console.log(
@@ -2563,31 +2934,21 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2563
2934
  throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
2564
2935
  }
2565
2936
  for (const routerFilePath of routerFilePaths) {
2566
- let routerSource;
2567
- try {
2568
- routerSource = readFileSync2(routerFilePath, "utf-8");
2569
- } catch {
2570
- continue;
2571
- }
2572
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2573
- if (!urlsVarName) continue;
2574
- let result;
2575
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2576
- if (imported) {
2577
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2578
- if (!targetFile) continue;
2579
- result = buildCombinedRouteMapWithSearch(
2580
- targetFile,
2581
- imported.exportedName
2582
- );
2583
- } else {
2584
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2937
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
2938
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
2939
+ let routerSource;
2940
+ try {
2941
+ routerSource = readFileSync2(routerFilePath, "utf-8");
2942
+ } catch {
2943
+ continue;
2944
+ }
2945
+ if (!extractUrlsFromRouter(routerSource)) continue;
2585
2946
  }
2586
2947
  const routerBasename = pathBasename(routerFilePath).replace(
2587
2948
  /\.(tsx?|jsx?)$/,
2588
2949
  ""
2589
2950
  );
2590
- const outPath = join2(
2951
+ const outPath = join(
2591
2952
  dirname2(routerFilePath),
2592
2953
  `${routerBasename}.named-routes.gen.ts`
2593
2954
  );
@@ -2717,8 +3078,9 @@ function createVersionPlugin() {
2717
3078
  let isDev = false;
2718
3079
  let server = null;
2719
3080
  const clientModuleSignatures = /* @__PURE__ */ new Map();
3081
+ let versionCounter = 0;
2720
3082
  const bumpVersion = (reason) => {
2721
- currentVersion = Date.now().toString(16);
3083
+ currentVersion = Date.now().toString(16) + String(++versionCounter);
2722
3084
  console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
2723
3085
  const rscEnv = server?.environments?.rsc;
2724
3086
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
@@ -2774,6 +3136,9 @@ function createVersionPlugin() {
2774
3136
  if (!isDev) return;
2775
3137
  const isRscModule = this.environment?.name === "rsc";
2776
3138
  if (!isRscModule) return;
3139
+ if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
3140
+ return;
3141
+ }
2777
3142
  if (isCodeModule(ctx.file)) {
2778
3143
  const filePath = normalizeModuleId(ctx.file);
2779
3144
  const previousSignature = clientModuleSignatures.get(filePath);
@@ -2803,6 +3168,73 @@ function createVersionPlugin() {
2803
3168
 
2804
3169
  // src/vite/utils/shared-utils.ts
2805
3170
  import * as Vite from "vite";
3171
+
3172
+ // src/vite/plugins/performance-tracks.ts
3173
+ import { readFile } from "node:fs/promises";
3174
+ var debug6 = createRangoDebugger(NS.transform);
3175
+ var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
3176
+ function buildPatchReplacement(match, debugInfoVar) {
3177
+ return `${match}
3178
+ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
3179
+ var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
3180
+ if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
3181
+ ${debugInfoVar} = _resolved._debugInfo;
3182
+ }
3183
+ }`;
3184
+ }
3185
+ function patchRsdwClientDebugInfoRecovery(code) {
3186
+ const match = code.match(RSDW_PATCH_RE);
3187
+ if (!match) {
3188
+ return { code, debugInfoVar: null };
3189
+ }
3190
+ return {
3191
+ code: code.replace(match[1], buildPatchReplacement(match[1], match[2])),
3192
+ debugInfoVar: match[2]
3193
+ };
3194
+ }
3195
+ function performanceTracksOptimizeDepsPlugin() {
3196
+ return {
3197
+ name: "@rangojs/router:performance-tracks-optimize-deps",
3198
+ setup(build) {
3199
+ build.onLoad(
3200
+ {
3201
+ filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
3202
+ },
3203
+ async (args) => {
3204
+ const code = await readFile(args.path, "utf8");
3205
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3206
+ return {
3207
+ contents: patched.code,
3208
+ loader: "js"
3209
+ };
3210
+ }
3211
+ );
3212
+ }
3213
+ };
3214
+ }
3215
+ function performanceTracksPlugin() {
3216
+ const counter = createCounter(debug6, "performance-tracks");
3217
+ return {
3218
+ name: "@rangojs/router:performance-tracks",
3219
+ buildEnd() {
3220
+ counter?.flush();
3221
+ },
3222
+ transform(code, id) {
3223
+ if (!id.includes("react-server-dom") || !id.includes("client")) return;
3224
+ const start = counter ? performance.now() : 0;
3225
+ try {
3226
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3227
+ if (!patched.debugInfoVar) return;
3228
+ debug6?.("patched RSDW client (var: %s)", patched.debugInfoVar);
3229
+ return patched.code;
3230
+ } finally {
3231
+ counter?.record(id, performance.now() - start);
3232
+ }
3233
+ }
3234
+ };
3235
+ }
3236
+
3237
+ // src/vite/utils/shared-utils.ts
2806
3238
  var versionEsbuildPlugin = {
2807
3239
  name: "@rangojs/router-version",
2808
3240
  setup(build) {
@@ -2820,7 +3252,7 @@ var versionEsbuildPlugin = {
2820
3252
  }
2821
3253
  };
2822
3254
  var sharedEsbuildOptions = {
2823
- plugins: [versionEsbuildPlugin]
3255
+ plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
2824
3256
  };
2825
3257
  function createVirtualEntriesPlugin(entries, routerPathRef) {
2826
3258
  const virtualModules = {};
@@ -2903,11 +3335,11 @@ ${dim} \u2571${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2
2903
3335
  ${dim} ${reset}${bold}\u2551 \u2551${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2727. \u2571${reset}
2904
3336
  ${dim} ${reset}${bold}\u2554\u2557 \u2551 \u2551 \u2551 \u2551${reset}${dim} * \u2571${reset}
2905
3337
  ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2551 \u2551 \u2551 \u2566\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557${reset}${dim} \u2727 \u2726${reset}
2906
- ${dim} ${reset}${bold}\u2550\u2563\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
3338
+ ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
2907
3339
  ${dim} ${reset}${bold}\u2551\u255A\u2550\u255D \u2554\u2550\u2550\u2550\u255D \u2569\u255A\u2550\u2569 \u2569\u255D\u255A\u255D\u255A\u2550\u255D\u255A\u2550\u255D${reset}${dim} \u2726 . *${reset}
2908
3340
  ${dim} ${reset}${bold}\u255A\u2550\u2550\u2557 \u2551${reset}${dim} * RSC Wrangler \u2727 \u2726${reset}
2909
- ${dim} * ${reset}${bold}\u2551 \u2560\u2550${reset}${dim} * \u2727. \u2571${reset}
2910
- ${bold}\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
3341
+ ${dim} * ${reset}${bold}\u2551 \u2551${reset}${dim} * \u2727. \u2571${reset}
3342
+ ${dim} ${reset}${bold}\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
2911
3343
 
2912
3344
  v${version} \xB7 ${preset} \xB7 ${mode}
2913
3345
  `;
@@ -2968,6 +3400,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
2968
3400
  }
2969
3401
 
2970
3402
  // src/vite/plugins/cjs-to-esm.ts
3403
+ var debug7 = createRangoDebugger(NS.transform);
2971
3404
  function createCjsToEsmPlugin() {
2972
3405
  return {
2973
3406
  name: "@rangojs/router:cjs-to-esm",
@@ -2977,6 +3410,7 @@ function createCjsToEsmPlugin() {
2977
3410
  if (cleanId.includes("vendor/react-server-dom/client.browser.js") || cleanId.includes("vendor\\react-server-dom\\client.browser.js")) {
2978
3411
  const isProd = process.env.NODE_ENV === "production";
2979
3412
  const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
3413
+ debug7?.("cjs-to-esm entry redirect %s", id);
2980
3414
  return {
2981
3415
  code: `export * from "${cjsFile}";`,
2982
3416
  map: null
@@ -3012,6 +3446,7 @@ function createCjsToEsmPlugin() {
3012
3446
  "export const $1 ="
3013
3447
  );
3014
3448
  transformed = license + "\n" + transformed;
3449
+ debug7?.("cjs-to-esm body rewrite %s", id);
3015
3450
  return {
3016
3451
  code: transformed,
3017
3452
  map: null
@@ -3026,6 +3461,8 @@ function createCjsToEsmPlugin() {
3026
3461
  import { createServer as createViteServer } from "vite";
3027
3462
  import { resolve as resolve8 } from "node:path";
3028
3463
  import { readFileSync as readFileSync6 } from "node:fs";
3464
+ import { createRequire as createRequire2, register } from "node:module";
3465
+ import { pathToFileURL } from "node:url";
3029
3466
 
3030
3467
  // src/vite/plugins/virtual-stub-plugin.ts
3031
3468
  function createVirtualStubPlugin() {
@@ -3051,9 +3488,117 @@ function createVirtualStubPlugin() {
3051
3488
  };
3052
3489
  }
3053
3490
 
3491
+ // src/vite/plugins/cloudflare-protocol-stub.ts
3492
+ var VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
3493
+ var NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
3494
+ var CF_PREFIX = "cloudflare:";
3495
+ var BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
3496
+ var SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
3497
+ var IMPORT_NODE_TYPES = /* @__PURE__ */ new Set([
3498
+ "ImportDeclaration",
3499
+ "ImportExpression",
3500
+ "ExportNamedDeclaration",
3501
+ "ExportAllDeclaration"
3502
+ ]);
3503
+ var STUBS = {
3504
+ "cloudflare:workers": `
3505
+ export class DurableObject { constructor(_ctx, _env) {} }
3506
+ export class WorkerEntrypoint { constructor(_ctx, _env) {} }
3507
+ export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
3508
+ export class RpcTarget {}
3509
+ export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
3510
+ export default {};
3511
+ `,
3512
+ "cloudflare:email": `
3513
+ export class EmailMessage { constructor(_from, _to, _raw) {} }
3514
+ export default {};
3515
+ `,
3516
+ "cloudflare:sockets": `
3517
+ export function connect() { return {}; }
3518
+ export default {};
3519
+ `,
3520
+ "cloudflare:workflows": `
3521
+ export class NonRetryableError extends Error {
3522
+ constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
3523
+ }
3524
+ export default {};
3525
+ `
3526
+ };
3527
+ var FALLBACK_STUB = `export default {};
3528
+ `;
3529
+ function createCloudflareProtocolStubPlugin() {
3530
+ return {
3531
+ name: "@rangojs/router:cloudflare-protocol-stub",
3532
+ transform(code, id) {
3533
+ const cleanId = id.split("?")[0] ?? id;
3534
+ if (!SOURCE_EXT_RE.test(cleanId)) return null;
3535
+ if (!code.includes(CF_PREFIX)) return null;
3536
+ let ast;
3537
+ try {
3538
+ ast = this.parse(code);
3539
+ } catch {
3540
+ return null;
3541
+ }
3542
+ const hits = [];
3543
+ walk(ast, (node) => {
3544
+ if (!IMPORT_NODE_TYPES.has(node.type)) return;
3545
+ const source = node.source;
3546
+ if (!source || source.type !== "Literal") return;
3547
+ if (typeof source.value !== "string") return;
3548
+ if (!source.value.startsWith(CF_PREFIX)) return;
3549
+ if (typeof source.start !== "number" || typeof source.end !== "number")
3550
+ return;
3551
+ hits.push({
3552
+ start: source.start,
3553
+ end: source.end,
3554
+ value: source.value
3555
+ });
3556
+ });
3557
+ if (hits.length === 0) return null;
3558
+ hits.sort((a, b) => b.start - a.start);
3559
+ let out = code;
3560
+ for (const hit of hits) {
3561
+ const submodule = hit.value.slice(CF_PREFIX.length);
3562
+ const quote = code[hit.start] === "'" ? "'" : '"';
3563
+ out = out.slice(0, hit.start) + quote + VIRTUAL_PREFIX + submodule + quote + out.slice(hit.end);
3564
+ }
3565
+ return { code: out, map: null };
3566
+ },
3567
+ resolveId(id) {
3568
+ if (id.startsWith(VIRTUAL_PREFIX)) {
3569
+ return "\0" + id;
3570
+ }
3571
+ return null;
3572
+ },
3573
+ load(id) {
3574
+ if (!id.startsWith(NULL_PREFIX)) return null;
3575
+ const submodule = id.slice(NULL_PREFIX.length);
3576
+ const specifier = CF_PREFIX + submodule;
3577
+ return STUBS[specifier] ?? FALLBACK_STUB;
3578
+ }
3579
+ };
3580
+ }
3581
+ function walk(node, visit) {
3582
+ if (!node || typeof node !== "object") return;
3583
+ if (Array.isArray(node)) {
3584
+ for (const child of node) walk(child, visit);
3585
+ return;
3586
+ }
3587
+ const n = node;
3588
+ if (typeof n.type !== "string") return;
3589
+ visit(n);
3590
+ for (const key in n) {
3591
+ if (key === "loc" || key === "start" || key === "end" || key === "range") {
3592
+ continue;
3593
+ }
3594
+ walk(n[key], visit);
3595
+ }
3596
+ }
3597
+
3054
3598
  // src/vite/plugins/client-ref-hashing.ts
3055
- import { relative as relative2 } from "node:path";
3599
+ import { relative } from "node:path";
3056
3600
  import { createHash as createHash2 } from "node:crypto";
3601
+ var debug8 = createRangoDebugger(NS.transform);
3057
3602
  var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3058
3603
  var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3059
3604
  var FS_PREFIX = "/@fs/";
@@ -3065,10 +3610,10 @@ function computeProductionHash(projectRoot, refKey) {
3065
3610
  const absPath = decodeURIComponent(
3066
3611
  refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3067
3612
  );
3068
- toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3613
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3069
3614
  } else if (refKey.startsWith(FS_PREFIX)) {
3070
3615
  const absPath = refKey.slice(FS_PREFIX.length - 1);
3071
- toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3616
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3072
3617
  } else if (refKey.startsWith("/")) {
3073
3618
  toHash = refKey.slice(1);
3074
3619
  } else {
@@ -3092,6 +3637,7 @@ function transformClientRefs(code, projectRoot) {
3092
3637
  return hasReplacement ? result : null;
3093
3638
  }
3094
3639
  function hashClientRefs(projectRoot) {
3640
+ const counter = createCounter(debug8, "hash-client-refs");
3095
3641
  return {
3096
3642
  name: "@rangojs/router:hash-client-refs",
3097
3643
  // Run after the RSC plugin's transform (default enforce is normal)
@@ -3099,10 +3645,18 @@ function hashClientRefs(projectRoot) {
3099
3645
  applyToEnvironment(env) {
3100
3646
  return env.name === "rsc";
3101
3647
  },
3102
- transform(code, _id) {
3103
- const result = transformClientRefs(code, projectRoot);
3104
- if (result === null) return;
3105
- return { code: result, map: null };
3648
+ buildEnd() {
3649
+ counter?.flush();
3650
+ },
3651
+ transform(code, id) {
3652
+ const start = counter ? performance.now() : 0;
3653
+ try {
3654
+ const result = transformClientRefs(code, projectRoot);
3655
+ if (result === null) return;
3656
+ return { code: result, map: null };
3657
+ } finally {
3658
+ counter?.record(id, performance.now() - start);
3659
+ }
3106
3660
  }
3107
3661
  };
3108
3662
  }
@@ -3209,8 +3763,8 @@ function createDiscoveryState(entryPath, opts) {
3209
3763
  perRouterManifestDataMap: /* @__PURE__ */ new Map(),
3210
3764
  prerenderManifestEntries: null,
3211
3765
  staticManifestEntries: null,
3212
- handlerChunkInfo: null,
3213
- staticHandlerChunkInfo: null,
3766
+ handlerChunkInfoMap: /* @__PURE__ */ new Map(),
3767
+ staticHandlerChunkInfoMap: /* @__PURE__ */ new Map(),
3214
3768
  rscEntryFileName: null,
3215
3769
  resolvedPrerenderModules: void 0,
3216
3770
  resolvedStaticModules: void 0,
@@ -3293,8 +3847,17 @@ function jsonParseExpression(value) {
3293
3847
  }
3294
3848
 
3295
3849
  // src/context-var.ts
3850
+ var NON_CACHEABLE_KEYS = /* @__PURE__ */ Symbol.for(
3851
+ "rango:non-cacheable-keys"
3852
+ );
3853
+ function getNonCacheableKeys(variables) {
3854
+ if (!variables[NON_CACHEABLE_KEYS]) {
3855
+ variables[NON_CACHEABLE_KEYS] = /* @__PURE__ */ new Set();
3856
+ }
3857
+ return variables[NON_CACHEABLE_KEYS];
3858
+ }
3296
3859
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3297
- function contextSet(variables, keyOrVar, value) {
3860
+ function contextSet(variables, keyOrVar, value, options) {
3298
3861
  if (typeof keyOrVar === "string") {
3299
3862
  if (FORBIDDEN_KEYS.has(keyOrVar)) {
3300
3863
  throw new Error(
@@ -3302,8 +3865,14 @@ function contextSet(variables, keyOrVar, value) {
3302
3865
  );
3303
3866
  }
3304
3867
  variables[keyOrVar] = value;
3868
+ if (options?.cache === false) {
3869
+ getNonCacheableKeys(variables).add(keyOrVar);
3870
+ }
3305
3871
  } else {
3306
3872
  variables[keyOrVar.key] = value;
3873
+ if (options?.cache === false) {
3874
+ getNonCacheableKeys(variables).add(keyOrVar.key);
3875
+ }
3307
3876
  }
3308
3877
  }
3309
3878
 
@@ -3326,13 +3895,31 @@ function encodePathParam(value) {
3326
3895
  }
3327
3896
  function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3328
3897
  let result = pattern;
3898
+ let hadOmittedOptional = false;
3329
3899
  for (const [key, value] of Object.entries(params)) {
3330
3900
  const escaped = escapeRegExp2(key);
3331
- result = result.replace(
3332
- new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3333
- encode(value)
3334
- );
3335
- result = result.replace(`*${key}`, encode(value));
3901
+ if (value === "") {
3902
+ result = result.replace(
3903
+ new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
3904
+ ""
3905
+ );
3906
+ result = result.replace(`*${key}`, "");
3907
+ } else {
3908
+ result = result.replace(
3909
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3910
+ encode(value)
3911
+ );
3912
+ result = result.replace(`*${key}`, encode(value));
3913
+ }
3914
+ }
3915
+ result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3916
+ hadOmittedOptional = true;
3917
+ return "";
3918
+ });
3919
+ if (hadOmittedOptional) {
3920
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
3921
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
3922
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
3336
3923
  }
3337
3924
  return result;
3338
3925
  }
@@ -3429,8 +4016,14 @@ function copyStagedBuildAssets(projectRoot, fileNames) {
3429
4016
  }
3430
4017
 
3431
4018
  // src/vite/discovery/prerender-collection.ts
4019
+ var debug9 = createRangoDebugger(NS.prerender);
3432
4020
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3433
4021
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode) return;
4022
+ const overallStart = debug9 ? performance.now() : 0;
4023
+ debug9?.(
4024
+ "expandPrerenderRoutes: start (%d router manifest(s))",
4025
+ allManifests.length
4026
+ );
3434
4027
  const entries = [];
3435
4028
  const allRoutes = {};
3436
4029
  for (const { manifest: m } of allManifests) {
@@ -3442,91 +4035,151 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3442
4035
  if (!params) return pattern;
3443
4036
  return substituteRouteParams(pattern, params);
3444
4037
  };
4038
+ let resolvedRoutes = 0;
4039
+ let totalDynamic = 0;
3445
4040
  for (const { manifest } of allManifests) {
3446
4041
  if (!manifest.prerenderRoutes) continue;
3447
- const defs = manifest._prerenderDefs || {};
3448
4042
  for (const routeName of manifest.prerenderRoutes) {
3449
4043
  const pattern = manifest.routeManifest[routeName];
3450
- if (!pattern) continue;
3451
- const def = defs[routeName];
3452
- const isPassthroughRoute = !!def?.options?.passthrough;
3453
- const hasDynamic = pattern.includes(":") || pattern.includes("*");
3454
- if (!hasDynamic) {
3455
- entries.push({
3456
- urlPath: pattern.replace(/\/$/, "") || "/",
3457
- routeName,
3458
- concurrency: 1,
3459
- isPassthroughRoute
3460
- });
3461
- } else {
3462
- if (def?.getParams) {
3463
- try {
3464
- const buildVars = {};
3465
- const getParamsCtx = {
3466
- build: true,
3467
- set: ((keyOrVar, value) => {
3468
- contextSet(buildVars, keyOrVar, value);
3469
- }),
3470
- reverse: getParamsReverse
3471
- };
3472
- const paramsList = await def.getParams(getParamsCtx);
3473
- const concurrency = def.options?.concurrency ?? 1;
3474
- const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3475
- for (const params of paramsList) {
3476
- let url = substituteRouteParams(
3477
- pattern,
3478
- params,
3479
- encodePathParam
3480
- );
3481
- if (url.includes("*")) {
3482
- const wildcardValue = params["*"] ?? params.splat;
3483
- if (wildcardValue !== void 0) {
3484
- url = url.replace(/\*[^/]*$/, encodePathParam(wildcardValue));
4044
+ if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
4045
+ totalDynamic++;
4046
+ }
4047
+ }
4048
+ }
4049
+ const paramsStart = performance.now();
4050
+ const progressInterval = totalDynamic > 0 ? setInterval(() => {
4051
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4052
+ console.log(
4053
+ `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4054
+ );
4055
+ }, 5e3) : void 0;
4056
+ try {
4057
+ for (const { manifest } of allManifests) {
4058
+ if (!manifest.prerenderRoutes) continue;
4059
+ const defs = manifest._prerenderDefs || {};
4060
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
4061
+ for (const routeName of manifest.prerenderRoutes) {
4062
+ const pattern = manifest.routeManifest[routeName];
4063
+ if (!pattern) continue;
4064
+ const def = defs[routeName];
4065
+ const isPassthroughRoute = passthroughSet.has(routeName);
4066
+ const hasDynamic = pattern.includes(":") || pattern.includes("*");
4067
+ if (!hasDynamic) {
4068
+ entries.push({
4069
+ urlPath: pattern.replace(/\/$/, "") || "/",
4070
+ routeName,
4071
+ concurrency: 1,
4072
+ isPassthroughRoute
4073
+ });
4074
+ } else {
4075
+ if (def?.getParams) {
4076
+ const getParamsStart = debug9 ? performance.now() : 0;
4077
+ try {
4078
+ const buildVars = {};
4079
+ const buildEnv = state.resolvedBuildEnv;
4080
+ const getParamsCtx = {
4081
+ build: true,
4082
+ dev: !state.isBuildMode,
4083
+ set: ((keyOrVar, value) => {
4084
+ contextSet(buildVars, keyOrVar, value);
4085
+ }),
4086
+ reverse: getParamsReverse,
4087
+ get env() {
4088
+ if (buildEnv !== void 0) return buildEnv;
4089
+ throw new Error(
4090
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
4091
+ );
3485
4092
  }
3486
- }
3487
- entries.push({
3488
- urlPath: url.replace(/\/$/, "") || "/",
4093
+ };
4094
+ const paramsList = await def.getParams(getParamsCtx);
4095
+ debug9?.(
4096
+ "getParams %s -> %d params (%sms)",
3489
4097
  routeName,
3490
- concurrency,
3491
- ...hasBuildVars ? { buildVars } : {},
3492
- isPassthroughRoute
3493
- });
3494
- }
3495
- } catch (err) {
3496
- if (err.name === "Skip") {
3497
- console.log(
3498
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
4098
+ paramsList.length,
4099
+ (performance.now() - getParamsStart).toFixed(1)
3499
4100
  );
3500
- notifyOnError(
3501
- registry,
3502
- err,
3503
- "prerender",
3504
- routeName,
3505
- void 0,
3506
- true
4101
+ const concurrency = def.options?.concurrency ?? 1;
4102
+ const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
4103
+ for (const params of paramsList) {
4104
+ let url = substituteRouteParams(
4105
+ pattern,
4106
+ params,
4107
+ encodePathParam
4108
+ );
4109
+ if (url.includes("*")) {
4110
+ const wildcardValue = params["*"] ?? params.splat;
4111
+ if (wildcardValue !== void 0) {
4112
+ url = url.replace(
4113
+ /\*[^/]*$/,
4114
+ encodePathParam(wildcardValue)
4115
+ );
4116
+ }
4117
+ }
4118
+ entries.push({
4119
+ urlPath: url.replace(/\/$/, "") || "/",
4120
+ routeName,
4121
+ concurrency,
4122
+ ...hasBuildVars ? { buildVars } : {},
4123
+ isPassthroughRoute
4124
+ });
4125
+ }
4126
+ resolvedRoutes++;
4127
+ } catch (err) {
4128
+ resolvedRoutes++;
4129
+ if (err.name === "Skip") {
4130
+ console.log(
4131
+ `[rsc-router] SKIP route "${routeName}" - ${err.message}`
4132
+ );
4133
+ notifyOnError(
4134
+ registry,
4135
+ err,
4136
+ "prerender",
4137
+ routeName,
4138
+ void 0,
4139
+ true
4140
+ );
4141
+ continue;
4142
+ }
4143
+ console.error(
4144
+ `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3507
4145
  );
3508
- continue;
4146
+ notifyOnError(registry, err, "prerender", routeName);
4147
+ throw err;
3509
4148
  }
3510
- console.error(
3511
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
4149
+ } else {
4150
+ console.warn(
4151
+ `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3512
4152
  );
3513
- notifyOnError(registry, err, "prerender", routeName);
3514
- throw err;
3515
4153
  }
3516
- } else {
3517
- console.warn(
3518
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3519
- );
3520
4154
  }
3521
4155
  }
3522
4156
  }
4157
+ } finally {
4158
+ if (progressInterval) {
4159
+ clearInterval(progressInterval);
4160
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4161
+ console.log(
4162
+ `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4163
+ );
4164
+ }
4165
+ }
4166
+ if (entries.length === 0) {
4167
+ debug9?.(
4168
+ "no prerender entries (done in %sms)",
4169
+ (performance.now() - overallStart).toFixed(1)
4170
+ );
4171
+ return;
3523
4172
  }
3524
- if (entries.length === 0) return;
3525
4173
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
3526
4174
  const concurrencyNote = maxConcurrency > 1 ? ` (concurrency: ${maxConcurrency})` : "";
3527
4175
  console.log(
3528
4176
  `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
3529
4177
  );
4178
+ debug9?.(
4179
+ "prerender loop: %d entries, max concurrency %d",
4180
+ entries.length,
4181
+ maxConcurrency
4182
+ );
3530
4183
  const { hashParams } = await rscEnv.runner.import("@rangojs/router/build");
3531
4184
  const manifestEntries = {};
3532
4185
  let doneCount = 0;
@@ -3546,7 +4199,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3546
4199
  entry.urlPath,
3547
4200
  {},
3548
4201
  entry.buildVars,
3549
- entry.isPassthroughRoute
4202
+ entry.isPassthroughRoute,
4203
+ state.resolvedBuildEnv
3550
4204
  );
3551
4205
  if (!result) continue;
3552
4206
  if (result.passthrough) {
@@ -3632,10 +4286,22 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3632
4286
  console.log(
3633
4287
  `[rsc-router] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
3634
4288
  );
4289
+ debug9?.(
4290
+ "expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
4291
+ doneCount,
4292
+ skipCount,
4293
+ totalElapsed,
4294
+ (performance.now() - overallStart).toFixed(1)
4295
+ );
3635
4296
  }
3636
4297
  async function renderStaticHandlers(state, rscEnv, registry) {
3637
4298
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode || !state.resolvedStaticModules?.size)
3638
4299
  return;
4300
+ const overallStart = debug9 ? performance.now() : 0;
4301
+ debug9?.(
4302
+ "renderStaticHandlers: start (%d static module(s))",
4303
+ state.resolvedStaticModules.size
4304
+ );
3639
4305
  const manifestEntries = {};
3640
4306
  let staticDone = 0;
3641
4307
  let staticSkip = 0;
@@ -3670,7 +4336,9 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3670
4336
  const result = await routerInstance.renderStaticSegment(
3671
4337
  def.handler,
3672
4338
  def.$$id,
3673
- def.$$routePrefix
4339
+ def.$$routePrefix,
4340
+ state.resolvedBuildEnv,
4341
+ !state.isBuildMode
3674
4342
  );
3675
4343
  if (result) {
3676
4344
  const hasHandles = Object.keys(result.handles).length > 0;
@@ -3723,13 +4391,29 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3723
4391
  console.log(
3724
4392
  `[rsc-router] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
3725
4393
  );
4394
+ debug9?.(
4395
+ "renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
4396
+ staticDone,
4397
+ staticSkip,
4398
+ totalStaticElapsed,
4399
+ (performance.now() - overallStart).toFixed(1)
4400
+ );
3726
4401
  }
3727
4402
 
3728
4403
  // src/vite/discovery/discover-routers.ts
4404
+ var debug10 = createRangoDebugger(NS.discovery);
3729
4405
  async function discoverRouters(state, rscEnv) {
3730
4406
  if (!state.resolvedEntryPath) return;
3731
- await rscEnv.runner.import(state.resolvedEntryPath);
3732
- const serverMod = await rscEnv.runner.import("@rangojs/router/server");
4407
+ await timed(
4408
+ debug10,
4409
+ "inner: import entry",
4410
+ () => rscEnv.runner.import(state.resolvedEntryPath)
4411
+ );
4412
+ const serverMod = await timed(
4413
+ debug10,
4414
+ "inner: import @rangojs/router/server",
4415
+ () => rscEnv.runner.import("@rangojs/router/server")
4416
+ );
3733
4417
  let registry = serverMod.RouterRegistry;
3734
4418
  if (!registry || registry.size === 0) {
3735
4419
  try {
@@ -3771,8 +4455,13 @@ async function discoverRouters(state, rscEnv) {
3771
4455
  );
3772
4456
  }
3773
4457
  }
3774
- const buildMod = await rscEnv.runner.import("@rangojs/router/build");
4458
+ const buildMod = await timed(
4459
+ debug10,
4460
+ "inner: import @rangojs/router/build",
4461
+ () => rscEnv.runner.import("@rangojs/router/build")
4462
+ );
3775
4463
  const generateManifestFull = buildMod.generateManifestFull;
4464
+ debug10?.("inner: found %d router(s) in registry", registry.size);
3776
4465
  const nestedRouterConflict = findNestedRouterConflict(
3777
4466
  [...registry.values()].map((router) => router.__sourceFile).filter(
3778
4467
  (sourceFile) => typeof sourceFile === "string"
@@ -3791,11 +4480,16 @@ async function discoverRouters(state, rscEnv) {
3791
4480
  let mergedRouteTrailingSlash = {};
3792
4481
  let routerMountIndex = 0;
3793
4482
  const allManifests = [];
4483
+ const manifestGenStart = debug10 ? performance.now() : 0;
3794
4484
  for (const [id, router] of registry) {
3795
4485
  if (!router.urlpatterns || !generateManifestFull) {
3796
4486
  continue;
3797
4487
  }
3798
- const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
4488
+ const manifest = generateManifestFull(
4489
+ router.urlpatterns,
4490
+ routerMountIndex,
4491
+ router.__basename ? { urlPrefix: router.__basename } : void 0
4492
+ );
3799
4493
  routerMountIndex++;
3800
4494
  allManifests.push({ id, manifest });
3801
4495
  const routeCount = Object.keys(manifest.routeManifest).length;
@@ -3861,7 +4555,13 @@ async function discoverRouters(state, rscEnv) {
3861
4555
  );
3862
4556
  }
3863
4557
  }
4558
+ debug10?.(
4559
+ "inner: generated manifests for %d router(s) (%sms)",
4560
+ allManifests.length,
4561
+ (performance.now() - manifestGenStart).toFixed(1)
4562
+ );
3864
4563
  let newMergedRouteTrie = null;
4564
+ const trieStart = debug10 ? performance.now() : 0;
3865
4565
  if (Object.keys(newMergedRouteManifest).length > 0) {
3866
4566
  const buildRouteTrie = buildMod.buildRouteTrie;
3867
4567
  if (buildRouteTrie && mergedRouteAncestry) {
@@ -3924,6 +4624,10 @@ async function discoverRouters(state, rscEnv) {
3924
4624
  }
3925
4625
  }
3926
4626
  }
4627
+ debug10?.(
4628
+ "inner: trie build done (%sms)",
4629
+ (performance.now() - trieStart).toFixed(1)
4630
+ );
3927
4631
  state.mergedRouteManifest = newMergedRouteManifest;
3928
4632
  state.mergedPrecomputedEntries = newMergedPrecomputedEntries;
3929
4633
  state.perRouterManifests = newPerRouterManifests;
@@ -3937,7 +4641,7 @@ async function discoverRouters(state, rscEnv) {
3937
4641
  }
3938
4642
 
3939
4643
  // src/vite/discovery/route-types-writer.ts
3940
- import { dirname as dirname3, basename, join as join3, resolve as resolve6 } from "node:path";
4644
+ import { dirname as dirname3, basename, join as join2, resolve as resolve6 } from "node:path";
3941
4645
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
3942
4646
  function filterUserNamedRoutes(manifest) {
3943
4647
  const filtered = {};
@@ -3958,7 +4662,7 @@ function writeCombinedRouteTypesWithTracking(state, opts) {
3958
4662
  /\.(tsx?|jsx?)$/,
3959
4663
  ""
3960
4664
  );
3961
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4665
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
3962
4666
  try {
3963
4667
  preContent.set(outPath, readFileSync4(outPath, "utf-8"));
3964
4668
  } catch {
@@ -3971,7 +4675,7 @@ function writeCombinedRouteTypesWithTracking(state, opts) {
3971
4675
  /\.(tsx?|jsx?)$/,
3972
4676
  ""
3973
4677
  );
3974
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4678
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
3975
4679
  if (!existsSync5(outPath)) continue;
3976
4680
  try {
3977
4681
  const content = readFileSync4(outPath, "utf-8");
@@ -3988,7 +4692,7 @@ function writeRouteTypesFiles(state) {
3988
4692
  const entryDir = dirname3(
3989
4693
  resolve6(state.projectRoot, state.resolvedEntryPath)
3990
4694
  );
3991
- const oldCombinedPath = join3(entryDir, "named-routes.gen.ts");
4695
+ const oldCombinedPath = join2(entryDir, "named-routes.gen.ts");
3992
4696
  if (existsSync5(oldCombinedPath)) {
3993
4697
  unlinkSync2(oldCombinedPath);
3994
4698
  console.log(
@@ -4013,7 +4717,7 @@ Set an explicit \`id\` on createRouter() or check the call site.`
4013
4717
  }
4014
4718
  const routerDir = dirname3(sourceFile);
4015
4719
  const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4016
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4720
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4017
4721
  const userRoutes = filterUserNamedRoutes(routeManifest);
4018
4722
  let effectiveSearchSchemas = routeSearchSchemas;
4019
4723
  if ((!effectiveSearchSchemas || Object.keys(effectiveSearchSchemas).length === 0) && sourceFile) {
@@ -4078,7 +4782,7 @@ function supplementGenFilesWithRuntimeRoutes(state) {
4078
4782
  }
4079
4783
  const routerDir = dirname3(sourceFile);
4080
4784
  const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4081
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4785
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4082
4786
  const source = generateRouteTypesSource(
4083
4787
  mergedRoutes,
4084
4788
  Object.keys(mergedSearchSchemas).length > 0 ? mergedSearchSchemas : void 0
@@ -4092,7 +4796,7 @@ function supplementGenFilesWithRuntimeRoutes(state) {
4092
4796
  }
4093
4797
 
4094
4798
  // src/vite/discovery/virtual-module-codegen.ts
4095
- import { dirname as dirname4, basename as basename2, join as join4 } from "node:path";
4799
+ import { dirname as dirname4, basename as basename2, join as join3 } from "node:path";
4096
4800
  function generateRoutesManifestModule(state) {
4097
4801
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
4098
4802
  if (hasManifest) {
@@ -4107,7 +4811,7 @@ function generateRoutesManifestModule(state) {
4107
4811
  /\.(tsx?|jsx?)$/,
4108
4812
  ""
4109
4813
  );
4110
- const genPath = join4(
4814
+ const genPath = join3(
4111
4815
  routerDir,
4112
4816
  `${routerBasename}.named-routes.gen.js`
4113
4817
  ).replaceAll("\\", "/");
@@ -4204,7 +4908,7 @@ function generatePerRouterModule(state, routerId) {
4204
4908
  /\.(tsx?|jsx?)$/,
4205
4909
  ""
4206
4910
  );
4207
- const genPath = join4(
4911
+ const genPath = join3(
4208
4912
  routerDir,
4209
4913
  `${routerBasename}.named-routes.gen.js`
4210
4914
  ).replaceAll("\\", "/");
@@ -4244,48 +4948,45 @@ function postprocessBundle(state) {
4244
4948
  );
4245
4949
  const evictionTargets = [
4246
4950
  {
4247
- info: state.handlerChunkInfo,
4951
+ infos: state.handlerChunkInfoMap.values(),
4248
4952
  fnName: "Prerender",
4249
4953
  brand: "prerenderHandler",
4250
4954
  label: "handler code from RSC bundle"
4251
4955
  },
4252
4956
  {
4253
- info: state.staticHandlerChunkInfo,
4957
+ infos: state.staticHandlerChunkInfoMap.values(),
4254
4958
  fnName: "Static",
4255
4959
  brand: "staticHandler",
4256
4960
  label: "static handler code"
4257
4961
  }
4258
4962
  ];
4259
4963
  for (const target of evictionTargets) {
4260
- if (!target.info) continue;
4261
- const chunkPath = resolve7(
4262
- state.projectRoot,
4263
- "dist/rsc",
4264
- target.info.fileName
4265
- );
4266
- try {
4267
- const code = readFileSync5(chunkPath, "utf-8");
4268
- const result = evictHandlerCode(
4269
- code,
4270
- target.info.exports,
4271
- target.fnName,
4272
- target.brand
4273
- );
4274
- if (result) {
4275
- writeFileSync4(chunkPath, result.code);
4276
- const savedKB = (result.savedBytes / 1024).toFixed(1);
4277
- console.log(
4278
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${target.info.fileName}`
4964
+ for (const info of target.infos) {
4965
+ const chunkPath = resolve7(state.projectRoot, "dist/rsc", info.fileName);
4966
+ try {
4967
+ const code = readFileSync5(chunkPath, "utf-8");
4968
+ const result = evictHandlerCode(
4969
+ code,
4970
+ info.exports,
4971
+ target.fnName,
4972
+ target.brand
4973
+ );
4974
+ if (result) {
4975
+ writeFileSync4(chunkPath, result.code);
4976
+ const savedKB = (result.savedBytes / 1024).toFixed(1);
4977
+ console.log(
4978
+ `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4979
+ );
4980
+ }
4981
+ } catch (replaceErr) {
4982
+ console.warn(
4983
+ `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4279
4984
  );
4280
4985
  }
4281
- } catch (replaceErr) {
4282
- console.warn(
4283
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4284
- );
4285
4986
  }
4286
4987
  }
4287
- state.handlerChunkInfo = null;
4288
- state.staticHandlerChunkInfo = null;
4988
+ state.handlerChunkInfoMap.clear();
4989
+ state.staticHandlerChunkInfoMap.clear();
4289
4990
  if (hasPrerenderData && existsSync6(rscEntryPath)) {
4290
4991
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4291
4992
  if (!rscCode.includes("__prerender-manifest.js")) {
@@ -4328,7 +5029,7 @@ function postprocessBundle(state) {
4328
5029
  }
4329
5030
  if (hasStaticData && existsSync6(rscEntryPath)) {
4330
5031
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4331
- if (!rscCode.includes("__STATIC_MANIFEST")) {
5032
+ if (!rscCode.includes("__static-manifest.js")) {
4332
5033
  try {
4333
5034
  const manifestEntries = [];
4334
5035
  let totalBytes = copyStagedBuildAssets(
@@ -4367,7 +5068,26 @@ function postprocessBundle(state) {
4367
5068
  }
4368
5069
 
4369
5070
  // src/vite/router-discovery.ts
5071
+ var debugDiscovery = createRangoDebugger(NS.discovery);
5072
+ var debugRoutes = createRangoDebugger(NS.routes);
5073
+ var debugBuild = createRangoDebugger(NS.build);
5074
+ var debugDev = createRangoDebugger(NS.dev);
5075
+ var loaderHookRegistered = false;
5076
+ function ensureCloudflareProtocolLoaderRegistered() {
5077
+ if (loaderHookRegistered) return;
5078
+ loaderHookRegistered = true;
5079
+ try {
5080
+ register(
5081
+ new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url)
5082
+ );
5083
+ } catch (err) {
5084
+ console.warn(
5085
+ `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5086
+ );
5087
+ }
5088
+ }
4370
5089
  async function createTempRscServer(state, options = {}) {
5090
+ ensureCloudflareProtocolLoaderRegistered();
4371
5091
  const { default: rsc } = await import("@vitejs/plugin-rsc");
4372
5092
  return createViteServer({
4373
5093
  root: state.projectRoot,
@@ -4390,6 +5110,7 @@ async function createTempRscServer(state, options = {}) {
4390
5110
  ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
4391
5111
  createVersionPlugin(),
4392
5112
  createVirtualStubPlugin(),
5113
+ createCloudflareProtocolStubPlugin(),
4393
5114
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
4394
5115
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
4395
5116
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
@@ -4397,8 +5118,69 @@ async function createTempRscServer(state, options = {}) {
4397
5118
  ]
4398
5119
  });
4399
5120
  }
5121
+ async function resolveBuildEnv(option, factoryCtx) {
5122
+ if (!option) return null;
5123
+ if (option === "auto") {
5124
+ if (factoryCtx.preset !== "cloudflare") {
5125
+ throw new Error(
5126
+ '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
5127
+ );
5128
+ }
5129
+ try {
5130
+ const userRequire = createRequire2(
5131
+ resolve8(factoryCtx.root, "package.json")
5132
+ );
5133
+ const wranglerPath = userRequire.resolve("wrangler");
5134
+ const { getPlatformProxy } = await import(pathToFileURL(wranglerPath).href);
5135
+ const proxy = await getPlatformProxy();
5136
+ return {
5137
+ env: proxy.env,
5138
+ dispose: proxy.dispose
5139
+ };
5140
+ } catch (err) {
5141
+ throw new Error(
5142
+ `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
5143
+ Install it with: pnpm add -D wrangler
5144
+ ${err.message}`
5145
+ );
5146
+ }
5147
+ }
5148
+ if (typeof option === "function") {
5149
+ return await option(factoryCtx);
5150
+ }
5151
+ return { env: option };
5152
+ }
5153
+ async function acquireBuildEnv(s, command, mode) {
5154
+ const option = s.opts?.buildEnv;
5155
+ if (!option) return false;
5156
+ const result = await resolveBuildEnv(option, {
5157
+ root: s.projectRoot,
5158
+ mode,
5159
+ command,
5160
+ preset: s.opts?.preset ?? "node"
5161
+ });
5162
+ if (!result) return false;
5163
+ s.resolvedBuildEnv = result.env;
5164
+ s.buildEnvDispose = result.dispose ?? null;
5165
+ globalThis[BUILD_ENV_GLOBAL_KEY] = result.env;
5166
+ return true;
5167
+ }
5168
+ async function releaseBuildEnv(s) {
5169
+ if (s.buildEnvDispose) {
5170
+ try {
5171
+ await s.buildEnvDispose();
5172
+ } catch (err) {
5173
+ console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
5174
+ }
5175
+ s.buildEnvDispose = null;
5176
+ }
5177
+ s.resolvedBuildEnv = void 0;
5178
+ delete globalThis[BUILD_ENV_GLOBAL_KEY];
5179
+ }
4400
5180
  function createRouterDiscoveryPlugin(entryPath, opts) {
4401
5181
  const s = createDiscoveryState(entryPath, opts);
5182
+ let viteCommand = "build";
5183
+ let viteMode = "production";
4402
5184
  return {
4403
5185
  name: "@rangojs/router:discovery",
4404
5186
  config() {
@@ -4407,31 +5189,13 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4407
5189
  __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG)
4408
5190
  }
4409
5191
  };
4410
- if (opts?.enableBuildPrerender) {
4411
- config.environments = {
4412
- rsc: {
4413
- build: {
4414
- rollupOptions: {
4415
- output: {
4416
- manualChunks(id) {
4417
- if (s.resolvedPrerenderModules?.has(id)) {
4418
- return "__prerender-handlers";
4419
- }
4420
- if (s.resolvedStaticModules?.has(id)) {
4421
- return "__static-handlers";
4422
- }
4423
- }
4424
- }
4425
- }
4426
- }
4427
- }
4428
- };
4429
- }
4430
5192
  return config;
4431
5193
  },
4432
5194
  configResolved(config) {
4433
5195
  s.projectRoot = config.root;
4434
5196
  s.isBuildMode = config.command === "build";
5197
+ viteCommand = config.command;
5198
+ viteMode = config.mode;
4435
5199
  s.userResolveAlias = config.resolve.alias;
4436
5200
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
4437
5201
  s.resolvedEntryPath = opts.routerPathRef.path;
@@ -4445,12 +5209,6 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4445
5209
  s.resolvedEntryPath = entries[0];
4446
5210
  }
4447
5211
  }
4448
- if (opts?.include || opts?.exclude) {
4449
- s.scanFilter = createScanFilter(s.projectRoot, {
4450
- include: opts.include,
4451
- exclude: opts.exclude
4452
- });
4453
- }
4454
5212
  if (opts?.staticRouteTypesGeneration !== false) {
4455
5213
  s.cachedRouterFiles = findRouterFiles(s.projectRoot, s.scanFilter);
4456
5214
  writeCombinedRouteTypesWithTracking(s, { preserveIfLarger: true });
@@ -4482,6 +5240,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4482
5240
  });
4483
5241
  prerenderTempServer = null;
4484
5242
  }
5243
+ releaseBuildEnv(s).catch(() => {
5244
+ });
4485
5245
  });
4486
5246
  async function getOrCreateTempServer() {
4487
5247
  if (prerenderNodeRegistry) {
@@ -4508,14 +5268,33 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4508
5268
  return null;
4509
5269
  }
4510
5270
  const discover = async () => {
5271
+ const discoverStart = performance.now();
4511
5272
  const rscEnv = server.environments?.rsc;
4512
5273
  if (!rscEnv?.runner) {
5274
+ debugDiscovery?.("dev: no rsc runner (cloudflare path)");
4513
5275
  s.devServerOrigin = getDevServerOrigin();
4514
5276
  try {
4515
- const tempRscEnv = await getOrCreateTempServer();
5277
+ await timed(
5278
+ debugDiscovery,
5279
+ "acquireBuildEnv",
5280
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5281
+ );
5282
+ const tempRscEnv = await timed(
5283
+ debugDiscovery,
5284
+ "getOrCreateTempServer",
5285
+ () => getOrCreateTempServer()
5286
+ );
4516
5287
  if (tempRscEnv) {
4517
- await discoverRouters(s, tempRscEnv);
4518
- writeRouteTypesFiles(s);
5288
+ await timed(
5289
+ debugDiscovery,
5290
+ "discoverRouters (cloudflare)",
5291
+ () => discoverRouters(s, tempRscEnv)
5292
+ );
5293
+ timedSync(
5294
+ debugDiscovery,
5295
+ "writeRouteTypesFiles",
5296
+ () => writeRouteTypesFiles(s)
5297
+ );
4519
5298
  }
4520
5299
  } catch (err) {
4521
5300
  console.warn(
@@ -4523,26 +5302,54 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4523
5302
  ${err.stack}`
4524
5303
  );
4525
5304
  }
5305
+ debugDiscovery?.(
5306
+ "dev discovery done (%sms)",
5307
+ (performance.now() - discoverStart).toFixed(1)
5308
+ );
4526
5309
  resolveDiscovery();
4527
5310
  return;
4528
5311
  }
4529
5312
  try {
4530
- const serverMod = await rscEnv.runner.import(
4531
- "@rangojs/router/server"
5313
+ debugDiscovery?.("dev: node path start");
5314
+ await timed(
5315
+ debugDiscovery,
5316
+ "acquireBuildEnv",
5317
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5318
+ );
5319
+ const serverMod = await timed(
5320
+ debugDiscovery,
5321
+ "import @rangojs/router/server",
5322
+ () => rscEnv.runner.import("@rangojs/router/server")
4532
5323
  );
4533
5324
  if (serverMod?.setManifestReadyPromise) {
4534
5325
  serverMod.setManifestReadyPromise(discoveryPromise);
4535
5326
  }
4536
- await discoverRouters(s, rscEnv);
5327
+ await timed(
5328
+ debugDiscovery,
5329
+ "discoverRouters",
5330
+ () => discoverRouters(s, rscEnv)
5331
+ );
4537
5332
  s.devServerOrigin = getDevServerOrigin();
4538
- writeRouteTypesFiles(s);
4539
- await propagateDiscoveryState(rscEnv);
5333
+ timedSync(
5334
+ debugDiscovery,
5335
+ "writeRouteTypesFiles",
5336
+ () => writeRouteTypesFiles(s)
5337
+ );
5338
+ await timed(
5339
+ debugDiscovery,
5340
+ "propagateDiscoveryState",
5341
+ () => propagateDiscoveryState(rscEnv)
5342
+ );
4540
5343
  } catch (err) {
4541
5344
  console.warn(
4542
5345
  `[rsc-router] Router discovery failed: ${err.message}
4543
5346
  ${err.stack}`
4544
5347
  );
4545
5348
  } finally {
5349
+ debugDiscovery?.(
5350
+ "dev discovery done (%sms)",
5351
+ (performance.now() - discoverStart).toFixed(1)
5352
+ );
4546
5353
  resolveDiscovery();
4547
5354
  }
4548
5355
  };
@@ -4583,15 +5390,46 @@ ${err.stack}`
4583
5390
  }
4584
5391
  };
4585
5392
  server.middlewares.use("/__rsc_prerender", async (req, res) => {
5393
+ const reqStart = debugDev ? performance.now() : 0;
5394
+ const logResult = (status, note) => {
5395
+ debugDev?.(
5396
+ "/__rsc_prerender %s -> %d %s (%sms)",
5397
+ req.url,
5398
+ status,
5399
+ note,
5400
+ (performance.now() - reqStart).toFixed(1)
5401
+ );
5402
+ };
4586
5403
  if (s.discoveryDone) await s.discoveryDone;
4587
5404
  const url = new URL(req.url || "/", "http://localhost");
4588
5405
  const pathname = url.searchParams.get("pathname");
4589
5406
  if (!pathname) {
4590
5407
  res.statusCode = 400;
4591
5408
  res.end("Missing pathname");
5409
+ logResult(400, "missing pathname");
4592
5410
  return;
4593
5411
  }
4594
- let registry = mainRegistry;
5412
+ const rscEnv = server.environments?.rsc;
5413
+ let registry = null;
5414
+ if (rscEnv?.runner && s.resolvedEntryPath) {
5415
+ try {
5416
+ await rscEnv.runner.import(s.resolvedEntryPath);
5417
+ const serverMod = await rscEnv.runner.import(
5418
+ "@rangojs/router/server"
5419
+ );
5420
+ registry = serverMod.RouterRegistry ?? null;
5421
+ } catch (err) {
5422
+ console.warn(
5423
+ `[rsc-router] Dev prerender module refresh failed: ${err.message}`
5424
+ );
5425
+ res.statusCode = 500;
5426
+ res.end(`Prerender handler error: ${err.message}`);
5427
+ logResult(500, "module refresh failed");
5428
+ return;
5429
+ }
5430
+ } else {
5431
+ registry = mainRegistry;
5432
+ }
4595
5433
  if (!registry) {
4596
5434
  if (!prerenderNodeRegistry) {
4597
5435
  await getOrCreateTempServer();
@@ -4601,6 +5439,7 @@ ${err.stack}`
4601
5439
  if (!registry || registry.size === 0) {
4602
5440
  res.statusCode = 503;
4603
5441
  res.end("Prerender runner not available");
5442
+ logResult(503, "no registry");
4604
5443
  return;
4605
5444
  }
4606
5445
  const wantIntercept = url.searchParams.get("intercept") === "1";
@@ -4613,7 +5452,10 @@ ${err.stack}`
4613
5452
  pathname,
4614
5453
  {},
4615
5454
  void 0,
4616
- wantPassthrough
5455
+ wantPassthrough,
5456
+ s.resolvedBuildEnv,
5457
+ true
5458
+ // devMode: check getParams for passthrough routes
4617
5459
  );
4618
5460
  if (!result) continue;
4619
5461
  if (result.passthrough) continue;
@@ -4632,6 +5474,7 @@ ${err.stack}`
4632
5474
  payload = { segments: result.segments, handles: result.handles };
4633
5475
  }
4634
5476
  res.end(JSON.stringify(payload));
5477
+ logResult(200, `match ${result.routeName}`);
4635
5478
  return;
4636
5479
  } catch (err) {
4637
5480
  console.warn(
@@ -4641,6 +5484,7 @@ ${err.stack}`
4641
5484
  }
4642
5485
  res.statusCode = 404;
4643
5486
  res.end("No prerender match");
5487
+ logResult(404, "no match");
4644
5488
  });
4645
5489
  if (opts?.staticRouteTypesGeneration !== false) {
4646
5490
  const isGeneratedRouteFile = (filePath) => filePath.endsWith(".gen.ts") && (filePath.includes("named-routes.gen.ts") || filePath.includes("urls.gen.ts"));
@@ -4665,15 +5509,32 @@ ${err.stack}`
4665
5509
  const rscEnv = server.environments?.rsc;
4666
5510
  if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
4667
5511
  runtimeRediscoveryInProgress = true;
5512
+ const hmrStart = performance.now();
4668
5513
  try {
4669
- await discoverRouters(s, rscEnv);
4670
- writeRouteTypesFiles(s);
4671
- await propagateDiscoveryState(rscEnv);
5514
+ await timed(
5515
+ debugDiscovery,
5516
+ "hmr discoverRouters",
5517
+ () => discoverRouters(s, rscEnv)
5518
+ );
5519
+ timedSync(
5520
+ debugDiscovery,
5521
+ "hmr writeRouteTypesFiles",
5522
+ () => writeRouteTypesFiles(s)
5523
+ );
5524
+ await timed(
5525
+ debugDiscovery,
5526
+ "hmr propagateDiscoveryState",
5527
+ () => propagateDiscoveryState(rscEnv)
5528
+ );
4672
5529
  } catch (err) {
4673
5530
  console.warn(
4674
5531
  `[rsc-router] Runtime re-discovery failed: ${err.message}`
4675
5532
  );
4676
5533
  } finally {
5534
+ debugDiscovery?.(
5535
+ "hmr re-discovery done (%sms)",
5536
+ (performance.now() - hmrStart).toFixed(1)
5537
+ );
4677
5538
  runtimeRediscoveryInProgress = false;
4678
5539
  }
4679
5540
  };
@@ -4681,6 +5542,7 @@ ${err.stack}`
4681
5542
  clearTimeout(routeChangeTimer);
4682
5543
  routeChangeTimer = setTimeout(() => {
4683
5544
  routeChangeTimer = void 0;
5545
+ const regenStart = debugDiscovery ? performance.now() : 0;
4684
5546
  try {
4685
5547
  writeCombinedRouteTypesWithTracking(s);
4686
5548
  if (s.perRouterManifests.length > 0) {
@@ -4691,6 +5553,10 @@ ${err.stack}`
4691
5553
  `[rsc-router] Route regeneration error: ${err.message}`
4692
5554
  );
4693
5555
  }
5556
+ debugDiscovery?.(
5557
+ "watcher: regenerated gen files (%sms)",
5558
+ (performance.now() - regenStart).toFixed(1)
5559
+ );
4694
5560
  if (s.perRouterManifests.length > 0) {
4695
5561
  refreshRuntimeDiscovery().catch((err) => {
4696
5562
  console.warn(
@@ -4713,6 +5579,12 @@ ${err.stack}`
4713
5579
  const hasUrls = source.includes("urls(");
4714
5580
  const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
4715
5581
  if (!hasUrls && !hasCreateRouter) return;
5582
+ debugDiscovery?.(
5583
+ "watcher: %s matches (urls=%s, router=%s)",
5584
+ filePath,
5585
+ hasUrls,
5586
+ hasCreateRouter
5587
+ );
4716
5588
  if (hasCreateRouter) {
4717
5589
  const nestedRouterConflict = findNestedRouterConflict([
4718
5590
  ...s.cachedRouterFiles ?? [],
@@ -4745,14 +5617,31 @@ ${err.stack}`
4745
5617
  // The manifest data is stored for the virtual module's load hook.
4746
5618
  async buildStart() {
4747
5619
  if (!s.isBuildMode) return;
4748
- if (s.mergedRouteManifest !== null) return;
5620
+ if (s.mergedRouteManifest !== null) {
5621
+ debugDiscovery?.(
5622
+ "build: skip (already discovered, env=%s)",
5623
+ this.environment?.name ?? "?"
5624
+ );
5625
+ return;
5626
+ }
5627
+ const buildStartTime = performance.now();
5628
+ debugDiscovery?.("build: start (env=%s)", this.environment?.name ?? "?");
4749
5629
  resetStagedBuildAssets(s.projectRoot);
4750
5630
  s.prerenderManifestEntries = null;
4751
5631
  s.staticManifestEntries = null;
5632
+ await timed(
5633
+ debugDiscovery,
5634
+ "build acquireBuildEnv",
5635
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5636
+ );
4752
5637
  let tempServer = null;
4753
5638
  globalThis.__rscRouterDiscoveryActive = true;
4754
5639
  try {
4755
- tempServer = await createTempRscServer(s, { forceBuild: true });
5640
+ tempServer = await timed(
5641
+ debugDiscovery,
5642
+ "build createTempRscServer",
5643
+ () => createTempRscServer(s, { forceBuild: true })
5644
+ );
4756
5645
  const rscEnv = tempServer.environments?.rsc;
4757
5646
  if (!rscEnv?.runner) {
4758
5647
  console.warn(
@@ -4766,8 +5655,16 @@ ${err.stack}`
4766
5655
  if (tempIdsPlugin?.api?.staticHandlerModules) {
4767
5656
  s.resolvedStaticModules = tempIdsPlugin.api.staticHandlerModules;
4768
5657
  }
4769
- await discoverRouters(s, rscEnv);
4770
- writeRouteTypesFiles(s);
5658
+ await timed(
5659
+ debugDiscovery,
5660
+ "build discoverRouters",
5661
+ () => discoverRouters(s, rscEnv)
5662
+ );
5663
+ timedSync(
5664
+ debugDiscovery,
5665
+ "build writeRouteTypesFiles",
5666
+ () => writeRouteTypesFiles(s)
5667
+ );
4771
5668
  } catch (err) {
4772
5669
  const sourceFile = err.stack?.split("\n").find(
4773
5670
  (line) => line.includes(s.projectRoot) && !line.includes("node_modules")
@@ -4786,8 +5683,17 @@ ${details}`
4786
5683
  } finally {
4787
5684
  delete globalThis.__rscRouterDiscoveryActive;
4788
5685
  if (tempServer) {
4789
- await tempServer.close();
5686
+ await timed(
5687
+ debugDiscovery,
5688
+ "build tempServer.close",
5689
+ () => tempServer.close()
5690
+ );
4790
5691
  }
5692
+ await releaseBuildEnv(s);
5693
+ debugDiscovery?.(
5694
+ "build discovery done (%sms)",
5695
+ (performance.now() - buildStartTime).toFixed(1)
5696
+ );
4791
5697
  }
4792
5698
  },
4793
5699
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -4804,17 +5710,36 @@ ${details}`
4804
5710
  async load(id) {
4805
5711
  if (id === "\0" + VIRTUAL_ROUTES_MANIFEST_ID) {
4806
5712
  if (s.discoveryDone) {
4807
- await s.discoveryDone;
5713
+ await timed(
5714
+ debugRoutes,
5715
+ "await discoveryDone (manifest)",
5716
+ () => s.discoveryDone
5717
+ );
4808
5718
  }
4809
- return generateRoutesManifestModule(s);
5719
+ const code = await timed(
5720
+ debugRoutes,
5721
+ "generateRoutesManifestModule",
5722
+ () => generateRoutesManifestModule(s)
5723
+ );
5724
+ debugRoutes?.("manifest module emitted (%d bytes)", code?.length ?? 0);
5725
+ return code;
4810
5726
  }
4811
5727
  const perRouterPrefix = "\0" + VIRTUAL_ROUTES_MANIFEST_ID + "/";
4812
5728
  if (id.startsWith(perRouterPrefix)) {
4813
5729
  if (s.discoveryDone) {
4814
- await s.discoveryDone;
5730
+ await timed(
5731
+ debugRoutes,
5732
+ "await discoveryDone (per-router)",
5733
+ () => s.discoveryDone
5734
+ );
4815
5735
  }
4816
5736
  const routerId = id.slice(perRouterPrefix.length);
4817
- return generatePerRouterModule(s, routerId);
5737
+ const code = await timed(
5738
+ debugRoutes,
5739
+ `generatePerRouterModule ${routerId}`,
5740
+ () => generatePerRouterModule(s, routerId)
5741
+ );
5742
+ return code;
4818
5743
  }
4819
5744
  return null;
4820
5745
  },
@@ -4822,28 +5747,44 @@ ${details}`
4822
5747
  // Used by closeBundle for handler code eviction and prerender data injection.
4823
5748
  generateBundle(_options, bundle) {
4824
5749
  if (this.environment?.name !== "rsc") return;
5750
+ const genStart = debugBuild ? performance.now() : 0;
4825
5751
  for (const [fileName, chunk] of Object.entries(bundle)) {
4826
5752
  if (chunk.type === "chunk" && chunk.isEntry) {
4827
5753
  s.rscEntryFileName = fileName;
4828
5754
  break;
4829
5755
  }
4830
5756
  }
4831
- if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
5757
+ if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size) {
5758
+ debugBuild?.(
5759
+ "generateBundle (rsc): no handlers to scan (%sms)",
5760
+ (performance.now() - genStart).toFixed(1)
5761
+ );
4832
5762
  return;
5763
+ }
5764
+ s.handlerChunkInfoMap.clear();
5765
+ s.staticHandlerChunkInfoMap.clear();
4833
5766
  for (const [fileName, chunk] of Object.entries(bundle)) {
4834
5767
  if (chunk.type !== "chunk") continue;
4835
- if (fileName.includes("__prerender-handlers") && s.resolvedPrerenderModules?.size) {
5768
+ if (s.resolvedPrerenderModules?.size) {
4836
5769
  const handlers = extractHandlerExportsFromChunk(
4837
5770
  chunk.code,
4838
5771
  s.resolvedPrerenderModules,
4839
5772
  "Prerender",
4840
- true
5773
+ false
4841
5774
  );
4842
5775
  if (handlers.length > 0) {
4843
- s.handlerChunkInfo = { fileName, exports: handlers };
5776
+ const existing = s.handlerChunkInfoMap.get(fileName);
5777
+ if (existing) {
5778
+ existing.exports.push(...handlers);
5779
+ } else {
5780
+ s.handlerChunkInfoMap.set(fileName, {
5781
+ fileName,
5782
+ exports: handlers
5783
+ });
5784
+ }
4844
5785
  }
4845
5786
  }
4846
- if (fileName.includes("__static-handlers") && s.resolvedStaticModules?.size) {
5787
+ if (s.resolvedStaticModules?.size) {
4847
5788
  const handlers = extractHandlerExportsFromChunk(
4848
5789
  chunk.code,
4849
5790
  s.resolvedStaticModules,
@@ -4851,10 +5792,25 @@ ${details}`
4851
5792
  false
4852
5793
  );
4853
5794
  if (handlers.length > 0) {
4854
- s.staticHandlerChunkInfo = { fileName, exports: handlers };
5795
+ const existing = s.staticHandlerChunkInfoMap.get(fileName);
5796
+ if (existing) {
5797
+ existing.exports.push(...handlers);
5798
+ } else {
5799
+ s.staticHandlerChunkInfoMap.set(fileName, {
5800
+ fileName,
5801
+ exports: handlers
5802
+ });
5803
+ }
4855
5804
  }
4856
5805
  }
4857
5806
  }
5807
+ debugBuild?.(
5808
+ "generateBundle (rsc): scanned %d chunks, %d prerender chunk(s), %d static chunk(s) (%sms)",
5809
+ Object.keys(bundle).length,
5810
+ s.handlerChunkInfoMap.size,
5811
+ s.staticHandlerChunkInfoMap.size,
5812
+ (performance.now() - genStart).toFixed(1)
5813
+ );
4858
5814
  },
4859
5815
  // Build-time pre-rendering: evict handler code and inject collected prerender data.
4860
5816
  // Collection now happens in-process during discoverRouters() via RSC runner.
@@ -4865,21 +5821,41 @@ ${details}`
4865
5821
  async handler() {
4866
5822
  if (!s.isBuildMode) return;
4867
5823
  if (this.environment && this.environment.name !== "rsc") return;
4868
- postprocessBundle(s);
5824
+ timedSync(
5825
+ debugBuild,
5826
+ "closeBundle postprocessBundle",
5827
+ () => postprocessBundle(s)
5828
+ );
4869
5829
  }
4870
5830
  }
4871
5831
  };
4872
5832
  }
4873
5833
 
4874
5834
  // src/vite/rango.ts
5835
+ var debugConfig = createRangoDebugger(NS.config);
4875
5836
  async function rango(options) {
5837
+ const rangoStart = performance.now();
4876
5838
  const resolvedOptions = options ?? { preset: "node" };
4877
5839
  const preset = resolvedOptions.preset ?? "node";
4878
5840
  const showBanner = resolvedOptions.banner ?? true;
5841
+ debugConfig?.("rango(%s) setup start", preset);
4879
5842
  const plugins = [];
4880
- const rangoAliases = getPackageAliases();
4881
- const excludeDeps = getExcludeDeps();
4882
- let rscEntryPath = null;
5843
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
5844
+ const excludeDeps = [
5845
+ ...getExcludeDeps(),
5846
+ // plugin-rsc itself injects these into the client env's
5847
+ // optimizeDeps.include, which overrides exclude for the dep's own
5848
+ // pre-bundle entry. What exclude still controls is how *other*
5849
+ // pre-bundled deps treat imports of these specs (external vs inlined)
5850
+ // via esbuildCjsExternalPlugin. The cjs-to-esm transform in
5851
+ // plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
5852
+ // where client.browser's bare include fails to resolve and Vite ends up
5853
+ // serving the raw CJS file at dev-serve time.
5854
+ "@vitejs/plugin-rsc/browser",
5855
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5856
+ ];
5857
+ const pkg = getPublishedPackageName();
5858
+ const nested = (spec) => `${pkg} > ${spec}`;
4883
5859
  const routerRef = { path: void 0 };
4884
5860
  const prerenderEnabled = true;
4885
5861
  if (preset === "cloudflare") {
@@ -4917,7 +5893,7 @@ async function rango(options) {
4917
5893
  // Pre-bundle rsc-html-stream to prevent discovery during first request
4918
5894
  // Exclude rsc-router modules to ensure same Context instance
4919
5895
  optimizeDeps: {
4920
- include: ["rsc-html-stream/client"],
5896
+ include: [nested("rsc-html-stream/client")],
4921
5897
  exclude: excludeDeps,
4922
5898
  esbuildOptions: sharedEsbuildOptions
4923
5899
  }
@@ -4942,8 +5918,10 @@ async function rango(options) {
4942
5918
  "react-dom/static.edge",
4943
5919
  "react/jsx-runtime",
4944
5920
  "react/jsx-dev-runtime",
4945
- "rsc-html-stream/server",
4946
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5921
+ nested("rsc-html-stream/server"),
5922
+ nested(
5923
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5924
+ )
4947
5925
  ],
4948
5926
  exclude: excludeDeps,
4949
5927
  esbuildOptions: sharedEsbuildOptions
@@ -4958,7 +5936,9 @@ async function rango(options) {
4958
5936
  "react",
4959
5937
  "react/jsx-runtime",
4960
5938
  "react/jsx-dev-runtime",
4961
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5939
+ nested(
5940
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5941
+ )
4962
5942
  ],
4963
5943
  exclude: excludeDeps,
4964
5944
  esbuildOptions: sharedEsbuildOptions
@@ -4975,6 +5955,7 @@ async function rango(options) {
4975
5955
  }
4976
5956
  });
4977
5957
  plugins.push(createVirtualEntriesPlugin(finalEntries));
5958
+ plugins.push(performanceTracksPlugin());
4978
5959
  plugins.push(
4979
5960
  rsc({
4980
5961
  entries: finalEntries,
@@ -4983,153 +5964,126 @@ async function rango(options) {
4983
5964
  );
4984
5965
  plugins.push(clientRefDedup());
4985
5966
  } else {
4986
- const nodeOptions = resolvedOptions;
4987
- routerRef.path = nodeOptions.router;
4988
- if (!routerRef.path) {
4989
- plugins.push({
4990
- name: "@rangojs/router:auto-discover",
4991
- config(userConfig) {
4992
- if (routerRef.path) return;
4993
- const root = userConfig.root ? resolve9(process.cwd(), userConfig.root) : process.cwd();
4994
- const filter = createScanFilter(root, {
4995
- include: resolvedOptions.include,
4996
- exclude: resolvedOptions.exclude
4997
- });
4998
- const candidates = findRouterFiles(root, filter);
4999
- if (candidates.length === 1) {
5000
- const abs = candidates[0];
5001
- routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
5002
- } else if (candidates.length > 1) {
5003
- const list = candidates.map(
5004
- (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
5005
- ).join("\n");
5006
- throw new Error(
5007
- `[rsc-router] Multiple routers found. Specify \`router\` to choose one:
5008
- ${list}`
5009
- );
5010
- }
5967
+ plugins.push({
5968
+ name: "@rangojs/router:auto-discover",
5969
+ config(userConfig) {
5970
+ if (routerRef.path) return;
5971
+ const root = userConfig.root ? resolve9(process.cwd(), userConfig.root) : process.cwd();
5972
+ const candidates = findRouterFiles(root);
5973
+ if (candidates.length === 1) {
5974
+ const abs = candidates[0];
5975
+ routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
5976
+ } else if (candidates.length > 1) {
5977
+ const list = candidates.map(
5978
+ (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
5979
+ ).join("\n");
5980
+ throw new Error(`[rsc-router] Multiple routers found:
5981
+ ${list}`);
5011
5982
  }
5012
- });
5013
- }
5014
- const rscOption = nodeOptions.rsc ?? true;
5015
- if (rscOption !== false) {
5016
- const { default: rsc } = await import("@vitejs/plugin-rsc");
5017
- const userEntries = typeof rscOption === "boolean" ? {} : rscOption.entries || {};
5018
- const finalEntries = {
5019
- client: userEntries.client ?? VIRTUAL_IDS.browser,
5020
- ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
5021
- rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc
5022
- };
5023
- rscEntryPath = userEntries.rsc ?? null;
5024
- let hasWarnedDuplicate = false;
5025
- plugins.push({
5026
- name: "@rangojs/router:rsc-integration",
5027
- enforce: "pre",
5028
- config() {
5029
- const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
5030
- const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
5031
- const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
5032
- return {
5033
- // Exclude rsc-router modules from optimization to prevent module duplication
5034
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
5035
- optimizeDeps: {
5036
- exclude: excludeDeps,
5037
- esbuildOptions: sharedEsbuildOptions
5038
- },
5039
- build: {
5040
- rollupOptions: { onwarn }
5041
- },
5042
- resolve: {
5043
- alias: rangoAliases
5044
- },
5045
- environments: {
5046
- client: {
5047
- build: {
5048
- rollupOptions: {
5049
- output: {
5050
- manualChunks: getManualChunks
5051
- }
5052
- }
5053
- },
5054
- // Always exclude rsc-router modules, conditionally add virtual entry
5055
- optimizeDeps: {
5056
- // Pre-bundle React and rsc-html-stream to prevent late discovery
5057
- // triggering ERR_OUTDATED_OPTIMIZED_DEP on cold starts
5058
- include: [
5059
- "react",
5060
- "react-dom",
5061
- "react/jsx-runtime",
5062
- "react/jsx-dev-runtime",
5063
- "rsc-html-stream/client"
5064
- ],
5065
- exclude: excludeDeps,
5066
- esbuildOptions: sharedEsbuildOptions,
5067
- ...useVirtualClient && {
5068
- // Tell Vite to scan the virtual entry for dependencies
5069
- entries: [VIRTUAL_IDS.browser]
5070
- }
5071
- }
5072
- },
5073
- ...useVirtualSSR && {
5074
- ssr: {
5075
- optimizeDeps: {
5076
- entries: [VIRTUAL_IDS.ssr],
5077
- // Pre-bundle all SSR deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
5078
- include: [
5079
- "react",
5080
- "react-dom",
5081
- "react-dom/server.edge",
5082
- "react-dom/static.edge",
5083
- "react/jsx-runtime",
5084
- "react/jsx-dev-runtime",
5085
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5086
- ],
5087
- exclude: excludeDeps,
5088
- esbuildOptions: sharedEsbuildOptions
5983
+ }
5984
+ });
5985
+ const finalEntries = {
5986
+ client: VIRTUAL_IDS.browser,
5987
+ ssr: VIRTUAL_IDS.ssr,
5988
+ rsc: VIRTUAL_IDS.rsc
5989
+ };
5990
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
5991
+ let hasWarnedDuplicate = false;
5992
+ plugins.push({
5993
+ name: "@rangojs/router:rsc-integration",
5994
+ enforce: "pre",
5995
+ config() {
5996
+ return {
5997
+ optimizeDeps: {
5998
+ exclude: excludeDeps,
5999
+ esbuildOptions: sharedEsbuildOptions
6000
+ },
6001
+ build: {
6002
+ rollupOptions: { onwarn }
6003
+ },
6004
+ resolve: {
6005
+ alias: rangoAliases
6006
+ },
6007
+ environments: {
6008
+ client: {
6009
+ build: {
6010
+ rollupOptions: {
6011
+ output: {
6012
+ manualChunks: getManualChunks
5089
6013
  }
5090
6014
  }
5091
6015
  },
5092
- ...useVirtualRSC && {
5093
- rsc: {
5094
- optimizeDeps: {
5095
- entries: [VIRTUAL_IDS.rsc],
5096
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
5097
- include: [
5098
- "react",
5099
- "react/jsx-runtime",
5100
- "react/jsx-dev-runtime",
5101
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5102
- ],
5103
- esbuildOptions: sharedEsbuildOptions
5104
- }
5105
- }
6016
+ optimizeDeps: {
6017
+ include: [
6018
+ "react",
6019
+ "react-dom",
6020
+ "react/jsx-runtime",
6021
+ "react/jsx-dev-runtime",
6022
+ nested("rsc-html-stream/client")
6023
+ ],
6024
+ exclude: excludeDeps,
6025
+ esbuildOptions: sharedEsbuildOptions,
6026
+ entries: [VIRTUAL_IDS.browser]
6027
+ }
6028
+ },
6029
+ ssr: {
6030
+ optimizeDeps: {
6031
+ entries: [VIRTUAL_IDS.ssr],
6032
+ include: [
6033
+ "react",
6034
+ "react-dom",
6035
+ "react-dom/server.edge",
6036
+ "react-dom/static.edge",
6037
+ "react/jsx-runtime",
6038
+ "react/jsx-dev-runtime",
6039
+ nested(
6040
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6041
+ )
6042
+ ],
6043
+ exclude: excludeDeps,
6044
+ esbuildOptions: sharedEsbuildOptions
6045
+ }
6046
+ },
6047
+ rsc: {
6048
+ optimizeDeps: {
6049
+ entries: [VIRTUAL_IDS.rsc],
6050
+ include: [
6051
+ "react",
6052
+ "react/jsx-runtime",
6053
+ "react/jsx-dev-runtime",
6054
+ nested(
6055
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6056
+ )
6057
+ ],
6058
+ esbuildOptions: sharedEsbuildOptions
5106
6059
  }
5107
6060
  }
5108
- };
5109
- },
5110
- configResolved(config) {
5111
- if (showBanner) {
5112
- const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
5113
- printBanner(mode, "node", rangoVersion);
5114
- }
5115
- const rscMinimalCount = config.plugins.filter(
5116
- (p) => p.name === "rsc:minimal"
5117
- ).length;
5118
- if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
5119
- hasWarnedDuplicate = true;
5120
- console.warn(
5121
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your config or use rango({ rsc: false }) for manual configuration."
5122
- );
5123
6061
  }
6062
+ };
6063
+ },
6064
+ configResolved(config) {
6065
+ if (showBanner) {
6066
+ const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
6067
+ printBanner(mode, "node", rangoVersion);
5124
6068
  }
5125
- });
5126
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
5127
- plugins.push(
5128
- rsc({
5129
- entries: finalEntries
5130
- })
5131
- );
5132
- }
6069
+ const rscMinimalCount = config.plugins.filter(
6070
+ (p) => p.name === "rsc:minimal"
6071
+ ).length;
6072
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
6073
+ hasWarnedDuplicate = true;
6074
+ console.warn(
6075
+ "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
6076
+ );
6077
+ }
6078
+ }
6079
+ });
6080
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
6081
+ plugins.push(performanceTracksPlugin());
6082
+ plugins.push(
6083
+ rsc({
6084
+ entries: finalEntries
6085
+ })
6086
+ );
5133
6087
  plugins.push(clientRefDedup());
5134
6088
  }
5135
6089
  plugins.push({
@@ -5157,20 +6111,24 @@ ${list}`
5157
6111
  plugins.push(createVersionPlugin());
5158
6112
  const discoveryEntryPath = preset !== "cloudflare" ? routerRef.path : void 0;
5159
6113
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : void 0;
5160
- const injectorEntryPath = rscEntryPath ?? (preset === "cloudflare" ? void 0 : null);
5161
- if (injectorEntryPath !== null) {
5162
- plugins.push(createVersionInjectorPlugin(injectorEntryPath));
6114
+ if (preset === "cloudflare") {
6115
+ plugins.push(createVersionInjectorPlugin(void 0));
5163
6116
  }
5164
6117
  plugins.push(createCjsToEsmPlugin());
5165
6118
  plugins.push(
5166
6119
  createRouterDiscoveryPlugin(discoveryEntryPath, {
5167
6120
  routerPathRef: discoveryRouterRef,
5168
6121
  enableBuildPrerender: prerenderEnabled,
5169
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
5170
- include: resolvedOptions.include,
5171
- exclude: resolvedOptions.exclude
6122
+ buildEnv: options?.buildEnv,
6123
+ preset
5172
6124
  })
5173
6125
  );
6126
+ debugConfig?.(
6127
+ "rango(%s) setup done: %d plugin(s) (%sms)",
6128
+ preset,
6129
+ plugins.length,
6130
+ (performance.now() - rangoStart).toFixed(1)
6131
+ );
5174
6132
  return plugins;
5175
6133
  }
5176
6134
 
@@ -5181,29 +6139,75 @@ function poke() {
5181
6139
  apply: "serve",
5182
6140
  configureServer(server) {
5183
6141
  const stdin = process.stdin;
5184
- const previousRawMode = stdin.isTTY ? stdin.isRaw : null;
6142
+ const debug11 = process.env.RANGO_POKE_DEBUG === "1";
6143
+ const triggerReload = (source) => {
6144
+ server.hot.send({ type: "full-reload", path: "*" });
6145
+ server.config.logger.info(` browser reload (${source})`, {
6146
+ timestamp: true
6147
+ });
6148
+ };
6149
+ const toBuffer = (chunk) => {
6150
+ return typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
6151
+ };
6152
+ const formatChunk = (chunk) => {
6153
+ const data = toBuffer(chunk);
6154
+ const hex = Array.from(data).map((byte) => `0x${byte.toString(16).padStart(2, "0")}`).join(" ");
6155
+ const ascii = Array.from(data).map((byte) => {
6156
+ if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
6157
+ if (byte === 10) return "\\n";
6158
+ if (byte === 13) return "\\r";
6159
+ if (byte === 9) return "\\t";
6160
+ return ".";
6161
+ }).join("");
6162
+ return `len=${data.length} hex=[${hex}] ascii="${ascii}"`;
6163
+ };
6164
+ const readCtrlR = (chunk) => {
6165
+ const data = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
6166
+ return data.length === 1 && data[0] === 18;
6167
+ };
6168
+ const readSubmittedCommands = (chunk) => {
6169
+ const text = toBuffer(chunk).toString("utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
6170
+ if (!text.includes("\n")) return [];
6171
+ const lines = text.split("\n");
6172
+ lines.pop();
6173
+ return lines;
6174
+ };
6175
+ if (debug11) {
6176
+ server.config.logger.info(
6177
+ ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
6178
+ { timestamp: true }
6179
+ );
6180
+ }
5185
6181
  if (stdin.isTTY) {
5186
- stdin.setRawMode(true);
6182
+ server.config.logger.info(
6183
+ " poke ready: press e + enter to reload browser (ctrl+r also works when available)",
6184
+ { timestamp: true }
6185
+ );
5187
6186
  }
5188
6187
  const onData = (data) => {
5189
- if (data.length !== 1) return;
5190
- if (data[0] === 3) {
5191
- process.emit("SIGINT", "SIGINT");
5192
- return;
5193
- }
5194
- if (data[0] === 18) {
5195
- server.hot.send({ type: "full-reload", path: "*" });
5196
- server.config.logger.info(" browser reload (ctrl+r)", {
6188
+ if (debug11) {
6189
+ server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5197
6190
  timestamp: true
5198
6191
  });
5199
6192
  }
6193
+ if (readCtrlR(data)) {
6194
+ triggerReload("ctrl+r");
6195
+ return;
6196
+ }
6197
+ for (const command of readSubmittedCommands(data)) {
6198
+ if (command === "e") {
6199
+ triggerReload("e+enter");
6200
+ return;
6201
+ }
6202
+ if (command === "\x1Br") {
6203
+ triggerReload("option+r+enter");
6204
+ return;
6205
+ }
6206
+ }
5200
6207
  };
5201
6208
  stdin.on("data", onData);
5202
6209
  server.httpServer?.on("close", () => {
5203
6210
  stdin.off("data", onData);
5204
- if (stdin.isTTY && previousRawMode !== null) {
5205
- stdin.setRawMode(previousRawMode);
5206
- }
5207
6211
  });
5208
6212
  }
5209
6213
  };