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

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 +867 -385
  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 +50 -20
  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 +41 -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 {
@@ -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.8678bb02",
1868
+ version: "0.0.0-experimental.88",
1749
1869
  description: "Django-inspired RSC router with composable URL patterns",
1750
1870
  keywords: [
1751
1871
  "react",
@@ -1878,16 +1998,16 @@ 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.14",
2010
+ "@vitejs/plugin-rsc": "^0.5.23",
1891
2011
  "magic-string": "^0.30.17",
1892
2012
  picomatch: "^4.0.3",
1893
2013
  "rsc-html-stream": "^0.0.7"
@@ -1907,7 +2027,7 @@ var package_default = {
1907
2027
  },
1908
2028
  peerDependencies: {
1909
2029
  "@cloudflare/vite-plugin": "^1.25.0",
1910
- "@vitejs/plugin-rsc": "^0.5.14",
2030
+ "@vitejs/plugin-rsc": "^0.5.23",
1911
2031
  react: "^18.0.0 || ^19.0.0",
1912
2032
  vite: "^7.3.0"
1913
2033
  },
@@ -1922,6 +2042,7 @@ var package_default = {
1922
2042
  };
1923
2043
 
1924
2044
  // src/vite/utils/package-resolution.ts
2045
+ var require2 = createRequire(import.meta.url);
1925
2046
  var VIRTUAL_PACKAGE_NAME = "@rangojs/router";
1926
2047
  function getPublishedPackageName() {
1927
2048
  return package_default.name;
@@ -1962,6 +2083,20 @@ function getPackageAliases() {
1962
2083
  }
1963
2084
  return aliases;
1964
2085
  }
2086
+ function getVendorAliases() {
2087
+ const specs = [
2088
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge",
2089
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
2090
+ ];
2091
+ const aliases = {};
2092
+ for (const spec of specs) {
2093
+ try {
2094
+ aliases[spec] = require2.resolve(spec);
2095
+ } catch {
2096
+ }
2097
+ }
2098
+ return aliases;
2099
+ }
1965
2100
 
1966
2101
  // src/build/route-types/param-extraction.ts
1967
2102
  function extractParamsFromPattern(pattern) {
@@ -2317,7 +2452,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2317
2452
  }
2318
2453
  return routeMap;
2319
2454
  }
2320
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
2455
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
2321
2456
  visited = visited ?? /* @__PURE__ */ new Set();
2322
2457
  const realPath = resolve2(filePath);
2323
2458
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -2333,7 +2468,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2333
2468
  return { routes: {}, searchSchemas: {} };
2334
2469
  }
2335
2470
  let block;
2336
- if (variableName) {
2471
+ if (inlineBlock) {
2472
+ block = inlineBlock;
2473
+ } else if (variableName) {
2337
2474
  const extracted = extractUrlsBlockForVariable(source, variableName);
2338
2475
  if (!extracted) return { routes: {}, searchSchemas: {} };
2339
2476
  block = extracted;
@@ -2452,7 +2589,7 @@ Router root: ${conflict.ancestor}
2452
2589
  Nested router: ${conflict.nested}
2453
2590
  Move the nested router into a sibling directory or configure it as a separate app root.`;
2454
2591
  }
2455
- function extractUrlsVariableFromRouter(code) {
2592
+ function extractUrlsFromRouter(code) {
2456
2593
  const sourceFile = ts5.createSourceFile(
2457
2594
  "router.tsx",
2458
2595
  code,
@@ -2466,24 +2603,70 @@ function extractUrlsVariableFromRouter(code) {
2466
2603
  const callee = node.expression;
2467
2604
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
2468
2605
  }
2606
+ function isInlineBuilder(node) {
2607
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
2608
+ }
2609
+ function isRoutesOnCreateRouter(node) {
2610
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
2611
+ return false;
2612
+ let inner = node.expression.expression;
2613
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2614
+ inner = inner.expression.expression;
2615
+ }
2616
+ return isCreateRouterCall(inner);
2617
+ }
2469
2618
  function visit(node) {
2470
2619
  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;
2620
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
2621
+ const arg = node.arguments[0];
2622
+ if (ts5.isIdentifier(arg)) {
2623
+ result = { kind: "variable", name: arg.text };
2624
+ } else if (isInlineBuilder(arg)) {
2625
+ result = { kind: "inline", block: arg.getText(sourceFile) };
2479
2626
  }
2627
+ return;
2480
2628
  }
2481
2629
  if (isCreateRouterCall(node)) {
2482
2630
  const callExpr = node;
2483
- for (const arg of callExpr.arguments) {
2631
+ for (const callArg of callExpr.arguments) {
2632
+ if (ts5.isObjectLiteralExpression(callArg)) {
2633
+ for (const prop of callArg.properties) {
2634
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
2635
+ if (ts5.isIdentifier(prop.initializer)) {
2636
+ result = { kind: "variable", name: prop.initializer.text };
2637
+ } else if (isInlineBuilder(prop.initializer)) {
2638
+ result = {
2639
+ kind: "inline",
2640
+ block: prop.initializer.getText(sourceFile)
2641
+ };
2642
+ }
2643
+ return;
2644
+ }
2645
+ }
2646
+ }
2647
+ }
2648
+ }
2649
+ ts5.forEachChild(node, visit);
2650
+ }
2651
+ visit(sourceFile);
2652
+ return result;
2653
+ }
2654
+ function extractBasenameFromRouter(code) {
2655
+ const sourceFile = ts5.createSourceFile(
2656
+ "router.tsx",
2657
+ code,
2658
+ ts5.ScriptTarget.Latest,
2659
+ true,
2660
+ ts5.ScriptKind.TSX
2661
+ );
2662
+ let result;
2663
+ function visit(node) {
2664
+ if (result !== void 0) return;
2665
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
2666
+ for (const arg of node.arguments) {
2484
2667
  if (ts5.isObjectLiteralExpression(arg)) {
2485
2668
  for (const prop of arg.properties) {
2486
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
2669
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
2487
2670
  result = prop.initializer.text;
2488
2671
  return;
2489
2672
  }
@@ -2496,6 +2679,19 @@ function extractUrlsVariableFromRouter(code) {
2496
2679
  visit(sourceFile);
2497
2680
  return result;
2498
2681
  }
2682
+ function applyBasenameToRoutes(result, basename3) {
2683
+ const prefixed = {};
2684
+ for (const [name, pattern] of Object.entries(result.routes)) {
2685
+ if (pattern === "/") {
2686
+ prefixed[name] = basename3;
2687
+ } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2688
+ prefixed[name] = basename3 + pattern.slice(1);
2689
+ } else {
2690
+ prefixed[name] = basename3 + pattern;
2691
+ }
2692
+ }
2693
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
2694
+ }
2499
2695
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2500
2696
  let routerSource;
2501
2697
  try {
@@ -2503,19 +2699,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2503
2699
  } catch {
2504
2700
  return { routes: {}, searchSchemas: {} };
2505
2701
  }
2506
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2507
- if (!urlsVarName) {
2702
+ const extraction = extractUrlsFromRouter(routerSource);
2703
+ if (!extraction) {
2508
2704
  return { routes: {}, searchSchemas: {} };
2509
2705
  }
2510
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2511
- if (imported) {
2512
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2513
- if (!targetFile) {
2514
- return { routes: {}, searchSchemas: {} };
2706
+ const rawBasename = extractBasenameFromRouter(routerSource);
2707
+ const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2708
+ let result;
2709
+ if (extraction.kind === "inline") {
2710
+ result = buildCombinedRouteMapWithSearch(
2711
+ routerFilePath,
2712
+ void 0,
2713
+ void 0,
2714
+ void 0,
2715
+ extraction.block
2716
+ );
2717
+ } else {
2718
+ const imported = resolveImportedVariable(routerSource, extraction.name);
2719
+ if (imported) {
2720
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2721
+ if (!targetFile) {
2722
+ return { routes: {}, searchSchemas: {} };
2723
+ }
2724
+ result = buildCombinedRouteMapWithSearch(
2725
+ targetFile,
2726
+ imported.exportedName
2727
+ );
2728
+ } else {
2729
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2515
2730
  }
2516
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2517
2731
  }
2518
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2732
+ if (basename3) {
2733
+ result = applyBasenameToRoutes(result, basename3);
2734
+ }
2735
+ return result;
2519
2736
  }
2520
2737
  function findRouterFiles(root, filter) {
2521
2738
  const result = [];
@@ -2540,25 +2757,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2540
2757
  throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
2541
2758
  }
2542
2759
  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);
2760
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
2761
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
2762
+ let routerSource;
2763
+ try {
2764
+ routerSource = readFileSync2(routerFilePath, "utf-8");
2765
+ } catch {
2766
+ continue;
2767
+ }
2768
+ if (!extractUrlsFromRouter(routerSource)) continue;
2562
2769
  }
2563
2770
  const routerBasename = pathBasename(routerFilePath).replace(
2564
2771
  /\.(tsx?|jsx?)$/,
@@ -2784,6 +2991,68 @@ function createVersionPlugin() {
2784
2991
 
2785
2992
  // src/vite/utils/shared-utils.ts
2786
2993
  import * as Vite from "vite";
2994
+
2995
+ // src/vite/plugins/performance-tracks.ts
2996
+ import { readFile } from "node:fs/promises";
2997
+ var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2998
+ function buildPatchReplacement(match, debugInfoVar) {
2999
+ return `${match}
3000
+ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
3001
+ var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
3002
+ if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
3003
+ ${debugInfoVar} = _resolved._debugInfo;
3004
+ }
3005
+ }`;
3006
+ }
3007
+ function patchRsdwClientDebugInfoRecovery(code) {
3008
+ const match = code.match(RSDW_PATCH_RE);
3009
+ if (!match) {
3010
+ return { code, debugInfoVar: null };
3011
+ }
3012
+ return {
3013
+ code: code.replace(match[1], buildPatchReplacement(match[1], match[2])),
3014
+ debugInfoVar: match[2]
3015
+ };
3016
+ }
3017
+ function performanceTracksOptimizeDepsPlugin() {
3018
+ return {
3019
+ name: "@rangojs/router:performance-tracks-optimize-deps",
3020
+ setup(build) {
3021
+ build.onLoad(
3022
+ {
3023
+ filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
3024
+ },
3025
+ async (args) => {
3026
+ const code = await readFile(args.path, "utf8");
3027
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3028
+ return {
3029
+ contents: patched.code,
3030
+ loader: "js"
3031
+ };
3032
+ }
3033
+ );
3034
+ }
3035
+ };
3036
+ }
3037
+ function performanceTracksPlugin() {
3038
+ return {
3039
+ name: "@rangojs/router:performance-tracks",
3040
+ transform(code, id) {
3041
+ if (!id.includes("react-server-dom") || !id.includes("client")) return;
3042
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3043
+ if (!patched.debugInfoVar) return;
3044
+ if (process.env.INTERNAL_RANGO_DEBUG)
3045
+ console.log(
3046
+ "[perf-tracks] patched RSDW client (var:",
3047
+ patched.debugInfoVar,
3048
+ ")"
3049
+ );
3050
+ return patched.code;
3051
+ }
3052
+ };
3053
+ }
3054
+
3055
+ // src/vite/utils/shared-utils.ts
2787
3056
  var versionEsbuildPlugin = {
2788
3057
  name: "@rangojs/router-version",
2789
3058
  setup(build) {
@@ -2801,7 +3070,7 @@ var versionEsbuildPlugin = {
2801
3070
  }
2802
3071
  };
2803
3072
  var sharedEsbuildOptions = {
2804
- plugins: [versionEsbuildPlugin]
3073
+ plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
2805
3074
  };
2806
3075
  function createVirtualEntriesPlugin(entries, routerPathRef) {
2807
3076
  const virtualModules = {};
@@ -3007,6 +3276,8 @@ function createCjsToEsmPlugin() {
3007
3276
  import { createServer as createViteServer } from "vite";
3008
3277
  import { resolve as resolve8 } from "node:path";
3009
3278
  import { readFileSync as readFileSync6 } from "node:fs";
3279
+ import { createRequire as createRequire2, register } from "node:module";
3280
+ import { pathToFileURL } from "node:url";
3010
3281
 
3011
3282
  // src/vite/plugins/virtual-stub-plugin.ts
3012
3283
  function createVirtualStubPlugin() {
@@ -3032,6 +3303,113 @@ function createVirtualStubPlugin() {
3032
3303
  };
3033
3304
  }
3034
3305
 
3306
+ // src/vite/plugins/cloudflare-protocol-stub.ts
3307
+ var VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
3308
+ var NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
3309
+ var CF_PREFIX = "cloudflare:";
3310
+ var BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
3311
+ var SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
3312
+ var IMPORT_NODE_TYPES = /* @__PURE__ */ new Set([
3313
+ "ImportDeclaration",
3314
+ "ImportExpression",
3315
+ "ExportNamedDeclaration",
3316
+ "ExportAllDeclaration"
3317
+ ]);
3318
+ var STUBS = {
3319
+ "cloudflare:workers": `
3320
+ export class DurableObject { constructor(_ctx, _env) {} }
3321
+ export class WorkerEntrypoint { constructor(_ctx, _env) {} }
3322
+ export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
3323
+ export class RpcTarget {}
3324
+ export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
3325
+ export default {};
3326
+ `,
3327
+ "cloudflare:email": `
3328
+ export class EmailMessage { constructor(_from, _to, _raw) {} }
3329
+ export default {};
3330
+ `,
3331
+ "cloudflare:sockets": `
3332
+ export function connect() { return {}; }
3333
+ export default {};
3334
+ `,
3335
+ "cloudflare:workflows": `
3336
+ export class NonRetryableError extends Error {
3337
+ constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
3338
+ }
3339
+ export default {};
3340
+ `
3341
+ };
3342
+ var FALLBACK_STUB = `export default {};
3343
+ `;
3344
+ function createCloudflareProtocolStubPlugin() {
3345
+ return {
3346
+ name: "@rangojs/router:cloudflare-protocol-stub",
3347
+ transform(code, id) {
3348
+ const cleanId = id.split("?")[0] ?? id;
3349
+ if (!SOURCE_EXT_RE.test(cleanId)) return null;
3350
+ if (!code.includes(CF_PREFIX)) return null;
3351
+ let ast;
3352
+ try {
3353
+ ast = this.parse(code);
3354
+ } catch {
3355
+ return null;
3356
+ }
3357
+ const hits = [];
3358
+ walk(ast, (node) => {
3359
+ if (!IMPORT_NODE_TYPES.has(node.type)) return;
3360
+ const source = node.source;
3361
+ if (!source || source.type !== "Literal") return;
3362
+ if (typeof source.value !== "string") return;
3363
+ if (!source.value.startsWith(CF_PREFIX)) return;
3364
+ if (typeof source.start !== "number" || typeof source.end !== "number")
3365
+ return;
3366
+ hits.push({
3367
+ start: source.start,
3368
+ end: source.end,
3369
+ value: source.value
3370
+ });
3371
+ });
3372
+ if (hits.length === 0) return null;
3373
+ hits.sort((a, b) => b.start - a.start);
3374
+ let out = code;
3375
+ for (const hit of hits) {
3376
+ const submodule = hit.value.slice(CF_PREFIX.length);
3377
+ const quote = code[hit.start] === "'" ? "'" : '"';
3378
+ out = out.slice(0, hit.start) + quote + VIRTUAL_PREFIX + submodule + quote + out.slice(hit.end);
3379
+ }
3380
+ return { code: out, map: null };
3381
+ },
3382
+ resolveId(id) {
3383
+ if (id.startsWith(VIRTUAL_PREFIX)) {
3384
+ return "\0" + id;
3385
+ }
3386
+ return null;
3387
+ },
3388
+ load(id) {
3389
+ if (!id.startsWith(NULL_PREFIX)) return null;
3390
+ const submodule = id.slice(NULL_PREFIX.length);
3391
+ const specifier = CF_PREFIX + submodule;
3392
+ return STUBS[specifier] ?? FALLBACK_STUB;
3393
+ }
3394
+ };
3395
+ }
3396
+ function walk(node, visit) {
3397
+ if (!node || typeof node !== "object") return;
3398
+ if (Array.isArray(node)) {
3399
+ for (const child of node) walk(child, visit);
3400
+ return;
3401
+ }
3402
+ const n = node;
3403
+ if (typeof n.type !== "string") return;
3404
+ visit(n);
3405
+ for (const key in n) {
3406
+ if (key === "loc" || key === "start" || key === "end" || key === "range") {
3407
+ continue;
3408
+ }
3409
+ walk(n[key], visit);
3410
+ }
3411
+ }
3412
+
3035
3413
  // src/vite/plugins/client-ref-hashing.ts
3036
3414
  import { relative } from "node:path";
3037
3415
  import { createHash as createHash2 } from "node:crypto";
@@ -3190,8 +3568,8 @@ function createDiscoveryState(entryPath, opts) {
3190
3568
  perRouterManifestDataMap: /* @__PURE__ */ new Map(),
3191
3569
  prerenderManifestEntries: null,
3192
3570
  staticManifestEntries: null,
3193
- handlerChunkInfo: null,
3194
- staticHandlerChunkInfo: null,
3571
+ handlerChunkInfoMap: /* @__PURE__ */ new Map(),
3572
+ staticHandlerChunkInfoMap: /* @__PURE__ */ new Map(),
3195
3573
  rscEntryFileName: null,
3196
3574
  resolvedPrerenderModules: void 0,
3197
3575
  resolvedStaticModules: void 0,
@@ -3322,13 +3700,31 @@ function encodePathParam(value) {
3322
3700
  }
3323
3701
  function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3324
3702
  let result = pattern;
3703
+ let hadOmittedOptional = false;
3325
3704
  for (const [key, value] of Object.entries(params)) {
3326
3705
  const escaped = escapeRegExp2(key);
3327
- result = result.replace(
3328
- new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3329
- encode(value)
3330
- );
3331
- result = result.replace(`*${key}`, encode(value));
3706
+ if (value === "") {
3707
+ result = result.replace(
3708
+ new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
3709
+ ""
3710
+ );
3711
+ result = result.replace(`*${key}`, "");
3712
+ } else {
3713
+ result = result.replace(
3714
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3715
+ encode(value)
3716
+ );
3717
+ result = result.replace(`*${key}`, encode(value));
3718
+ }
3719
+ }
3720
+ result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3721
+ hadOmittedOptional = true;
3722
+ return "";
3723
+ });
3724
+ if (hadOmittedOptional) {
3725
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
3726
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
3727
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
3332
3728
  }
3333
3729
  return result;
3334
3730
  }
@@ -3438,84 +3834,126 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3438
3834
  if (!params) return pattern;
3439
3835
  return substituteRouteParams(pattern, params);
3440
3836
  };
3837
+ let resolvedRoutes = 0;
3838
+ let totalDynamic = 0;
3441
3839
  for (const { manifest } of allManifests) {
3442
3840
  if (!manifest.prerenderRoutes) continue;
3443
- const defs = manifest._prerenderDefs || {};
3444
3841
  for (const routeName of manifest.prerenderRoutes) {
3445
3842
  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));
3843
+ if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
3844
+ totalDynamic++;
3845
+ }
3846
+ }
3847
+ }
3848
+ const paramsStart = performance.now();
3849
+ const progressInterval = totalDynamic > 0 ? setInterval(() => {
3850
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3851
+ console.log(
3852
+ `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3853
+ );
3854
+ }, 5e3) : void 0;
3855
+ try {
3856
+ for (const { manifest } of allManifests) {
3857
+ if (!manifest.prerenderRoutes) continue;
3858
+ const defs = manifest._prerenderDefs || {};
3859
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
3860
+ for (const routeName of manifest.prerenderRoutes) {
3861
+ const pattern = manifest.routeManifest[routeName];
3862
+ if (!pattern) continue;
3863
+ const def = defs[routeName];
3864
+ const isPassthroughRoute = passthroughSet.has(routeName);
3865
+ const hasDynamic = pattern.includes(":") || pattern.includes("*");
3866
+ if (!hasDynamic) {
3867
+ entries.push({
3868
+ urlPath: pattern.replace(/\/$/, "") || "/",
3869
+ routeName,
3870
+ concurrency: 1,
3871
+ isPassthroughRoute
3872
+ });
3873
+ } else {
3874
+ if (def?.getParams) {
3875
+ try {
3876
+ const buildVars = {};
3877
+ const buildEnv = state.resolvedBuildEnv;
3878
+ const getParamsCtx = {
3879
+ build: true,
3880
+ dev: !state.isBuildMode,
3881
+ set: ((keyOrVar, value) => {
3882
+ contextSet(buildVars, keyOrVar, value);
3883
+ }),
3884
+ reverse: getParamsReverse,
3885
+ get env() {
3886
+ if (buildEnv !== void 0) return buildEnv;
3887
+ throw new Error(
3888
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3889
+ );
3481
3890
  }
3891
+ };
3892
+ const paramsList = await def.getParams(getParamsCtx);
3893
+ const concurrency = def.options?.concurrency ?? 1;
3894
+ const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3895
+ for (const params of paramsList) {
3896
+ let url = substituteRouteParams(
3897
+ pattern,
3898
+ params,
3899
+ encodePathParam
3900
+ );
3901
+ if (url.includes("*")) {
3902
+ const wildcardValue = params["*"] ?? params.splat;
3903
+ if (wildcardValue !== void 0) {
3904
+ url = url.replace(
3905
+ /\*[^/]*$/,
3906
+ encodePathParam(wildcardValue)
3907
+ );
3908
+ }
3909
+ }
3910
+ entries.push({
3911
+ urlPath: url.replace(/\/$/, "") || "/",
3912
+ routeName,
3913
+ concurrency,
3914
+ ...hasBuildVars ? { buildVars } : {},
3915
+ isPassthroughRoute
3916
+ });
3482
3917
  }
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
3918
+ resolvedRoutes++;
3919
+ } catch (err) {
3920
+ resolvedRoutes++;
3921
+ if (err.name === "Skip") {
3922
+ console.log(
3923
+ `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3924
+ );
3925
+ notifyOnError(
3926
+ registry,
3927
+ err,
3928
+ "prerender",
3929
+ routeName,
3930
+ void 0,
3931
+ true
3932
+ );
3933
+ continue;
3934
+ }
3935
+ console.error(
3936
+ `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3503
3937
  );
3504
- continue;
3938
+ notifyOnError(registry, err, "prerender", routeName);
3939
+ throw err;
3505
3940
  }
3506
- console.error(
3507
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3941
+ } else {
3942
+ console.warn(
3943
+ `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3508
3944
  );
3509
- notifyOnError(registry, err, "prerender", routeName);
3510
- throw err;
3511
3945
  }
3512
- } else {
3513
- console.warn(
3514
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3515
- );
3516
3946
  }
3517
3947
  }
3518
3948
  }
3949
+ } finally {
3950
+ if (progressInterval) {
3951
+ clearInterval(progressInterval);
3952
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3953
+ console.log(
3954
+ `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3955
+ );
3956
+ }
3519
3957
  }
3520
3958
  if (entries.length === 0) return;
3521
3959
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
@@ -3542,7 +3980,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3542
3980
  entry.urlPath,
3543
3981
  {},
3544
3982
  entry.buildVars,
3545
- entry.isPassthroughRoute
3983
+ entry.isPassthroughRoute,
3984
+ state.resolvedBuildEnv
3546
3985
  );
3547
3986
  if (!result) continue;
3548
3987
  if (result.passthrough) {
@@ -3666,7 +4105,9 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3666
4105
  const result = await routerInstance.renderStaticSegment(
3667
4106
  def.handler,
3668
4107
  def.$$id,
3669
- def.$$routePrefix
4108
+ def.$$routePrefix,
4109
+ state.resolvedBuildEnv,
4110
+ !state.isBuildMode
3670
4111
  );
3671
4112
  if (result) {
3672
4113
  const hasHandles = Object.keys(result.handles).length > 0;
@@ -3791,7 +4232,11 @@ async function discoverRouters(state, rscEnv) {
3791
4232
  if (!router.urlpatterns || !generateManifestFull) {
3792
4233
  continue;
3793
4234
  }
3794
- const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
4235
+ const manifest = generateManifestFull(
4236
+ router.urlpatterns,
4237
+ routerMountIndex,
4238
+ router.__basename ? { urlPrefix: router.__basename } : void 0
4239
+ );
3795
4240
  routerMountIndex++;
3796
4241
  allManifests.push({ id, manifest });
3797
4242
  const routeCount = Object.keys(manifest.routeManifest).length;
@@ -4240,48 +4685,45 @@ function postprocessBundle(state) {
4240
4685
  );
4241
4686
  const evictionTargets = [
4242
4687
  {
4243
- info: state.handlerChunkInfo,
4688
+ infos: state.handlerChunkInfoMap.values(),
4244
4689
  fnName: "Prerender",
4245
4690
  brand: "prerenderHandler",
4246
4691
  label: "handler code from RSC bundle"
4247
4692
  },
4248
4693
  {
4249
- info: state.staticHandlerChunkInfo,
4694
+ infos: state.staticHandlerChunkInfoMap.values(),
4250
4695
  fnName: "Static",
4251
4696
  brand: "staticHandler",
4252
4697
  label: "static handler code"
4253
4698
  }
4254
4699
  ];
4255
4700
  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}`
4701
+ for (const info of target.infos) {
4702
+ const chunkPath = resolve7(state.projectRoot, "dist/rsc", info.fileName);
4703
+ try {
4704
+ const code = readFileSync5(chunkPath, "utf-8");
4705
+ const result = evictHandlerCode(
4706
+ code,
4707
+ info.exports,
4708
+ target.fnName,
4709
+ target.brand
4710
+ );
4711
+ if (result) {
4712
+ writeFileSync4(chunkPath, result.code);
4713
+ const savedKB = (result.savedBytes / 1024).toFixed(1);
4714
+ console.log(
4715
+ `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4716
+ );
4717
+ }
4718
+ } catch (replaceErr) {
4719
+ console.warn(
4720
+ `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4275
4721
  );
4276
4722
  }
4277
- } catch (replaceErr) {
4278
- console.warn(
4279
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4280
- );
4281
4723
  }
4282
4724
  }
4283
- state.handlerChunkInfo = null;
4284
- state.staticHandlerChunkInfo = null;
4725
+ state.handlerChunkInfoMap.clear();
4726
+ state.staticHandlerChunkInfoMap.clear();
4285
4727
  if (hasPrerenderData && existsSync6(rscEntryPath)) {
4286
4728
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4287
4729
  if (!rscCode.includes("__prerender-manifest.js")) {
@@ -4324,7 +4766,7 @@ function postprocessBundle(state) {
4324
4766
  }
4325
4767
  if (hasStaticData && existsSync6(rscEntryPath)) {
4326
4768
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4327
- if (!rscCode.includes("__STATIC_MANIFEST")) {
4769
+ if (!rscCode.includes("__static-manifest.js")) {
4328
4770
  try {
4329
4771
  const manifestEntries = [];
4330
4772
  let totalBytes = copyStagedBuildAssets(
@@ -4363,7 +4805,22 @@ function postprocessBundle(state) {
4363
4805
  }
4364
4806
 
4365
4807
  // src/vite/router-discovery.ts
4808
+ var loaderHookRegistered = false;
4809
+ function ensureCloudflareProtocolLoaderRegistered() {
4810
+ if (loaderHookRegistered) return;
4811
+ loaderHookRegistered = true;
4812
+ try {
4813
+ register(
4814
+ new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url)
4815
+ );
4816
+ } catch (err) {
4817
+ console.warn(
4818
+ `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
4819
+ );
4820
+ }
4821
+ }
4366
4822
  async function createTempRscServer(state, options = {}) {
4823
+ ensureCloudflareProtocolLoaderRegistered();
4367
4824
  const { default: rsc } = await import("@vitejs/plugin-rsc");
4368
4825
  return createViteServer({
4369
4826
  root: state.projectRoot,
@@ -4386,6 +4843,7 @@ async function createTempRscServer(state, options = {}) {
4386
4843
  ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
4387
4844
  createVersionPlugin(),
4388
4845
  createVirtualStubPlugin(),
4846
+ createCloudflareProtocolStubPlugin(),
4389
4847
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
4390
4848
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
4391
4849
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
@@ -4393,8 +4851,69 @@ async function createTempRscServer(state, options = {}) {
4393
4851
  ]
4394
4852
  });
4395
4853
  }
4854
+ async function resolveBuildEnv(option, factoryCtx) {
4855
+ if (!option) return null;
4856
+ if (option === "auto") {
4857
+ if (factoryCtx.preset !== "cloudflare") {
4858
+ throw new Error(
4859
+ '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
4860
+ );
4861
+ }
4862
+ try {
4863
+ const userRequire = createRequire2(
4864
+ resolve8(factoryCtx.root, "package.json")
4865
+ );
4866
+ const wranglerPath = userRequire.resolve("wrangler");
4867
+ const { getPlatformProxy } = await import(pathToFileURL(wranglerPath).href);
4868
+ const proxy = await getPlatformProxy();
4869
+ return {
4870
+ env: proxy.env,
4871
+ dispose: proxy.dispose
4872
+ };
4873
+ } catch (err) {
4874
+ throw new Error(
4875
+ `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
4876
+ Install it with: pnpm add -D wrangler
4877
+ ${err.message}`
4878
+ );
4879
+ }
4880
+ }
4881
+ if (typeof option === "function") {
4882
+ return await option(factoryCtx);
4883
+ }
4884
+ return { env: option };
4885
+ }
4886
+ async function acquireBuildEnv(s, command, mode) {
4887
+ const option = s.opts?.buildEnv;
4888
+ if (!option) return false;
4889
+ const result = await resolveBuildEnv(option, {
4890
+ root: s.projectRoot,
4891
+ mode,
4892
+ command,
4893
+ preset: s.opts?.preset ?? "node"
4894
+ });
4895
+ if (!result) return false;
4896
+ s.resolvedBuildEnv = result.env;
4897
+ s.buildEnvDispose = result.dispose ?? null;
4898
+ globalThis[BUILD_ENV_GLOBAL_KEY] = result.env;
4899
+ return true;
4900
+ }
4901
+ async function releaseBuildEnv(s) {
4902
+ if (s.buildEnvDispose) {
4903
+ try {
4904
+ await s.buildEnvDispose();
4905
+ } catch (err) {
4906
+ console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
4907
+ }
4908
+ s.buildEnvDispose = null;
4909
+ }
4910
+ s.resolvedBuildEnv = void 0;
4911
+ delete globalThis[BUILD_ENV_GLOBAL_KEY];
4912
+ }
4396
4913
  function createRouterDiscoveryPlugin(entryPath, opts) {
4397
4914
  const s = createDiscoveryState(entryPath, opts);
4915
+ let viteCommand = "build";
4916
+ let viteMode = "production";
4398
4917
  return {
4399
4918
  name: "@rangojs/router:discovery",
4400
4919
  config() {
@@ -4403,31 +4922,13 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4403
4922
  __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG)
4404
4923
  }
4405
4924
  };
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
4925
  return config;
4427
4926
  },
4428
4927
  configResolved(config) {
4429
4928
  s.projectRoot = config.root;
4430
4929
  s.isBuildMode = config.command === "build";
4930
+ viteCommand = config.command;
4931
+ viteMode = config.mode;
4431
4932
  s.userResolveAlias = config.resolve.alias;
4432
4933
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
4433
4934
  s.resolvedEntryPath = opts.routerPathRef.path;
@@ -4472,6 +4973,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4472
4973
  });
4473
4974
  prerenderTempServer = null;
4474
4975
  }
4976
+ releaseBuildEnv(s).catch(() => {
4977
+ });
4475
4978
  });
4476
4979
  async function getOrCreateTempServer() {
4477
4980
  if (prerenderNodeRegistry) {
@@ -4502,6 +5005,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4502
5005
  if (!rscEnv?.runner) {
4503
5006
  s.devServerOrigin = getDevServerOrigin();
4504
5007
  try {
5008
+ await acquireBuildEnv(s, viteCommand, viteMode);
4505
5009
  const tempRscEnv = await getOrCreateTempServer();
4506
5010
  if (tempRscEnv) {
4507
5011
  await discoverRouters(s, tempRscEnv);
@@ -4517,6 +5021,7 @@ ${err.stack}`
4517
5021
  return;
4518
5022
  }
4519
5023
  try {
5024
+ await acquireBuildEnv(s, viteCommand, viteMode);
4520
5025
  const serverMod = await rscEnv.runner.import(
4521
5026
  "@rangojs/router/server"
4522
5027
  );
@@ -4581,7 +5086,26 @@ ${err.stack}`
4581
5086
  res.end("Missing pathname");
4582
5087
  return;
4583
5088
  }
4584
- let registry = mainRegistry;
5089
+ const rscEnv = server.environments?.rsc;
5090
+ let registry = null;
5091
+ if (rscEnv?.runner && s.resolvedEntryPath) {
5092
+ try {
5093
+ await rscEnv.runner.import(s.resolvedEntryPath);
5094
+ const serverMod = await rscEnv.runner.import(
5095
+ "@rangojs/router/server"
5096
+ );
5097
+ registry = serverMod.RouterRegistry ?? null;
5098
+ } catch (err) {
5099
+ console.warn(
5100
+ `[rsc-router] Dev prerender module refresh failed: ${err.message}`
5101
+ );
5102
+ res.statusCode = 500;
5103
+ res.end(`Prerender handler error: ${err.message}`);
5104
+ return;
5105
+ }
5106
+ } else {
5107
+ registry = mainRegistry;
5108
+ }
4585
5109
  if (!registry) {
4586
5110
  if (!prerenderNodeRegistry) {
4587
5111
  await getOrCreateTempServer();
@@ -4603,7 +5127,10 @@ ${err.stack}`
4603
5127
  pathname,
4604
5128
  {},
4605
5129
  void 0,
4606
- wantPassthrough
5130
+ wantPassthrough,
5131
+ s.resolvedBuildEnv,
5132
+ true
5133
+ // devMode: check getParams for passthrough routes
4607
5134
  );
4608
5135
  if (!result) continue;
4609
5136
  if (result.passthrough) continue;
@@ -4739,6 +5266,7 @@ ${err.stack}`
4739
5266
  resetStagedBuildAssets(s.projectRoot);
4740
5267
  s.prerenderManifestEntries = null;
4741
5268
  s.staticManifestEntries = null;
5269
+ await acquireBuildEnv(s, viteCommand, viteMode);
4742
5270
  let tempServer = null;
4743
5271
  globalThis.__rscRouterDiscoveryActive = true;
4744
5272
  try {
@@ -4778,6 +5306,7 @@ ${details}`
4778
5306
  if (tempServer) {
4779
5307
  await tempServer.close();
4780
5308
  }
5309
+ await releaseBuildEnv(s);
4781
5310
  }
4782
5311
  },
4783
5312
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -4820,20 +5349,30 @@ ${details}`
4820
5349
  }
4821
5350
  if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
4822
5351
  return;
5352
+ s.handlerChunkInfoMap.clear();
5353
+ s.staticHandlerChunkInfoMap.clear();
4823
5354
  for (const [fileName, chunk] of Object.entries(bundle)) {
4824
5355
  if (chunk.type !== "chunk") continue;
4825
- if (fileName.includes("__prerender-handlers") && s.resolvedPrerenderModules?.size) {
5356
+ if (s.resolvedPrerenderModules?.size) {
4826
5357
  const handlers = extractHandlerExportsFromChunk(
4827
5358
  chunk.code,
4828
5359
  s.resolvedPrerenderModules,
4829
5360
  "Prerender",
4830
- true
5361
+ false
4831
5362
  );
4832
5363
  if (handlers.length > 0) {
4833
- s.handlerChunkInfo = { fileName, exports: handlers };
5364
+ const existing = s.handlerChunkInfoMap.get(fileName);
5365
+ if (existing) {
5366
+ existing.exports.push(...handlers);
5367
+ } else {
5368
+ s.handlerChunkInfoMap.set(fileName, {
5369
+ fileName,
5370
+ exports: handlers
5371
+ });
5372
+ }
4834
5373
  }
4835
5374
  }
4836
- if (fileName.includes("__static-handlers") && s.resolvedStaticModules?.size) {
5375
+ if (s.resolvedStaticModules?.size) {
4837
5376
  const handlers = extractHandlerExportsFromChunk(
4838
5377
  chunk.code,
4839
5378
  s.resolvedStaticModules,
@@ -4841,7 +5380,15 @@ ${details}`
4841
5380
  false
4842
5381
  );
4843
5382
  if (handlers.length > 0) {
4844
- s.staticHandlerChunkInfo = { fileName, exports: handlers };
5383
+ const existing = s.staticHandlerChunkInfoMap.get(fileName);
5384
+ if (existing) {
5385
+ existing.exports.push(...handlers);
5386
+ } else {
5387
+ s.staticHandlerChunkInfoMap.set(fileName, {
5388
+ fileName,
5389
+ exports: handlers
5390
+ });
5391
+ }
4845
5392
  }
4846
5393
  }
4847
5394
  }
@@ -4861,144 +5408,28 @@ ${details}`
4861
5408
  };
4862
5409
  }
4863
5410
 
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
5411
  // src/vite/rango.ts
4994
5412
  async function rango(options) {
4995
5413
  const resolvedOptions = options ?? { preset: "node" };
4996
5414
  const preset = resolvedOptions.preset ?? "node";
4997
- console.log("[perf-tracks] rango() called, preset:", preset);
4998
5415
  const showBanner = resolvedOptions.banner ?? true;
4999
5416
  const plugins = [];
5000
- const rangoAliases = getPackageAliases();
5001
- const excludeDeps = getExcludeDeps();
5417
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
5418
+ const excludeDeps = [
5419
+ ...getExcludeDeps(),
5420
+ // plugin-rsc itself injects these into the client env's
5421
+ // optimizeDeps.include, which overrides exclude for the dep's own
5422
+ // pre-bundle entry. What exclude still controls is how *other*
5423
+ // pre-bundled deps treat imports of these specs (external vs inlined)
5424
+ // via esbuildCjsExternalPlugin. The cjs-to-esm transform in
5425
+ // plugins/cjs-to-esm.ts is the fallback for strict-pnpm consumers,
5426
+ // where client.browser's bare include fails to resolve and Vite ends up
5427
+ // serving the raw CJS file at dev-serve time.
5428
+ "@vitejs/plugin-rsc/browser",
5429
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5430
+ ];
5431
+ const pkg = getPublishedPackageName();
5432
+ const nested = (spec) => `${pkg} > ${spec}`;
5002
5433
  const routerRef = { path: void 0 };
5003
5434
  const prerenderEnabled = true;
5004
5435
  if (preset === "cloudflare") {
@@ -5036,7 +5467,7 @@ async function rango(options) {
5036
5467
  // Pre-bundle rsc-html-stream to prevent discovery during first request
5037
5468
  // Exclude rsc-router modules to ensure same Context instance
5038
5469
  optimizeDeps: {
5039
- include: ["rsc-html-stream/client"],
5470
+ include: [nested("rsc-html-stream/client")],
5040
5471
  exclude: excludeDeps,
5041
5472
  esbuildOptions: sharedEsbuildOptions
5042
5473
  }
@@ -5061,8 +5492,10 @@ async function rango(options) {
5061
5492
  "react-dom/static.edge",
5062
5493
  "react/jsx-runtime",
5063
5494
  "react/jsx-dev-runtime",
5064
- "rsc-html-stream/server",
5065
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5495
+ nested("rsc-html-stream/server"),
5496
+ nested(
5497
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5498
+ )
5066
5499
  ],
5067
5500
  exclude: excludeDeps,
5068
5501
  esbuildOptions: sharedEsbuildOptions
@@ -5077,7 +5510,9 @@ async function rango(options) {
5077
5510
  "react",
5078
5511
  "react/jsx-runtime",
5079
5512
  "react/jsx-dev-runtime",
5080
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5513
+ nested(
5514
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5515
+ )
5081
5516
  ],
5082
5517
  exclude: excludeDeps,
5083
5518
  esbuildOptions: sharedEsbuildOptions
@@ -5094,6 +5529,7 @@ async function rango(options) {
5094
5529
  }
5095
5530
  });
5096
5531
  plugins.push(createVirtualEntriesPlugin(finalEntries));
5532
+ plugins.push(performanceTracksPlugin());
5097
5533
  plugins.push(
5098
5534
  rsc({
5099
5535
  entries: finalEntries,
@@ -5157,7 +5593,7 @@ ${list}`);
5157
5593
  "react-dom",
5158
5594
  "react/jsx-runtime",
5159
5595
  "react/jsx-dev-runtime",
5160
- "rsc-html-stream/client"
5596
+ nested("rsc-html-stream/client")
5161
5597
  ],
5162
5598
  exclude: excludeDeps,
5163
5599
  esbuildOptions: sharedEsbuildOptions,
@@ -5174,7 +5610,9 @@ ${list}`);
5174
5610
  "react-dom/static.edge",
5175
5611
  "react/jsx-runtime",
5176
5612
  "react/jsx-dev-runtime",
5177
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5613
+ nested(
5614
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5615
+ )
5178
5616
  ],
5179
5617
  exclude: excludeDeps,
5180
5618
  esbuildOptions: sharedEsbuildOptions
@@ -5187,7 +5625,9 @@ ${list}`);
5187
5625
  "react",
5188
5626
  "react/jsx-runtime",
5189
5627
  "react/jsx-dev-runtime",
5190
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5628
+ nested(
5629
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5630
+ )
5191
5631
  ],
5192
5632
  esbuildOptions: sharedEsbuildOptions
5193
5633
  }
@@ -5212,12 +5652,7 @@ ${list}`);
5212
5652
  }
5213
5653
  });
5214
5654
  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);
5655
+ plugins.push(performanceTracksPlugin());
5221
5656
  plugins.push(
5222
5657
  rsc({
5223
5658
  entries: finalEntries
@@ -5258,7 +5693,8 @@ ${list}`);
5258
5693
  createRouterDiscoveryPlugin(discoveryEntryPath, {
5259
5694
  routerPathRef: discoveryRouterRef,
5260
5695
  enableBuildPrerender: prerenderEnabled,
5261
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration
5696
+ buildEnv: options?.buildEnv,
5697
+ preset
5262
5698
  })
5263
5699
  );
5264
5700
  return plugins;
@@ -5271,29 +5707,75 @@ function poke() {
5271
5707
  apply: "serve",
5272
5708
  configureServer(server) {
5273
5709
  const stdin = process.stdin;
5274
- const previousRawMode = stdin.isTTY ? stdin.isRaw : null;
5710
+ const debug = process.env.RANGO_POKE_DEBUG === "1";
5711
+ const triggerReload = (source) => {
5712
+ server.hot.send({ type: "full-reload", path: "*" });
5713
+ server.config.logger.info(` browser reload (${source})`, {
5714
+ timestamp: true
5715
+ });
5716
+ };
5717
+ const toBuffer = (chunk) => {
5718
+ return typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5719
+ };
5720
+ const formatChunk = (chunk) => {
5721
+ const data = toBuffer(chunk);
5722
+ const hex = Array.from(data).map((byte) => `0x${byte.toString(16).padStart(2, "0")}`).join(" ");
5723
+ const ascii = Array.from(data).map((byte) => {
5724
+ if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
5725
+ if (byte === 10) return "\\n";
5726
+ if (byte === 13) return "\\r";
5727
+ if (byte === 9) return "\\t";
5728
+ return ".";
5729
+ }).join("");
5730
+ return `len=${data.length} hex=[${hex}] ascii="${ascii}"`;
5731
+ };
5732
+ const readCtrlR = (chunk) => {
5733
+ const data = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5734
+ return data.length === 1 && data[0] === 18;
5735
+ };
5736
+ const readSubmittedCommands = (chunk) => {
5737
+ const text = toBuffer(chunk).toString("utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5738
+ if (!text.includes("\n")) return [];
5739
+ const lines = text.split("\n");
5740
+ lines.pop();
5741
+ return lines;
5742
+ };
5743
+ if (debug) {
5744
+ server.config.logger.info(
5745
+ ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
5746
+ { timestamp: true }
5747
+ );
5748
+ }
5275
5749
  if (stdin.isTTY) {
5276
- stdin.setRawMode(true);
5750
+ server.config.logger.info(
5751
+ " poke ready: press e + enter to reload browser (ctrl+r also works when available)",
5752
+ { timestamp: true }
5753
+ );
5277
5754
  }
5278
5755
  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)", {
5756
+ if (debug) {
5757
+ server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5287
5758
  timestamp: true
5288
5759
  });
5289
5760
  }
5761
+ if (readCtrlR(data)) {
5762
+ triggerReload("ctrl+r");
5763
+ return;
5764
+ }
5765
+ for (const command of readSubmittedCommands(data)) {
5766
+ if (command === "e") {
5767
+ triggerReload("e+enter");
5768
+ return;
5769
+ }
5770
+ if (command === "\x1Br") {
5771
+ triggerReload("option+r+enter");
5772
+ return;
5773
+ }
5774
+ }
5290
5775
  };
5291
5776
  stdin.on("data", onData);
5292
5777
  server.httpServer?.on("close", () => {
5293
5778
  stdin.off("data", onData);
5294
- if (stdin.isTTY && previousRawMode !== null) {
5295
- stdin.setRawMode(previousRawMode);
5296
- }
5297
5779
  });
5298
5780
  }
5299
5781
  };