@rangojs/router 0.0.0-experimental.69 → 0.0.0-experimental.6c70a2ab

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 (123) hide show
  1. package/README.md +112 -17
  2. package/dist/vite/index.js +1456 -467
  3. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  4. package/package.json +7 -5
  5. package/skills/breadcrumbs/SKILL.md +3 -1
  6. package/skills/handler-use/SKILL.md +364 -0
  7. package/skills/hooks/SKILL.md +54 -20
  8. package/skills/i18n/SKILL.md +276 -0
  9. package/skills/intercept/SKILL.md +45 -0
  10. package/skills/layout/SKILL.md +24 -0
  11. package/skills/links/SKILL.md +234 -16
  12. package/skills/loader/SKILL.md +70 -3
  13. package/skills/middleware/SKILL.md +34 -3
  14. package/skills/migrate-nextjs/SKILL.md +562 -0
  15. package/skills/migrate-react-router/SKILL.md +769 -0
  16. package/skills/parallel/SKILL.md +68 -0
  17. package/skills/rango/SKILL.md +26 -22
  18. package/skills/response-routes/SKILL.md +8 -0
  19. package/skills/route/SKILL.md +48 -0
  20. package/skills/server-actions/SKILL.md +739 -0
  21. package/skills/streams-and-websockets/SKILL.md +283 -0
  22. package/skills/typesafety/SKILL.md +9 -1
  23. package/skills/view-transitions/SKILL.md +212 -0
  24. package/src/browser/app-shell.ts +52 -0
  25. package/src/browser/event-controller.ts +44 -4
  26. package/src/browser/navigation-bridge.ts +80 -5
  27. package/src/browser/navigation-client.ts +64 -13
  28. package/src/browser/navigation-store.ts +25 -1
  29. package/src/browser/partial-update.ts +58 -12
  30. package/src/browser/prefetch/cache.ts +129 -21
  31. package/src/browser/prefetch/fetch.ts +148 -16
  32. package/src/browser/prefetch/queue.ts +36 -5
  33. package/src/browser/rango-state.ts +53 -13
  34. package/src/browser/react/Link.tsx +30 -2
  35. package/src/browser/react/NavigationProvider.tsx +70 -18
  36. package/src/browser/react/filter-segment-order.ts +51 -7
  37. package/src/browser/react/index.ts +3 -0
  38. package/src/browser/react/use-navigation.ts +22 -2
  39. package/src/browser/react/use-params.ts +17 -4
  40. package/src/browser/react/use-reverse.ts +99 -0
  41. package/src/browser/react/use-router.ts +8 -1
  42. package/src/browser/react/use-segments.ts +11 -8
  43. package/src/browser/rsc-router.tsx +34 -6
  44. package/src/browser/scroll-restoration.ts +22 -14
  45. package/src/browser/segment-reconciler.ts +36 -14
  46. package/src/browser/types.ts +19 -0
  47. package/src/build/route-trie.ts +52 -25
  48. package/src/cache/cf/cf-cache-store.ts +5 -7
  49. package/src/client.rsc.tsx +3 -0
  50. package/src/client.tsx +87 -175
  51. package/src/href-client.ts +4 -1
  52. package/src/index.rsc.ts +3 -0
  53. package/src/index.ts +40 -9
  54. package/src/outlet-context.ts +1 -1
  55. package/src/response-utils.ts +28 -0
  56. package/src/reverse.ts +62 -36
  57. package/src/route-definition/dsl-helpers.ts +175 -23
  58. package/src/route-definition/helpers-types.ts +63 -14
  59. package/src/route-definition/resolve-handler-use.ts +6 -0
  60. package/src/route-types.ts +7 -0
  61. package/src/router/handler-context.ts +21 -38
  62. package/src/router/lazy-includes.ts +6 -6
  63. package/src/router/loader-resolution.ts +3 -0
  64. package/src/router/manifest.ts +22 -13
  65. package/src/router/match-api.ts +4 -3
  66. package/src/router/match-handlers.ts +1 -0
  67. package/src/router/match-middleware/cache-lookup.ts +2 -1
  68. package/src/router/match-result.ts +101 -4
  69. package/src/router/middleware-types.ts +14 -25
  70. package/src/router/middleware.ts +54 -7
  71. package/src/router/pattern-matching.ts +101 -17
  72. package/src/router/revalidation.ts +15 -1
  73. package/src/router/segment-resolution/fresh.ts +13 -0
  74. package/src/router/segment-resolution/revalidation.ts +135 -101
  75. package/src/router/substitute-pattern-params.ts +56 -0
  76. package/src/router/trie-matching.ts +18 -13
  77. package/src/router/url-params.ts +49 -0
  78. package/src/router.ts +1 -2
  79. package/src/rsc/handler.ts +16 -8
  80. package/src/rsc/helpers.ts +69 -41
  81. package/src/rsc/progressive-enhancement.ts +4 -0
  82. package/src/rsc/response-route-handler.ts +14 -1
  83. package/src/rsc/rsc-rendering.ts +10 -0
  84. package/src/rsc/server-action.ts +4 -0
  85. package/src/rsc/types.ts +6 -0
  86. package/src/segment-content-promise.ts +67 -0
  87. package/src/segment-loader-promise.ts +122 -0
  88. package/src/segment-system.tsx +71 -70
  89. package/src/server/context.ts +26 -3
  90. package/src/server/request-context.ts +10 -42
  91. package/src/ssr/index.tsx +5 -1
  92. package/src/types/handler-context.ts +12 -39
  93. package/src/types/loader-types.ts +5 -6
  94. package/src/types/request-scope.ts +126 -0
  95. package/src/types/route-entry.ts +11 -0
  96. package/src/types/segments.ts +18 -1
  97. package/src/urls/include-helper.ts +24 -14
  98. package/src/urls/path-helper-types.ts +30 -4
  99. package/src/urls/response-types.ts +2 -10
  100. package/src/use-loader.tsx +4 -1
  101. package/src/vite/debug.ts +184 -0
  102. package/src/vite/discovery/discover-routers.ts +31 -3
  103. package/src/vite/discovery/gate-state.ts +171 -0
  104. package/src/vite/discovery/prerender-collection.ts +172 -84
  105. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  106. package/src/vite/plugins/cjs-to-esm.ts +5 -0
  107. package/src/vite/plugins/client-ref-dedup.ts +16 -0
  108. package/src/vite/plugins/client-ref-hashing.ts +16 -4
  109. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  110. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  111. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  112. package/src/vite/plugins/expose-action-id.ts +52 -28
  113. package/src/vite/plugins/expose-id-utils.ts +12 -0
  114. package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
  115. package/src/vite/plugins/expose-internal-ids.ts +540 -376
  116. package/src/vite/plugins/performance-tracks.ts +17 -9
  117. package/src/vite/plugins/use-cache-transform.ts +56 -43
  118. package/src/vite/plugins/version-injector.ts +37 -11
  119. package/src/vite/rango.ts +49 -14
  120. package/src/vite/router-discovery.ts +558 -53
  121. package/src/vite/utils/banner.ts +1 -1
  122. package/src/vite/utils/package-resolution.ts +41 -1
  123. package/src/vite/utils/prerender-utils.ts +21 -6
@@ -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,6 +383,10 @@ 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);
@@ -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
  }
@@ -950,6 +1065,7 @@ ${binding.localName}.$$id = "${handlerId}";`;
950
1065
  import MagicString3 from "magic-string";
951
1066
  import path3 from "node:path";
952
1067
  import { createHash } from "node:crypto";
1068
+ var debug2 = createRangoDebugger(NS.transform);
953
1069
  function transformRouter(code, filePath, routerFnNames, absolutePath) {
954
1070
  const pat = new RegExp(
955
1071
  `\\b(?:${routerFnNames.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\s*(?:<[^>]*>)?\\s*\\(`,
@@ -992,11 +1108,15 @@ function transformRouter(code, filePath, routerFnNames, absolutePath) {
992
1108
  }
993
1109
  function exposeRouterId() {
994
1110
  let projectRoot = "";
1111
+ const counter = createCounter(debug2, "expose-router-id");
995
1112
  return {
996
1113
  name: "@rangojs/router:expose-router-id",
997
1114
  configResolved(config) {
998
1115
  projectRoot = config.root;
999
1116
  },
1117
+ buildEnd() {
1118
+ counter?.flush();
1119
+ },
1000
1120
  transform(code, id) {
1001
1121
  if (!code.includes("createRouter")) return null;
1002
1122
  if (!/import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router(?:\/server)?["']/.test(
@@ -1005,14 +1125,25 @@ function exposeRouterId() {
1005
1125
  return null;
1006
1126
  }
1007
1127
  if (id.includes("node_modules")) return null;
1008
- const filePath = normalizePath(path3.relative(projectRoot, id));
1009
- const routerFnNames = getImportedFnNames(code, "createRouter");
1010
- 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
+ }
1011
1141
  }
1012
1142
  };
1013
1143
  }
1014
1144
 
1015
1145
  // src/vite/plugins/expose-internal-ids.ts
1146
+ var debug3 = createRangoDebugger(NS.transform);
1016
1147
  var VIRTUAL_LOADER_MANIFEST = "virtual:rsc-router/loader-manifest";
1017
1148
  var RESOLVED_VIRTUAL_LOADER_MANIFEST = "\0" + VIRTUAL_LOADER_MANIFEST;
1018
1149
  var VIRTUAL_HANDLER_PREFIX = "virtual:handler-extract:";
@@ -1025,9 +1156,13 @@ function exposeInternalIds(options) {
1025
1156
  const staticHandlerModules = /* @__PURE__ */ new Map();
1026
1157
  const virtualHandlers = /* @__PURE__ */ new Map();
1027
1158
  const unsupportedShapeWarnings = /* @__PURE__ */ new Set();
1159
+ const counter = createCounter(debug3, "expose-internal-ids");
1028
1160
  return {
1029
1161
  name: "@rangojs/router:expose-internal-ids",
1030
1162
  enforce: "post",
1163
+ buildEnd() {
1164
+ counter?.flush();
1165
+ },
1031
1166
  api: {
1032
1167
  prerenderHandlerModules,
1033
1168
  staticHandlerModules
@@ -1141,11 +1276,13 @@ ${lazyImports.join(",\n")}
1141
1276
  // --------------- Unified transform ---------------
1142
1277
  transform(code, id) {
1143
1278
  if (id.includes("/node_modules/")) return;
1144
- const filePath = normalizePath(path4.relative(projectRoot, id));
1145
- const isRscEnv = this.environment?.name === "rsc";
1146
- if (id.includes(".named-routes.gen.") && !isRscEnv && this.environment?.name === "client") {
1147
- this.warn(
1148
- `
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
+ `
1149
1286
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1150
1287
  !! !!
1151
1288
  !! WARNING: NamedRoutes imported in a CLIENT component! !!
@@ -1165,277 +1302,373 @@ ${lazyImports.join(",\n")}
1165
1302
  !! !!
1166
1303
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1167
1304
  `
1168
- );
1169
- }
1170
- if (!code.includes("@rangojs/router")) return;
1171
- const has = detectImports(code);
1172
- const hasLoaderCode = has.loader && code.includes("createLoader");
1173
- const hasHandleCode = has.handle && code.includes("createHandle");
1174
- const hasLocationStateCode = has.locationState && code.includes("createLocationState");
1175
- const hasPrerenderHandlerCode = has.prerenderHandler && code.includes("Prerender");
1176
- const hasStaticHandlerCode = has.staticHandler && code.includes("Static");
1177
- if (!hasLoaderCode && !hasHandleCode && !hasLocationStateCode && !hasPrerenderHandlerCode && !hasStaticHandlerCode) {
1178
- return;
1179
- }
1180
- const _fnNamesCache = /* @__PURE__ */ new Map();
1181
- const _bindingsCache = /* @__PURE__ */ new Map();
1182
- let _cachedAst;
1183
- let _astParseFailed = false;
1184
- let _astCodeRef = code;
1185
- const getFnNames = (canonicalName) => {
1186
- let result = _fnNamesCache.get(canonicalName);
1187
- if (!result) {
1188
- result = getImportedFnNames(code, canonicalName);
1189
- _fnNamesCache.set(canonicalName, result);
1305
+ );
1190
1306
  }
1191
- return result;
1192
- };
1193
- const lazyAst = () => {
1194
- if (code !== _astCodeRef) {
1195
- _cachedAst = void 0;
1196
- _astParseFailed = false;
1197
- _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;
1198
1316
  }
1199
- if (_cachedAst !== void 0 || _astParseFailed) return _cachedAst;
1200
- try {
1201
- _cachedAst = parseAst2(code, { jsx: true });
1202
- } catch {
1203
- _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));
1204
1368
  }
1205
- return _cachedAst;
1206
- };
1207
- const getBindings = (currentCode, fnNames) => {
1208
- const key = fnNames.join("\0");
1209
- let result = _bindingsCache.get(key);
1210
- if (!result) {
1211
- result = collectCreateExportBindings(currentCode, fnNames, lazyAst());
1212
- _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
+ }
1213
1380
  }
1214
- return result;
1215
- };
1216
- for (const cfg of STRICT_CREATE_CONFIGS) {
1217
- const hasCode = cfg.fnName === "createLoader" ? hasLoaderCode : cfg.fnName === "createHandle" ? hasHandleCode : hasLocationStateCode;
1218
- if (!hasCode) continue;
1219
- const fnNames = getFnNames(cfg.fnName);
1220
- const totalCalls = countCreateCallsForNames(code, fnNames);
1221
- const supportedBindings = getBindings(code, fnNames).length;
1222
- if (totalCalls <= supportedBindings) continue;
1223
- const warnKey = `${id}::${cfg.fnName}`;
1224
- if (unsupportedShapeWarnings.has(warnKey)) continue;
1225
- unsupportedShapeWarnings.add(warnKey);
1226
- this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
1227
- }
1228
- if (hasLoaderCode && isRscEnv) {
1229
- const fnNames = getFnNames("createLoader");
1230
- const bindings = getBindings(code, fnNames);
1231
- for (const binding of bindings) {
1232
- const exportName = binding.exportNames[0];
1233
- const hashedId = hashId(filePath, exportName);
1234
- 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,
1235
1387
  filePath,
1236
- exportName
1237
- });
1238
- }
1239
- }
1240
- if (hasLoaderCode && !isRscEnv) {
1241
- const fnNames = getFnNames("createLoader");
1242
- const bindings = getBindings(code, fnNames);
1243
- const stubResult = generateClientLoaderStubs(
1244
- bindings,
1245
- code,
1246
- filePath,
1247
- isBuild
1248
- );
1249
- if (stubResult) return stubResult;
1250
- }
1251
- if (hasPrerenderHandlerCode && !isRscEnv) {
1252
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1253
- const bindings = getBindings(code, fnNames);
1254
- const wholeFile = generateWholeFileStubs(
1255
- PRERENDER_CONFIG,
1256
- bindings,
1257
- code,
1258
- filePath,
1259
- isBuild
1260
- );
1261
- if (wholeFile) return wholeFile;
1262
- }
1263
- if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1264
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1265
- const exportNames = getBindings(code, fnNames).map(
1266
- (b) => b.exportNames[0]
1267
- );
1268
- if (exportNames.length > 0) {
1269
- prerenderHandlerModules.set(id, exportNames);
1388
+ isBuild
1389
+ );
1390
+ if (stubResult) return stubResult;
1270
1391
  }
1271
- }
1272
- let changed = false;
1273
- const handlerConfigs = [
1274
- hasStaticHandlerCode && STATIC_CONFIG,
1275
- hasPrerenderHandlerCode && PRERENDER_CONFIG
1276
- ].filter((c) => !!c).map((cfg) => {
1277
- const fnNames = getFnNames(cfg.fnName);
1278
- return { cfg, fnNames };
1279
- });
1280
- for (const { cfg, fnNames } of handlerConfigs) {
1281
- const totalCalls = countCreateCallsForNames(code, fnNames);
1282
- const supportedBindings = getBindings(code, fnNames).length;
1283
- if (totalCalls > supportedBindings) {
1284
- const iterS = new MagicString4(code);
1285
- const result = transformInlineHandlers(
1286
- cfg.fnName,
1287
- VIRTUAL_HANDLER_PREFIX,
1288
- iterS,
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,
1289
1398
  code,
1290
1399
  filePath,
1291
- virtualHandlers,
1292
- id,
1293
- parseAst2
1400
+ isBuild
1294
1401
  );
1295
- if (result) {
1296
- changed = true;
1297
- code = iterS.toString();
1298
- _bindingsCache.clear();
1299
- }
1300
- }
1301
- }
1302
- if (hasStaticHandlerCode && !isRscEnv) {
1303
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1304
- const bindings = getBindings(code, fnNames);
1305
- const wholeFile = generateWholeFileStubs(
1306
- STATIC_CONFIG,
1307
- bindings,
1308
- code,
1309
- filePath,
1310
- isBuild
1311
- );
1312
- if (wholeFile) return wholeFile;
1313
- }
1314
- if (!isRscEnv && (hasPrerenderHandlerCode || hasStaticHandlerCode)) {
1315
- const allBindings = [];
1316
- if (hasLoaderCode) {
1317
- allBindings.push(...getBindings(code, getFnNames("createLoader")));
1318
- }
1319
- if (hasHandleCode) {
1320
- allBindings.push(...getBindings(code, getFnNames("createHandle")));
1402
+ if (wholeFile) return wholeFile;
1321
1403
  }
1322
- if (hasLocationStateCode) {
1323
- allBindings.push(
1324
- ...getBindings(code, getFnNames("createLocationState"))
1404
+ if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1405
+ const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1406
+ const exportNames = getBindings(code, fnNames).map(
1407
+ (b) => b.exportNames[0]
1325
1408
  );
1409
+ if (exportNames.length > 0) {
1410
+ prerenderHandlerModules.set(id, exportNames);
1411
+ }
1326
1412
  }
1327
- if (hasPrerenderHandlerCode) {
1328
- allBindings.push(
1329
- ...getBindings(code, getFnNames(PRERENDER_CONFIG.fnName))
1330
- );
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
+ }
1331
1442
  }
1332
- if (hasStaticHandlerCode) {
1333
- allBindings.push(
1334
- ...getBindings(code, getFnNames(STATIC_CONFIG.fnName))
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,
1449
+ code,
1450
+ filePath,
1451
+ isBuild
1335
1452
  );
1453
+ if (wholeFile) return wholeFile;
1336
1454
  }
1337
- if (allBindings.length > 0 && isExportOnlyFile(code, allBindings)) {
1338
- const stubs = [];
1339
- for (const binding of allBindings) {
1340
- const name = binding.exportNames[0];
1341
- const stubId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1342
- const fnCall = code.slice(
1343
- binding.callExprStart,
1344
- binding.callOpenParenPos + 1
1345
- );
1346
- let brand = "loader";
1347
- if (hasPrerenderHandlerCode && getFnNames(PRERENDER_CONFIG.fnName).some(
1348
- (n) => fnCall.includes(n)
1349
- )) {
1350
- brand = PRERENDER_CONFIG.brand;
1351
- } else if (hasStaticHandlerCode && getFnNames(STATIC_CONFIG.fnName).some((n) => fnCall.includes(n))) {
1352
- brand = STATIC_CONFIG.brand;
1353
- } else if (hasHandleCode && getFnNames("createHandle").some((n) => fnCall.includes(n))) {
1354
- brand = "handle";
1355
- } else if (hasLocationStateCode && getFnNames("createLocationState").some((n) => fnCall.includes(n))) {
1356
- brand = "locationState";
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));
1357
1471
  }
1358
- stubs.push(
1359
- `export const ${name} = { __brand: "${brand}", $$id: "${stubId}" };`
1360
- );
1361
1472
  }
1362
- return { code: stubs.join("\n") + "\n", map: null };
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 };
1588
+ }
1363
1589
  }
1364
- }
1365
- if (hasStaticHandlerCode && isRscEnv && isBuild) {
1366
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1367
- const exportNames = getBindings(code, fnNames).map(
1368
- (b) => b.exportNames[0]
1369
- );
1370
- if (exportNames.length > 0) {
1371
- 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
+ }
1372
1598
  }
1373
- }
1374
- const s = new MagicString4(code);
1375
- if (hasLoaderCode) {
1376
- const fnNames = getFnNames("createLoader");
1377
- changed = transformLoaders(getBindings(code, fnNames), s, filePath, isBuild) || changed;
1378
- }
1379
- if (hasHandleCode) {
1380
- const fnNames = getFnNames("createHandle");
1381
- changed = transformHandles(
1382
- getBindings(code, fnNames),
1383
- s,
1384
- code,
1385
- filePath,
1386
- isBuild
1387
- ) || changed;
1388
- }
1389
- if (hasLocationStateCode) {
1390
- const fnNames = getFnNames("createLocationState");
1391
- changed = transformLocationState(
1392
- getBindings(code, fnNames),
1393
- s,
1394
- filePath,
1395
- isBuild
1396
- ) || changed;
1397
- }
1398
- if (hasPrerenderHandlerCode) {
1399
- const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1400
- const bindings = getBindings(code, fnNames);
1401
- if (isRscEnv) {
1402
- changed = transformHandlerIds(
1403
- PRERENDER_CONFIG,
1404
- bindings,
1599
+ const s = new MagicString4(code);
1600
+ if (hasLoaderCode) {
1601
+ const fnNames = getFnNames("createLoader");
1602
+ changed = transformLoaders(
1603
+ getBindings(code, fnNames),
1405
1604
  s,
1406
1605
  filePath,
1407
1606
  isBuild
1408
1607
  ) || changed;
1409
- } else {
1410
- changed = stubHandlerExprs(
1411
- PRERENDER_CONFIG,
1412
- bindings,
1608
+ }
1609
+ if (hasHandleCode) {
1610
+ const fnNames = getFnNames("createHandle");
1611
+ changed = transformHandles(
1612
+ getBindings(code, fnNames),
1413
1613
  s,
1614
+ code,
1414
1615
  filePath,
1415
1616
  isBuild
1416
1617
  ) || changed;
1417
1618
  }
1418
- }
1419
- if (hasStaticHandlerCode) {
1420
- const fnNames = getFnNames(STATIC_CONFIG.fnName);
1421
- const bindings = getBindings(code, fnNames);
1422
- if (isRscEnv) {
1423
- changed = transformHandlerIds(
1424
- STATIC_CONFIG,
1425
- bindings,
1619
+ if (hasLocationStateCode) {
1620
+ const fnNames = getFnNames("createLocationState");
1621
+ changed = transformLocationState(
1622
+ getBindings(code, fnNames),
1426
1623
  s,
1427
1624
  filePath,
1428
1625
  isBuild
1429
1626
  ) || changed;
1430
- } else {
1431
- changed = stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) || changed;
1432
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);
1433
1671
  }
1434
- if (!changed) return;
1435
- return {
1436
- code: s.toString(),
1437
- map: s.generateMap({ source: id, includeContent: true })
1438
- };
1439
1672
  }
1440
1673
  };
1441
1674
  }
@@ -1443,12 +1676,14 @@ ${lazyImports.join(",\n")}
1443
1676
  // src/vite/plugins/use-cache-transform.ts
1444
1677
  import path5 from "node:path";
1445
1678
  import MagicString5 from "magic-string";
1679
+ var debug4 = createRangoDebugger(NS.transform);
1446
1680
  var CACHE_RUNTIME_IMPORT = "@rangojs/router/cache-runtime";
1447
1681
  var LAYOUT_TEMPLATE_PATTERN = /\/(layout|template)\.(tsx?|jsx?)$/;
1448
1682
  function useCacheTransform() {
1449
1683
  let projectRoot = "";
1450
1684
  let isBuild = false;
1451
1685
  let rscTransforms = null;
1686
+ const counter = createCounter(debug4, "use-cache");
1452
1687
  return {
1453
1688
  name: "@rangojs/router:use-cache",
1454
1689
  enforce: "post",
@@ -1456,53 +1691,61 @@ function useCacheTransform() {
1456
1691
  projectRoot = config.root;
1457
1692
  isBuild = config.command === "build";
1458
1693
  },
1694
+ buildEnd() {
1695
+ counter?.flush();
1696
+ },
1459
1697
  async transform(code, id) {
1460
1698
  if (this.environment?.name !== "rsc") return;
1461
1699
  if (!code.includes("use cache")) return;
1462
1700
  if (id.includes("/node_modules/") || id.startsWith("\0")) return;
1463
1701
  if (!/\.(tsx?|jsx?|mjs)$/.test(id)) return;
1464
- 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;
1465
1717
  try {
1466
- rscTransforms = await import("@vitejs/plugin-rsc/transforms");
1718
+ const { parseAst: parseAst4 } = await import("vite");
1719
+ ast = parseAst4(code);
1467
1720
  } catch {
1468
1721
  return;
1469
1722
  }
1470
- }
1471
- const {
1472
- hasDirective,
1473
- transformWrapExport,
1474
- transformHoistInlineDirective
1475
- } = rscTransforms;
1476
- let ast;
1477
- try {
1478
- const { parseAst: parseAst4 } = await import("vite");
1479
- ast = parseAst4(code);
1480
- } catch {
1481
- return;
1482
- }
1483
- const filePath = normalizePath(path5.relative(projectRoot, id));
1484
- const isLayoutOrTemplate = LAYOUT_TEMPLATE_PATTERN.test(id);
1485
- if (hasDirective(ast.body, "use cache")) {
1486
- 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(
1487
1737
  code,
1488
1738
  ast,
1489
1739
  filePath,
1490
1740
  id,
1491
1741
  isBuild,
1492
- isLayoutOrTemplate,
1493
- transformWrapExport
1742
+ transformHoistInlineDirective
1494
1743
  );
1744
+ warnOnNearMissDirectives(ast, id, this.warn.bind(this));
1745
+ if (functionResult) return functionResult;
1746
+ } finally {
1747
+ counter?.record(id, performance.now() - start);
1495
1748
  }
1496
- const functionResult = transformFunctionLevelUseCache(
1497
- code,
1498
- ast,
1499
- filePath,
1500
- id,
1501
- isBuild,
1502
- transformHoistInlineDirective
1503
- );
1504
- warnOnNearMissDirectives(ast, id, this.warn.bind(this));
1505
- if (functionResult) return functionResult;
1506
1749
  }
1507
1750
  };
1508
1751
  }
@@ -1625,6 +1868,7 @@ function warnOnNearMissDirectives(ast, fileId, warn) {
1625
1868
  }
1626
1869
 
1627
1870
  // src/vite/plugins/client-ref-dedup.ts
1871
+ var debug5 = createRangoDebugger(NS.transform);
1628
1872
  var CLIENT_IN_SERVER_PROXY_PREFIX = "virtual:vite-rsc/client-in-server-package-proxy/";
1629
1873
  function extractPackageName(absolutePath) {
1630
1874
  const marker = "/node_modules/";
@@ -1641,6 +1885,7 @@ function extractPackageName(absolutePath) {
1641
1885
  }
1642
1886
  function clientRefDedup() {
1643
1887
  let clientExclude = [];
1888
+ const dedupedPackages = /* @__PURE__ */ new Set();
1644
1889
  return {
1645
1890
  name: "@rangojs/router:client-ref-dedup",
1646
1891
  enforce: "pre",
@@ -1649,6 +1894,15 @@ function clientRefDedup() {
1649
1894
  const clientEnv = config.environments?.["client"];
1650
1895
  clientExclude = clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
1651
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
+ },
1652
1906
  resolveId(source, importer, options) {
1653
1907
  if (this.environment?.name !== "client") return;
1654
1908
  if (!importer?.includes(CLIENT_IN_SERVER_PROXY_PREFIX)) return;
@@ -1657,6 +1911,7 @@ function clientRefDedup() {
1657
1911
  const packageName = extractPackageName(source);
1658
1912
  if (!packageName) return;
1659
1913
  if (clientExclude.includes(packageName)) return;
1914
+ if (debug5) dedupedPackages.add(packageName);
1660
1915
  return `\0rango:dedup/${packageName}`;
1661
1916
  },
1662
1917
  load(id) {
@@ -1779,12 +2034,13 @@ function getVirtualVersionContent(version) {
1779
2034
 
1780
2035
  // src/vite/utils/package-resolution.ts
1781
2036
  import { existsSync } from "node:fs";
2037
+ import { createRequire } from "node:module";
1782
2038
  import { resolve } from "node:path";
1783
2039
 
1784
2040
  // package.json
1785
2041
  var package_default = {
1786
2042
  name: "@rangojs/router",
1787
- version: "0.0.0-experimental.69",
2043
+ version: "0.0.0-experimental.6c70a2ab",
1788
2044
  description: "Django-inspired RSC router with composable URL patterns",
1789
2045
  keywords: [
1790
2046
  "react",
@@ -1917,16 +2173,18 @@ var package_default = {
1917
2173
  tag: "experimental"
1918
2174
  },
1919
2175
  scripts: {
1920
- 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",
1921
2177
  prepublishOnly: "pnpm build",
1922
- typecheck: "tsc --noEmit",
2178
+ typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit",
1923
2179
  test: "playwright test",
1924
2180
  "test:ui": "playwright test --ui",
1925
2181
  "test:unit": "vitest run",
1926
2182
  "test:unit:watch": "vitest"
1927
2183
  },
1928
2184
  dependencies: {
1929
- "@vitejs/plugin-rsc": "^0.5.19",
2185
+ "@types/debug": "^4.1.12",
2186
+ "@vitejs/plugin-rsc": "^0.5.23",
2187
+ debug: "^4.4.1",
1930
2188
  "magic-string": "^0.30.17",
1931
2189
  picomatch: "^4.0.3",
1932
2190
  "rsc-html-stream": "^0.0.7"
@@ -1946,7 +2204,7 @@ var package_default = {
1946
2204
  },
1947
2205
  peerDependencies: {
1948
2206
  "@cloudflare/vite-plugin": "^1.25.0",
1949
- "@vitejs/plugin-rsc": "^0.5.14",
2207
+ "@vitejs/plugin-rsc": "^0.5.23",
1950
2208
  react: "^18.0.0 || ^19.0.0",
1951
2209
  vite: "^7.3.0"
1952
2210
  },
@@ -1961,6 +2219,7 @@ var package_default = {
1961
2219
  };
1962
2220
 
1963
2221
  // src/vite/utils/package-resolution.ts
2222
+ var require2 = createRequire(import.meta.url);
1964
2223
  var VIRTUAL_PACKAGE_NAME = "@rangojs/router";
1965
2224
  function getPublishedPackageName() {
1966
2225
  return package_default.name;
@@ -2001,6 +2260,20 @@ function getPackageAliases() {
2001
2260
  }
2002
2261
  return aliases;
2003
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
+ }
2004
2277
 
2005
2278
  // src/build/route-types/param-extraction.ts
2006
2279
  function extractParamsFromPattern(pattern) {
@@ -2898,6 +3171,7 @@ import * as Vite from "vite";
2898
3171
 
2899
3172
  // src/vite/plugins/performance-tracks.ts
2900
3173
  import { readFile } from "node:fs/promises";
3174
+ var debug6 = createRangoDebugger(NS.transform);
2901
3175
  var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2902
3176
  function buildPatchReplacement(match, debugInfoVar) {
2903
3177
  return `${match}
@@ -2939,19 +3213,23 @@ function performanceTracksOptimizeDepsPlugin() {
2939
3213
  };
2940
3214
  }
2941
3215
  function performanceTracksPlugin() {
3216
+ const counter = createCounter(debug6, "performance-tracks");
2942
3217
  return {
2943
3218
  name: "@rangojs/router:performance-tracks",
3219
+ buildEnd() {
3220
+ counter?.flush();
3221
+ },
2944
3222
  transform(code, id) {
2945
3223
  if (!id.includes("react-server-dom") || !id.includes("client")) return;
2946
- const patched = patchRsdwClientDebugInfoRecovery(code);
2947
- if (!patched.debugInfoVar) return;
2948
- if (process.env.INTERNAL_RANGO_DEBUG)
2949
- console.log(
2950
- "[perf-tracks] patched RSDW client (var:",
2951
- patched.debugInfoVar,
2952
- ")"
2953
- );
2954
- return patched.code;
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
+ }
2955
3233
  }
2956
3234
  };
2957
3235
  }
@@ -3098,11 +3376,10 @@ function createVersionInjectorPlugin(rscEntryPath) {
3098
3376
  if (normalizedId !== normalizedEntry) {
3099
3377
  return null;
3100
3378
  }
3101
- const prepend = [];
3379
+ const prepend = [
3380
+ `import "virtual:rsc-router/routes-manifest";`
3381
+ ];
3102
3382
  let newCode = code;
3103
- if (!code.includes("virtual:rsc-router/routes-manifest")) {
3104
- prepend.push(`import "virtual:rsc-router/routes-manifest";`);
3105
- }
3106
3383
  const needsVersion = code.includes("createRSCHandler") && !code.includes("@rangojs/router:version") && /createRSCHandler\s*\(\s*\{/.test(code);
3107
3384
  if (needsVersion) {
3108
3385
  prepend.push(`import { VERSION } from "@rangojs/router:version";`);
@@ -3111,8 +3388,21 @@ function createVersionInjectorPlugin(rscEntryPath) {
3111
3388
  "createRSCHandler({\n version: VERSION,"
3112
3389
  );
3113
3390
  }
3114
- if (prepend.length === 0 && newCode === code) return null;
3115
- newCode = prepend.join("\n") + (prepend.length > 0 ? "\n" : "") + newCode;
3391
+ const lines = newCode.split("\n");
3392
+ let insertAt = 0;
3393
+ while (insertAt < lines.length) {
3394
+ const trimmed = lines[insertAt].trim();
3395
+ if (trimmed === "" || /^\/\/\/\s*<reference\b/.test(trimmed)) {
3396
+ insertAt++;
3397
+ } else {
3398
+ break;
3399
+ }
3400
+ }
3401
+ newCode = [
3402
+ ...lines.slice(0, insertAt),
3403
+ ...prepend,
3404
+ ...lines.slice(insertAt)
3405
+ ].join("\n");
3116
3406
  return {
3117
3407
  code: newCode,
3118
3408
  map: null
@@ -3122,6 +3412,7 @@ function createVersionInjectorPlugin(rscEntryPath) {
3122
3412
  }
3123
3413
 
3124
3414
  // src/vite/plugins/cjs-to-esm.ts
3415
+ var debug7 = createRangoDebugger(NS.transform);
3125
3416
  function createCjsToEsmPlugin() {
3126
3417
  return {
3127
3418
  name: "@rangojs/router:cjs-to-esm",
@@ -3131,6 +3422,7 @@ function createCjsToEsmPlugin() {
3131
3422
  if (cleanId.includes("vendor/react-server-dom/client.browser.js") || cleanId.includes("vendor\\react-server-dom\\client.browser.js")) {
3132
3423
  const isProd = process.env.NODE_ENV === "production";
3133
3424
  const cjsFile = isProd ? "./cjs/react-server-dom-webpack-client.browser.production.js" : "./cjs/react-server-dom-webpack-client.browser.development.js";
3425
+ debug7?.("cjs-to-esm entry redirect %s", id);
3134
3426
  return {
3135
3427
  code: `export * from "${cjsFile}";`,
3136
3428
  map: null
@@ -3166,6 +3458,7 @@ function createCjsToEsmPlugin() {
3166
3458
  "export const $1 ="
3167
3459
  );
3168
3460
  transformed = license + "\n" + transformed;
3461
+ debug7?.("cjs-to-esm body rewrite %s", id);
3169
3462
  return {
3170
3463
  code: transformed,
3171
3464
  map: null
@@ -3180,7 +3473,7 @@ function createCjsToEsmPlugin() {
3180
3473
  import { createServer as createViteServer } from "vite";
3181
3474
  import { resolve as resolve8 } from "node:path";
3182
3475
  import { readFileSync as readFileSync6 } from "node:fs";
3183
- import { createRequire } from "node:module";
3476
+ import { createRequire as createRequire2, register } from "node:module";
3184
3477
  import { pathToFileURL } from "node:url";
3185
3478
 
3186
3479
  // src/vite/plugins/virtual-stub-plugin.ts
@@ -3207,9 +3500,117 @@ function createVirtualStubPlugin() {
3207
3500
  };
3208
3501
  }
3209
3502
 
3503
+ // src/vite/plugins/cloudflare-protocol-stub.ts
3504
+ var VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
3505
+ var NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
3506
+ var CF_PREFIX = "cloudflare:";
3507
+ var BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
3508
+ var SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
3509
+ var IMPORT_NODE_TYPES = /* @__PURE__ */ new Set([
3510
+ "ImportDeclaration",
3511
+ "ImportExpression",
3512
+ "ExportNamedDeclaration",
3513
+ "ExportAllDeclaration"
3514
+ ]);
3515
+ var STUBS = {
3516
+ "cloudflare:workers": `
3517
+ export class DurableObject { constructor(_ctx, _env) {} }
3518
+ export class WorkerEntrypoint { constructor(_ctx, _env) {} }
3519
+ export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
3520
+ export class RpcTarget {}
3521
+ export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
3522
+ export default {};
3523
+ `,
3524
+ "cloudflare:email": `
3525
+ export class EmailMessage { constructor(_from, _to, _raw) {} }
3526
+ export default {};
3527
+ `,
3528
+ "cloudflare:sockets": `
3529
+ export function connect() { return {}; }
3530
+ export default {};
3531
+ `,
3532
+ "cloudflare:workflows": `
3533
+ export class NonRetryableError extends Error {
3534
+ constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
3535
+ }
3536
+ export default {};
3537
+ `
3538
+ };
3539
+ var FALLBACK_STUB = `export default {};
3540
+ `;
3541
+ function createCloudflareProtocolStubPlugin() {
3542
+ return {
3543
+ name: "@rangojs/router:cloudflare-protocol-stub",
3544
+ transform(code, id) {
3545
+ const cleanId = id.split("?")[0] ?? id;
3546
+ if (!SOURCE_EXT_RE.test(cleanId)) return null;
3547
+ if (!code.includes(CF_PREFIX)) return null;
3548
+ let ast;
3549
+ try {
3550
+ ast = this.parse(code);
3551
+ } catch {
3552
+ return null;
3553
+ }
3554
+ const hits = [];
3555
+ walk(ast, (node) => {
3556
+ if (!IMPORT_NODE_TYPES.has(node.type)) return;
3557
+ const source = node.source;
3558
+ if (!source || source.type !== "Literal") return;
3559
+ if (typeof source.value !== "string") return;
3560
+ if (!source.value.startsWith(CF_PREFIX)) return;
3561
+ if (typeof source.start !== "number" || typeof source.end !== "number")
3562
+ return;
3563
+ hits.push({
3564
+ start: source.start,
3565
+ end: source.end,
3566
+ value: source.value
3567
+ });
3568
+ });
3569
+ if (hits.length === 0) return null;
3570
+ hits.sort((a, b) => b.start - a.start);
3571
+ let out = code;
3572
+ for (const hit of hits) {
3573
+ const submodule = hit.value.slice(CF_PREFIX.length);
3574
+ const quote = code[hit.start] === "'" ? "'" : '"';
3575
+ out = out.slice(0, hit.start) + quote + VIRTUAL_PREFIX + submodule + quote + out.slice(hit.end);
3576
+ }
3577
+ return { code: out, map: null };
3578
+ },
3579
+ resolveId(id) {
3580
+ if (id.startsWith(VIRTUAL_PREFIX)) {
3581
+ return "\0" + id;
3582
+ }
3583
+ return null;
3584
+ },
3585
+ load(id) {
3586
+ if (!id.startsWith(NULL_PREFIX)) return null;
3587
+ const submodule = id.slice(NULL_PREFIX.length);
3588
+ const specifier = CF_PREFIX + submodule;
3589
+ return STUBS[specifier] ?? FALLBACK_STUB;
3590
+ }
3591
+ };
3592
+ }
3593
+ function walk(node, visit) {
3594
+ if (!node || typeof node !== "object") return;
3595
+ if (Array.isArray(node)) {
3596
+ for (const child of node) walk(child, visit);
3597
+ return;
3598
+ }
3599
+ const n = node;
3600
+ if (typeof n.type !== "string") return;
3601
+ visit(n);
3602
+ for (const key in n) {
3603
+ if (key === "loc" || key === "start" || key === "end" || key === "range") {
3604
+ continue;
3605
+ }
3606
+ walk(n[key], visit);
3607
+ }
3608
+ }
3609
+
3210
3610
  // src/vite/plugins/client-ref-hashing.ts
3211
3611
  import { relative } from "node:path";
3212
3612
  import { createHash as createHash2 } from "node:crypto";
3613
+ var debug8 = createRangoDebugger(NS.transform);
3213
3614
  var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3214
3615
  var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
3215
3616
  var FS_PREFIX = "/@fs/";
@@ -3248,6 +3649,7 @@ function transformClientRefs(code, projectRoot) {
3248
3649
  return hasReplacement ? result : null;
3249
3650
  }
3250
3651
  function hashClientRefs(projectRoot) {
3652
+ const counter = createCounter(debug8, "hash-client-refs");
3251
3653
  return {
3252
3654
  name: "@rangojs/router:hash-client-refs",
3253
3655
  // Run after the RSC plugin's transform (default enforce is normal)
@@ -3255,10 +3657,18 @@ function hashClientRefs(projectRoot) {
3255
3657
  applyToEnvironment(env) {
3256
3658
  return env.name === "rsc";
3257
3659
  },
3258
- transform(code, _id) {
3259
- const result = transformClientRefs(code, projectRoot);
3260
- if (result === null) return;
3261
- return { code: result, map: null };
3660
+ buildEnd() {
3661
+ counter?.flush();
3662
+ },
3663
+ transform(code, id) {
3664
+ const start = counter ? performance.now() : 0;
3665
+ try {
3666
+ const result = transformClientRefs(code, projectRoot);
3667
+ if (result === null) return;
3668
+ return { code: result, map: null };
3669
+ } finally {
3670
+ counter?.record(id, performance.now() - start);
3671
+ }
3262
3672
  }
3263
3673
  };
3264
3674
  }
@@ -3386,6 +3796,12 @@ function markSelfGenWrite(state, filePath, content) {
3386
3796
  state.selfWrittenGenFiles.set(filePath, { at: Date.now(), hash });
3387
3797
  }
3388
3798
  function consumeSelfGenWrite(state, filePath) {
3799
+ return checkSelfGenWrite(state, filePath, true);
3800
+ }
3801
+ function peekSelfGenWrite(state, filePath) {
3802
+ return checkSelfGenWrite(state, filePath, false);
3803
+ }
3804
+ function checkSelfGenWrite(state, filePath, consume) {
3389
3805
  const info = state.selfWrittenGenFiles.get(filePath);
3390
3806
  if (!info) return false;
3391
3807
  if (Date.now() - info.at > state.SELF_WRITE_WINDOW_MS) {
@@ -3396,7 +3812,7 @@ function consumeSelfGenWrite(state, filePath) {
3396
3812
  const current = readFileSync3(filePath, "utf-8");
3397
3813
  const currentHash = createHash3("sha256").update(current).digest("hex");
3398
3814
  if (currentHash === info.hash) {
3399
- state.selfWrittenGenFiles.delete(filePath);
3815
+ if (consume) state.selfWrittenGenFiles.delete(filePath);
3400
3816
  return true;
3401
3817
  }
3402
3818
  return false;
@@ -3500,11 +3916,19 @@ function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3500
3916
  let hadOmittedOptional = false;
3501
3917
  for (const [key, value] of Object.entries(params)) {
3502
3918
  const escaped = escapeRegExp2(key);
3503
- result = result.replace(
3504
- new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3505
- encode(value)
3506
- );
3507
- result = result.replace(`*${key}`, encode(value));
3919
+ if (value === "") {
3920
+ result = result.replace(
3921
+ new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
3922
+ ""
3923
+ );
3924
+ result = result.replace(`*${key}`, "");
3925
+ } else {
3926
+ result = result.replace(
3927
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3928
+ encode(value)
3929
+ );
3930
+ result = result.replace(`*${key}`, encode(value));
3931
+ }
3508
3932
  }
3509
3933
  result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3510
3934
  hadOmittedOptional = true;
@@ -3610,8 +4034,14 @@ function copyStagedBuildAssets(projectRoot, fileNames) {
3610
4034
  }
3611
4035
 
3612
4036
  // src/vite/discovery/prerender-collection.ts
4037
+ var debug9 = createRangoDebugger(NS.prerender);
3613
4038
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3614
4039
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode) return;
4040
+ const overallStart = debug9 ? performance.now() : 0;
4041
+ debug9?.(
4042
+ "expandPrerenderRoutes: start (%d router manifest(s))",
4043
+ allManifests.length
4044
+ );
3615
4045
  const entries = [];
3616
4046
  const allRoutes = {};
3617
4047
  for (const { manifest: m } of allManifests) {
@@ -3623,100 +4053,151 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3623
4053
  if (!params) return pattern;
3624
4054
  return substituteRouteParams(pattern, params);
3625
4055
  };
4056
+ let resolvedRoutes = 0;
4057
+ let totalDynamic = 0;
3626
4058
  for (const { manifest } of allManifests) {
3627
4059
  if (!manifest.prerenderRoutes) continue;
3628
- const defs = manifest._prerenderDefs || {};
3629
- const passthroughSet = new Set(manifest.passthroughRoutes || []);
3630
4060
  for (const routeName of manifest.prerenderRoutes) {
3631
4061
  const pattern = manifest.routeManifest[routeName];
3632
- if (!pattern) continue;
3633
- const def = defs[routeName];
3634
- const isPassthroughRoute = passthroughSet.has(routeName);
3635
- const hasDynamic = pattern.includes(":") || pattern.includes("*");
3636
- if (!hasDynamic) {
3637
- entries.push({
3638
- urlPath: pattern.replace(/\/$/, "") || "/",
3639
- routeName,
3640
- concurrency: 1,
3641
- isPassthroughRoute
3642
- });
3643
- } else {
3644
- if (def?.getParams) {
3645
- try {
3646
- const buildVars = {};
3647
- const buildEnv = state.resolvedBuildEnv;
3648
- const getParamsCtx = {
3649
- build: true,
3650
- dev: !state.isBuildMode,
3651
- set: ((keyOrVar, value) => {
3652
- contextSet(buildVars, keyOrVar, value);
3653
- }),
3654
- reverse: getParamsReverse,
3655
- get env() {
3656
- if (buildEnv !== void 0) return buildEnv;
3657
- throw new Error(
3658
- "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3659
- );
3660
- }
3661
- };
3662
- const paramsList = await def.getParams(getParamsCtx);
3663
- const concurrency = def.options?.concurrency ?? 1;
3664
- const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3665
- for (const params of paramsList) {
3666
- let url = substituteRouteParams(
3667
- pattern,
3668
- params,
3669
- encodePathParam
3670
- );
3671
- if (url.includes("*")) {
3672
- const wildcardValue = params["*"] ?? params.splat;
3673
- if (wildcardValue !== void 0) {
3674
- url = url.replace(/\*[^/]*$/, encodePathParam(wildcardValue));
4062
+ if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
4063
+ totalDynamic++;
4064
+ }
4065
+ }
4066
+ }
4067
+ const paramsStart = performance.now();
4068
+ const progressInterval = totalDynamic > 0 ? setInterval(() => {
4069
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4070
+ console.log(
4071
+ `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4072
+ );
4073
+ }, 5e3) : void 0;
4074
+ try {
4075
+ for (const { manifest } of allManifests) {
4076
+ if (!manifest.prerenderRoutes) continue;
4077
+ const defs = manifest._prerenderDefs || {};
4078
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
4079
+ for (const routeName of manifest.prerenderRoutes) {
4080
+ const pattern = manifest.routeManifest[routeName];
4081
+ if (!pattern) continue;
4082
+ const def = defs[routeName];
4083
+ const isPassthroughRoute = passthroughSet.has(routeName);
4084
+ const hasDynamic = pattern.includes(":") || pattern.includes("*");
4085
+ if (!hasDynamic) {
4086
+ entries.push({
4087
+ urlPath: pattern.replace(/\/$/, "") || "/",
4088
+ routeName,
4089
+ concurrency: 1,
4090
+ isPassthroughRoute
4091
+ });
4092
+ } else {
4093
+ if (def?.getParams) {
4094
+ const getParamsStart = debug9 ? performance.now() : 0;
4095
+ try {
4096
+ const buildVars = {};
4097
+ const buildEnv = state.resolvedBuildEnv;
4098
+ const getParamsCtx = {
4099
+ build: true,
4100
+ dev: !state.isBuildMode,
4101
+ set: ((keyOrVar, value) => {
4102
+ contextSet(buildVars, keyOrVar, value);
4103
+ }),
4104
+ reverse: getParamsReverse,
4105
+ get env() {
4106
+ if (buildEnv !== void 0) return buildEnv;
4107
+ throw new Error(
4108
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
4109
+ );
3675
4110
  }
3676
- }
3677
- entries.push({
3678
- urlPath: url.replace(/\/$/, "") || "/",
4111
+ };
4112
+ const paramsList = await def.getParams(getParamsCtx);
4113
+ debug9?.(
4114
+ "getParams %s -> %d params (%sms)",
3679
4115
  routeName,
3680
- concurrency,
3681
- ...hasBuildVars ? { buildVars } : {},
3682
- isPassthroughRoute
3683
- });
3684
- }
3685
- } catch (err) {
3686
- if (err.name === "Skip") {
3687
- console.log(
3688
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
4116
+ paramsList.length,
4117
+ (performance.now() - getParamsStart).toFixed(1)
3689
4118
  );
3690
- notifyOnError(
3691
- registry,
3692
- err,
3693
- "prerender",
3694
- routeName,
3695
- void 0,
3696
- true
4119
+ const concurrency = def.options?.concurrency ?? 1;
4120
+ const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
4121
+ for (const params of paramsList) {
4122
+ let url = substituteRouteParams(
4123
+ pattern,
4124
+ params,
4125
+ encodePathParam
4126
+ );
4127
+ if (url.includes("*")) {
4128
+ const wildcardValue = params["*"] ?? params.splat;
4129
+ if (wildcardValue !== void 0) {
4130
+ url = url.replace(
4131
+ /\*[^/]*$/,
4132
+ encodePathParam(wildcardValue)
4133
+ );
4134
+ }
4135
+ }
4136
+ entries.push({
4137
+ urlPath: url.replace(/\/$/, "") || "/",
4138
+ routeName,
4139
+ concurrency,
4140
+ ...hasBuildVars ? { buildVars } : {},
4141
+ isPassthroughRoute
4142
+ });
4143
+ }
4144
+ resolvedRoutes++;
4145
+ } catch (err) {
4146
+ resolvedRoutes++;
4147
+ if (err.name === "Skip") {
4148
+ console.log(
4149
+ `[rsc-router] SKIP route "${routeName}" - ${err.message}`
4150
+ );
4151
+ notifyOnError(
4152
+ registry,
4153
+ err,
4154
+ "prerender",
4155
+ routeName,
4156
+ void 0,
4157
+ true
4158
+ );
4159
+ continue;
4160
+ }
4161
+ console.error(
4162
+ `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3697
4163
  );
3698
- continue;
4164
+ notifyOnError(registry, err, "prerender", routeName);
4165
+ throw err;
3699
4166
  }
3700
- console.error(
3701
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
4167
+ } else {
4168
+ console.warn(
4169
+ `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3702
4170
  );
3703
- notifyOnError(registry, err, "prerender", routeName);
3704
- throw err;
3705
4171
  }
3706
- } else {
3707
- console.warn(
3708
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3709
- );
3710
4172
  }
3711
4173
  }
3712
4174
  }
4175
+ } finally {
4176
+ if (progressInterval) {
4177
+ clearInterval(progressInterval);
4178
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
4179
+ console.log(
4180
+ `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
4181
+ );
4182
+ }
4183
+ }
4184
+ if (entries.length === 0) {
4185
+ debug9?.(
4186
+ "no prerender entries (done in %sms)",
4187
+ (performance.now() - overallStart).toFixed(1)
4188
+ );
4189
+ return;
3713
4190
  }
3714
- if (entries.length === 0) return;
3715
4191
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
3716
4192
  const concurrencyNote = maxConcurrency > 1 ? ` (concurrency: ${maxConcurrency})` : "";
3717
4193
  console.log(
3718
4194
  `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
3719
4195
  );
4196
+ debug9?.(
4197
+ "prerender loop: %d entries, max concurrency %d",
4198
+ entries.length,
4199
+ maxConcurrency
4200
+ );
3720
4201
  const { hashParams } = await rscEnv.runner.import("@rangojs/router/build");
3721
4202
  const manifestEntries = {};
3722
4203
  let doneCount = 0;
@@ -3823,10 +4304,22 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3823
4304
  console.log(
3824
4305
  `[rsc-router] Pre-render complete: ${parts.join(", ")} (${totalElapsed}ms total)`
3825
4306
  );
4307
+ debug9?.(
4308
+ "expandPrerenderRoutes done: %d done, %d skipped, %sms (overall %sms)",
4309
+ doneCount,
4310
+ skipCount,
4311
+ totalElapsed,
4312
+ (performance.now() - overallStart).toFixed(1)
4313
+ );
3826
4314
  }
3827
4315
  async function renderStaticHandlers(state, rscEnv, registry) {
3828
4316
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode || !state.resolvedStaticModules?.size)
3829
4317
  return;
4318
+ const overallStart = debug9 ? performance.now() : 0;
4319
+ debug9?.(
4320
+ "renderStaticHandlers: start (%d static module(s))",
4321
+ state.resolvedStaticModules.size
4322
+ );
3830
4323
  const manifestEntries = {};
3831
4324
  let staticDone = 0;
3832
4325
  let staticSkip = 0;
@@ -3916,13 +4409,29 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3916
4409
  console.log(
3917
4410
  `[rsc-router] Static render complete: ${staticParts.join(", ")} (${totalStaticElapsed}ms total)`
3918
4411
  );
4412
+ debug9?.(
4413
+ "renderStaticHandlers done: %d done, %d skipped, %sms (overall %sms)",
4414
+ staticDone,
4415
+ staticSkip,
4416
+ totalStaticElapsed,
4417
+ (performance.now() - overallStart).toFixed(1)
4418
+ );
3919
4419
  }
3920
4420
 
3921
4421
  // src/vite/discovery/discover-routers.ts
4422
+ var debug10 = createRangoDebugger(NS.discovery);
3922
4423
  async function discoverRouters(state, rscEnv) {
3923
4424
  if (!state.resolvedEntryPath) return;
3924
- await rscEnv.runner.import(state.resolvedEntryPath);
3925
- const serverMod = await rscEnv.runner.import("@rangojs/router/server");
4425
+ await timed(
4426
+ debug10,
4427
+ "inner: import entry",
4428
+ () => rscEnv.runner.import(state.resolvedEntryPath)
4429
+ );
4430
+ const serverMod = await timed(
4431
+ debug10,
4432
+ "inner: import @rangojs/router/server",
4433
+ () => rscEnv.runner.import("@rangojs/router/server")
4434
+ );
3926
4435
  let registry = serverMod.RouterRegistry;
3927
4436
  if (!registry || registry.size === 0) {
3928
4437
  try {
@@ -3964,8 +4473,13 @@ async function discoverRouters(state, rscEnv) {
3964
4473
  );
3965
4474
  }
3966
4475
  }
3967
- const buildMod = await rscEnv.runner.import("@rangojs/router/build");
4476
+ const buildMod = await timed(
4477
+ debug10,
4478
+ "inner: import @rangojs/router/build",
4479
+ () => rscEnv.runner.import("@rangojs/router/build")
4480
+ );
3968
4481
  const generateManifestFull = buildMod.generateManifestFull;
4482
+ debug10?.("inner: found %d router(s) in registry", registry.size);
3969
4483
  const nestedRouterConflict = findNestedRouterConflict(
3970
4484
  [...registry.values()].map((router) => router.__sourceFile).filter(
3971
4485
  (sourceFile) => typeof sourceFile === "string"
@@ -3984,6 +4498,7 @@ async function discoverRouters(state, rscEnv) {
3984
4498
  let mergedRouteTrailingSlash = {};
3985
4499
  let routerMountIndex = 0;
3986
4500
  const allManifests = [];
4501
+ const manifestGenStart = debug10 ? performance.now() : 0;
3987
4502
  for (const [id, router] of registry) {
3988
4503
  if (!router.urlpatterns || !generateManifestFull) {
3989
4504
  continue;
@@ -4058,7 +4573,13 @@ async function discoverRouters(state, rscEnv) {
4058
4573
  );
4059
4574
  }
4060
4575
  }
4576
+ debug10?.(
4577
+ "inner: generated manifests for %d router(s) (%sms)",
4578
+ allManifests.length,
4579
+ (performance.now() - manifestGenStart).toFixed(1)
4580
+ );
4061
4581
  let newMergedRouteTrie = null;
4582
+ const trieStart = debug10 ? performance.now() : 0;
4062
4583
  if (Object.keys(newMergedRouteManifest).length > 0) {
4063
4584
  const buildRouteTrie = buildMod.buildRouteTrie;
4064
4585
  if (buildRouteTrie && mergedRouteAncestry) {
@@ -4121,6 +4642,10 @@ async function discoverRouters(state, rscEnv) {
4121
4642
  }
4122
4643
  }
4123
4644
  }
4645
+ debug10?.(
4646
+ "inner: trie build done (%sms)",
4647
+ (performance.now() - trieStart).toFixed(1)
4648
+ );
4124
4649
  state.mergedRouteManifest = newMergedRouteManifest;
4125
4650
  state.mergedPrecomputedEntries = newMergedPrecomputedEntries;
4126
4651
  state.perRouterManifests = newPerRouterManifests;
@@ -4560,8 +5085,101 @@ function postprocessBundle(state) {
4560
5085
  }
4561
5086
  }
4562
5087
 
5088
+ // src/vite/discovery/gate-state.ts
5089
+ function createDiscoveryGate(s, debug11) {
5090
+ let gatePending = false;
5091
+ let gateResolver = () => {
5092
+ };
5093
+ let inProgress = false;
5094
+ let queued = false;
5095
+ let pendingEvents = false;
5096
+ const beginGate = () => {
5097
+ if (gatePending) return;
5098
+ s.discoveryDone = new Promise((resolve10) => {
5099
+ gateResolver = resolve10;
5100
+ });
5101
+ gatePending = true;
5102
+ };
5103
+ const resolveGate = () => {
5104
+ if (!gatePending) return;
5105
+ if (inProgress || queued || pendingEvents) {
5106
+ debug11?.(
5107
+ "hmr: resolveGate deferred \u2014 work in flight (inProgress=%s queued=%s pendingEvents=%s)",
5108
+ inProgress,
5109
+ queued,
5110
+ pendingEvents
5111
+ );
5112
+ return;
5113
+ }
5114
+ gatePending = false;
5115
+ debug11?.("hmr: discoveryDone resolved");
5116
+ gateResolver();
5117
+ };
5118
+ const noteRouteEvent = () => {
5119
+ pendingEvents = true;
5120
+ beginGate();
5121
+ };
5122
+ const runRefreshCycle = async (work) => {
5123
+ if (inProgress) {
5124
+ queued = true;
5125
+ debug11?.("hmr: rediscovery in flight \u2014 queued for a follow-up cycle");
5126
+ return;
5127
+ }
5128
+ pendingEvents = false;
5129
+ inProgress = true;
5130
+ try {
5131
+ await work();
5132
+ } finally {
5133
+ inProgress = false;
5134
+ if (queued) {
5135
+ queued = false;
5136
+ debug11?.("hmr: consuming queued rediscovery");
5137
+ runRefreshCycle(work).catch((err) => {
5138
+ debug11?.(
5139
+ "hmr: queued cycle rejected \u2014 releasing gate (%s)",
5140
+ err instanceof Error ? err.message : String(err)
5141
+ );
5142
+ resolveGate();
5143
+ });
5144
+ } else if (pendingEvents) {
5145
+ debug11?.(
5146
+ "hmr: holding gate for pending events (debounce not yet fired)"
5147
+ );
5148
+ } else {
5149
+ resolveGate();
5150
+ }
5151
+ }
5152
+ };
5153
+ return {
5154
+ beginGate,
5155
+ resolveGate,
5156
+ noteRouteEvent,
5157
+ runRefreshCycle,
5158
+ state: () => ({ gatePending, inProgress, queued, pendingEvents })
5159
+ };
5160
+ }
5161
+
4563
5162
  // src/vite/router-discovery.ts
5163
+ var debugDiscovery = createRangoDebugger(NS.discovery);
5164
+ var debugRoutes = createRangoDebugger(NS.routes);
5165
+ var debugBuild = createRangoDebugger(NS.build);
5166
+ var debugDev = createRangoDebugger(NS.dev);
5167
+ var loaderHookRegistered = false;
5168
+ function ensureCloudflareProtocolLoaderRegistered() {
5169
+ if (loaderHookRegistered) return;
5170
+ loaderHookRegistered = true;
5171
+ try {
5172
+ register(
5173
+ new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url)
5174
+ );
5175
+ } catch (err) {
5176
+ console.warn(
5177
+ `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
5178
+ );
5179
+ }
5180
+ }
4564
5181
  async function createTempRscServer(state, options = {}) {
5182
+ ensureCloudflareProtocolLoaderRegistered();
4565
5183
  const { default: rsc } = await import("@vitejs/plugin-rsc");
4566
5184
  return createViteServer({
4567
5185
  root: state.projectRoot,
@@ -4584,6 +5202,7 @@ async function createTempRscServer(state, options = {}) {
4584
5202
  ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
4585
5203
  createVersionPlugin(),
4586
5204
  createVirtualStubPlugin(),
5205
+ createCloudflareProtocolStubPlugin(),
4587
5206
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
4588
5207
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
4589
5208
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
@@ -4600,7 +5219,7 @@ async function resolveBuildEnv(option, factoryCtx) {
4600
5219
  );
4601
5220
  }
4602
5221
  try {
4603
- const userRequire = createRequire(
5222
+ const userRequire = createRequire2(
4604
5223
  resolve8(factoryCtx.root, "package.json")
4605
5224
  );
4606
5225
  const wranglerPath = userRequire.resolve("wrangler");
@@ -4635,6 +5254,7 @@ async function acquireBuildEnv(s, command, mode) {
4635
5254
  if (!result) return false;
4636
5255
  s.resolvedBuildEnv = result.env;
4637
5256
  s.buildEnvDispose = result.dispose ?? null;
5257
+ globalThis[BUILD_ENV_GLOBAL_KEY] = result.env;
4638
5258
  return true;
4639
5259
  }
4640
5260
  async function releaseBuildEnv(s) {
@@ -4647,6 +5267,7 @@ async function releaseBuildEnv(s) {
4647
5267
  s.buildEnvDispose = null;
4648
5268
  }
4649
5269
  s.resolvedBuildEnv = void 0;
5270
+ delete globalThis[BUILD_ENV_GLOBAL_KEY];
4650
5271
  }
4651
5272
  function createRouterDiscoveryPlugin(entryPath, opts) {
4652
5273
  const s = createDiscoveryState(entryPath, opts);
@@ -4702,6 +5323,9 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4702
5323
  const discoveryPromise = new Promise((resolve10) => {
4703
5324
  resolveDiscovery = resolve10;
4704
5325
  });
5326
+ const gate = createDiscoveryGate(s, debugDiscovery);
5327
+ const beginDiscoveryGate = gate.beginGate;
5328
+ const resolveDiscoveryGate = gate.resolveGate;
4705
5329
  const getDevServerOrigin = () => server.resolvedUrls?.local?.[0]?.replace(/\/$/, "") || `http://localhost:${server.config.server.port || 5173}`;
4706
5330
  let prerenderTempServer = null;
4707
5331
  let prerenderNodeRegistry = null;
@@ -4714,40 +5338,180 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4714
5338
  releaseBuildEnv(s).catch(() => {
4715
5339
  });
4716
5340
  });
5341
+ async function importEntryAndRegistry(tempRscEnv) {
5342
+ const flagAlreadySet = !!globalThis.__rscRouterDiscoveryActive;
5343
+ if (!flagAlreadySet) {
5344
+ globalThis.__rscRouterDiscoveryActive = true;
5345
+ }
5346
+ try {
5347
+ debugDiscovery?.(
5348
+ "importEntryAndRegistry: importing entry (flag=%s)",
5349
+ globalThis.__rscRouterDiscoveryActive ?? false
5350
+ );
5351
+ await tempRscEnv.runner.import(s.resolvedEntryPath);
5352
+ debugDiscovery?.(
5353
+ "importEntryAndRegistry: entry import OK, fetching RouterRegistry"
5354
+ );
5355
+ const serverMod = await tempRscEnv.runner.import(
5356
+ "@rangojs/router/server"
5357
+ );
5358
+ prerenderNodeRegistry = serverMod.RouterRegistry;
5359
+ debugDiscovery?.(
5360
+ "importEntryAndRegistry: registry size=%d",
5361
+ prerenderNodeRegistry?.size ?? 0
5362
+ );
5363
+ } finally {
5364
+ if (!flagAlreadySet) {
5365
+ delete globalThis.__rscRouterDiscoveryActive;
5366
+ debugDiscovery?.(
5367
+ "importEntryAndRegistry: cleared __rscRouterDiscoveryActive"
5368
+ );
5369
+ }
5370
+ }
5371
+ }
4717
5372
  async function getOrCreateTempServer() {
4718
- if (prerenderNodeRegistry) {
4719
- return prerenderTempServer.environments?.rsc ?? null;
5373
+ if (prerenderTempServer) {
5374
+ const existingEnv = prerenderTempServer.environments?.rsc;
5375
+ if (existingEnv?.runner) {
5376
+ if (prerenderNodeRegistry) {
5377
+ debugDiscovery?.(
5378
+ "getOrCreateTempServer: cached temp runner reused"
5379
+ );
5380
+ return existingEnv;
5381
+ }
5382
+ debugDiscovery?.(
5383
+ "getOrCreateTempServer: server alive but registry missing \u2014 re-importing"
5384
+ );
5385
+ try {
5386
+ await importEntryAndRegistry(existingEnv);
5387
+ return existingEnv;
5388
+ } catch (err) {
5389
+ debugDiscovery?.(
5390
+ "getOrCreateTempServer: reuse import failed (%s) \u2014 closing orphan and creating fresh",
5391
+ err?.message ?? String(err)
5392
+ );
5393
+ await prerenderTempServer.close().catch(() => {
5394
+ });
5395
+ prerenderTempServer = null;
5396
+ prerenderNodeRegistry = null;
5397
+ }
5398
+ } else {
5399
+ debugDiscovery?.(
5400
+ "getOrCreateTempServer: existing server has no rsc.runner \u2014 closing and recreating"
5401
+ );
5402
+ await prerenderTempServer.close().catch(() => {
5403
+ });
5404
+ prerenderTempServer = null;
5405
+ prerenderNodeRegistry = null;
5406
+ }
4720
5407
  }
5408
+ debugDiscovery?.(
5409
+ "getOrCreateTempServer: creating new temp server, entry=%s",
5410
+ s.resolvedEntryPath ?? "(unset)"
5411
+ );
4721
5412
  try {
4722
5413
  prerenderTempServer = await createTempRscServer(s, {
4723
5414
  cacheDir: "node_modules/.vite_prerender"
4724
5415
  });
4725
5416
  const tempRscEnv = prerenderTempServer.environments?.rsc;
4726
5417
  if (tempRscEnv?.runner) {
4727
- await tempRscEnv.runner.import(s.resolvedEntryPath);
4728
- const serverMod = await tempRscEnv.runner.import(
4729
- "@rangojs/router/server"
4730
- );
4731
- prerenderNodeRegistry = serverMod.RouterRegistry;
5418
+ await importEntryAndRegistry(tempRscEnv);
4732
5419
  return tempRscEnv;
4733
5420
  }
5421
+ debugDiscovery?.(
5422
+ "getOrCreateTempServer: tempRscEnv.runner unavailable"
5423
+ );
4734
5424
  } catch (err) {
5425
+ debugDiscovery?.(
5426
+ "getOrCreateTempServer: FAILED message=%s",
5427
+ err.message
5428
+ );
4735
5429
  console.warn(
4736
5430
  `[rsc-router] Failed to create temp runner: ${err.message}`
4737
5431
  );
4738
5432
  }
4739
5433
  return null;
4740
5434
  }
5435
+ async function clearTempRegistries(tempRscEnv) {
5436
+ try {
5437
+ const serverMod = await tempRscEnv.runner.import(
5438
+ "@rangojs/router/server"
5439
+ );
5440
+ if (typeof serverMod?.RouterRegistry?.clear === "function") {
5441
+ serverMod.RouterRegistry.clear();
5442
+ }
5443
+ if (typeof serverMod?.HostRouterRegistry?.clear === "function") {
5444
+ serverMod.HostRouterRegistry.clear();
5445
+ }
5446
+ debugDiscovery?.(
5447
+ "clearTempRegistries: cleared RouterRegistry + HostRouterRegistry"
5448
+ );
5449
+ } catch (err) {
5450
+ debugDiscovery?.(
5451
+ "clearTempRegistries: import @rangojs/router/server failed (%s)",
5452
+ err?.message ?? String(err)
5453
+ );
5454
+ }
5455
+ }
5456
+ async function refreshTempRscEnv() {
5457
+ let tempRscEnv = await getOrCreateTempServer();
5458
+ if (!tempRscEnv) return null;
5459
+ const envGraph = tempRscEnv.moduleGraph;
5460
+ const serverGraph = prerenderTempServer?.moduleGraph;
5461
+ const target = envGraph?.invalidateAll ? envGraph : serverGraph?.invalidateAll ? serverGraph : null;
5462
+ if (!target) {
5463
+ debugDiscovery?.(
5464
+ "refreshTempRscEnv: invalidateAll unavailable on env+server graphs, falling back to close+recreate"
5465
+ );
5466
+ if (prerenderTempServer) {
5467
+ await prerenderTempServer.close().catch(() => {
5468
+ });
5469
+ prerenderTempServer = null;
5470
+ prerenderNodeRegistry = null;
5471
+ }
5472
+ return await getOrCreateTempServer();
5473
+ }
5474
+ debugDiscovery?.(
5475
+ "refreshTempRscEnv: invalidating module graph (%s)",
5476
+ envGraph?.invalidateAll ? "env" : "server"
5477
+ );
5478
+ target.invalidateAll();
5479
+ prerenderNodeRegistry = null;
5480
+ await clearTempRegistries(tempRscEnv);
5481
+ await importEntryAndRegistry(tempRscEnv);
5482
+ return tempRscEnv;
5483
+ }
4741
5484
  const discover = async () => {
5485
+ const discoverStart = performance.now();
4742
5486
  const rscEnv = server.environments?.rsc;
4743
5487
  if (!rscEnv?.runner) {
5488
+ debugDiscovery?.(
5489
+ "dev: cloudflare path start, __rscRouterDiscoveryActive=%s",
5490
+ globalThis.__rscRouterDiscoveryActive ?? false
5491
+ );
4744
5492
  s.devServerOrigin = getDevServerOrigin();
4745
5493
  try {
4746
- await acquireBuildEnv(s, viteCommand, viteMode);
4747
- const tempRscEnv = await getOrCreateTempServer();
5494
+ await timed(
5495
+ debugDiscovery,
5496
+ "acquireBuildEnv",
5497
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5498
+ );
5499
+ const tempRscEnv = await timed(
5500
+ debugDiscovery,
5501
+ "getOrCreateTempServer",
5502
+ () => getOrCreateTempServer()
5503
+ );
4748
5504
  if (tempRscEnv) {
4749
- await discoverRouters(s, tempRscEnv);
4750
- writeRouteTypesFiles(s);
5505
+ await timed(
5506
+ debugDiscovery,
5507
+ "discoverRouters (cloudflare)",
5508
+ () => discoverRouters(s, tempRscEnv)
5509
+ );
5510
+ timedSync(
5511
+ debugDiscovery,
5512
+ "writeRouteTypesFiles",
5513
+ () => writeRouteTypesFiles(s)
5514
+ );
4751
5515
  }
4752
5516
  } catch (err) {
4753
5517
  console.warn(
@@ -4755,33 +5519,62 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4755
5519
  ${err.stack}`
4756
5520
  );
4757
5521
  }
5522
+ debugDiscovery?.(
5523
+ "dev discovery done (%sms)",
5524
+ (performance.now() - discoverStart).toFixed(1)
5525
+ );
4758
5526
  resolveDiscovery();
4759
5527
  return;
4760
5528
  }
4761
5529
  try {
4762
- await acquireBuildEnv(s, viteCommand, viteMode);
4763
- const serverMod = await rscEnv.runner.import(
4764
- "@rangojs/router/server"
5530
+ debugDiscovery?.("dev: node path start");
5531
+ await timed(
5532
+ debugDiscovery,
5533
+ "acquireBuildEnv",
5534
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5535
+ );
5536
+ const serverMod = await timed(
5537
+ debugDiscovery,
5538
+ "import @rangojs/router/server",
5539
+ () => rscEnv.runner.import("@rangojs/router/server")
4765
5540
  );
4766
5541
  if (serverMod?.setManifestReadyPromise) {
4767
5542
  serverMod.setManifestReadyPromise(discoveryPromise);
4768
5543
  }
4769
- await discoverRouters(s, rscEnv);
5544
+ await timed(
5545
+ debugDiscovery,
5546
+ "discoverRouters",
5547
+ () => discoverRouters(s, rscEnv)
5548
+ );
4770
5549
  s.devServerOrigin = getDevServerOrigin();
4771
- writeRouteTypesFiles(s);
4772
- await propagateDiscoveryState(rscEnv);
5550
+ timedSync(
5551
+ debugDiscovery,
5552
+ "writeRouteTypesFiles",
5553
+ () => writeRouteTypesFiles(s)
5554
+ );
5555
+ await timed(
5556
+ debugDiscovery,
5557
+ "propagateDiscoveryState",
5558
+ () => propagateDiscoveryState(rscEnv)
5559
+ );
4773
5560
  } catch (err) {
4774
5561
  console.warn(
4775
5562
  `[rsc-router] Router discovery failed: ${err.message}
4776
5563
  ${err.stack}`
4777
5564
  );
4778
5565
  } finally {
5566
+ debugDiscovery?.(
5567
+ "dev discovery done (%sms)",
5568
+ (performance.now() - discoverStart).toFixed(1)
5569
+ );
4779
5570
  resolveDiscovery();
4780
5571
  }
4781
5572
  };
4782
- s.discoveryDone = new Promise((resolve10) => {
4783
- setTimeout(() => discover().then(resolve10, resolve10), 0);
4784
- });
5573
+ beginDiscoveryGate();
5574
+ setTimeout(
5575
+ () => discover().then(resolveDiscoveryGate, resolveDiscoveryGate),
5576
+ 0
5577
+ );
4785
5578
  let mainRegistry = null;
4786
5579
  const propagateDiscoveryState = async (rscEnv) => {
4787
5580
  const serverMod = await rscEnv.runner.import("@rangojs/router/server");
@@ -4816,12 +5609,23 @@ ${err.stack}`
4816
5609
  }
4817
5610
  };
4818
5611
  server.middlewares.use("/__rsc_prerender", async (req, res) => {
5612
+ const reqStart = debugDev ? performance.now() : 0;
5613
+ const logResult = (status, note) => {
5614
+ debugDev?.(
5615
+ "/__rsc_prerender %s -> %d %s (%sms)",
5616
+ req.url,
5617
+ status,
5618
+ note,
5619
+ (performance.now() - reqStart).toFixed(1)
5620
+ );
5621
+ };
4819
5622
  if (s.discoveryDone) await s.discoveryDone;
4820
5623
  const url = new URL(req.url || "/", "http://localhost");
4821
5624
  const pathname = url.searchParams.get("pathname");
4822
5625
  if (!pathname) {
4823
5626
  res.statusCode = 400;
4824
5627
  res.end("Missing pathname");
5628
+ logResult(400, "missing pathname");
4825
5629
  return;
4826
5630
  }
4827
5631
  const rscEnv = server.environments?.rsc;
@@ -4839,6 +5643,7 @@ ${err.stack}`
4839
5643
  );
4840
5644
  res.statusCode = 500;
4841
5645
  res.end(`Prerender handler error: ${err.message}`);
5646
+ logResult(500, "module refresh failed");
4842
5647
  return;
4843
5648
  }
4844
5649
  } else {
@@ -4853,6 +5658,7 @@ ${err.stack}`
4853
5658
  if (!registry || registry.size === 0) {
4854
5659
  res.statusCode = 503;
4855
5660
  res.end("Prerender runner not available");
5661
+ logResult(503, "no registry");
4856
5662
  return;
4857
5663
  }
4858
5664
  const wantIntercept = url.searchParams.get("intercept") === "1";
@@ -4887,6 +5693,7 @@ ${err.stack}`
4887
5693
  payload = { segments: result.segments, handles: result.handles };
4888
5694
  }
4889
5695
  res.end(JSON.stringify(payload));
5696
+ logResult(200, `match ${result.routeName}`);
4890
5697
  return;
4891
5698
  } catch (err) {
4892
5699
  console.warn(
@@ -4896,6 +5703,7 @@ ${err.stack}`
4896
5703
  }
4897
5704
  res.statusCode = 404;
4898
5705
  res.end("No prerender match");
5706
+ logResult(404, "no match");
4899
5707
  });
4900
5708
  if (opts?.staticRouteTypesGeneration !== false) {
4901
5709
  const isGeneratedRouteFile = (filePath) => filePath.endsWith(".gen.ts") && (filePath.includes("named-routes.gen.ts") || filePath.includes("urls.gen.ts"));
@@ -4915,42 +5723,96 @@ ${err.stack}`
4915
5723
  return true;
4916
5724
  };
4917
5725
  let routeChangeTimer;
4918
- let runtimeRediscoveryInProgress = false;
4919
5726
  const refreshRuntimeDiscovery = async () => {
4920
5727
  const rscEnv = server.environments?.rsc;
4921
- if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
4922
- runtimeRediscoveryInProgress = true;
4923
- try {
4924
- await discoverRouters(s, rscEnv);
4925
- writeRouteTypesFiles(s);
4926
- await propagateDiscoveryState(rscEnv);
4927
- } catch (err) {
4928
- console.warn(
4929
- `[rsc-router] Runtime re-discovery failed: ${err.message}`
4930
- );
4931
- } finally {
4932
- runtimeRediscoveryInProgress = false;
4933
- }
5728
+ const hasMainRunner = !!rscEnv?.runner;
5729
+ if (!hasMainRunner && s.perRouterManifests.length === 0) return;
5730
+ await gate.runRefreshCycle(async () => {
5731
+ const hmrStart = performance.now();
5732
+ try {
5733
+ if (hasMainRunner) {
5734
+ await timed(
5735
+ debugDiscovery,
5736
+ "hmr discoverRouters",
5737
+ () => discoverRouters(s, rscEnv)
5738
+ );
5739
+ timedSync(
5740
+ debugDiscovery,
5741
+ "hmr writeRouteTypesFiles",
5742
+ () => writeRouteTypesFiles(s)
5743
+ );
5744
+ await timed(
5745
+ debugDiscovery,
5746
+ "hmr propagateDiscoveryState",
5747
+ () => propagateDiscoveryState(rscEnv)
5748
+ );
5749
+ } else {
5750
+ const tempRscEnv = await timed(
5751
+ debugDiscovery,
5752
+ "hmr refreshTempRscEnv (cloudflare)",
5753
+ () => refreshTempRscEnv()
5754
+ );
5755
+ if (!tempRscEnv) {
5756
+ throw new Error(
5757
+ "temp runner unavailable for cloudflare HMR rediscovery"
5758
+ );
5759
+ }
5760
+ await timed(
5761
+ debugDiscovery,
5762
+ "hmr discoverRouters (cloudflare)",
5763
+ () => discoverRouters(s, tempRscEnv)
5764
+ );
5765
+ timedSync(
5766
+ debugDiscovery,
5767
+ "hmr writeRouteTypesFiles",
5768
+ () => writeRouteTypesFiles(s)
5769
+ );
5770
+ }
5771
+ } catch (err) {
5772
+ console.warn(
5773
+ `[rsc-router] Runtime re-discovery failed: ${err.message}`
5774
+ );
5775
+ } finally {
5776
+ debugDiscovery?.(
5777
+ "hmr re-discovery done (%sms)",
5778
+ (performance.now() - hmrStart).toFixed(1)
5779
+ );
5780
+ }
5781
+ });
4934
5782
  };
4935
5783
  const scheduleRouteRegeneration = () => {
4936
5784
  clearTimeout(routeChangeTimer);
4937
5785
  routeChangeTimer = setTimeout(() => {
4938
5786
  routeChangeTimer = void 0;
5787
+ const regenStart = debugDiscovery ? performance.now() : 0;
5788
+ const rscEnv = server.environments?.rsc;
5789
+ const skipStaticWrite = !rscEnv?.runner && s.perRouterManifests.length > 0;
4939
5790
  try {
4940
- writeCombinedRouteTypesWithTracking(s);
4941
- if (s.perRouterManifests.length > 0) {
4942
- supplementGenFilesWithRuntimeRoutes(s);
5791
+ if (skipStaticWrite) {
5792
+ debugDiscovery?.(
5793
+ "watcher: skipping static write (cloudflare HMR \u2014 runtime rediscovery owns gen file)"
5794
+ );
5795
+ } else {
5796
+ writeCombinedRouteTypesWithTracking(s);
5797
+ if (s.perRouterManifests.length > 0) {
5798
+ supplementGenFilesWithRuntimeRoutes(s);
5799
+ }
4943
5800
  }
4944
5801
  } catch (err) {
4945
5802
  console.error(
4946
5803
  `[rsc-router] Route regeneration error: ${err.message}`
4947
5804
  );
4948
5805
  }
5806
+ debugDiscovery?.(
5807
+ "watcher: regenerated gen files (%sms)",
5808
+ (performance.now() - regenStart).toFixed(1)
5809
+ );
4949
5810
  if (s.perRouterManifests.length > 0) {
4950
5811
  refreshRuntimeDiscovery().catch((err) => {
4951
5812
  console.warn(
4952
5813
  `[rsc-router] Runtime re-discovery error: ${err.message}`
4953
5814
  );
5815
+ resolveDiscoveryGate();
4954
5816
  });
4955
5817
  }
4956
5818
  }, 100);
@@ -4968,6 +5830,12 @@ ${err.stack}`
4968
5830
  const hasUrls = source.includes("urls(");
4969
5831
  const hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
4970
5832
  if (!hasUrls && !hasCreateRouter) return;
5833
+ debugDiscovery?.(
5834
+ "watcher: %s matches (urls=%s, router=%s)",
5835
+ filePath,
5836
+ hasUrls,
5837
+ hasCreateRouter
5838
+ );
4971
5839
  if (hasCreateRouter) {
4972
5840
  const nestedRouterConflict = findNestedRouterConflict([
4973
5841
  ...s.cachedRouterFiles ?? [],
@@ -4981,6 +5849,9 @@ ${err.stack}`
4981
5849
  }
4982
5850
  s.cachedRouterFiles = void 0;
4983
5851
  }
5852
+ if (s.perRouterManifests.length > 0) {
5853
+ gate.noteRouteEvent();
5854
+ }
4984
5855
  scheduleRouteRegeneration();
4985
5856
  } catch {
4986
5857
  }
@@ -5000,15 +5871,31 @@ ${err.stack}`
5000
5871
  // The manifest data is stored for the virtual module's load hook.
5001
5872
  async buildStart() {
5002
5873
  if (!s.isBuildMode) return;
5003
- if (s.mergedRouteManifest !== null) return;
5874
+ if (s.mergedRouteManifest !== null) {
5875
+ debugDiscovery?.(
5876
+ "build: skip (already discovered, env=%s)",
5877
+ this.environment?.name ?? "?"
5878
+ );
5879
+ return;
5880
+ }
5881
+ const buildStartTime = performance.now();
5882
+ debugDiscovery?.("build: start (env=%s)", this.environment?.name ?? "?");
5004
5883
  resetStagedBuildAssets(s.projectRoot);
5005
5884
  s.prerenderManifestEntries = null;
5006
5885
  s.staticManifestEntries = null;
5007
- await acquireBuildEnv(s, viteCommand, viteMode);
5886
+ await timed(
5887
+ debugDiscovery,
5888
+ "build acquireBuildEnv",
5889
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5890
+ );
5008
5891
  let tempServer = null;
5009
5892
  globalThis.__rscRouterDiscoveryActive = true;
5010
5893
  try {
5011
- tempServer = await createTempRscServer(s, { forceBuild: true });
5894
+ tempServer = await timed(
5895
+ debugDiscovery,
5896
+ "build createTempRscServer",
5897
+ () => createTempRscServer(s, { forceBuild: true })
5898
+ );
5012
5899
  const rscEnv = tempServer.environments?.rsc;
5013
5900
  if (!rscEnv?.runner) {
5014
5901
  console.warn(
@@ -5022,8 +5909,16 @@ ${err.stack}`
5022
5909
  if (tempIdsPlugin?.api?.staticHandlerModules) {
5023
5910
  s.resolvedStaticModules = tempIdsPlugin.api.staticHandlerModules;
5024
5911
  }
5025
- await discoverRouters(s, rscEnv);
5026
- writeRouteTypesFiles(s);
5912
+ await timed(
5913
+ debugDiscovery,
5914
+ "build discoverRouters",
5915
+ () => discoverRouters(s, rscEnv)
5916
+ );
5917
+ timedSync(
5918
+ debugDiscovery,
5919
+ "build writeRouteTypesFiles",
5920
+ () => writeRouteTypesFiles(s)
5921
+ );
5027
5922
  } catch (err) {
5028
5923
  const sourceFile = err.stack?.split("\n").find(
5029
5924
  (line) => line.includes(s.projectRoot) && !line.includes("node_modules")
@@ -5042,9 +5937,45 @@ ${details}`
5042
5937
  } finally {
5043
5938
  delete globalThis.__rscRouterDiscoveryActive;
5044
5939
  if (tempServer) {
5045
- await tempServer.close();
5940
+ await timed(
5941
+ debugDiscovery,
5942
+ "build tempServer.close",
5943
+ () => tempServer.close()
5944
+ );
5046
5945
  }
5047
5946
  await releaseBuildEnv(s);
5947
+ debugDiscovery?.(
5948
+ "build discovery done (%sms)",
5949
+ (performance.now() - buildStartTime).toFixed(1)
5950
+ );
5951
+ }
5952
+ },
5953
+ // Suppress vite's HMR cascade for our own gen-file writes.
5954
+ //
5955
+ // After every cf HMR cycle, refreshTempRscEnv → writeRouteTypesFiles
5956
+ // writes the configured gen files (default `router.named-routes.gen.ts`,
5957
+ // but the source filenames and gen suffix are user-configurable). The
5958
+ // chokidar watcher then fires twice independently: our
5959
+ // `handleRouteFileChange` (already short-circuited by
5960
+ // `consumeSelfGenWrite` inside `maybeHandleGeneratedRouteFileMutation`),
5961
+ // AND vite's own HMR pipeline (which invalidates the gen file's
5962
+ // importers and triggers a second workerd full reload — visible to the
5963
+ // user as a duplicate "[RSCRouter] HMR: version changed" on the client).
5964
+ //
5965
+ // `peekSelfGenWrite` is the authoritative filter: its map only contains
5966
+ // paths that `markSelfGenWrite` has registered, so it natively works
5967
+ // for any configured gen-file name. It is non-consuming so the chokidar
5968
+ // handler that fires later can still consume the same entry. Returning
5969
+ // [] tells vite "no modules invalidated by this change" — safe because
5970
+ // `s.perRouterManifests` is already up-to-date (the write that just
5971
+ // happened is the consequence of our just-completed rediscovery).
5972
+ handleHotUpdate(ctx) {
5973
+ if (peekSelfGenWrite(s, ctx.file)) {
5974
+ debugDiscovery?.(
5975
+ "handleHotUpdate: suppressing self-write HMR cascade for %s",
5976
+ ctx.file
5977
+ );
5978
+ return [];
5048
5979
  }
5049
5980
  },
5050
5981
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -5061,17 +5992,36 @@ ${details}`
5061
5992
  async load(id) {
5062
5993
  if (id === "\0" + VIRTUAL_ROUTES_MANIFEST_ID) {
5063
5994
  if (s.discoveryDone) {
5064
- await s.discoveryDone;
5995
+ await timed(
5996
+ debugRoutes,
5997
+ "await discoveryDone (manifest)",
5998
+ () => s.discoveryDone
5999
+ );
5065
6000
  }
5066
- return generateRoutesManifestModule(s);
6001
+ const code = await timed(
6002
+ debugRoutes,
6003
+ "generateRoutesManifestModule",
6004
+ () => generateRoutesManifestModule(s)
6005
+ );
6006
+ debugRoutes?.("manifest module emitted (%d bytes)", code?.length ?? 0);
6007
+ return code;
5067
6008
  }
5068
6009
  const perRouterPrefix = "\0" + VIRTUAL_ROUTES_MANIFEST_ID + "/";
5069
6010
  if (id.startsWith(perRouterPrefix)) {
5070
6011
  if (s.discoveryDone) {
5071
- await s.discoveryDone;
6012
+ await timed(
6013
+ debugRoutes,
6014
+ "await discoveryDone (per-router)",
6015
+ () => s.discoveryDone
6016
+ );
5072
6017
  }
5073
6018
  const routerId = id.slice(perRouterPrefix.length);
5074
- return generatePerRouterModule(s, routerId);
6019
+ const code = await timed(
6020
+ debugRoutes,
6021
+ `generatePerRouterModule ${routerId}`,
6022
+ () => generatePerRouterModule(s, routerId)
6023
+ );
6024
+ return code;
5075
6025
  }
5076
6026
  return null;
5077
6027
  },
@@ -5079,14 +6029,20 @@ ${details}`
5079
6029
  // Used by closeBundle for handler code eviction and prerender data injection.
5080
6030
  generateBundle(_options, bundle) {
5081
6031
  if (this.environment?.name !== "rsc") return;
6032
+ const genStart = debugBuild ? performance.now() : 0;
5082
6033
  for (const [fileName, chunk] of Object.entries(bundle)) {
5083
6034
  if (chunk.type === "chunk" && chunk.isEntry) {
5084
6035
  s.rscEntryFileName = fileName;
5085
6036
  break;
5086
6037
  }
5087
6038
  }
5088
- if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
6039
+ if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size) {
6040
+ debugBuild?.(
6041
+ "generateBundle (rsc): no handlers to scan (%sms)",
6042
+ (performance.now() - genStart).toFixed(1)
6043
+ );
5089
6044
  return;
6045
+ }
5090
6046
  s.handlerChunkInfoMap.clear();
5091
6047
  s.staticHandlerChunkInfoMap.clear();
5092
6048
  for (const [fileName, chunk] of Object.entries(bundle)) {
@@ -5130,6 +6086,13 @@ ${details}`
5130
6086
  }
5131
6087
  }
5132
6088
  }
6089
+ debugBuild?.(
6090
+ "generateBundle (rsc): scanned %d chunks, %d prerender chunk(s), %d static chunk(s) (%sms)",
6091
+ Object.keys(bundle).length,
6092
+ s.handlerChunkInfoMap.size,
6093
+ s.staticHandlerChunkInfoMap.size,
6094
+ (performance.now() - genStart).toFixed(1)
6095
+ );
5133
6096
  },
5134
6097
  // Build-time pre-rendering: evict handler code and inject collected prerender data.
5135
6098
  // Collection now happens in-process during discoverRouters() via RSC runner.
@@ -5140,29 +6103,41 @@ ${details}`
5140
6103
  async handler() {
5141
6104
  if (!s.isBuildMode) return;
5142
6105
  if (this.environment && this.environment.name !== "rsc") return;
5143
- postprocessBundle(s);
6106
+ timedSync(
6107
+ debugBuild,
6108
+ "closeBundle postprocessBundle",
6109
+ () => postprocessBundle(s)
6110
+ );
5144
6111
  }
5145
6112
  }
5146
6113
  };
5147
6114
  }
5148
6115
 
5149
6116
  // src/vite/rango.ts
6117
+ var debugConfig = createRangoDebugger(NS.config);
5150
6118
  async function rango(options) {
6119
+ const rangoStart = performance.now();
5151
6120
  const resolvedOptions = options ?? { preset: "node" };
5152
6121
  const preset = resolvedOptions.preset ?? "node";
5153
6122
  const showBanner = resolvedOptions.banner ?? true;
6123
+ debugConfig?.("rango(%s) setup start", preset);
5154
6124
  const plugins = [];
5155
- const rangoAliases = getPackageAliases();
6125
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
5156
6126
  const excludeDeps = [
5157
6127
  ...getExcludeDeps(),
5158
- // The public browser entry re-exports the RSDW browser client.
5159
- // Excluding both keeps Vite from freezing the unpatched bundle into
5160
- // .vite/deps before our source transforms run.
6128
+ // plugin-rsc itself injects these into the client env's
6129
+ // optimizeDeps.include, which overrides exclude for the dep's own
6130
+ // pre-bundle entry. What exclude still controls is how *other*
6131
+ // pre-bundled deps treat imports of these specs (external vs inlined)
6132
+ // via esbuildCjsExternalPlugin. The cjs-to-esm transform in
6133
+ // plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
6134
+ // where client.browser's bare include fails to resolve and Vite ends up
6135
+ // serving the raw CJS file at dev-serve time.
5161
6136
  "@vitejs/plugin-rsc/browser",
5162
- // Keep the browser RSDW client out of Vite's dep optimizer so our
5163
- // cjs-to-esm transform can patch the real file.
5164
6137
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5165
6138
  ];
6139
+ const pkg = getPublishedPackageName();
6140
+ const nested = (spec) => `${pkg} > ${spec}`;
5166
6141
  const routerRef = { path: void 0 };
5167
6142
  const prerenderEnabled = true;
5168
6143
  if (preset === "cloudflare") {
@@ -5200,7 +6175,7 @@ async function rango(options) {
5200
6175
  // Pre-bundle rsc-html-stream to prevent discovery during first request
5201
6176
  // Exclude rsc-router modules to ensure same Context instance
5202
6177
  optimizeDeps: {
5203
- include: ["rsc-html-stream/client"],
6178
+ include: [nested("rsc-html-stream/client")],
5204
6179
  exclude: excludeDeps,
5205
6180
  esbuildOptions: sharedEsbuildOptions
5206
6181
  }
@@ -5225,8 +6200,10 @@ async function rango(options) {
5225
6200
  "react-dom/static.edge",
5226
6201
  "react/jsx-runtime",
5227
6202
  "react/jsx-dev-runtime",
5228
- "rsc-html-stream/server",
5229
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6203
+ nested("rsc-html-stream/server"),
6204
+ nested(
6205
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6206
+ )
5230
6207
  ],
5231
6208
  exclude: excludeDeps,
5232
6209
  esbuildOptions: sharedEsbuildOptions
@@ -5241,7 +6218,9 @@ async function rango(options) {
5241
6218
  "react",
5242
6219
  "react/jsx-runtime",
5243
6220
  "react/jsx-dev-runtime",
5244
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6221
+ nested(
6222
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6223
+ )
5245
6224
  ],
5246
6225
  exclude: excludeDeps,
5247
6226
  esbuildOptions: sharedEsbuildOptions
@@ -5322,7 +6301,7 @@ ${list}`);
5322
6301
  "react-dom",
5323
6302
  "react/jsx-runtime",
5324
6303
  "react/jsx-dev-runtime",
5325
- "rsc-html-stream/client"
6304
+ nested("rsc-html-stream/client")
5326
6305
  ],
5327
6306
  exclude: excludeDeps,
5328
6307
  esbuildOptions: sharedEsbuildOptions,
@@ -5339,7 +6318,9 @@ ${list}`);
5339
6318
  "react-dom/static.edge",
5340
6319
  "react/jsx-runtime",
5341
6320
  "react/jsx-dev-runtime",
5342
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6321
+ nested(
6322
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
6323
+ )
5343
6324
  ],
5344
6325
  exclude: excludeDeps,
5345
6326
  esbuildOptions: sharedEsbuildOptions
@@ -5352,7 +6333,9 @@ ${list}`);
5352
6333
  "react",
5353
6334
  "react/jsx-runtime",
5354
6335
  "react/jsx-dev-runtime",
5355
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6336
+ nested(
6337
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
6338
+ )
5356
6339
  ],
5357
6340
  esbuildOptions: sharedEsbuildOptions
5358
6341
  }
@@ -5422,6 +6405,12 @@ ${list}`);
5422
6405
  preset
5423
6406
  })
5424
6407
  );
6408
+ debugConfig?.(
6409
+ "rango(%s) setup done: %d plugin(s) (%sms)",
6410
+ preset,
6411
+ plugins.length,
6412
+ (performance.now() - rangoStart).toFixed(1)
6413
+ );
5425
6414
  return plugins;
5426
6415
  }
5427
6416
 
@@ -5432,7 +6421,7 @@ function poke() {
5432
6421
  apply: "serve",
5433
6422
  configureServer(server) {
5434
6423
  const stdin = process.stdin;
5435
- const debug = process.env.RANGO_POKE_DEBUG === "1";
6424
+ const debug11 = process.env.RANGO_POKE_DEBUG === "1";
5436
6425
  const triggerReload = (source) => {
5437
6426
  server.hot.send({ type: "full-reload", path: "*" });
5438
6427
  server.config.logger.info(` browser reload (${source})`, {
@@ -5465,7 +6454,7 @@ function poke() {
5465
6454
  lines.pop();
5466
6455
  return lines;
5467
6456
  };
5468
- if (debug) {
6457
+ if (debug11) {
5469
6458
  server.config.logger.info(
5470
6459
  ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
5471
6460
  { timestamp: true }
@@ -5478,7 +6467,7 @@ function poke() {
5478
6467
  );
5479
6468
  }
5480
6469
  const onData = (data) => {
5481
- if (debug) {
6470
+ if (debug11) {
5482
6471
  server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5483
6472
  timestamp: true
5484
6473
  });