@rangojs/router 0.0.0-experimental.8678bb02 → 0.0.0-experimental.87

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 (147) hide show
  1. package/README.md +126 -38
  2. package/dist/bin/rango.js +130 -47
  3. package/dist/vite/index.js +847 -384
  4. package/dist/vite/index.js.bak +5448 -0
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +5 -5
  7. package/skills/breadcrumbs/SKILL.md +3 -1
  8. package/skills/handler-use/SKILL.md +362 -0
  9. package/skills/hooks/SKILL.md +28 -20
  10. package/skills/intercept/SKILL.md +20 -0
  11. package/skills/layout/SKILL.md +22 -0
  12. package/skills/links/SKILL.md +91 -17
  13. package/skills/loader/SKILL.md +35 -2
  14. package/skills/middleware/SKILL.md +34 -3
  15. package/skills/migrate-nextjs/SKILL.md +560 -0
  16. package/skills/migrate-react-router/SKILL.md +765 -0
  17. package/skills/parallel/SKILL.md +59 -0
  18. package/skills/prerender/SKILL.md +110 -68
  19. package/skills/rango/SKILL.md +24 -22
  20. package/skills/response-routes/SKILL.md +8 -0
  21. package/skills/route/SKILL.md +24 -0
  22. package/skills/router-setup/SKILL.md +35 -0
  23. package/skills/streams-and-websockets/SKILL.md +283 -0
  24. package/skills/typesafety/SKILL.md +3 -1
  25. package/src/__internal.ts +1 -1
  26. package/src/browser/app-shell.ts +52 -0
  27. package/src/browser/app-version.ts +14 -0
  28. package/src/browser/navigation-bridge.ts +87 -6
  29. package/src/browser/navigation-client.ts +128 -77
  30. package/src/browser/navigation-store.ts +68 -9
  31. package/src/browser/partial-update.ts +60 -7
  32. package/src/browser/prefetch/cache.ts +129 -21
  33. package/src/browser/prefetch/fetch.ts +156 -18
  34. package/src/browser/prefetch/queue.ts +36 -5
  35. package/src/browser/rango-state.ts +53 -13
  36. package/src/browser/react/Link.tsx +72 -8
  37. package/src/browser/react/NavigationProvider.tsx +57 -11
  38. package/src/browser/react/context.ts +7 -2
  39. package/src/browser/react/use-handle.ts +9 -58
  40. package/src/browser/react/use-navigation.ts +22 -2
  41. package/src/browser/react/use-params.ts +11 -1
  42. package/src/browser/react/use-router.ts +29 -9
  43. package/src/browser/rsc-router.tsx +60 -9
  44. package/src/browser/scroll-restoration.ts +10 -8
  45. package/src/browser/segment-reconciler.ts +36 -14
  46. package/src/browser/server-action-bridge.ts +8 -18
  47. package/src/browser/types.ts +33 -5
  48. package/src/build/generate-manifest.ts +6 -6
  49. package/src/build/generate-route-types.ts +3 -0
  50. package/src/build/route-trie.ts +50 -24
  51. package/src/build/route-types/include-resolution.ts +8 -1
  52. package/src/build/route-types/router-processing.ts +211 -72
  53. package/src/build/route-types/scan-filter.ts +8 -1
  54. package/src/cache/cf/cf-cache-store.ts +5 -7
  55. package/src/client.tsx +84 -230
  56. package/src/deps/browser.ts +0 -1
  57. package/src/handle.ts +40 -0
  58. package/src/index.rsc.ts +6 -1
  59. package/src/index.ts +49 -6
  60. package/src/outlet-context.ts +1 -1
  61. package/src/prerender/store.ts +5 -4
  62. package/src/prerender.ts +138 -77
  63. package/src/response-utils.ts +28 -0
  64. package/src/reverse.ts +27 -2
  65. package/src/route-definition/dsl-helpers.ts +210 -35
  66. package/src/route-definition/helpers-types.ts +61 -14
  67. package/src/route-definition/index.ts +3 -0
  68. package/src/route-definition/redirect.ts +9 -1
  69. package/src/route-definition/resolve-handler-use.ts +155 -0
  70. package/src/route-types.ts +18 -0
  71. package/src/router/content-negotiation.ts +100 -1
  72. package/src/router/handler-context.ts +70 -17
  73. package/src/router/intercept-resolution.ts +9 -4
  74. package/src/router/lazy-includes.ts +6 -6
  75. package/src/router/loader-resolution.ts +153 -21
  76. package/src/router/manifest.ts +22 -13
  77. package/src/router/match-api.ts +127 -192
  78. package/src/router/match-middleware/cache-lookup.ts +28 -8
  79. package/src/router/match-middleware/segment-resolution.ts +53 -0
  80. package/src/router/match-result.ts +82 -4
  81. package/src/router/middleware-types.ts +2 -28
  82. package/src/router/middleware.ts +32 -7
  83. package/src/router/navigation-snapshot.ts +182 -0
  84. package/src/router/pattern-matching.ts +60 -9
  85. package/src/router/prerender-match.ts +110 -10
  86. package/src/router/preview-match.ts +30 -102
  87. package/src/router/request-classification.ts +310 -0
  88. package/src/router/route-snapshot.ts +245 -0
  89. package/src/router/router-interfaces.ts +36 -4
  90. package/src/router/router-options.ts +37 -11
  91. package/src/router/segment-resolution/fresh.ts +70 -5
  92. package/src/router/segment-resolution/revalidation.ts +87 -9
  93. package/src/router/trie-matching.ts +10 -4
  94. package/src/router/url-params.ts +49 -0
  95. package/src/router.ts +54 -7
  96. package/src/rsc/handler.ts +478 -399
  97. package/src/rsc/helpers.ts +69 -41
  98. package/src/rsc/loader-fetch.ts +18 -3
  99. package/src/rsc/manifest-init.ts +5 -1
  100. package/src/rsc/progressive-enhancement.ts +14 -3
  101. package/src/rsc/response-route-handler.ts +14 -1
  102. package/src/rsc/rsc-rendering.ts +15 -2
  103. package/src/rsc/server-action.ts +10 -2
  104. package/src/rsc/ssr-setup.ts +2 -2
  105. package/src/rsc/types.ts +6 -4
  106. package/src/segment-content-promise.ts +67 -0
  107. package/src/segment-loader-promise.ts +122 -0
  108. package/src/segment-system.tsx +11 -61
  109. package/src/server/context.ts +65 -5
  110. package/src/server/handle-store.ts +19 -0
  111. package/src/server/loader-registry.ts +9 -8
  112. package/src/server/request-context.ts +142 -55
  113. package/src/ssr/index.tsx +3 -0
  114. package/src/static-handler.ts +18 -6
  115. package/src/types/cache-types.ts +4 -4
  116. package/src/types/handler-context.ts +17 -43
  117. package/src/types/loader-types.ts +37 -11
  118. package/src/types/request-scope.ts +126 -0
  119. package/src/types/route-entry.ts +12 -1
  120. package/src/types/segments.ts +1 -1
  121. package/src/urls/include-helper.ts +24 -14
  122. package/src/urls/path-helper-types.ts +39 -6
  123. package/src/urls/path-helper.ts +47 -12
  124. package/src/urls/pattern-types.ts +12 -0
  125. package/src/urls/response-types.ts +18 -16
  126. package/src/use-loader.tsx +77 -5
  127. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  128. package/src/vite/discovery/discover-routers.ts +5 -1
  129. package/src/vite/discovery/prerender-collection.ts +128 -74
  130. package/src/vite/discovery/state.ts +13 -4
  131. package/src/vite/index.ts +4 -0
  132. package/src/vite/plugin-types.ts +60 -5
  133. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  134. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  135. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  136. package/src/vite/plugins/expose-id-utils.ts +12 -0
  137. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  138. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  139. package/src/vite/plugins/performance-tracks.ts +64 -206
  140. package/src/vite/plugins/refresh-cmd.ts +88 -26
  141. package/src/vite/rango.ts +40 -18
  142. package/src/vite/router-discovery.ts +237 -37
  143. package/src/vite/utils/banner.ts +1 -1
  144. package/src/vite/utils/package-resolution.ts +1 -1
  145. package/src/vite/utils/prerender-utils.ts +37 -5
  146. package/src/vite/utils/shared-utils.ts +3 -2
  147. package/src/browser/debug-channel.ts +0 -93
@@ -9,18 +9,21 @@ import fs from "node:fs";
9
9
 
10
10
  // src/vite/plugins/expose-id-utils.ts
11
11
  import path from "node:path";
12
- import crypto2 from "node:crypto";
12
+ import crypto from "node:crypto";
13
13
  function normalizePath(p) {
14
14
  return p.split(path.sep).join("/");
15
15
  }
16
16
  function hashId(filePath, exportName) {
17
17
  const input = `${filePath}#${exportName}`;
18
- const hash = crypto2.createHash("sha256").update(input).digest("hex");
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
- return crypto2.createHash("sha256").update(input).digest("hex").slice(0, 8);
26
+ return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
24
27
  }
25
28
  function buildExportMap(program) {
26
29
  const exportMap = /* @__PURE__ */ new Map();
@@ -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 {
@@ -1745,7 +1864,7 @@ import { resolve } from "node:path";
1745
1864
  // package.json
1746
1865
  var package_default = {
1747
1866
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.8678bb02",
1867
+ version: "0.0.0-experimental.87",
1749
1868
  description: "Django-inspired RSC router with composable URL patterns",
1750
1869
  keywords: [
1751
1870
  "react",
@@ -1878,16 +1997,16 @@ var package_default = {
1878
1997
  tag: "experimental"
1879
1998
  },
1880
1999
  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",
2000
+ 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
2001
  prepublishOnly: "pnpm build",
1883
- typecheck: "tsc --noEmit",
2002
+ typecheck: "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit",
1884
2003
  test: "playwright test",
1885
2004
  "test:ui": "playwright test --ui",
1886
2005
  "test:unit": "vitest run",
1887
2006
  "test:unit:watch": "vitest"
1888
2007
  },
1889
2008
  dependencies: {
1890
- "@vitejs/plugin-rsc": "^0.5.14",
2009
+ "@vitejs/plugin-rsc": "^0.5.23",
1891
2010
  "magic-string": "^0.30.17",
1892
2011
  picomatch: "^4.0.3",
1893
2012
  "rsc-html-stream": "^0.0.7"
@@ -1907,7 +2026,7 @@ var package_default = {
1907
2026
  },
1908
2027
  peerDependencies: {
1909
2028
  "@cloudflare/vite-plugin": "^1.25.0",
1910
- "@vitejs/plugin-rsc": "^0.5.14",
2029
+ "@vitejs/plugin-rsc": "^0.5.23",
1911
2030
  react: "^18.0.0 || ^19.0.0",
1912
2031
  vite: "^7.3.0"
1913
2032
  },
@@ -2317,7 +2436,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2317
2436
  }
2318
2437
  return routeMap;
2319
2438
  }
2320
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
2439
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
2321
2440
  visited = visited ?? /* @__PURE__ */ new Set();
2322
2441
  const realPath = resolve2(filePath);
2323
2442
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -2333,7 +2452,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2333
2452
  return { routes: {}, searchSchemas: {} };
2334
2453
  }
2335
2454
  let block;
2336
- if (variableName) {
2455
+ if (inlineBlock) {
2456
+ block = inlineBlock;
2457
+ } else if (variableName) {
2337
2458
  const extracted = extractUrlsBlockForVariable(source, variableName);
2338
2459
  if (!extracted) return { routes: {}, searchSchemas: {} };
2339
2460
  block = extracted;
@@ -2452,7 +2573,7 @@ Router root: ${conflict.ancestor}
2452
2573
  Nested router: ${conflict.nested}
2453
2574
  Move the nested router into a sibling directory or configure it as a separate app root.`;
2454
2575
  }
2455
- function extractUrlsVariableFromRouter(code) {
2576
+ function extractUrlsFromRouter(code) {
2456
2577
  const sourceFile = ts5.createSourceFile(
2457
2578
  "router.tsx",
2458
2579
  code,
@@ -2466,24 +2587,70 @@ function extractUrlsVariableFromRouter(code) {
2466
2587
  const callee = node.expression;
2467
2588
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
2468
2589
  }
2590
+ function isInlineBuilder(node) {
2591
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
2592
+ }
2593
+ function isRoutesOnCreateRouter(node) {
2594
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
2595
+ return false;
2596
+ let inner = node.expression.expression;
2597
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2598
+ inner = inner.expression.expression;
2599
+ }
2600
+ return isCreateRouterCall(inner);
2601
+ }
2469
2602
  function visit(node) {
2470
2603
  if (result) return;
2471
- if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
2472
- let inner = node.expression.expression;
2473
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2474
- inner = inner.expression.expression;
2475
- }
2476
- if (isCreateRouterCall(inner)) {
2477
- result = node.arguments[0].text;
2478
- return;
2604
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
2605
+ const arg = node.arguments[0];
2606
+ if (ts5.isIdentifier(arg)) {
2607
+ result = { kind: "variable", name: arg.text };
2608
+ } else if (isInlineBuilder(arg)) {
2609
+ result = { kind: "inline", block: arg.getText(sourceFile) };
2479
2610
  }
2611
+ return;
2480
2612
  }
2481
2613
  if (isCreateRouterCall(node)) {
2482
2614
  const callExpr = node;
2483
- for (const arg of callExpr.arguments) {
2615
+ for (const callArg of callExpr.arguments) {
2616
+ if (ts5.isObjectLiteralExpression(callArg)) {
2617
+ for (const prop of callArg.properties) {
2618
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
2619
+ if (ts5.isIdentifier(prop.initializer)) {
2620
+ result = { kind: "variable", name: prop.initializer.text };
2621
+ } else if (isInlineBuilder(prop.initializer)) {
2622
+ result = {
2623
+ kind: "inline",
2624
+ block: prop.initializer.getText(sourceFile)
2625
+ };
2626
+ }
2627
+ return;
2628
+ }
2629
+ }
2630
+ }
2631
+ }
2632
+ }
2633
+ ts5.forEachChild(node, visit);
2634
+ }
2635
+ visit(sourceFile);
2636
+ return result;
2637
+ }
2638
+ function extractBasenameFromRouter(code) {
2639
+ const sourceFile = ts5.createSourceFile(
2640
+ "router.tsx",
2641
+ code,
2642
+ ts5.ScriptTarget.Latest,
2643
+ true,
2644
+ ts5.ScriptKind.TSX
2645
+ );
2646
+ let result;
2647
+ function visit(node) {
2648
+ if (result !== void 0) return;
2649
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
2650
+ for (const arg of node.arguments) {
2484
2651
  if (ts5.isObjectLiteralExpression(arg)) {
2485
2652
  for (const prop of arg.properties) {
2486
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
2653
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
2487
2654
  result = prop.initializer.text;
2488
2655
  return;
2489
2656
  }
@@ -2496,6 +2663,19 @@ function extractUrlsVariableFromRouter(code) {
2496
2663
  visit(sourceFile);
2497
2664
  return result;
2498
2665
  }
2666
+ function applyBasenameToRoutes(result, basename3) {
2667
+ const prefixed = {};
2668
+ for (const [name, pattern] of Object.entries(result.routes)) {
2669
+ if (pattern === "/") {
2670
+ prefixed[name] = basename3;
2671
+ } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2672
+ prefixed[name] = basename3 + pattern.slice(1);
2673
+ } else {
2674
+ prefixed[name] = basename3 + pattern;
2675
+ }
2676
+ }
2677
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
2678
+ }
2499
2679
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2500
2680
  let routerSource;
2501
2681
  try {
@@ -2503,19 +2683,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2503
2683
  } catch {
2504
2684
  return { routes: {}, searchSchemas: {} };
2505
2685
  }
2506
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2507
- if (!urlsVarName) {
2686
+ const extraction = extractUrlsFromRouter(routerSource);
2687
+ if (!extraction) {
2508
2688
  return { routes: {}, searchSchemas: {} };
2509
2689
  }
2510
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2511
- if (imported) {
2512
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2513
- if (!targetFile) {
2514
- return { routes: {}, searchSchemas: {} };
2690
+ const rawBasename = extractBasenameFromRouter(routerSource);
2691
+ const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2692
+ let result;
2693
+ if (extraction.kind === "inline") {
2694
+ result = buildCombinedRouteMapWithSearch(
2695
+ routerFilePath,
2696
+ void 0,
2697
+ void 0,
2698
+ void 0,
2699
+ extraction.block
2700
+ );
2701
+ } else {
2702
+ const imported = resolveImportedVariable(routerSource, extraction.name);
2703
+ if (imported) {
2704
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2705
+ if (!targetFile) {
2706
+ return { routes: {}, searchSchemas: {} };
2707
+ }
2708
+ result = buildCombinedRouteMapWithSearch(
2709
+ targetFile,
2710
+ imported.exportedName
2711
+ );
2712
+ } else {
2713
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2515
2714
  }
2516
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2517
2715
  }
2518
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2716
+ if (basename3) {
2717
+ result = applyBasenameToRoutes(result, basename3);
2718
+ }
2719
+ return result;
2519
2720
  }
2520
2721
  function findRouterFiles(root, filter) {
2521
2722
  const result = [];
@@ -2540,25 +2741,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2540
2741
  throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
2541
2742
  }
2542
2743
  for (const routerFilePath of routerFilePaths) {
2543
- let routerSource;
2544
- try {
2545
- routerSource = readFileSync2(routerFilePath, "utf-8");
2546
- } catch {
2547
- continue;
2548
- }
2549
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2550
- if (!urlsVarName) continue;
2551
- let result;
2552
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2553
- if (imported) {
2554
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2555
- if (!targetFile) continue;
2556
- result = buildCombinedRouteMapWithSearch(
2557
- targetFile,
2558
- imported.exportedName
2559
- );
2560
- } else {
2561
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2744
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
2745
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
2746
+ let routerSource;
2747
+ try {
2748
+ routerSource = readFileSync2(routerFilePath, "utf-8");
2749
+ } catch {
2750
+ continue;
2751
+ }
2752
+ if (!extractUrlsFromRouter(routerSource)) continue;
2562
2753
  }
2563
2754
  const routerBasename = pathBasename(routerFilePath).replace(
2564
2755
  /\.(tsx?|jsx?)$/,
@@ -2784,6 +2975,68 @@ function createVersionPlugin() {
2784
2975
 
2785
2976
  // src/vite/utils/shared-utils.ts
2786
2977
  import * as Vite from "vite";
2978
+
2979
+ // src/vite/plugins/performance-tracks.ts
2980
+ import { readFile } from "node:fs/promises";
2981
+ var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2982
+ function buildPatchReplacement(match, debugInfoVar) {
2983
+ return `${match}
2984
+ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
2985
+ var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
2986
+ if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
2987
+ ${debugInfoVar} = _resolved._debugInfo;
2988
+ }
2989
+ }`;
2990
+ }
2991
+ function patchRsdwClientDebugInfoRecovery(code) {
2992
+ const match = code.match(RSDW_PATCH_RE);
2993
+ if (!match) {
2994
+ return { code, debugInfoVar: null };
2995
+ }
2996
+ return {
2997
+ code: code.replace(match[1], buildPatchReplacement(match[1], match[2])),
2998
+ debugInfoVar: match[2]
2999
+ };
3000
+ }
3001
+ function performanceTracksOptimizeDepsPlugin() {
3002
+ return {
3003
+ name: "@rangojs/router:performance-tracks-optimize-deps",
3004
+ setup(build) {
3005
+ build.onLoad(
3006
+ {
3007
+ filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
3008
+ },
3009
+ async (args) => {
3010
+ const code = await readFile(args.path, "utf8");
3011
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3012
+ return {
3013
+ contents: patched.code,
3014
+ loader: "js"
3015
+ };
3016
+ }
3017
+ );
3018
+ }
3019
+ };
3020
+ }
3021
+ function performanceTracksPlugin() {
3022
+ return {
3023
+ name: "@rangojs/router:performance-tracks",
3024
+ transform(code, id) {
3025
+ if (!id.includes("react-server-dom") || !id.includes("client")) return;
3026
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3027
+ if (!patched.debugInfoVar) return;
3028
+ if (process.env.INTERNAL_RANGO_DEBUG)
3029
+ console.log(
3030
+ "[perf-tracks] patched RSDW client (var:",
3031
+ patched.debugInfoVar,
3032
+ ")"
3033
+ );
3034
+ return patched.code;
3035
+ }
3036
+ };
3037
+ }
3038
+
3039
+ // src/vite/utils/shared-utils.ts
2787
3040
  var versionEsbuildPlugin = {
2788
3041
  name: "@rangojs/router-version",
2789
3042
  setup(build) {
@@ -2801,7 +3054,7 @@ var versionEsbuildPlugin = {
2801
3054
  }
2802
3055
  };
2803
3056
  var sharedEsbuildOptions = {
2804
- plugins: [versionEsbuildPlugin]
3057
+ plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
2805
3058
  };
2806
3059
  function createVirtualEntriesPlugin(entries, routerPathRef) {
2807
3060
  const virtualModules = {};
@@ -3007,6 +3260,8 @@ function createCjsToEsmPlugin() {
3007
3260
  import { createServer as createViteServer } from "vite";
3008
3261
  import { resolve as resolve8 } from "node:path";
3009
3262
  import { readFileSync as readFileSync6 } from "node:fs";
3263
+ import { createRequire, register } from "node:module";
3264
+ import { pathToFileURL } from "node:url";
3010
3265
 
3011
3266
  // src/vite/plugins/virtual-stub-plugin.ts
3012
3267
  function createVirtualStubPlugin() {
@@ -3032,6 +3287,113 @@ function createVirtualStubPlugin() {
3032
3287
  };
3033
3288
  }
3034
3289
 
3290
+ // src/vite/plugins/cloudflare-protocol-stub.ts
3291
+ var VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
3292
+ var NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
3293
+ var CF_PREFIX = "cloudflare:";
3294
+ var BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
3295
+ var SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
3296
+ var IMPORT_NODE_TYPES = /* @__PURE__ */ new Set([
3297
+ "ImportDeclaration",
3298
+ "ImportExpression",
3299
+ "ExportNamedDeclaration",
3300
+ "ExportAllDeclaration"
3301
+ ]);
3302
+ var STUBS = {
3303
+ "cloudflare:workers": `
3304
+ export class DurableObject { constructor(_ctx, _env) {} }
3305
+ export class WorkerEntrypoint { constructor(_ctx, _env) {} }
3306
+ export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
3307
+ export class RpcTarget {}
3308
+ export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
3309
+ export default {};
3310
+ `,
3311
+ "cloudflare:email": `
3312
+ export class EmailMessage { constructor(_from, _to, _raw) {} }
3313
+ export default {};
3314
+ `,
3315
+ "cloudflare:sockets": `
3316
+ export function connect() { return {}; }
3317
+ export default {};
3318
+ `,
3319
+ "cloudflare:workflows": `
3320
+ export class NonRetryableError extends Error {
3321
+ constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
3322
+ }
3323
+ export default {};
3324
+ `
3325
+ };
3326
+ var FALLBACK_STUB = `export default {};
3327
+ `;
3328
+ function createCloudflareProtocolStubPlugin() {
3329
+ return {
3330
+ name: "@rangojs/router:cloudflare-protocol-stub",
3331
+ transform(code, id) {
3332
+ const cleanId = id.split("?")[0] ?? id;
3333
+ if (!SOURCE_EXT_RE.test(cleanId)) return null;
3334
+ if (!code.includes(CF_PREFIX)) return null;
3335
+ let ast;
3336
+ try {
3337
+ ast = this.parse(code);
3338
+ } catch {
3339
+ return null;
3340
+ }
3341
+ const hits = [];
3342
+ walk(ast, (node) => {
3343
+ if (!IMPORT_NODE_TYPES.has(node.type)) return;
3344
+ const source = node.source;
3345
+ if (!source || source.type !== "Literal") return;
3346
+ if (typeof source.value !== "string") return;
3347
+ if (!source.value.startsWith(CF_PREFIX)) return;
3348
+ if (typeof source.start !== "number" || typeof source.end !== "number")
3349
+ return;
3350
+ hits.push({
3351
+ start: source.start,
3352
+ end: source.end,
3353
+ value: source.value
3354
+ });
3355
+ });
3356
+ if (hits.length === 0) return null;
3357
+ hits.sort((a, b) => b.start - a.start);
3358
+ let out = code;
3359
+ for (const hit of hits) {
3360
+ const submodule = hit.value.slice(CF_PREFIX.length);
3361
+ const quote = code[hit.start] === "'" ? "'" : '"';
3362
+ out = out.slice(0, hit.start) + quote + VIRTUAL_PREFIX + submodule + quote + out.slice(hit.end);
3363
+ }
3364
+ return { code: out, map: null };
3365
+ },
3366
+ resolveId(id) {
3367
+ if (id.startsWith(VIRTUAL_PREFIX)) {
3368
+ return "\0" + id;
3369
+ }
3370
+ return null;
3371
+ },
3372
+ load(id) {
3373
+ if (!id.startsWith(NULL_PREFIX)) return null;
3374
+ const submodule = id.slice(NULL_PREFIX.length);
3375
+ const specifier = CF_PREFIX + submodule;
3376
+ return STUBS[specifier] ?? FALLBACK_STUB;
3377
+ }
3378
+ };
3379
+ }
3380
+ function walk(node, visit) {
3381
+ if (!node || typeof node !== "object") return;
3382
+ if (Array.isArray(node)) {
3383
+ for (const child of node) walk(child, visit);
3384
+ return;
3385
+ }
3386
+ const n = node;
3387
+ if (typeof n.type !== "string") return;
3388
+ visit(n);
3389
+ for (const key in n) {
3390
+ if (key === "loc" || key === "start" || key === "end" || key === "range") {
3391
+ continue;
3392
+ }
3393
+ walk(n[key], visit);
3394
+ }
3395
+ }
3396
+
3035
3397
  // src/vite/plugins/client-ref-hashing.ts
3036
3398
  import { relative } from "node:path";
3037
3399
  import { createHash as createHash2 } from "node:crypto";
@@ -3190,8 +3552,8 @@ function createDiscoveryState(entryPath, opts) {
3190
3552
  perRouterManifestDataMap: /* @__PURE__ */ new Map(),
3191
3553
  prerenderManifestEntries: null,
3192
3554
  staticManifestEntries: null,
3193
- handlerChunkInfo: null,
3194
- staticHandlerChunkInfo: null,
3555
+ handlerChunkInfoMap: /* @__PURE__ */ new Map(),
3556
+ staticHandlerChunkInfoMap: /* @__PURE__ */ new Map(),
3195
3557
  rscEntryFileName: null,
3196
3558
  resolvedPrerenderModules: void 0,
3197
3559
  resolvedStaticModules: void 0,
@@ -3322,13 +3684,31 @@ function encodePathParam(value) {
3322
3684
  }
3323
3685
  function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3324
3686
  let result = pattern;
3687
+ let hadOmittedOptional = false;
3325
3688
  for (const [key, value] of Object.entries(params)) {
3326
3689
  const escaped = escapeRegExp2(key);
3327
- result = result.replace(
3328
- new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3329
- encode(value)
3330
- );
3331
- result = result.replace(`*${key}`, encode(value));
3690
+ if (value === "") {
3691
+ result = result.replace(
3692
+ new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
3693
+ ""
3694
+ );
3695
+ result = result.replace(`*${key}`, "");
3696
+ } else {
3697
+ result = result.replace(
3698
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3699
+ encode(value)
3700
+ );
3701
+ result = result.replace(`*${key}`, encode(value));
3702
+ }
3703
+ }
3704
+ result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3705
+ hadOmittedOptional = true;
3706
+ return "";
3707
+ });
3708
+ if (hadOmittedOptional) {
3709
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
3710
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
3711
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
3332
3712
  }
3333
3713
  return result;
3334
3714
  }
@@ -3438,84 +3818,126 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3438
3818
  if (!params) return pattern;
3439
3819
  return substituteRouteParams(pattern, params);
3440
3820
  };
3821
+ let resolvedRoutes = 0;
3822
+ let totalDynamic = 0;
3441
3823
  for (const { manifest } of allManifests) {
3442
3824
  if (!manifest.prerenderRoutes) continue;
3443
- const defs = manifest._prerenderDefs || {};
3444
3825
  for (const routeName of manifest.prerenderRoutes) {
3445
3826
  const pattern = manifest.routeManifest[routeName];
3446
- if (!pattern) continue;
3447
- const def = defs[routeName];
3448
- const isPassthroughRoute = !!def?.options?.passthrough;
3449
- const hasDynamic = pattern.includes(":") || pattern.includes("*");
3450
- if (!hasDynamic) {
3451
- entries.push({
3452
- urlPath: pattern.replace(/\/$/, "") || "/",
3453
- routeName,
3454
- concurrency: 1,
3455
- isPassthroughRoute
3456
- });
3457
- } else {
3458
- if (def?.getParams) {
3459
- try {
3460
- const buildVars = {};
3461
- const getParamsCtx = {
3462
- build: true,
3463
- set: ((keyOrVar, value) => {
3464
- contextSet(buildVars, keyOrVar, value);
3465
- }),
3466
- reverse: getParamsReverse
3467
- };
3468
- const paramsList = await def.getParams(getParamsCtx);
3469
- const concurrency = def.options?.concurrency ?? 1;
3470
- const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3471
- for (const params of paramsList) {
3472
- let url = substituteRouteParams(
3473
- pattern,
3474
- params,
3475
- encodePathParam
3476
- );
3477
- if (url.includes("*")) {
3478
- const wildcardValue = params["*"] ?? params.splat;
3479
- if (wildcardValue !== void 0) {
3480
- url = url.replace(/\*[^/]*$/, encodePathParam(wildcardValue));
3827
+ if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
3828
+ totalDynamic++;
3829
+ }
3830
+ }
3831
+ }
3832
+ const paramsStart = performance.now();
3833
+ const progressInterval = totalDynamic > 0 ? setInterval(() => {
3834
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3835
+ console.log(
3836
+ `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3837
+ );
3838
+ }, 5e3) : void 0;
3839
+ try {
3840
+ for (const { manifest } of allManifests) {
3841
+ if (!manifest.prerenderRoutes) continue;
3842
+ const defs = manifest._prerenderDefs || {};
3843
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
3844
+ for (const routeName of manifest.prerenderRoutes) {
3845
+ const pattern = manifest.routeManifest[routeName];
3846
+ if (!pattern) continue;
3847
+ const def = defs[routeName];
3848
+ const isPassthroughRoute = passthroughSet.has(routeName);
3849
+ const hasDynamic = pattern.includes(":") || pattern.includes("*");
3850
+ if (!hasDynamic) {
3851
+ entries.push({
3852
+ urlPath: pattern.replace(/\/$/, "") || "/",
3853
+ routeName,
3854
+ concurrency: 1,
3855
+ isPassthroughRoute
3856
+ });
3857
+ } else {
3858
+ if (def?.getParams) {
3859
+ try {
3860
+ const buildVars = {};
3861
+ const buildEnv = state.resolvedBuildEnv;
3862
+ const getParamsCtx = {
3863
+ build: true,
3864
+ dev: !state.isBuildMode,
3865
+ set: ((keyOrVar, value) => {
3866
+ contextSet(buildVars, keyOrVar, value);
3867
+ }),
3868
+ reverse: getParamsReverse,
3869
+ get env() {
3870
+ if (buildEnv !== void 0) return buildEnv;
3871
+ throw new Error(
3872
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3873
+ );
3874
+ }
3875
+ };
3876
+ const paramsList = await def.getParams(getParamsCtx);
3877
+ const concurrency = def.options?.concurrency ?? 1;
3878
+ const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3879
+ for (const params of paramsList) {
3880
+ let url = substituteRouteParams(
3881
+ pattern,
3882
+ params,
3883
+ encodePathParam
3884
+ );
3885
+ if (url.includes("*")) {
3886
+ const wildcardValue = params["*"] ?? params.splat;
3887
+ if (wildcardValue !== void 0) {
3888
+ url = url.replace(
3889
+ /\*[^/]*$/,
3890
+ encodePathParam(wildcardValue)
3891
+ );
3892
+ }
3481
3893
  }
3894
+ entries.push({
3895
+ urlPath: url.replace(/\/$/, "") || "/",
3896
+ routeName,
3897
+ concurrency,
3898
+ ...hasBuildVars ? { buildVars } : {},
3899
+ isPassthroughRoute
3900
+ });
3482
3901
  }
3483
- entries.push({
3484
- urlPath: url.replace(/\/$/, "") || "/",
3485
- routeName,
3486
- concurrency,
3487
- ...hasBuildVars ? { buildVars } : {},
3488
- isPassthroughRoute
3489
- });
3490
- }
3491
- } catch (err) {
3492
- if (err.name === "Skip") {
3493
- console.log(
3494
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3495
- );
3496
- notifyOnError(
3497
- registry,
3498
- err,
3499
- "prerender",
3500
- routeName,
3501
- void 0,
3502
- true
3902
+ resolvedRoutes++;
3903
+ } catch (err) {
3904
+ resolvedRoutes++;
3905
+ if (err.name === "Skip") {
3906
+ console.log(
3907
+ `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3908
+ );
3909
+ notifyOnError(
3910
+ registry,
3911
+ err,
3912
+ "prerender",
3913
+ routeName,
3914
+ void 0,
3915
+ true
3916
+ );
3917
+ continue;
3918
+ }
3919
+ console.error(
3920
+ `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3503
3921
  );
3504
- continue;
3922
+ notifyOnError(registry, err, "prerender", routeName);
3923
+ throw err;
3505
3924
  }
3506
- console.error(
3507
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3925
+ } else {
3926
+ console.warn(
3927
+ `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3508
3928
  );
3509
- notifyOnError(registry, err, "prerender", routeName);
3510
- throw err;
3511
3929
  }
3512
- } else {
3513
- console.warn(
3514
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3515
- );
3516
3930
  }
3517
3931
  }
3518
3932
  }
3933
+ } finally {
3934
+ if (progressInterval) {
3935
+ clearInterval(progressInterval);
3936
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3937
+ console.log(
3938
+ `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3939
+ );
3940
+ }
3519
3941
  }
3520
3942
  if (entries.length === 0) return;
3521
3943
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
@@ -3542,7 +3964,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3542
3964
  entry.urlPath,
3543
3965
  {},
3544
3966
  entry.buildVars,
3545
- entry.isPassthroughRoute
3967
+ entry.isPassthroughRoute,
3968
+ state.resolvedBuildEnv
3546
3969
  );
3547
3970
  if (!result) continue;
3548
3971
  if (result.passthrough) {
@@ -3666,7 +4089,9 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3666
4089
  const result = await routerInstance.renderStaticSegment(
3667
4090
  def.handler,
3668
4091
  def.$$id,
3669
- def.$$routePrefix
4092
+ def.$$routePrefix,
4093
+ state.resolvedBuildEnv,
4094
+ !state.isBuildMode
3670
4095
  );
3671
4096
  if (result) {
3672
4097
  const hasHandles = Object.keys(result.handles).length > 0;
@@ -3791,7 +4216,11 @@ async function discoverRouters(state, rscEnv) {
3791
4216
  if (!router.urlpatterns || !generateManifestFull) {
3792
4217
  continue;
3793
4218
  }
3794
- const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
4219
+ const manifest = generateManifestFull(
4220
+ router.urlpatterns,
4221
+ routerMountIndex,
4222
+ router.__basename ? { urlPrefix: router.__basename } : void 0
4223
+ );
3795
4224
  routerMountIndex++;
3796
4225
  allManifests.push({ id, manifest });
3797
4226
  const routeCount = Object.keys(manifest.routeManifest).length;
@@ -4240,48 +4669,45 @@ function postprocessBundle(state) {
4240
4669
  );
4241
4670
  const evictionTargets = [
4242
4671
  {
4243
- info: state.handlerChunkInfo,
4672
+ infos: state.handlerChunkInfoMap.values(),
4244
4673
  fnName: "Prerender",
4245
4674
  brand: "prerenderHandler",
4246
4675
  label: "handler code from RSC bundle"
4247
4676
  },
4248
4677
  {
4249
- info: state.staticHandlerChunkInfo,
4678
+ infos: state.staticHandlerChunkInfoMap.values(),
4250
4679
  fnName: "Static",
4251
4680
  brand: "staticHandler",
4252
4681
  label: "static handler code"
4253
4682
  }
4254
4683
  ];
4255
4684
  for (const target of evictionTargets) {
4256
- if (!target.info) continue;
4257
- const chunkPath = resolve7(
4258
- state.projectRoot,
4259
- "dist/rsc",
4260
- target.info.fileName
4261
- );
4262
- try {
4263
- const code = readFileSync5(chunkPath, "utf-8");
4264
- const result = evictHandlerCode(
4265
- code,
4266
- target.info.exports,
4267
- target.fnName,
4268
- target.brand
4269
- );
4270
- if (result) {
4271
- writeFileSync4(chunkPath, result.code);
4272
- const savedKB = (result.savedBytes / 1024).toFixed(1);
4273
- console.log(
4274
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${target.info.fileName}`
4685
+ for (const info of target.infos) {
4686
+ const chunkPath = resolve7(state.projectRoot, "dist/rsc", info.fileName);
4687
+ try {
4688
+ const code = readFileSync5(chunkPath, "utf-8");
4689
+ const result = evictHandlerCode(
4690
+ code,
4691
+ info.exports,
4692
+ target.fnName,
4693
+ target.brand
4694
+ );
4695
+ if (result) {
4696
+ writeFileSync4(chunkPath, result.code);
4697
+ const savedKB = (result.savedBytes / 1024).toFixed(1);
4698
+ console.log(
4699
+ `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4700
+ );
4701
+ }
4702
+ } catch (replaceErr) {
4703
+ console.warn(
4704
+ `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4275
4705
  );
4276
4706
  }
4277
- } catch (replaceErr) {
4278
- console.warn(
4279
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4280
- );
4281
4707
  }
4282
4708
  }
4283
- state.handlerChunkInfo = null;
4284
- state.staticHandlerChunkInfo = null;
4709
+ state.handlerChunkInfoMap.clear();
4710
+ state.staticHandlerChunkInfoMap.clear();
4285
4711
  if (hasPrerenderData && existsSync6(rscEntryPath)) {
4286
4712
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4287
4713
  if (!rscCode.includes("__prerender-manifest.js")) {
@@ -4324,7 +4750,7 @@ function postprocessBundle(state) {
4324
4750
  }
4325
4751
  if (hasStaticData && existsSync6(rscEntryPath)) {
4326
4752
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4327
- if (!rscCode.includes("__STATIC_MANIFEST")) {
4753
+ if (!rscCode.includes("__static-manifest.js")) {
4328
4754
  try {
4329
4755
  const manifestEntries = [];
4330
4756
  let totalBytes = copyStagedBuildAssets(
@@ -4363,7 +4789,22 @@ function postprocessBundle(state) {
4363
4789
  }
4364
4790
 
4365
4791
  // src/vite/router-discovery.ts
4792
+ var loaderHookRegistered = false;
4793
+ function ensureCloudflareProtocolLoaderRegistered() {
4794
+ if (loaderHookRegistered) return;
4795
+ loaderHookRegistered = true;
4796
+ try {
4797
+ register(
4798
+ new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url)
4799
+ );
4800
+ } catch (err) {
4801
+ console.warn(
4802
+ `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
4803
+ );
4804
+ }
4805
+ }
4366
4806
  async function createTempRscServer(state, options = {}) {
4807
+ ensureCloudflareProtocolLoaderRegistered();
4367
4808
  const { default: rsc } = await import("@vitejs/plugin-rsc");
4368
4809
  return createViteServer({
4369
4810
  root: state.projectRoot,
@@ -4386,6 +4827,7 @@ async function createTempRscServer(state, options = {}) {
4386
4827
  ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
4387
4828
  createVersionPlugin(),
4388
4829
  createVirtualStubPlugin(),
4830
+ createCloudflareProtocolStubPlugin(),
4389
4831
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
4390
4832
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
4391
4833
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
@@ -4393,8 +4835,69 @@ async function createTempRscServer(state, options = {}) {
4393
4835
  ]
4394
4836
  });
4395
4837
  }
4838
+ async function resolveBuildEnv(option, factoryCtx) {
4839
+ if (!option) return null;
4840
+ if (option === "auto") {
4841
+ if (factoryCtx.preset !== "cloudflare") {
4842
+ throw new Error(
4843
+ '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
4844
+ );
4845
+ }
4846
+ try {
4847
+ const userRequire = createRequire(
4848
+ resolve8(factoryCtx.root, "package.json")
4849
+ );
4850
+ const wranglerPath = userRequire.resolve("wrangler");
4851
+ const { getPlatformProxy } = await import(pathToFileURL(wranglerPath).href);
4852
+ const proxy = await getPlatformProxy();
4853
+ return {
4854
+ env: proxy.env,
4855
+ dispose: proxy.dispose
4856
+ };
4857
+ } catch (err) {
4858
+ throw new Error(
4859
+ `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
4860
+ Install it with: pnpm add -D wrangler
4861
+ ${err.message}`
4862
+ );
4863
+ }
4864
+ }
4865
+ if (typeof option === "function") {
4866
+ return await option(factoryCtx);
4867
+ }
4868
+ return { env: option };
4869
+ }
4870
+ async function acquireBuildEnv(s, command, mode) {
4871
+ const option = s.opts?.buildEnv;
4872
+ if (!option) return false;
4873
+ const result = await resolveBuildEnv(option, {
4874
+ root: s.projectRoot,
4875
+ mode,
4876
+ command,
4877
+ preset: s.opts?.preset ?? "node"
4878
+ });
4879
+ if (!result) return false;
4880
+ s.resolvedBuildEnv = result.env;
4881
+ s.buildEnvDispose = result.dispose ?? null;
4882
+ globalThis[BUILD_ENV_GLOBAL_KEY] = result.env;
4883
+ return true;
4884
+ }
4885
+ async function releaseBuildEnv(s) {
4886
+ if (s.buildEnvDispose) {
4887
+ try {
4888
+ await s.buildEnvDispose();
4889
+ } catch (err) {
4890
+ console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
4891
+ }
4892
+ s.buildEnvDispose = null;
4893
+ }
4894
+ s.resolvedBuildEnv = void 0;
4895
+ delete globalThis[BUILD_ENV_GLOBAL_KEY];
4896
+ }
4396
4897
  function createRouterDiscoveryPlugin(entryPath, opts) {
4397
4898
  const s = createDiscoveryState(entryPath, opts);
4899
+ let viteCommand = "build";
4900
+ let viteMode = "production";
4398
4901
  return {
4399
4902
  name: "@rangojs/router:discovery",
4400
4903
  config() {
@@ -4403,31 +4906,13 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4403
4906
  __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG)
4404
4907
  }
4405
4908
  };
4406
- if (opts?.enableBuildPrerender) {
4407
- config.environments = {
4408
- rsc: {
4409
- build: {
4410
- rollupOptions: {
4411
- output: {
4412
- manualChunks(id) {
4413
- if (s.resolvedPrerenderModules?.has(id)) {
4414
- return "__prerender-handlers";
4415
- }
4416
- if (s.resolvedStaticModules?.has(id)) {
4417
- return "__static-handlers";
4418
- }
4419
- }
4420
- }
4421
- }
4422
- }
4423
- }
4424
- };
4425
- }
4426
4909
  return config;
4427
4910
  },
4428
4911
  configResolved(config) {
4429
4912
  s.projectRoot = config.root;
4430
4913
  s.isBuildMode = config.command === "build";
4914
+ viteCommand = config.command;
4915
+ viteMode = config.mode;
4431
4916
  s.userResolveAlias = config.resolve.alias;
4432
4917
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
4433
4918
  s.resolvedEntryPath = opts.routerPathRef.path;
@@ -4472,6 +4957,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4472
4957
  });
4473
4958
  prerenderTempServer = null;
4474
4959
  }
4960
+ releaseBuildEnv(s).catch(() => {
4961
+ });
4475
4962
  });
4476
4963
  async function getOrCreateTempServer() {
4477
4964
  if (prerenderNodeRegistry) {
@@ -4502,6 +4989,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4502
4989
  if (!rscEnv?.runner) {
4503
4990
  s.devServerOrigin = getDevServerOrigin();
4504
4991
  try {
4992
+ await acquireBuildEnv(s, viteCommand, viteMode);
4505
4993
  const tempRscEnv = await getOrCreateTempServer();
4506
4994
  if (tempRscEnv) {
4507
4995
  await discoverRouters(s, tempRscEnv);
@@ -4517,6 +5005,7 @@ ${err.stack}`
4517
5005
  return;
4518
5006
  }
4519
5007
  try {
5008
+ await acquireBuildEnv(s, viteCommand, viteMode);
4520
5009
  const serverMod = await rscEnv.runner.import(
4521
5010
  "@rangojs/router/server"
4522
5011
  );
@@ -4581,7 +5070,26 @@ ${err.stack}`
4581
5070
  res.end("Missing pathname");
4582
5071
  return;
4583
5072
  }
4584
- let registry = mainRegistry;
5073
+ const rscEnv = server.environments?.rsc;
5074
+ let registry = null;
5075
+ if (rscEnv?.runner && s.resolvedEntryPath) {
5076
+ try {
5077
+ await rscEnv.runner.import(s.resolvedEntryPath);
5078
+ const serverMod = await rscEnv.runner.import(
5079
+ "@rangojs/router/server"
5080
+ );
5081
+ registry = serverMod.RouterRegistry ?? null;
5082
+ } catch (err) {
5083
+ console.warn(
5084
+ `[rsc-router] Dev prerender module refresh failed: ${err.message}`
5085
+ );
5086
+ res.statusCode = 500;
5087
+ res.end(`Prerender handler error: ${err.message}`);
5088
+ return;
5089
+ }
5090
+ } else {
5091
+ registry = mainRegistry;
5092
+ }
4585
5093
  if (!registry) {
4586
5094
  if (!prerenderNodeRegistry) {
4587
5095
  await getOrCreateTempServer();
@@ -4603,7 +5111,10 @@ ${err.stack}`
4603
5111
  pathname,
4604
5112
  {},
4605
5113
  void 0,
4606
- wantPassthrough
5114
+ wantPassthrough,
5115
+ s.resolvedBuildEnv,
5116
+ true
5117
+ // devMode: check getParams for passthrough routes
4607
5118
  );
4608
5119
  if (!result) continue;
4609
5120
  if (result.passthrough) continue;
@@ -4739,6 +5250,7 @@ ${err.stack}`
4739
5250
  resetStagedBuildAssets(s.projectRoot);
4740
5251
  s.prerenderManifestEntries = null;
4741
5252
  s.staticManifestEntries = null;
5253
+ await acquireBuildEnv(s, viteCommand, viteMode);
4742
5254
  let tempServer = null;
4743
5255
  globalThis.__rscRouterDiscoveryActive = true;
4744
5256
  try {
@@ -4778,6 +5290,7 @@ ${details}`
4778
5290
  if (tempServer) {
4779
5291
  await tempServer.close();
4780
5292
  }
5293
+ await releaseBuildEnv(s);
4781
5294
  }
4782
5295
  },
4783
5296
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -4820,20 +5333,30 @@ ${details}`
4820
5333
  }
4821
5334
  if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
4822
5335
  return;
5336
+ s.handlerChunkInfoMap.clear();
5337
+ s.staticHandlerChunkInfoMap.clear();
4823
5338
  for (const [fileName, chunk] of Object.entries(bundle)) {
4824
5339
  if (chunk.type !== "chunk") continue;
4825
- if (fileName.includes("__prerender-handlers") && s.resolvedPrerenderModules?.size) {
5340
+ if (s.resolvedPrerenderModules?.size) {
4826
5341
  const handlers = extractHandlerExportsFromChunk(
4827
5342
  chunk.code,
4828
5343
  s.resolvedPrerenderModules,
4829
5344
  "Prerender",
4830
- true
5345
+ false
4831
5346
  );
4832
5347
  if (handlers.length > 0) {
4833
- s.handlerChunkInfo = { fileName, exports: handlers };
5348
+ const existing = s.handlerChunkInfoMap.get(fileName);
5349
+ if (existing) {
5350
+ existing.exports.push(...handlers);
5351
+ } else {
5352
+ s.handlerChunkInfoMap.set(fileName, {
5353
+ fileName,
5354
+ exports: handlers
5355
+ });
5356
+ }
4834
5357
  }
4835
5358
  }
4836
- if (fileName.includes("__static-handlers") && s.resolvedStaticModules?.size) {
5359
+ if (s.resolvedStaticModules?.size) {
4837
5360
  const handlers = extractHandlerExportsFromChunk(
4838
5361
  chunk.code,
4839
5362
  s.resolvedStaticModules,
@@ -4841,7 +5364,15 @@ ${details}`
4841
5364
  false
4842
5365
  );
4843
5366
  if (handlers.length > 0) {
4844
- s.staticHandlerChunkInfo = { fileName, exports: handlers };
5367
+ const existing = s.staticHandlerChunkInfoMap.get(fileName);
5368
+ if (existing) {
5369
+ existing.exports.push(...handlers);
5370
+ } else {
5371
+ s.staticHandlerChunkInfoMap.set(fileName, {
5372
+ fileName,
5373
+ exports: handlers
5374
+ });
5375
+ }
4845
5376
  }
4846
5377
  }
4847
5378
  }
@@ -4861,144 +5392,25 @@ ${details}`
4861
5392
  };
4862
5393
  }
4863
5394
 
4864
- // src/vite/plugins/performance-tracks.ts
4865
- import { Module } from "node:module";
4866
- var DEBUG_ID_HEADER = "X-RSC-Debug-Id";
4867
- var DEBUG_S2C_EVENT = "rango:perf-s2c";
4868
- var DEBUG_C2S_EVENT = "rango:perf-c2s";
4869
- var GLOBAL_KEY = "__RANGO_DEBUG_CHANNELS__";
4870
- function getRegistry() {
4871
- return Module[GLOBAL_KEY] ??= {
4872
- channels: /* @__PURE__ */ new Map(),
4873
- sessions: /* @__PURE__ */ new Map()
4874
- };
4875
- }
4876
- var bytesToBase64 = (bytes) => Buffer.from(bytes).toString("base64");
4877
- var base64ToBytes = (base64) => new Uint8Array(Buffer.from(base64, "base64"));
4878
- function performanceTracksPlugin() {
4879
- return {
4880
- name: "@rangojs/router:performance-tracks",
4881
- // Only configureServer hook — naturally dev-only
4882
- configureServer(server) {
4883
- console.log("[perf-tracks] plugin loaded, configureServer called");
4884
- const hot = server.environments.client.hot;
4885
- const registry = getRegistry();
4886
- const sessions = registry.sessions;
4887
- const sendChunk = (debugId, chunk) => {
4888
- hot.send(DEBUG_S2C_EVENT, {
4889
- i: debugId,
4890
- b: bytesToBase64(chunk)
4891
- });
4892
- };
4893
- const cleanupIfEnded = (debugId, session) => {
4894
- if (session.pendingChunks || !session.ended) return;
4895
- sessions.delete(debugId);
4896
- hot.send(DEBUG_S2C_EVENT, {
4897
- i: debugId,
4898
- d: true
4899
- });
4900
- };
4901
- const registerDebugChannel = (debugId) => {
4902
- let session = sessions.get(debugId);
4903
- if (!session) {
4904
- session = { pendingChunks: [], ended: false };
4905
- sessions.set(debugId, session);
4906
- }
4907
- const readable = new ReadableStream({
4908
- start(controller) {
4909
- session.cmdController = controller;
4910
- },
4911
- cancel() {
4912
- delete session.cmdController;
4913
- }
4914
- });
4915
- const writable = new WritableStream({
4916
- write(chunk) {
4917
- if (session.pendingChunks) {
4918
- session.pendingChunks.push(chunk);
4919
- } else {
4920
- sendChunk(debugId, chunk);
4921
- }
4922
- },
4923
- close() {
4924
- session.ended = true;
4925
- cleanupIfEnded(debugId, session);
4926
- },
4927
- abort() {
4928
- session.ended = true;
4929
- cleanupIfEnded(debugId, session);
4930
- }
4931
- });
4932
- registry.channels.set(debugId, { readable, writable });
4933
- };
4934
- hot.on(DEBUG_C2S_EVENT, (raw) => {
4935
- const payload = raw;
4936
- const session = sessions.get(payload.i);
4937
- if (payload.d) {
4938
- if (session?.cmdController) {
4939
- try {
4940
- session.cmdController.close();
4941
- } catch {
4942
- }
4943
- delete session.cmdController;
4944
- }
4945
- return;
4946
- }
4947
- if (payload.b) {
4948
- if (session?.cmdController) {
4949
- try {
4950
- session.cmdController.enqueue(base64ToBytes(payload.b));
4951
- } catch {
4952
- delete session.cmdController;
4953
- }
4954
- }
4955
- return;
4956
- }
4957
- if (session) {
4958
- if (session.pendingChunks) {
4959
- for (const chunk of session.pendingChunks) {
4960
- sendChunk(payload.i, chunk);
4961
- }
4962
- delete session.pendingChunks;
4963
- }
4964
- cleanupIfEnded(payload.i, session);
4965
- } else {
4966
- sessions.set(payload.i, { ended: false });
4967
- }
4968
- });
4969
- server.middlewares.use((req, _res, next) => {
4970
- const existingId = req.headers[DEBUG_ID_HEADER.toLowerCase()];
4971
- const debugId = existingId || crypto.randomUUID();
4972
- if (!existingId) {
4973
- const lowerName = DEBUG_ID_HEADER.toLowerCase();
4974
- req.headers[lowerName] = debugId;
4975
- if (req.rawHeaders) {
4976
- req.rawHeaders.push(DEBUG_ID_HEADER, debugId);
4977
- }
4978
- }
4979
- registerDebugChannel(debugId);
4980
- console.log(
4981
- "[perf-tracks] middleware: channel for",
4982
- debugId,
4983
- "url:",
4984
- req.url?.slice(0, 60),
4985
- existingId ? "(client)" : "(server-generated)"
4986
- );
4987
- next();
4988
- });
4989
- }
4990
- };
4991
- }
4992
-
4993
5395
  // src/vite/rango.ts
4994
5396
  async function rango(options) {
4995
5397
  const resolvedOptions = options ?? { preset: "node" };
4996
5398
  const preset = resolvedOptions.preset ?? "node";
4997
- console.log("[perf-tracks] rango() called, preset:", preset);
4998
5399
  const showBanner = resolvedOptions.banner ?? true;
4999
5400
  const plugins = [];
5000
5401
  const rangoAliases = getPackageAliases();
5001
- const excludeDeps = getExcludeDeps();
5402
+ const excludeDeps = [
5403
+ ...getExcludeDeps(),
5404
+ // The public browser entry re-exports the RSDW browser client.
5405
+ // Excluding both keeps Vite from freezing the unpatched bundle into
5406
+ // .vite/deps before our source transforms run.
5407
+ "@vitejs/plugin-rsc/browser",
5408
+ // Keep the browser RSDW client out of Vite's dep optimizer so our
5409
+ // cjs-to-esm transform can patch the real file.
5410
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5411
+ ];
5412
+ const pkg = getPublishedPackageName();
5413
+ const nested = (spec) => `${pkg} > ${spec}`;
5002
5414
  const routerRef = { path: void 0 };
5003
5415
  const prerenderEnabled = true;
5004
5416
  if (preset === "cloudflare") {
@@ -5036,7 +5448,7 @@ async function rango(options) {
5036
5448
  // Pre-bundle rsc-html-stream to prevent discovery during first request
5037
5449
  // Exclude rsc-router modules to ensure same Context instance
5038
5450
  optimizeDeps: {
5039
- include: ["rsc-html-stream/client"],
5451
+ include: [nested("rsc-html-stream/client")],
5040
5452
  exclude: excludeDeps,
5041
5453
  esbuildOptions: sharedEsbuildOptions
5042
5454
  }
@@ -5061,8 +5473,10 @@ async function rango(options) {
5061
5473
  "react-dom/static.edge",
5062
5474
  "react/jsx-runtime",
5063
5475
  "react/jsx-dev-runtime",
5064
- "rsc-html-stream/server",
5065
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5476
+ nested("rsc-html-stream/server"),
5477
+ nested(
5478
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5479
+ )
5066
5480
  ],
5067
5481
  exclude: excludeDeps,
5068
5482
  esbuildOptions: sharedEsbuildOptions
@@ -5077,7 +5491,9 @@ async function rango(options) {
5077
5491
  "react",
5078
5492
  "react/jsx-runtime",
5079
5493
  "react/jsx-dev-runtime",
5080
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5494
+ nested(
5495
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5496
+ )
5081
5497
  ],
5082
5498
  exclude: excludeDeps,
5083
5499
  esbuildOptions: sharedEsbuildOptions
@@ -5094,6 +5510,7 @@ async function rango(options) {
5094
5510
  }
5095
5511
  });
5096
5512
  plugins.push(createVirtualEntriesPlugin(finalEntries));
5513
+ plugins.push(performanceTracksPlugin());
5097
5514
  plugins.push(
5098
5515
  rsc({
5099
5516
  entries: finalEntries,
@@ -5157,7 +5574,7 @@ ${list}`);
5157
5574
  "react-dom",
5158
5575
  "react/jsx-runtime",
5159
5576
  "react/jsx-dev-runtime",
5160
- "rsc-html-stream/client"
5577
+ nested("rsc-html-stream/client")
5161
5578
  ],
5162
5579
  exclude: excludeDeps,
5163
5580
  esbuildOptions: sharedEsbuildOptions,
@@ -5174,7 +5591,9 @@ ${list}`);
5174
5591
  "react-dom/static.edge",
5175
5592
  "react/jsx-runtime",
5176
5593
  "react/jsx-dev-runtime",
5177
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5594
+ nested(
5595
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5596
+ )
5178
5597
  ],
5179
5598
  exclude: excludeDeps,
5180
5599
  esbuildOptions: sharedEsbuildOptions
@@ -5187,7 +5606,9 @@ ${list}`);
5187
5606
  "react",
5188
5607
  "react/jsx-runtime",
5189
5608
  "react/jsx-dev-runtime",
5190
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5609
+ nested(
5610
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5611
+ )
5191
5612
  ],
5192
5613
  esbuildOptions: sharedEsbuildOptions
5193
5614
  }
@@ -5212,12 +5633,7 @@ ${list}`);
5212
5633
  }
5213
5634
  });
5214
5635
  plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
5215
- const perfPlugin = performanceTracksPlugin();
5216
- console.log(
5217
- "[perf-tracks] rango: plugin created, has configureServer:",
5218
- !!perfPlugin.configureServer
5219
- );
5220
- plugins.push(perfPlugin);
5636
+ plugins.push(performanceTracksPlugin());
5221
5637
  plugins.push(
5222
5638
  rsc({
5223
5639
  entries: finalEntries
@@ -5258,7 +5674,8 @@ ${list}`);
5258
5674
  createRouterDiscoveryPlugin(discoveryEntryPath, {
5259
5675
  routerPathRef: discoveryRouterRef,
5260
5676
  enableBuildPrerender: prerenderEnabled,
5261
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration
5677
+ buildEnv: options?.buildEnv,
5678
+ preset
5262
5679
  })
5263
5680
  );
5264
5681
  return plugins;
@@ -5271,29 +5688,75 @@ function poke() {
5271
5688
  apply: "serve",
5272
5689
  configureServer(server) {
5273
5690
  const stdin = process.stdin;
5274
- const previousRawMode = stdin.isTTY ? stdin.isRaw : null;
5691
+ const debug = process.env.RANGO_POKE_DEBUG === "1";
5692
+ const triggerReload = (source) => {
5693
+ server.hot.send({ type: "full-reload", path: "*" });
5694
+ server.config.logger.info(` browser reload (${source})`, {
5695
+ timestamp: true
5696
+ });
5697
+ };
5698
+ const toBuffer = (chunk) => {
5699
+ return typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5700
+ };
5701
+ const formatChunk = (chunk) => {
5702
+ const data = toBuffer(chunk);
5703
+ const hex = Array.from(data).map((byte) => `0x${byte.toString(16).padStart(2, "0")}`).join(" ");
5704
+ const ascii = Array.from(data).map((byte) => {
5705
+ if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
5706
+ if (byte === 10) return "\\n";
5707
+ if (byte === 13) return "\\r";
5708
+ if (byte === 9) return "\\t";
5709
+ return ".";
5710
+ }).join("");
5711
+ return `len=${data.length} hex=[${hex}] ascii="${ascii}"`;
5712
+ };
5713
+ const readCtrlR = (chunk) => {
5714
+ const data = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5715
+ return data.length === 1 && data[0] === 18;
5716
+ };
5717
+ const readSubmittedCommands = (chunk) => {
5718
+ const text = toBuffer(chunk).toString("utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5719
+ if (!text.includes("\n")) return [];
5720
+ const lines = text.split("\n");
5721
+ lines.pop();
5722
+ return lines;
5723
+ };
5724
+ if (debug) {
5725
+ server.config.logger.info(
5726
+ ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
5727
+ { timestamp: true }
5728
+ );
5729
+ }
5275
5730
  if (stdin.isTTY) {
5276
- stdin.setRawMode(true);
5731
+ server.config.logger.info(
5732
+ " poke ready: press e + enter to reload browser (ctrl+r also works when available)",
5733
+ { timestamp: true }
5734
+ );
5277
5735
  }
5278
5736
  const onData = (data) => {
5279
- if (data.length !== 1) return;
5280
- if (data[0] === 3) {
5281
- process.emit("SIGINT", "SIGINT");
5282
- return;
5283
- }
5284
- if (data[0] === 18) {
5285
- server.hot.send({ type: "full-reload", path: "*" });
5286
- server.config.logger.info(" browser reload (ctrl+r)", {
5737
+ if (debug) {
5738
+ server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5287
5739
  timestamp: true
5288
5740
  });
5289
5741
  }
5742
+ if (readCtrlR(data)) {
5743
+ triggerReload("ctrl+r");
5744
+ return;
5745
+ }
5746
+ for (const command of readSubmittedCommands(data)) {
5747
+ if (command === "e") {
5748
+ triggerReload("e+enter");
5749
+ return;
5750
+ }
5751
+ if (command === "\x1Br") {
5752
+ triggerReload("option+r+enter");
5753
+ return;
5754
+ }
5755
+ }
5290
5756
  };
5291
5757
  stdin.on("data", onData);
5292
5758
  server.httpServer?.on("close", () => {
5293
5759
  stdin.off("data", onData);
5294
- if (stdin.isTTY && previousRawMode !== null) {
5295
- stdin.setRawMode(previousRawMode);
5296
- }
5297
5760
  });
5298
5761
  }
5299
5762
  };