@rangojs/router 0.0.0-experimental.88a3b2f7 → 0.0.0-experimental.8bcfea43

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 (102) hide show
  1. package/README.md +50 -20
  2. package/dist/vite/index.js +647 -176
  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 +362 -0
  7. package/skills/hooks/SKILL.md +28 -20
  8. package/skills/intercept/SKILL.md +20 -0
  9. package/skills/layout/SKILL.md +22 -0
  10. package/skills/links/SKILL.md +88 -16
  11. package/skills/loader/SKILL.md +35 -2
  12. package/skills/middleware/SKILL.md +32 -3
  13. package/skills/migrate-nextjs/SKILL.md +560 -0
  14. package/skills/migrate-react-router/SKILL.md +765 -0
  15. package/skills/parallel/SKILL.md +59 -0
  16. package/skills/rango/SKILL.md +24 -22
  17. package/skills/response-routes/SKILL.md +8 -0
  18. package/skills/route/SKILL.md +24 -0
  19. package/skills/streams-and-websockets/SKILL.md +283 -0
  20. package/skills/typesafety/SKILL.md +3 -1
  21. package/src/browser/app-shell.ts +52 -0
  22. package/src/browser/navigation-bridge.ts +72 -4
  23. package/src/browser/navigation-client.ts +64 -13
  24. package/src/browser/navigation-store.ts +25 -1
  25. package/src/browser/partial-update.ts +34 -3
  26. package/src/browser/prefetch/cache.ts +129 -21
  27. package/src/browser/prefetch/fetch.ts +148 -16
  28. package/src/browser/prefetch/queue.ts +36 -5
  29. package/src/browser/rango-state.ts +53 -13
  30. package/src/browser/react/Link.tsx +30 -2
  31. package/src/browser/react/NavigationProvider.tsx +50 -11
  32. package/src/browser/react/use-navigation.ts +22 -2
  33. package/src/browser/react/use-params.ts +11 -1
  34. package/src/browser/react/use-router.ts +8 -1
  35. package/src/browser/rsc-router.tsx +34 -6
  36. package/src/browser/segment-reconciler.ts +36 -14
  37. package/src/browser/types.ts +13 -0
  38. package/src/build/route-trie.ts +50 -24
  39. package/src/cache/cf/cf-cache-store.ts +5 -7
  40. package/src/client.tsx +84 -230
  41. package/src/index.rsc.ts +3 -0
  42. package/src/index.ts +44 -9
  43. package/src/outlet-context.ts +1 -1
  44. package/src/response-utils.ts +28 -0
  45. package/src/reverse.ts +7 -3
  46. package/src/route-definition/dsl-helpers.ts +180 -24
  47. package/src/route-definition/helpers-types.ts +61 -14
  48. package/src/route-definition/resolve-handler-use.ts +6 -0
  49. package/src/route-types.ts +7 -0
  50. package/src/router/handler-context.ts +24 -4
  51. package/src/router/lazy-includes.ts +6 -6
  52. package/src/router/loader-resolution.ts +73 -46
  53. package/src/router/manifest.ts +22 -13
  54. package/src/router/match-api.ts +3 -3
  55. package/src/router/match-middleware/cache-lookup.ts +10 -5
  56. package/src/router/match-middleware/segment-resolution.ts +1 -1
  57. package/src/router/match-result.ts +82 -4
  58. package/src/router/middleware-types.ts +2 -22
  59. package/src/router/middleware.ts +32 -4
  60. package/src/router/pattern-matching.ts +60 -9
  61. package/src/router/segment-resolution/fresh.ts +52 -0
  62. package/src/router/segment-resolution/revalidation.ts +69 -1
  63. package/src/router/trie-matching.ts +10 -4
  64. package/src/router/url-params.ts +49 -0
  65. package/src/router.ts +1 -2
  66. package/src/rsc/handler.ts +21 -9
  67. package/src/rsc/helpers.ts +69 -41
  68. package/src/rsc/loader-fetch.ts +23 -3
  69. package/src/rsc/progressive-enhancement.ts +12 -2
  70. package/src/rsc/response-route-handler.ts +14 -1
  71. package/src/rsc/rsc-rendering.ts +12 -1
  72. package/src/rsc/server-action.ts +8 -0
  73. package/src/rsc/types.ts +1 -0
  74. package/src/segment-content-promise.ts +67 -0
  75. package/src/segment-loader-promise.ts +122 -0
  76. package/src/segment-system.tsx +11 -61
  77. package/src/server/context.ts +26 -3
  78. package/src/server/handle-store.ts +19 -0
  79. package/src/server/request-context.ts +64 -56
  80. package/src/types/handler-context.ts +2 -34
  81. package/src/types/loader-types.ts +5 -6
  82. package/src/types/request-scope.ts +126 -0
  83. package/src/types/route-entry.ts +11 -0
  84. package/src/types/segments.ts +1 -1
  85. package/src/urls/include-helper.ts +24 -14
  86. package/src/urls/path-helper-types.ts +34 -5
  87. package/src/urls/response-types.ts +2 -10
  88. package/src/use-loader.tsx +77 -5
  89. package/src/vite/debug.ts +55 -0
  90. package/src/vite/discovery/prerender-collection.ts +124 -83
  91. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  92. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  93. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  94. package/src/vite/plugins/expose-id-utils.ts +12 -0
  95. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  96. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  97. package/src/vite/plugins/performance-tracks.ts +4 -6
  98. package/src/vite/rango.ts +49 -14
  99. package/src/vite/router-discovery.ts +186 -26
  100. package/src/vite/utils/banner.ts +1 -1
  101. package/src/vite/utils/package-resolution.ts +41 -1
  102. package/src/vite/utils/prerender-utils.ts +20 -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);
@@ -910,9 +913,7 @@ function generateWholeFileStubs(cfg, bindings, code, filePath, isBuild) {
910
913
  });
911
914
  return { code: stubs.join("\n") + "\n", map: null };
912
915
  }
913
- function generateExprStubs(cfg, bindings, code, filePath, sourceId, isBuild) {
914
- if (bindings.length === 0) return null;
915
- const s = new MagicString2(code);
916
+ function stubHandlerExprs(cfg, bindings, s, filePath, isBuild) {
916
917
  let hasChanges = false;
917
918
  for (const binding of bindings) {
918
919
  const exportName = binding.exportNames[0];
@@ -924,15 +925,7 @@ function generateExprStubs(cfg, bindings, code, filePath, sourceId, isBuild) {
924
925
  );
925
926
  hasChanges = true;
926
927
  }
927
- if (!hasChanges) return null;
928
- return {
929
- code: s.toString(),
930
- map: s.generateMap({
931
- source: sourceId,
932
- includeContent: true,
933
- hires: "boundary"
934
- })
935
- };
928
+ return hasChanges;
936
929
  }
937
930
  function transformHandlerIds(cfg, bindings, s, filePath, isBuild) {
938
931
  let hasChanges = false;
@@ -1269,15 +1262,6 @@ ${lazyImports.join(",\n")}
1269
1262
  isBuild
1270
1263
  );
1271
1264
  if (wholeFile) return wholeFile;
1272
- const exprStubs = generateExprStubs(
1273
- PRERENDER_CONFIG,
1274
- bindings,
1275
- code,
1276
- filePath,
1277
- id,
1278
- isBuild
1279
- );
1280
- if (exprStubs) return exprStubs;
1281
1265
  }
1282
1266
  if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1283
1267
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
@@ -1329,15 +1313,134 @@ ${lazyImports.join(",\n")}
1329
1313
  isBuild
1330
1314
  );
1331
1315
  if (wholeFile) return wholeFile;
1332
- const exprStubs = generateExprStubs(
1333
- STATIC_CONFIG,
1334
- bindings,
1335
- code,
1336
- filePath,
1337
- id,
1338
- isBuild
1339
- );
1340
- if (exprStubs) return exprStubs;
1316
+ }
1317
+ if (!isRscEnv && (hasPrerenderHandlerCode || hasStaticHandlerCode)) {
1318
+ const prerenderFnNames = hasPrerenderHandlerCode ? getFnNames(PRERENDER_CONFIG.fnName) : [];
1319
+ const staticFnNames = hasStaticHandlerCode ? getFnNames(STATIC_CONFIG.fnName) : [];
1320
+ const loaderFnNames = hasLoaderCode ? getFnNames("createLoader") : [];
1321
+ const handleFnNames = hasHandleCode ? getFnNames("createHandle") : [];
1322
+ const lsFnNames = hasLocationStateCode ? getFnNames("createLocationState") : [];
1323
+ const allBindings = [];
1324
+ for (const fnNames of [
1325
+ prerenderFnNames,
1326
+ staticFnNames,
1327
+ loaderFnNames,
1328
+ handleFnNames,
1329
+ lsFnNames
1330
+ ]) {
1331
+ if (fnNames.length > 0) {
1332
+ allBindings.push(...getBindings(code, fnNames));
1333
+ }
1334
+ }
1335
+ let canStubWholeFile = allBindings.length > 0 && isExportOnlyFile(code, allBindings);
1336
+ if (canStubWholeFile && (handleFnNames.length > 0 || lsFnNames.length > 0)) {
1337
+ const exportedLocals = new Set(allBindings.map((b) => b.localName));
1338
+ const strippedBindings = [];
1339
+ const localDeclPattern = /(?:^|;|\n)\s*(?:const|let|var|function)\s+(\w+)/g;
1340
+ let declMatch;
1341
+ while ((declMatch = localDeclPattern.exec(code)) !== null) {
1342
+ const name = declMatch[1];
1343
+ if (!exportedLocals.has(name) && !/^_c\d*$/.test(name)) {
1344
+ strippedBindings.push(name);
1345
+ }
1346
+ }
1347
+ const importPattern = /import\s*\{([^}]*)\}\s*from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1348
+ let importMatch;
1349
+ while ((importMatch = importPattern.exec(code)) !== null) {
1350
+ for (const spec of importMatch[1].split(",")) {
1351
+ const m = spec.trim().match(/^[A-Za-z_$][\w$]*(?:\s+as\s+([A-Za-z_$][\w$]*))?$/);
1352
+ if (m) strippedBindings.push(m[1] || m[0].trim().split(/\s/)[0]);
1353
+ }
1354
+ }
1355
+ const defaultImportPattern = /import\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1356
+ while ((importMatch = defaultImportPattern.exec(code)) !== null) {
1357
+ strippedBindings.push(importMatch[1]);
1358
+ }
1359
+ const nsImportPattern = /import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s*["'](?!@rangojs\/router)[^"']*["']/g;
1360
+ while ((importMatch = nsImportPattern.exec(code)) !== null) {
1361
+ strippedBindings.push(importMatch[1]);
1362
+ }
1363
+ if (strippedBindings.length > 0) {
1364
+ const preservedBindings = allBindings.filter((b) => {
1365
+ const fc = code.slice(b.callExprStart, b.callOpenParenPos + 1);
1366
+ return handleFnNames.some((n) => fc.includes(n)) || lsFnNames.some((n) => fc.includes(n));
1367
+ });
1368
+ const strippedRe = new RegExp(
1369
+ `\\b(?:${strippedBindings.join("|")})\\b`
1370
+ );
1371
+ canStubWholeFile = !preservedBindings.some((b) => {
1372
+ const expr = code.slice(b.callExprStart, b.callCloseParenPos + 1);
1373
+ return strippedRe.test(expr);
1374
+ });
1375
+ }
1376
+ }
1377
+ if (canStubWholeFile) {
1378
+ const lines = [];
1379
+ const neededImports = [];
1380
+ if (handleFnNames.length > 0) neededImports.push("createHandle");
1381
+ if (lsFnNames.length > 0) neededImports.push("createLocationState");
1382
+ if (neededImports.length > 0) {
1383
+ lines.push(
1384
+ `import { ${neededImports.join(", ")} } from "@rangojs/router";`
1385
+ );
1386
+ }
1387
+ for (const binding of allBindings) {
1388
+ const fnCall = code.slice(
1389
+ binding.callExprStart,
1390
+ binding.callOpenParenPos + 1
1391
+ );
1392
+ const isHandle = handleFnNames.some((n) => fnCall.includes(n));
1393
+ const isLocationState = lsFnNames.some((n) => fnCall.includes(n));
1394
+ const primaryName = binding.exportNames[0];
1395
+ const stubId = makeStubId(filePath, primaryName, isBuild);
1396
+ if (isHandle || isLocationState) {
1397
+ const rawArgs = code.slice(binding.callOpenParenPos + 1, binding.callCloseParenPos).replace(/\b_c\d*\s*=\s*/g, "");
1398
+ const canonicalName = isHandle ? "createHandle" : "createLocationState";
1399
+ const activeFnNames = isHandle ? handleFnNames : lsFnNames;
1400
+ let rawCallee = code.slice(
1401
+ binding.callExprStart,
1402
+ binding.callOpenParenPos
1403
+ );
1404
+ for (const alias of activeFnNames) {
1405
+ if (alias !== canonicalName && rawCallee.startsWith(alias)) {
1406
+ rawCallee = canonicalName + rawCallee.slice(alias.length);
1407
+ break;
1408
+ }
1409
+ }
1410
+ if (isHandle) {
1411
+ const idParam = binding.argCount === 0 ? `undefined, "${stubId}"` : `, "${stubId}"`;
1412
+ lines.push(
1413
+ `export const ${primaryName} = ${rawCallee}(${rawArgs}${idParam});`
1414
+ );
1415
+ lines.push(`${primaryName}.$$id = "${stubId}";`);
1416
+ } else {
1417
+ lines.push(
1418
+ `export const ${primaryName} = ${rawCallee}(${rawArgs});`
1419
+ );
1420
+ lines.push(
1421
+ `${primaryName}.__rsc_ls_key = "__rsc_ls_${stubId}";`
1422
+ );
1423
+ }
1424
+ for (const name of binding.exportNames.slice(1)) {
1425
+ lines.push(`export const ${name} = ${primaryName};`);
1426
+ }
1427
+ } else {
1428
+ let brand = "loader";
1429
+ if (prerenderFnNames.some((n) => fnCall.includes(n))) {
1430
+ brand = PRERENDER_CONFIG.brand;
1431
+ } else if (staticFnNames.some((n) => fnCall.includes(n))) {
1432
+ brand = STATIC_CONFIG.brand;
1433
+ }
1434
+ lines.push(
1435
+ `export const ${primaryName} = { __brand: "${brand}", $$id: "${stubId}" };`
1436
+ );
1437
+ for (const name of binding.exportNames.slice(1)) {
1438
+ lines.push(`export const ${name} = ${primaryName};`);
1439
+ }
1440
+ }
1441
+ }
1442
+ return { code: lines.join("\n") + "\n", map: null };
1443
+ }
1341
1444
  }
1342
1445
  if (hasStaticHandlerCode && isRscEnv && isBuild) {
1343
1446
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
@@ -1372,25 +1475,41 @@ ${lazyImports.join(",\n")}
1372
1475
  isBuild
1373
1476
  ) || changed;
1374
1477
  }
1375
- if (hasPrerenderHandlerCode && isRscEnv) {
1478
+ if (hasPrerenderHandlerCode) {
1376
1479
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1377
- changed = transformHandlerIds(
1378
- PRERENDER_CONFIG,
1379
- getBindings(code, fnNames),
1380
- s,
1381
- filePath,
1382
- isBuild
1383
- ) || changed;
1480
+ const bindings = getBindings(code, fnNames);
1481
+ if (isRscEnv) {
1482
+ changed = transformHandlerIds(
1483
+ PRERENDER_CONFIG,
1484
+ bindings,
1485
+ s,
1486
+ filePath,
1487
+ isBuild
1488
+ ) || changed;
1489
+ } else {
1490
+ changed = stubHandlerExprs(
1491
+ PRERENDER_CONFIG,
1492
+ bindings,
1493
+ s,
1494
+ filePath,
1495
+ isBuild
1496
+ ) || changed;
1497
+ }
1384
1498
  }
1385
- if (hasStaticHandlerCode && isRscEnv) {
1499
+ if (hasStaticHandlerCode) {
1386
1500
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
1387
- changed = transformHandlerIds(
1388
- STATIC_CONFIG,
1389
- getBindings(code, fnNames),
1390
- s,
1391
- filePath,
1392
- isBuild
1393
- ) || changed;
1501
+ const bindings = getBindings(code, fnNames);
1502
+ if (isRscEnv) {
1503
+ changed = transformHandlerIds(
1504
+ STATIC_CONFIG,
1505
+ bindings,
1506
+ s,
1507
+ filePath,
1508
+ isBuild
1509
+ ) || changed;
1510
+ } else {
1511
+ changed = stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) || changed;
1512
+ }
1394
1513
  }
1395
1514
  if (!changed) return;
1396
1515
  return {
@@ -1740,12 +1859,13 @@ function getVirtualVersionContent(version) {
1740
1859
 
1741
1860
  // src/vite/utils/package-resolution.ts
1742
1861
  import { existsSync } from "node:fs";
1862
+ import { createRequire } from "node:module";
1743
1863
  import { resolve } from "node:path";
1744
1864
 
1745
1865
  // package.json
1746
1866
  var package_default = {
1747
1867
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.88a3b2f7",
1868
+ version: "0.0.0-experimental.8bcfea43",
1749
1869
  description: "Django-inspired RSC router with composable URL patterns",
1750
1870
  keywords: [
1751
1871
  "react",
@@ -1878,22 +1998,24 @@ var package_default = {
1878
1998
  tag: "experimental"
1879
1999
  },
1880
2000
  scripts: {
1881
- build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
2001
+ build: "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
1882
2002
  prepublishOnly: "pnpm build",
1883
- typecheck: "tsc --noEmit",
2003
+ typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit",
1884
2004
  test: "playwright test",
1885
2005
  "test:ui": "playwright test --ui",
1886
2006
  "test:unit": "vitest run",
1887
2007
  "test:unit:watch": "vitest"
1888
2008
  },
1889
2009
  dependencies: {
1890
- "@vitejs/plugin-rsc": "^0.5.19",
2010
+ "@vitejs/plugin-rsc": "^0.5.23",
2011
+ debug: "^4.4.1",
1891
2012
  "magic-string": "^0.30.17",
1892
2013
  picomatch: "^4.0.3",
1893
2014
  "rsc-html-stream": "^0.0.7"
1894
2015
  },
1895
2016
  devDependencies: {
1896
2017
  "@playwright/test": "^1.49.1",
2018
+ "@types/debug": "^4.1.12",
1897
2019
  "@types/node": "^24.10.1",
1898
2020
  "@types/react": "catalog:",
1899
2021
  "@types/react-dom": "catalog:",
@@ -1907,7 +2029,7 @@ var package_default = {
1907
2029
  },
1908
2030
  peerDependencies: {
1909
2031
  "@cloudflare/vite-plugin": "^1.25.0",
1910
- "@vitejs/plugin-rsc": "^0.5.14",
2032
+ "@vitejs/plugin-rsc": "^0.5.23",
1911
2033
  react: "^18.0.0 || ^19.0.0",
1912
2034
  vite: "^7.3.0"
1913
2035
  },
@@ -1922,6 +2044,7 @@ var package_default = {
1922
2044
  };
1923
2045
 
1924
2046
  // src/vite/utils/package-resolution.ts
2047
+ var require2 = createRequire(import.meta.url);
1925
2048
  var VIRTUAL_PACKAGE_NAME = "@rangojs/router";
1926
2049
  function getPublishedPackageName() {
1927
2050
  return package_default.name;
@@ -1962,6 +2085,20 @@ function getPackageAliases() {
1962
2085
  }
1963
2086
  return aliases;
1964
2087
  }
2088
+ function getVendorAliases() {
2089
+ const specs = [
2090
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
2091
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
2092
+ ];
2093
+ const aliases = {};
2094
+ for (const spec of specs) {
2095
+ try {
2096
+ aliases[spec] = require2.resolve(spec);
2097
+ } catch {
2098
+ }
2099
+ }
2100
+ return aliases;
2101
+ }
1965
2102
 
1966
2103
  // src/build/route-types/param-extraction.ts
1967
2104
  function extractParamsFromPattern(pattern) {
@@ -2859,6 +2996,29 @@ import * as Vite from "vite";
2859
2996
 
2860
2997
  // src/vite/plugins/performance-tracks.ts
2861
2998
  import { readFile } from "node:fs/promises";
2999
+
3000
+ // src/vite/debug.ts
3001
+ import debugFactory from "debug";
3002
+ if (process.env.INTERNAL_RANGO_DEBUG) {
3003
+ const existing = debugFactory.disable();
3004
+ debugFactory.enable(existing ? `${existing},rango:*` : "rango:*");
3005
+ }
3006
+ function createRangoDebugger(namespace) {
3007
+ const instance = debugFactory(namespace);
3008
+ return instance.enabled ? instance : void 0;
3009
+ }
3010
+ async function timed(debug2, label, fn) {
3011
+ if (!debug2) return await fn();
3012
+ const start = performance.now();
3013
+ try {
3014
+ return await fn();
3015
+ } finally {
3016
+ debug2("%s (%sms)", label, (performance.now() - start).toFixed(1));
3017
+ }
3018
+ }
3019
+
3020
+ // src/vite/plugins/performance-tracks.ts
3021
+ var debug = createRangoDebugger("rango:transform");
2862
3022
  var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2863
3023
  function buildPatchReplacement(match, debugInfoVar) {
2864
3024
  return `${match}
@@ -2906,12 +3066,7 @@ function performanceTracksPlugin() {
2906
3066
  if (!id.includes("react-server-dom") || !id.includes("client")) return;
2907
3067
  const patched = patchRsdwClientDebugInfoRecovery(code);
2908
3068
  if (!patched.debugInfoVar) return;
2909
- if (process.env.INTERNAL_RANGO_DEBUG)
2910
- console.log(
2911
- "[perf-tracks] patched RSDW client (var:",
2912
- patched.debugInfoVar,
2913
- ")"
2914
- );
3069
+ debug?.("patched RSDW client (var: %s)", patched.debugInfoVar);
2915
3070
  return patched.code;
2916
3071
  }
2917
3072
  };
@@ -3141,7 +3296,7 @@ function createCjsToEsmPlugin() {
3141
3296
  import { createServer as createViteServer } from "vite";
3142
3297
  import { resolve as resolve8 } from "node:path";
3143
3298
  import { readFileSync as readFileSync6 } from "node:fs";
3144
- import { createRequire } from "node:module";
3299
+ import { createRequire as createRequire2, register } from "node:module";
3145
3300
  import { pathToFileURL } from "node:url";
3146
3301
 
3147
3302
  // src/vite/plugins/virtual-stub-plugin.ts
@@ -3168,6 +3323,113 @@ function createVirtualStubPlugin() {
3168
3323
  };
3169
3324
  }
3170
3325
 
3326
+ // src/vite/plugins/cloudflare-protocol-stub.ts
3327
+ var VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
3328
+ var NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
3329
+ var CF_PREFIX = "cloudflare:";
3330
+ var BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
3331
+ var SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
3332
+ var IMPORT_NODE_TYPES = /* @__PURE__ */ new Set([
3333
+ "ImportDeclaration",
3334
+ "ImportExpression",
3335
+ "ExportNamedDeclaration",
3336
+ "ExportAllDeclaration"
3337
+ ]);
3338
+ var STUBS = {
3339
+ "cloudflare:workers": `
3340
+ export class DurableObject { constructor(_ctx, _env) {} }
3341
+ export class WorkerEntrypoint { constructor(_ctx, _env) {} }
3342
+ export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
3343
+ export class RpcTarget {}
3344
+ export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
3345
+ export default {};
3346
+ `,
3347
+ "cloudflare:email": `
3348
+ export class EmailMessage { constructor(_from, _to, _raw) {} }
3349
+ export default {};
3350
+ `,
3351
+ "cloudflare:sockets": `
3352
+ export function connect() { return {}; }
3353
+ export default {};
3354
+ `,
3355
+ "cloudflare:workflows": `
3356
+ export class NonRetryableError extends Error {
3357
+ constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
3358
+ }
3359
+ export default {};
3360
+ `
3361
+ };
3362
+ var FALLBACK_STUB = `export default {};
3363
+ `;
3364
+ function createCloudflareProtocolStubPlugin() {
3365
+ return {
3366
+ name: "@rangojs/router:cloudflare-protocol-stub",
3367
+ transform(code, id) {
3368
+ const cleanId = id.split("?")[0] ?? id;
3369
+ if (!SOURCE_EXT_RE.test(cleanId)) return null;
3370
+ if (!code.includes(CF_PREFIX)) return null;
3371
+ let ast;
3372
+ try {
3373
+ ast = this.parse(code);
3374
+ } catch {
3375
+ return null;
3376
+ }
3377
+ const hits = [];
3378
+ walk(ast, (node) => {
3379
+ if (!IMPORT_NODE_TYPES.has(node.type)) return;
3380
+ const source = node.source;
3381
+ if (!source || source.type !== "Literal") return;
3382
+ if (typeof source.value !== "string") return;
3383
+ if (!source.value.startsWith(CF_PREFIX)) return;
3384
+ if (typeof source.start !== "number" || typeof source.end !== "number")
3385
+ return;
3386
+ hits.push({
3387
+ start: source.start,
3388
+ end: source.end,
3389
+ value: source.value
3390
+ });
3391
+ });
3392
+ if (hits.length === 0) return null;
3393
+ hits.sort((a, b) => b.start - a.start);
3394
+ let out = code;
3395
+ for (const hit of hits) {
3396
+ const submodule = hit.value.slice(CF_PREFIX.length);
3397
+ const quote = code[hit.start] === "'" ? "'" : '"';
3398
+ out = out.slice(0, hit.start) + quote + VIRTUAL_PREFIX + submodule + quote + out.slice(hit.end);
3399
+ }
3400
+ return { code: out, map: null };
3401
+ },
3402
+ resolveId(id) {
3403
+ if (id.startsWith(VIRTUAL_PREFIX)) {
3404
+ return "\0" + id;
3405
+ }
3406
+ return null;
3407
+ },
3408
+ load(id) {
3409
+ if (!id.startsWith(NULL_PREFIX)) return null;
3410
+ const submodule = id.slice(NULL_PREFIX.length);
3411
+ const specifier = CF_PREFIX + submodule;
3412
+ return STUBS[specifier] ?? FALLBACK_STUB;
3413
+ }
3414
+ };
3415
+ }
3416
+ function walk(node, visit) {
3417
+ if (!node || typeof node !== "object") return;
3418
+ if (Array.isArray(node)) {
3419
+ for (const child of node) walk(child, visit);
3420
+ return;
3421
+ }
3422
+ const n = node;
3423
+ if (typeof n.type !== "string") return;
3424
+ visit(n);
3425
+ for (const key in n) {
3426
+ if (key === "loc" || key === "start" || key === "end" || key === "range") {
3427
+ continue;
3428
+ }
3429
+ walk(n[key], visit);
3430
+ }
3431
+ }
3432
+
3171
3433
  // src/vite/plugins/client-ref-hashing.ts
3172
3434
  import { relative } from "node:path";
3173
3435
  import { createHash as createHash2 } from "node:crypto";
@@ -3461,11 +3723,19 @@ function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3461
3723
  let hadOmittedOptional = false;
3462
3724
  for (const [key, value] of Object.entries(params)) {
3463
3725
  const escaped = escapeRegExp2(key);
3464
- result = result.replace(
3465
- new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3466
- encode(value)
3467
- );
3468
- result = result.replace(`*${key}`, encode(value));
3726
+ if (value === "") {
3727
+ result = result.replace(
3728
+ new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
3729
+ ""
3730
+ );
3731
+ result = result.replace(`*${key}`, "");
3732
+ } else {
3733
+ result = result.replace(
3734
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3735
+ encode(value)
3736
+ );
3737
+ result = result.replace(`*${key}`, encode(value));
3738
+ }
3469
3739
  }
3470
3740
  result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3471
3741
  hadOmittedOptional = true;
@@ -3584,93 +3854,126 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3584
3854
  if (!params) return pattern;
3585
3855
  return substituteRouteParams(pattern, params);
3586
3856
  };
3857
+ let resolvedRoutes = 0;
3858
+ let totalDynamic = 0;
3587
3859
  for (const { manifest } of allManifests) {
3588
3860
  if (!manifest.prerenderRoutes) continue;
3589
- const defs = manifest._prerenderDefs || {};
3590
- const passthroughSet = new Set(manifest.passthroughRoutes || []);
3591
3861
  for (const routeName of manifest.prerenderRoutes) {
3592
3862
  const pattern = manifest.routeManifest[routeName];
3593
- if (!pattern) continue;
3594
- const def = defs[routeName];
3595
- const isPassthroughRoute = passthroughSet.has(routeName);
3596
- const hasDynamic = pattern.includes(":") || pattern.includes("*");
3597
- if (!hasDynamic) {
3598
- entries.push({
3599
- urlPath: pattern.replace(/\/$/, "") || "/",
3600
- routeName,
3601
- concurrency: 1,
3602
- isPassthroughRoute
3603
- });
3604
- } else {
3605
- if (def?.getParams) {
3606
- try {
3607
- const buildVars = {};
3608
- const buildEnv = state.resolvedBuildEnv;
3609
- const getParamsCtx = {
3610
- build: true,
3611
- dev: !state.isBuildMode,
3612
- set: ((keyOrVar, value) => {
3613
- contextSet(buildVars, keyOrVar, value);
3614
- }),
3615
- reverse: getParamsReverse,
3616
- get env() {
3617
- if (buildEnv !== void 0) return buildEnv;
3618
- throw new Error(
3619
- "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3863
+ if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
3864
+ totalDynamic++;
3865
+ }
3866
+ }
3867
+ }
3868
+ const paramsStart = performance.now();
3869
+ const progressInterval = totalDynamic > 0 ? setInterval(() => {
3870
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3871
+ console.log(
3872
+ `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3873
+ );
3874
+ }, 5e3) : void 0;
3875
+ try {
3876
+ for (const { manifest } of allManifests) {
3877
+ if (!manifest.prerenderRoutes) continue;
3878
+ const defs = manifest._prerenderDefs || {};
3879
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
3880
+ for (const routeName of manifest.prerenderRoutes) {
3881
+ const pattern = manifest.routeManifest[routeName];
3882
+ if (!pattern) continue;
3883
+ const def = defs[routeName];
3884
+ const isPassthroughRoute = passthroughSet.has(routeName);
3885
+ const hasDynamic = pattern.includes(":") || pattern.includes("*");
3886
+ if (!hasDynamic) {
3887
+ entries.push({
3888
+ urlPath: pattern.replace(/\/$/, "") || "/",
3889
+ routeName,
3890
+ concurrency: 1,
3891
+ isPassthroughRoute
3892
+ });
3893
+ } else {
3894
+ if (def?.getParams) {
3895
+ try {
3896
+ const buildVars = {};
3897
+ const buildEnv = state.resolvedBuildEnv;
3898
+ const getParamsCtx = {
3899
+ build: true,
3900
+ dev: !state.isBuildMode,
3901
+ set: ((keyOrVar, value) => {
3902
+ contextSet(buildVars, keyOrVar, value);
3903
+ }),
3904
+ reverse: getParamsReverse,
3905
+ get env() {
3906
+ if (buildEnv !== void 0) return buildEnv;
3907
+ throw new Error(
3908
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3909
+ );
3910
+ }
3911
+ };
3912
+ const paramsList = await def.getParams(getParamsCtx);
3913
+ const concurrency = def.options?.concurrency ?? 1;
3914
+ const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3915
+ for (const params of paramsList) {
3916
+ let url = substituteRouteParams(
3917
+ pattern,
3918
+ params,
3919
+ encodePathParam
3620
3920
  );
3621
- }
3622
- };
3623
- const paramsList = await def.getParams(getParamsCtx);
3624
- const concurrency = def.options?.concurrency ?? 1;
3625
- const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3626
- for (const params of paramsList) {
3627
- let url = substituteRouteParams(
3628
- pattern,
3629
- params,
3630
- encodePathParam
3631
- );
3632
- if (url.includes("*")) {
3633
- const wildcardValue = params["*"] ?? params.splat;
3634
- if (wildcardValue !== void 0) {
3635
- url = url.replace(/\*[^/]*$/, encodePathParam(wildcardValue));
3921
+ if (url.includes("*")) {
3922
+ const wildcardValue = params["*"] ?? params.splat;
3923
+ if (wildcardValue !== void 0) {
3924
+ url = url.replace(
3925
+ /\*[^/]*$/,
3926
+ encodePathParam(wildcardValue)
3927
+ );
3928
+ }
3636
3929
  }
3930
+ entries.push({
3931
+ urlPath: url.replace(/\/$/, "") || "/",
3932
+ routeName,
3933
+ concurrency,
3934
+ ...hasBuildVars ? { buildVars } : {},
3935
+ isPassthroughRoute
3936
+ });
3637
3937
  }
3638
- entries.push({
3639
- urlPath: url.replace(/\/$/, "") || "/",
3640
- routeName,
3641
- concurrency,
3642
- ...hasBuildVars ? { buildVars } : {},
3643
- isPassthroughRoute
3644
- });
3645
- }
3646
- } catch (err) {
3647
- if (err.name === "Skip") {
3648
- console.log(
3649
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3650
- );
3651
- notifyOnError(
3652
- registry,
3653
- err,
3654
- "prerender",
3655
- routeName,
3656
- void 0,
3657
- true
3938
+ resolvedRoutes++;
3939
+ } catch (err) {
3940
+ resolvedRoutes++;
3941
+ if (err.name === "Skip") {
3942
+ console.log(
3943
+ `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3944
+ );
3945
+ notifyOnError(
3946
+ registry,
3947
+ err,
3948
+ "prerender",
3949
+ routeName,
3950
+ void 0,
3951
+ true
3952
+ );
3953
+ continue;
3954
+ }
3955
+ console.error(
3956
+ `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3658
3957
  );
3659
- continue;
3958
+ notifyOnError(registry, err, "prerender", routeName);
3959
+ throw err;
3660
3960
  }
3661
- console.error(
3662
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3961
+ } else {
3962
+ console.warn(
3963
+ `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3663
3964
  );
3664
- notifyOnError(registry, err, "prerender", routeName);
3665
- throw err;
3666
3965
  }
3667
- } else {
3668
- console.warn(
3669
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3670
- );
3671
3966
  }
3672
3967
  }
3673
3968
  }
3969
+ } finally {
3970
+ if (progressInterval) {
3971
+ clearInterval(progressInterval);
3972
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3973
+ console.log(
3974
+ `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3975
+ );
3976
+ }
3674
3977
  }
3675
3978
  if (entries.length === 0) return;
3676
3979
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
@@ -4522,7 +4825,24 @@ function postprocessBundle(state) {
4522
4825
  }
4523
4826
 
4524
4827
  // src/vite/router-discovery.ts
4828
+ var debugDiscovery = createRangoDebugger("rango:discovery");
4829
+ var debugRoutes = createRangoDebugger("rango:routes");
4830
+ var loaderHookRegistered = false;
4831
+ function ensureCloudflareProtocolLoaderRegistered() {
4832
+ if (loaderHookRegistered) return;
4833
+ loaderHookRegistered = true;
4834
+ try {
4835
+ register(
4836
+ new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url)
4837
+ );
4838
+ } catch (err) {
4839
+ console.warn(
4840
+ `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
4841
+ );
4842
+ }
4843
+ }
4525
4844
  async function createTempRscServer(state, options = {}) {
4845
+ ensureCloudflareProtocolLoaderRegistered();
4526
4846
  const { default: rsc } = await import("@vitejs/plugin-rsc");
4527
4847
  return createViteServer({
4528
4848
  root: state.projectRoot,
@@ -4545,6 +4865,7 @@ async function createTempRscServer(state, options = {}) {
4545
4865
  ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
4546
4866
  createVersionPlugin(),
4547
4867
  createVirtualStubPlugin(),
4868
+ createCloudflareProtocolStubPlugin(),
4548
4869
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
4549
4870
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
4550
4871
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
@@ -4561,7 +4882,7 @@ async function resolveBuildEnv(option, factoryCtx) {
4561
4882
  );
4562
4883
  }
4563
4884
  try {
4564
- const userRequire = createRequire(
4885
+ const userRequire = createRequire2(
4565
4886
  resolve8(factoryCtx.root, "package.json")
4566
4887
  );
4567
4888
  const wranglerPath = userRequire.resolve("wrangler");
@@ -4596,6 +4917,7 @@ async function acquireBuildEnv(s, command, mode) {
4596
4917
  if (!result) return false;
4597
4918
  s.resolvedBuildEnv = result.env;
4598
4919
  s.buildEnvDispose = result.dispose ?? null;
4920
+ globalThis[BUILD_ENV_GLOBAL_KEY] = result.env;
4599
4921
  return true;
4600
4922
  }
4601
4923
  async function releaseBuildEnv(s) {
@@ -4608,6 +4930,7 @@ async function releaseBuildEnv(s) {
4608
4930
  s.buildEnvDispose = null;
4609
4931
  }
4610
4932
  s.resolvedBuildEnv = void 0;
4933
+ delete globalThis[BUILD_ENV_GLOBAL_KEY];
4611
4934
  }
4612
4935
  function createRouterDiscoveryPlugin(entryPath, opts) {
4613
4936
  const s = createDiscoveryState(entryPath, opts);
@@ -4700,15 +5023,33 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4700
5023
  return null;
4701
5024
  }
4702
5025
  const discover = async () => {
5026
+ const discoverStart = performance.now();
4703
5027
  const rscEnv = server.environments?.rsc;
4704
5028
  if (!rscEnv?.runner) {
5029
+ debugDiscovery?.("dev: no rsc runner (cloudflare path)");
4705
5030
  s.devServerOrigin = getDevServerOrigin();
4706
5031
  try {
4707
- await acquireBuildEnv(s, viteCommand, viteMode);
4708
- const tempRscEnv = await getOrCreateTempServer();
5032
+ await timed(
5033
+ debugDiscovery,
5034
+ "acquireBuildEnv",
5035
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5036
+ );
5037
+ const tempRscEnv = await timed(
5038
+ debugDiscovery,
5039
+ "getOrCreateTempServer",
5040
+ () => getOrCreateTempServer()
5041
+ );
4709
5042
  if (tempRscEnv) {
4710
- await discoverRouters(s, tempRscEnv);
4711
- writeRouteTypesFiles(s);
5043
+ await timed(
5044
+ debugDiscovery,
5045
+ "discoverRouters (cloudflare)",
5046
+ () => discoverRouters(s, tempRscEnv)
5047
+ );
5048
+ timed(
5049
+ debugDiscovery,
5050
+ "writeRouteTypesFiles",
5051
+ () => writeRouteTypesFiles(s)
5052
+ );
4712
5053
  }
4713
5054
  } catch (err) {
4714
5055
  console.warn(
@@ -4716,27 +5057,54 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4716
5057
  ${err.stack}`
4717
5058
  );
4718
5059
  }
5060
+ debugDiscovery?.(
5061
+ "dev discovery done (%sms)",
5062
+ (performance.now() - discoverStart).toFixed(1)
5063
+ );
4719
5064
  resolveDiscovery();
4720
5065
  return;
4721
5066
  }
4722
5067
  try {
4723
- await acquireBuildEnv(s, viteCommand, viteMode);
4724
- const serverMod = await rscEnv.runner.import(
4725
- "@rangojs/router/server"
5068
+ debugDiscovery?.("dev: node path start");
5069
+ await timed(
5070
+ debugDiscovery,
5071
+ "acquireBuildEnv",
5072
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5073
+ );
5074
+ const serverMod = await timed(
5075
+ debugDiscovery,
5076
+ "import @rangojs/router/server",
5077
+ () => rscEnv.runner.import("@rangojs/router/server")
4726
5078
  );
4727
5079
  if (serverMod?.setManifestReadyPromise) {
4728
5080
  serverMod.setManifestReadyPromise(discoveryPromise);
4729
5081
  }
4730
- await discoverRouters(s, rscEnv);
5082
+ await timed(
5083
+ debugDiscovery,
5084
+ "discoverRouters",
5085
+ () => discoverRouters(s, rscEnv)
5086
+ );
4731
5087
  s.devServerOrigin = getDevServerOrigin();
4732
- writeRouteTypesFiles(s);
4733
- await propagateDiscoveryState(rscEnv);
5088
+ timed(
5089
+ debugDiscovery,
5090
+ "writeRouteTypesFiles",
5091
+ () => writeRouteTypesFiles(s)
5092
+ );
5093
+ await timed(
5094
+ debugDiscovery,
5095
+ "propagateDiscoveryState",
5096
+ () => propagateDiscoveryState(rscEnv)
5097
+ );
4734
5098
  } catch (err) {
4735
5099
  console.warn(
4736
5100
  `[rsc-router] Router discovery failed: ${err.message}
4737
5101
  ${err.stack}`
4738
5102
  );
4739
5103
  } finally {
5104
+ debugDiscovery?.(
5105
+ "dev discovery done (%sms)",
5106
+ (performance.now() - discoverStart).toFixed(1)
5107
+ );
4740
5108
  resolveDiscovery();
4741
5109
  }
4742
5110
  };
@@ -4785,7 +5153,26 @@ ${err.stack}`
4785
5153
  res.end("Missing pathname");
4786
5154
  return;
4787
5155
  }
4788
- let registry = mainRegistry;
5156
+ const rscEnv = server.environments?.rsc;
5157
+ let registry = null;
5158
+ if (rscEnv?.runner && s.resolvedEntryPath) {
5159
+ try {
5160
+ await rscEnv.runner.import(s.resolvedEntryPath);
5161
+ const serverMod = await rscEnv.runner.import(
5162
+ "@rangojs/router/server"
5163
+ );
5164
+ registry = serverMod.RouterRegistry ?? null;
5165
+ } catch (err) {
5166
+ console.warn(
5167
+ `[rsc-router] Dev prerender module refresh failed: ${err.message}`
5168
+ );
5169
+ res.statusCode = 500;
5170
+ res.end(`Prerender handler error: ${err.message}`);
5171
+ return;
5172
+ }
5173
+ } else {
5174
+ registry = mainRegistry;
5175
+ }
4789
5176
  if (!registry) {
4790
5177
  if (!prerenderNodeRegistry) {
4791
5178
  await getOrCreateTempServer();
@@ -4862,15 +5249,32 @@ ${err.stack}`
4862
5249
  const rscEnv = server.environments?.rsc;
4863
5250
  if (!rscEnv?.runner || runtimeRediscoveryInProgress) return;
4864
5251
  runtimeRediscoveryInProgress = true;
5252
+ const hmrStart = performance.now();
4865
5253
  try {
4866
- await discoverRouters(s, rscEnv);
4867
- writeRouteTypesFiles(s);
4868
- await propagateDiscoveryState(rscEnv);
5254
+ await timed(
5255
+ debugDiscovery,
5256
+ "hmr discoverRouters",
5257
+ () => discoverRouters(s, rscEnv)
5258
+ );
5259
+ timed(
5260
+ debugDiscovery,
5261
+ "hmr writeRouteTypesFiles",
5262
+ () => writeRouteTypesFiles(s)
5263
+ );
5264
+ await timed(
5265
+ debugDiscovery,
5266
+ "hmr propagateDiscoveryState",
5267
+ () => propagateDiscoveryState(rscEnv)
5268
+ );
4869
5269
  } catch (err) {
4870
5270
  console.warn(
4871
5271
  `[rsc-router] Runtime re-discovery failed: ${err.message}`
4872
5272
  );
4873
5273
  } finally {
5274
+ debugDiscovery?.(
5275
+ "hmr re-discovery done (%sms)",
5276
+ (performance.now() - hmrStart).toFixed(1)
5277
+ );
4874
5278
  runtimeRediscoveryInProgress = false;
4875
5279
  }
4876
5280
  };
@@ -4943,14 +5347,24 @@ ${err.stack}`
4943
5347
  async buildStart() {
4944
5348
  if (!s.isBuildMode) return;
4945
5349
  if (s.mergedRouteManifest !== null) return;
5350
+ const buildStartTime = performance.now();
5351
+ debugDiscovery?.("build: start");
4946
5352
  resetStagedBuildAssets(s.projectRoot);
4947
5353
  s.prerenderManifestEntries = null;
4948
5354
  s.staticManifestEntries = null;
4949
- await acquireBuildEnv(s, viteCommand, viteMode);
5355
+ await timed(
5356
+ debugDiscovery,
5357
+ "build acquireBuildEnv",
5358
+ () => acquireBuildEnv(s, viteCommand, viteMode)
5359
+ );
4950
5360
  let tempServer = null;
4951
5361
  globalThis.__rscRouterDiscoveryActive = true;
4952
5362
  try {
4953
- tempServer = await createTempRscServer(s, { forceBuild: true });
5363
+ tempServer = await timed(
5364
+ debugDiscovery,
5365
+ "build createTempRscServer",
5366
+ () => createTempRscServer(s, { forceBuild: true })
5367
+ );
4954
5368
  const rscEnv = tempServer.environments?.rsc;
4955
5369
  if (!rscEnv?.runner) {
4956
5370
  console.warn(
@@ -4964,8 +5378,16 @@ ${err.stack}`
4964
5378
  if (tempIdsPlugin?.api?.staticHandlerModules) {
4965
5379
  s.resolvedStaticModules = tempIdsPlugin.api.staticHandlerModules;
4966
5380
  }
4967
- await discoverRouters(s, rscEnv);
4968
- writeRouteTypesFiles(s);
5381
+ await timed(
5382
+ debugDiscovery,
5383
+ "build discoverRouters",
5384
+ () => discoverRouters(s, rscEnv)
5385
+ );
5386
+ timed(
5387
+ debugDiscovery,
5388
+ "build writeRouteTypesFiles",
5389
+ () => writeRouteTypesFiles(s)
5390
+ );
4969
5391
  } catch (err) {
4970
5392
  const sourceFile = err.stack?.split("\n").find(
4971
5393
  (line) => line.includes(s.projectRoot) && !line.includes("node_modules")
@@ -4984,9 +5406,17 @@ ${details}`
4984
5406
  } finally {
4985
5407
  delete globalThis.__rscRouterDiscoveryActive;
4986
5408
  if (tempServer) {
4987
- await tempServer.close();
5409
+ await timed(
5410
+ debugDiscovery,
5411
+ "build tempServer.close",
5412
+ () => tempServer.close()
5413
+ );
4988
5414
  }
4989
5415
  await releaseBuildEnv(s);
5416
+ debugDiscovery?.(
5417
+ "build discovery done (%sms)",
5418
+ (performance.now() - buildStartTime).toFixed(1)
5419
+ );
4990
5420
  }
4991
5421
  },
4992
5422
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -5003,17 +5433,36 @@ ${details}`
5003
5433
  async load(id) {
5004
5434
  if (id === "\0" + VIRTUAL_ROUTES_MANIFEST_ID) {
5005
5435
  if (s.discoveryDone) {
5006
- await s.discoveryDone;
5436
+ await timed(
5437
+ debugRoutes,
5438
+ "await discoveryDone (manifest)",
5439
+ () => Promise.resolve(s.discoveryDone)
5440
+ );
5007
5441
  }
5008
- return generateRoutesManifestModule(s);
5442
+ const code = await timed(
5443
+ debugRoutes,
5444
+ "generateRoutesManifestModule",
5445
+ () => generateRoutesManifestModule(s)
5446
+ );
5447
+ debugRoutes?.("manifest module emitted (%d bytes)", code?.length ?? 0);
5448
+ return code;
5009
5449
  }
5010
5450
  const perRouterPrefix = "\0" + VIRTUAL_ROUTES_MANIFEST_ID + "/";
5011
5451
  if (id.startsWith(perRouterPrefix)) {
5012
5452
  if (s.discoveryDone) {
5013
- await s.discoveryDone;
5453
+ await timed(
5454
+ debugRoutes,
5455
+ "await discoveryDone (per-router)",
5456
+ () => Promise.resolve(s.discoveryDone)
5457
+ );
5014
5458
  }
5015
5459
  const routerId = id.slice(perRouterPrefix.length);
5016
- return generatePerRouterModule(s, routerId);
5460
+ const code = await timed(
5461
+ debugRoutes,
5462
+ `generatePerRouterModule ${routerId}`,
5463
+ () => generatePerRouterModule(s, routerId)
5464
+ );
5465
+ return code;
5017
5466
  }
5018
5467
  return null;
5019
5468
  },
@@ -5089,22 +5538,30 @@ ${details}`
5089
5538
  }
5090
5539
 
5091
5540
  // src/vite/rango.ts
5541
+ var debugConfig = createRangoDebugger("rango:config");
5092
5542
  async function rango(options) {
5543
+ const rangoStart = performance.now();
5093
5544
  const resolvedOptions = options ?? { preset: "node" };
5094
5545
  const preset = resolvedOptions.preset ?? "node";
5095
5546
  const showBanner = resolvedOptions.banner ?? true;
5547
+ debugConfig?.("rango(%s) setup start", preset);
5096
5548
  const plugins = [];
5097
- const rangoAliases = getPackageAliases();
5549
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
5098
5550
  const excludeDeps = [
5099
5551
  ...getExcludeDeps(),
5100
- // The public browser entry re-exports the RSDW browser client.
5101
- // Excluding both keeps Vite from freezing the unpatched bundle into
5102
- // .vite/deps before our source transforms run.
5552
+ // plugin-rsc itself injects these into the client env's
5553
+ // optimizeDeps.include, which overrides exclude for the dep's own
5554
+ // pre-bundle entry. What exclude still controls is how *other*
5555
+ // pre-bundled deps treat imports of these specs (external vs inlined)
5556
+ // via esbuildCjsExternalPlugin. The cjs-to-esm transform in
5557
+ // plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
5558
+ // where client.browser's bare include fails to resolve and Vite ends up
5559
+ // serving the raw CJS file at dev-serve time.
5103
5560
  "@vitejs/plugin-rsc/browser",
5104
- // Keep the browser RSDW client out of Vite's dep optimizer so our
5105
- // cjs-to-esm transform can patch the real file.
5106
5561
  "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5107
5562
  ];
5563
+ const pkg = getPublishedPackageName();
5564
+ const nested = (spec) => `${pkg} > ${spec}`;
5108
5565
  const routerRef = { path: void 0 };
5109
5566
  const prerenderEnabled = true;
5110
5567
  if (preset === "cloudflare") {
@@ -5142,7 +5599,7 @@ async function rango(options) {
5142
5599
  // Pre-bundle rsc-html-stream to prevent discovery during first request
5143
5600
  // Exclude rsc-router modules to ensure same Context instance
5144
5601
  optimizeDeps: {
5145
- include: ["rsc-html-stream/client"],
5602
+ include: [nested("rsc-html-stream/client")],
5146
5603
  exclude: excludeDeps,
5147
5604
  esbuildOptions: sharedEsbuildOptions
5148
5605
  }
@@ -5167,8 +5624,10 @@ async function rango(options) {
5167
5624
  "react-dom/static.edge",
5168
5625
  "react/jsx-runtime",
5169
5626
  "react/jsx-dev-runtime",
5170
- "rsc-html-stream/server",
5171
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5627
+ nested("rsc-html-stream/server"),
5628
+ nested(
5629
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5630
+ )
5172
5631
  ],
5173
5632
  exclude: excludeDeps,
5174
5633
  esbuildOptions: sharedEsbuildOptions
@@ -5183,7 +5642,9 @@ async function rango(options) {
5183
5642
  "react",
5184
5643
  "react/jsx-runtime",
5185
5644
  "react/jsx-dev-runtime",
5186
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5645
+ nested(
5646
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5647
+ )
5187
5648
  ],
5188
5649
  exclude: excludeDeps,
5189
5650
  esbuildOptions: sharedEsbuildOptions
@@ -5264,7 +5725,7 @@ ${list}`);
5264
5725
  "react-dom",
5265
5726
  "react/jsx-runtime",
5266
5727
  "react/jsx-dev-runtime",
5267
- "rsc-html-stream/client"
5728
+ nested("rsc-html-stream/client")
5268
5729
  ],
5269
5730
  exclude: excludeDeps,
5270
5731
  esbuildOptions: sharedEsbuildOptions,
@@ -5281,7 +5742,9 @@ ${list}`);
5281
5742
  "react-dom/static.edge",
5282
5743
  "react/jsx-runtime",
5283
5744
  "react/jsx-dev-runtime",
5284
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5745
+ nested(
5746
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5747
+ )
5285
5748
  ],
5286
5749
  exclude: excludeDeps,
5287
5750
  esbuildOptions: sharedEsbuildOptions
@@ -5294,7 +5757,9 @@ ${list}`);
5294
5757
  "react",
5295
5758
  "react/jsx-runtime",
5296
5759
  "react/jsx-dev-runtime",
5297
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5760
+ nested(
5761
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5762
+ )
5298
5763
  ],
5299
5764
  esbuildOptions: sharedEsbuildOptions
5300
5765
  }
@@ -5364,6 +5829,12 @@ ${list}`);
5364
5829
  preset
5365
5830
  })
5366
5831
  );
5832
+ debugConfig?.(
5833
+ "rango(%s) setup done: %d plugin(s) (%sms)",
5834
+ preset,
5835
+ plugins.length,
5836
+ (performance.now() - rangoStart).toFixed(1)
5837
+ );
5367
5838
  return plugins;
5368
5839
  }
5369
5840
 
@@ -5374,7 +5845,7 @@ function poke() {
5374
5845
  apply: "serve",
5375
5846
  configureServer(server) {
5376
5847
  const stdin = process.stdin;
5377
- const debug = process.env.RANGO_POKE_DEBUG === "1";
5848
+ const debug2 = process.env.RANGO_POKE_DEBUG === "1";
5378
5849
  const triggerReload = (source) => {
5379
5850
  server.hot.send({ type: "full-reload", path: "*" });
5380
5851
  server.config.logger.info(` browser reload (${source})`, {
@@ -5407,7 +5878,7 @@ function poke() {
5407
5878
  lines.pop();
5408
5879
  return lines;
5409
5880
  };
5410
- if (debug) {
5881
+ if (debug2) {
5411
5882
  server.config.logger.info(
5412
5883
  ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
5413
5884
  { timestamp: true }
@@ -5420,7 +5891,7 @@ function poke() {
5420
5891
  );
5421
5892
  }
5422
5893
  const onData = (data) => {
5423
- if (debug) {
5894
+ if (debug2) {
5424
5895
  server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5425
5896
  timestamp: true
5426
5897
  });