@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.20dbba0c

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 (189) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +172 -50
  3. package/dist/bin/rango.js +138 -50
  4. package/dist/vite/index.js +1160 -508
  5. package/dist/vite/index.js.bak +5448 -0
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +17 -16
  8. package/skills/breadcrumbs/SKILL.md +252 -0
  9. package/skills/cache-guide/SKILL.md +32 -0
  10. package/skills/caching/SKILL.md +49 -8
  11. package/skills/document-cache/SKILL.md +2 -2
  12. package/skills/handler-use/SKILL.md +362 -0
  13. package/skills/hooks/SKILL.md +61 -51
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +20 -0
  16. package/skills/layout/SKILL.md +22 -0
  17. package/skills/links/SKILL.md +91 -17
  18. package/skills/loader/SKILL.md +107 -24
  19. package/skills/middleware/SKILL.md +34 -3
  20. package/skills/migrate-nextjs/SKILL.md +560 -0
  21. package/skills/migrate-react-router/SKILL.md +765 -0
  22. package/skills/parallel/SKILL.md +185 -0
  23. package/skills/prerender/SKILL.md +112 -70
  24. package/skills/rango/SKILL.md +24 -23
  25. package/skills/response-routes/SKILL.md +8 -0
  26. package/skills/route/SKILL.md +58 -4
  27. package/skills/router-setup/SKILL.md +95 -5
  28. package/skills/streams-and-websockets/SKILL.md +283 -0
  29. package/skills/typesafety/SKILL.md +38 -24
  30. package/src/__internal.ts +92 -0
  31. package/src/browser/app-shell.ts +52 -0
  32. package/src/browser/app-version.ts +14 -0
  33. package/src/browser/event-controller.ts +5 -0
  34. package/src/browser/link-interceptor.ts +4 -0
  35. package/src/browser/navigation-bridge.ts +175 -17
  36. package/src/browser/navigation-client.ts +177 -44
  37. package/src/browser/navigation-store.ts +68 -9
  38. package/src/browser/navigation-transaction.ts +11 -9
  39. package/src/browser/partial-update.ts +113 -17
  40. package/src/browser/prefetch/cache.ts +275 -28
  41. package/src/browser/prefetch/fetch.ts +191 -46
  42. package/src/browser/prefetch/policy.ts +6 -0
  43. package/src/browser/prefetch/queue.ts +123 -20
  44. package/src/browser/prefetch/resource-ready.ts +77 -0
  45. package/src/browser/rango-state.ts +53 -13
  46. package/src/browser/react/Link.tsx +98 -14
  47. package/src/browser/react/NavigationProvider.tsx +89 -14
  48. package/src/browser/react/context.ts +7 -2
  49. package/src/browser/react/use-handle.ts +9 -58
  50. package/src/browser/react/use-navigation.ts +22 -2
  51. package/src/browser/react/use-params.ts +11 -1
  52. package/src/browser/react/use-router.ts +29 -9
  53. package/src/browser/rsc-router.tsx +177 -66
  54. package/src/browser/scroll-restoration.ts +41 -42
  55. package/src/browser/segment-reconciler.ts +36 -9
  56. package/src/browser/server-action-bridge.ts +8 -6
  57. package/src/browser/types.ts +73 -5
  58. package/src/build/generate-manifest.ts +6 -6
  59. package/src/build/generate-route-types.ts +3 -0
  60. package/src/build/route-trie.ts +67 -25
  61. package/src/build/route-types/include-resolution.ts +8 -1
  62. package/src/build/route-types/router-processing.ts +223 -74
  63. package/src/build/route-types/scan-filter.ts +8 -1
  64. package/src/cache/cache-runtime.ts +15 -11
  65. package/src/cache/cache-scope.ts +48 -7
  66. package/src/cache/cf/cf-cache-store.ts +455 -15
  67. package/src/cache/cf/index.ts +5 -1
  68. package/src/cache/document-cache.ts +17 -7
  69. package/src/cache/index.ts +1 -0
  70. package/src/cache/taint.ts +55 -0
  71. package/src/client.rsc.tsx +2 -1
  72. package/src/client.tsx +85 -276
  73. package/src/context-var.ts +72 -2
  74. package/src/debug.ts +2 -2
  75. package/src/handle.ts +40 -0
  76. package/src/handles/breadcrumbs.ts +66 -0
  77. package/src/handles/index.ts +1 -0
  78. package/src/host/index.ts +0 -3
  79. package/src/index.rsc.ts +9 -36
  80. package/src/index.ts +79 -70
  81. package/src/outlet-context.ts +1 -1
  82. package/src/prerender/store.ts +57 -15
  83. package/src/prerender.ts +138 -77
  84. package/src/response-utils.ts +28 -0
  85. package/src/reverse.ts +27 -2
  86. package/src/route-definition/dsl-helpers.ts +240 -40
  87. package/src/route-definition/helpers-types.ts +67 -19
  88. package/src/route-definition/index.ts +3 -3
  89. package/src/route-definition/redirect.ts +11 -3
  90. package/src/route-definition/resolve-handler-use.ts +155 -0
  91. package/src/route-map-builder.ts +7 -1
  92. package/src/route-types.ts +18 -0
  93. package/src/router/content-negotiation.ts +100 -1
  94. package/src/router/find-match.ts +4 -2
  95. package/src/router/handler-context.ts +129 -26
  96. package/src/router/intercept-resolution.ts +11 -4
  97. package/src/router/lazy-includes.ts +10 -7
  98. package/src/router/loader-resolution.ts +160 -22
  99. package/src/router/logging.ts +5 -2
  100. package/src/router/manifest.ts +31 -16
  101. package/src/router/match-api.ts +128 -193
  102. package/src/router/match-middleware/background-revalidation.ts +30 -2
  103. package/src/router/match-middleware/cache-lookup.ts +94 -17
  104. package/src/router/match-middleware/cache-store.ts +53 -10
  105. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  106. package/src/router/match-middleware/segment-resolution.ts +61 -5
  107. package/src/router/match-result.ts +103 -18
  108. package/src/router/metrics.ts +238 -13
  109. package/src/router/middleware-types.ts +48 -27
  110. package/src/router/middleware.ts +201 -86
  111. package/src/router/navigation-snapshot.ts +182 -0
  112. package/src/router/pattern-matching.ts +77 -11
  113. package/src/router/prerender-match.ts +114 -10
  114. package/src/router/preview-match.ts +30 -102
  115. package/src/router/request-classification.ts +310 -0
  116. package/src/router/revalidation.ts +27 -7
  117. package/src/router/route-snapshot.ts +245 -0
  118. package/src/router/router-context.ts +6 -1
  119. package/src/router/router-interfaces.ts +50 -5
  120. package/src/router/router-options.ts +50 -19
  121. package/src/router/segment-resolution/fresh.ts +215 -19
  122. package/src/router/segment-resolution/helpers.ts +30 -25
  123. package/src/router/segment-resolution/loader-cache.ts +1 -0
  124. package/src/router/segment-resolution/revalidation.ts +454 -301
  125. package/src/router/segment-wrappers.ts +2 -0
  126. package/src/router/trie-matching.ts +30 -6
  127. package/src/router/types.ts +1 -0
  128. package/src/router/url-params.ts +49 -0
  129. package/src/router.ts +89 -17
  130. package/src/rsc/handler.ts +563 -364
  131. package/src/rsc/helpers.ts +69 -41
  132. package/src/rsc/index.ts +0 -20
  133. package/src/rsc/loader-fetch.ts +23 -3
  134. package/src/rsc/manifest-init.ts +5 -1
  135. package/src/rsc/progressive-enhancement.ts +37 -10
  136. package/src/rsc/response-route-handler.ts +14 -1
  137. package/src/rsc/rsc-rendering.ts +47 -44
  138. package/src/rsc/server-action.ts +24 -10
  139. package/src/rsc/ssr-setup.ts +128 -0
  140. package/src/rsc/types.ts +11 -1
  141. package/src/search-params.ts +16 -13
  142. package/src/segment-content-promise.ts +67 -0
  143. package/src/segment-loader-promise.ts +122 -0
  144. package/src/segment-system.tsx +109 -23
  145. package/src/server/context.ts +174 -19
  146. package/src/server/handle-store.ts +19 -0
  147. package/src/server/loader-registry.ts +9 -8
  148. package/src/server/request-context.ts +218 -65
  149. package/src/server.ts +6 -0
  150. package/src/ssr/index.tsx +4 -0
  151. package/src/static-handler.ts +18 -6
  152. package/src/theme/index.ts +4 -13
  153. package/src/types/cache-types.ts +4 -4
  154. package/src/types/handler-context.ts +140 -72
  155. package/src/types/loader-types.ts +41 -15
  156. package/src/types/request-scope.ts +126 -0
  157. package/src/types/route-config.ts +17 -8
  158. package/src/types/route-entry.ts +19 -1
  159. package/src/types/segments.ts +2 -5
  160. package/src/urls/include-helper.ts +24 -14
  161. package/src/urls/path-helper-types.ts +39 -6
  162. package/src/urls/path-helper.ts +48 -13
  163. package/src/urls/pattern-types.ts +12 -0
  164. package/src/urls/response-types.ts +18 -16
  165. package/src/use-loader.tsx +77 -5
  166. package/src/vite/discovery/bundle-postprocess.ts +61 -89
  167. package/src/vite/discovery/discover-routers.ts +7 -4
  168. package/src/vite/discovery/prerender-collection.ts +162 -88
  169. package/src/vite/discovery/state.ts +17 -13
  170. package/src/vite/index.ts +8 -3
  171. package/src/vite/plugin-types.ts +51 -79
  172. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  173. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  174. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  175. package/src/vite/plugins/expose-action-id.ts +1 -3
  176. package/src/vite/plugins/expose-id-utils.ts +12 -0
  177. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  178. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  179. package/src/vite/plugins/performance-tracks.ts +88 -0
  180. package/src/vite/plugins/refresh-cmd.ts +127 -0
  181. package/src/vite/plugins/version-plugin.ts +13 -1
  182. package/src/vite/rango.ts +190 -217
  183. package/src/vite/router-discovery.ts +241 -45
  184. package/src/vite/utils/banner.ts +4 -4
  185. package/src/vite/utils/package-resolution.ts +34 -1
  186. package/src/vite/utils/prerender-utils.ts +97 -5
  187. package/src/vite/utils/shared-utils.ts +3 -2
  188. package/skills/testing/SKILL.md +0 -226
  189. package/src/route-definition/route-function.ts +0 -119
@@ -1,6 +1,6 @@
1
1
  // src/vite/rango.ts
2
2
  import { readFileSync as readFileSync7 } from "node:fs";
3
- import { resolve as resolve8 } from "node:path";
3
+ import { resolve as resolve9 } from "node:path";
4
4
 
5
5
  // src/vite/plugins/expose-action-id.ts
6
6
  import MagicString from "magic-string";
@@ -18,6 +18,9 @@ function hashId(filePath, exportName) {
18
18
  const hash = crypto.createHash("sha256").update(input).digest("hex");
19
19
  return `${hash.slice(0, 8)}#${exportName}`;
20
20
  }
21
+ function makeStubId(filePath, exportName, isBuild) {
22
+ return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
23
+ }
21
24
  function hashInlineId(filePath, lineNumber, index) {
22
25
  const input = index !== void 0 && index > 0 ? `${filePath}:${lineNumber}:${index}` : `${filePath}:${lineNumber}`;
23
26
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
@@ -292,7 +295,7 @@ function exposeActionId() {
292
295
  }
293
296
  if (!rscPluginApi) {
294
297
  throw new Error(
295
- "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin.\nThe RSC plugin should be included automatically. If you disabled it with\nrango({ rsc: false }), add rsc() before rango() in your config."
298
+ "[rsc-router] Could not find @vitejs/plugin-rsc. @rangojs/router requires the Vite RSC plugin, which is included automatically by rango()."
296
299
  );
297
300
  }
298
301
  if (!isBuild) return;
@@ -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.20",
1868
+ version: "0.0.0-experimental.20dbba0c",
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,21 @@ 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
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
2091
+ ];
2092
+ const aliases = {};
2093
+ for (const spec of specs) {
2094
+ try {
2095
+ aliases[spec] = require2.resolve(spec);
2096
+ } catch {
2097
+ }
2098
+ }
2099
+ return aliases;
2100
+ }
1965
2101
 
1966
2102
  // src/build/route-types/param-extraction.ts
1967
2103
  function extractParamsFromPattern(pattern) {
@@ -2095,31 +2231,7 @@ declare global {
2095
2231
  }
2096
2232
 
2097
2233
  // src/build/route-types/scan-filter.ts
2098
- import { join, relative } from "node:path";
2099
2234
  import picomatch from "picomatch";
2100
- var DEFAULT_EXCLUDE_PATTERNS = [
2101
- "**/__tests__/**",
2102
- "**/__mocks__/**",
2103
- "**/dist/**",
2104
- "**/coverage/**",
2105
- "**/*.test.{ts,tsx,js,jsx}",
2106
- "**/*.spec.{ts,tsx,js,jsx}"
2107
- ];
2108
- function createScanFilter(root, opts) {
2109
- const { include, exclude } = opts;
2110
- const hasInclude = include && include.length > 0;
2111
- const hasCustomExclude = exclude !== void 0;
2112
- if (!hasInclude && !hasCustomExclude) return void 0;
2113
- const effectiveExclude = exclude ?? DEFAULT_EXCLUDE_PATTERNS;
2114
- const includeMatcher = hasInclude ? picomatch(include) : null;
2115
- const excludeMatcher = effectiveExclude.length > 0 ? picomatch(effectiveExclude) : null;
2116
- return (absolutePath) => {
2117
- const rel = relative(root, absolutePath);
2118
- if (excludeMatcher && excludeMatcher(rel)) return false;
2119
- if (includeMatcher) return includeMatcher(rel);
2120
- return true;
2121
- };
2122
- }
2123
2235
 
2124
2236
  // src/build/route-types/per-module-writer.ts
2125
2237
  import ts4 from "typescript";
@@ -2341,7 +2453,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2341
2453
  }
2342
2454
  return routeMap;
2343
2455
  }
2344
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
2456
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
2345
2457
  visited = visited ?? /* @__PURE__ */ new Set();
2346
2458
  const realPath = resolve2(filePath);
2347
2459
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -2357,7 +2469,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2357
2469
  return { routes: {}, searchSchemas: {} };
2358
2470
  }
2359
2471
  let block;
2360
- if (variableName) {
2472
+ if (inlineBlock) {
2473
+ block = inlineBlock;
2474
+ } else if (variableName) {
2361
2475
  const extracted = extractUrlsBlockForVariable(source, variableName);
2362
2476
  if (!extracted) return { routes: {}, searchSchemas: {} };
2363
2477
  block = extracted;
@@ -2386,7 +2500,7 @@ import {
2386
2500
  readdirSync
2387
2501
  } from "node:fs";
2388
2502
  import {
2389
- join as join2,
2503
+ join,
2390
2504
  dirname as dirname2,
2391
2505
  resolve as resolve3,
2392
2506
  sep,
@@ -2406,7 +2520,7 @@ function countPublicRouteEntries(source) {
2406
2520
  }
2407
2521
  var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
2408
2522
  function isRoutableSourceFile(name) {
2409
- return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.");
2523
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
2410
2524
  }
2411
2525
  function findRouterFilesRecursive(dir, filter, results) {
2412
2526
  let entries;
@@ -2421,9 +2535,10 @@ function findRouterFilesRecursive(dir, filter, results) {
2421
2535
  const childDirs = [];
2422
2536
  const routerFilesInDir = [];
2423
2537
  for (const entry of entries) {
2424
- const fullPath = join2(dir, entry.name);
2538
+ const fullPath = join(dir, entry.name);
2425
2539
  if (entry.isDirectory()) {
2426
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2540
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === "__tests__" || entry.name === "__mocks__" || entry.name.startsWith("."))
2541
+ continue;
2427
2542
  childDirs.push(fullPath);
2428
2543
  continue;
2429
2544
  }
@@ -2475,7 +2590,7 @@ Router root: ${conflict.ancestor}
2475
2590
  Nested router: ${conflict.nested}
2476
2591
  Move the nested router into a sibling directory or configure it as a separate app root.`;
2477
2592
  }
2478
- function extractUrlsVariableFromRouter(code) {
2593
+ function extractUrlsFromRouter(code) {
2479
2594
  const sourceFile = ts5.createSourceFile(
2480
2595
  "router.tsx",
2481
2596
  code,
@@ -2489,24 +2604,70 @@ function extractUrlsVariableFromRouter(code) {
2489
2604
  const callee = node.expression;
2490
2605
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
2491
2606
  }
2607
+ function isInlineBuilder(node) {
2608
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
2609
+ }
2610
+ function isRoutesOnCreateRouter(node) {
2611
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
2612
+ return false;
2613
+ let inner = node.expression.expression;
2614
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2615
+ inner = inner.expression.expression;
2616
+ }
2617
+ return isCreateRouterCall(inner);
2618
+ }
2492
2619
  function visit(node) {
2493
2620
  if (result) return;
2494
- if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
2495
- let inner = node.expression.expression;
2496
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2497
- inner = inner.expression.expression;
2498
- }
2499
- if (isCreateRouterCall(inner)) {
2500
- result = node.arguments[0].text;
2501
- return;
2621
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
2622
+ const arg = node.arguments[0];
2623
+ if (ts5.isIdentifier(arg)) {
2624
+ result = { kind: "variable", name: arg.text };
2625
+ } else if (isInlineBuilder(arg)) {
2626
+ result = { kind: "inline", block: arg.getText(sourceFile) };
2502
2627
  }
2628
+ return;
2503
2629
  }
2504
2630
  if (isCreateRouterCall(node)) {
2505
2631
  const callExpr = node;
2506
- for (const arg of callExpr.arguments) {
2632
+ for (const callArg of callExpr.arguments) {
2633
+ if (ts5.isObjectLiteralExpression(callArg)) {
2634
+ for (const prop of callArg.properties) {
2635
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
2636
+ if (ts5.isIdentifier(prop.initializer)) {
2637
+ result = { kind: "variable", name: prop.initializer.text };
2638
+ } else if (isInlineBuilder(prop.initializer)) {
2639
+ result = {
2640
+ kind: "inline",
2641
+ block: prop.initializer.getText(sourceFile)
2642
+ };
2643
+ }
2644
+ return;
2645
+ }
2646
+ }
2647
+ }
2648
+ }
2649
+ }
2650
+ ts5.forEachChild(node, visit);
2651
+ }
2652
+ visit(sourceFile);
2653
+ return result;
2654
+ }
2655
+ function extractBasenameFromRouter(code) {
2656
+ const sourceFile = ts5.createSourceFile(
2657
+ "router.tsx",
2658
+ code,
2659
+ ts5.ScriptTarget.Latest,
2660
+ true,
2661
+ ts5.ScriptKind.TSX
2662
+ );
2663
+ let result;
2664
+ function visit(node) {
2665
+ if (result !== void 0) return;
2666
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
2667
+ for (const arg of node.arguments) {
2507
2668
  if (ts5.isObjectLiteralExpression(arg)) {
2508
2669
  for (const prop of arg.properties) {
2509
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
2670
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
2510
2671
  result = prop.initializer.text;
2511
2672
  return;
2512
2673
  }
@@ -2519,6 +2680,19 @@ function extractUrlsVariableFromRouter(code) {
2519
2680
  visit(sourceFile);
2520
2681
  return result;
2521
2682
  }
2683
+ function applyBasenameToRoutes(result, basename3) {
2684
+ const prefixed = {};
2685
+ for (const [name, pattern] of Object.entries(result.routes)) {
2686
+ if (pattern === "/") {
2687
+ prefixed[name] = basename3;
2688
+ } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2689
+ prefixed[name] = basename3 + pattern.slice(1);
2690
+ } else {
2691
+ prefixed[name] = basename3 + pattern;
2692
+ }
2693
+ }
2694
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
2695
+ }
2522
2696
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2523
2697
  let routerSource;
2524
2698
  try {
@@ -2526,19 +2700,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2526
2700
  } catch {
2527
2701
  return { routes: {}, searchSchemas: {} };
2528
2702
  }
2529
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2530
- if (!urlsVarName) {
2703
+ const extraction = extractUrlsFromRouter(routerSource);
2704
+ if (!extraction) {
2531
2705
  return { routes: {}, searchSchemas: {} };
2532
2706
  }
2533
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2534
- if (imported) {
2535
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2536
- if (!targetFile) {
2537
- return { routes: {}, searchSchemas: {} };
2707
+ const rawBasename = extractBasenameFromRouter(routerSource);
2708
+ const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2709
+ let result;
2710
+ if (extraction.kind === "inline") {
2711
+ result = buildCombinedRouteMapWithSearch(
2712
+ routerFilePath,
2713
+ void 0,
2714
+ void 0,
2715
+ void 0,
2716
+ extraction.block
2717
+ );
2718
+ } else {
2719
+ const imported = resolveImportedVariable(routerSource, extraction.name);
2720
+ if (imported) {
2721
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2722
+ if (!targetFile) {
2723
+ return { routes: {}, searchSchemas: {} };
2724
+ }
2725
+ result = buildCombinedRouteMapWithSearch(
2726
+ targetFile,
2727
+ imported.exportedName
2728
+ );
2729
+ } else {
2730
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2538
2731
  }
2539
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2540
2732
  }
2541
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2733
+ if (basename3) {
2734
+ result = applyBasenameToRoutes(result, basename3);
2735
+ }
2736
+ return result;
2542
2737
  }
2543
2738
  function findRouterFiles(root, filter) {
2544
2739
  const result = [];
@@ -2547,7 +2742,7 @@ function findRouterFiles(root, filter) {
2547
2742
  }
2548
2743
  function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2549
2744
  try {
2550
- const oldCombinedPath = join2(root, "src", "named-routes.gen.ts");
2745
+ const oldCombinedPath = join(root, "src", "named-routes.gen.ts");
2551
2746
  if (existsSync3(oldCombinedPath)) {
2552
2747
  unlinkSync(oldCombinedPath);
2553
2748
  console.log(
@@ -2563,31 +2758,21 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2563
2758
  throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
2564
2759
  }
2565
2760
  for (const routerFilePath of routerFilePaths) {
2566
- let routerSource;
2567
- try {
2568
- routerSource = readFileSync2(routerFilePath, "utf-8");
2569
- } catch {
2570
- continue;
2571
- }
2572
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2573
- if (!urlsVarName) continue;
2574
- let result;
2575
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2576
- if (imported) {
2577
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2578
- if (!targetFile) continue;
2579
- result = buildCombinedRouteMapWithSearch(
2580
- targetFile,
2581
- imported.exportedName
2582
- );
2583
- } else {
2584
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2761
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
2762
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
2763
+ let routerSource;
2764
+ try {
2765
+ routerSource = readFileSync2(routerFilePath, "utf-8");
2766
+ } catch {
2767
+ continue;
2768
+ }
2769
+ if (!extractUrlsFromRouter(routerSource)) continue;
2585
2770
  }
2586
2771
  const routerBasename = pathBasename(routerFilePath).replace(
2587
2772
  /\.(tsx?|jsx?)$/,
2588
2773
  ""
2589
2774
  );
2590
- const outPath = join2(
2775
+ const outPath = join(
2591
2776
  dirname2(routerFilePath),
2592
2777
  `${routerBasename}.named-routes.gen.ts`
2593
2778
  );
@@ -2717,8 +2902,9 @@ function createVersionPlugin() {
2717
2902
  let isDev = false;
2718
2903
  let server = null;
2719
2904
  const clientModuleSignatures = /* @__PURE__ */ new Map();
2905
+ let versionCounter = 0;
2720
2906
  const bumpVersion = (reason) => {
2721
- currentVersion = Date.now().toString(16);
2907
+ currentVersion = Date.now().toString(16) + String(++versionCounter);
2722
2908
  console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
2723
2909
  const rscEnv = server?.environments?.rsc;
2724
2910
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
@@ -2774,6 +2960,9 @@ function createVersionPlugin() {
2774
2960
  if (!isDev) return;
2775
2961
  const isRscModule = this.environment?.name === "rsc";
2776
2962
  if (!isRscModule) return;
2963
+ if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
2964
+ return;
2965
+ }
2777
2966
  if (isCodeModule(ctx.file)) {
2778
2967
  const filePath = normalizeModuleId(ctx.file);
2779
2968
  const previousSignature = clientModuleSignatures.get(filePath);
@@ -2803,6 +2992,68 @@ function createVersionPlugin() {
2803
2992
 
2804
2993
  // src/vite/utils/shared-utils.ts
2805
2994
  import * as Vite from "vite";
2995
+
2996
+ // src/vite/plugins/performance-tracks.ts
2997
+ import { readFile } from "node:fs/promises";
2998
+ var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2999
+ function buildPatchReplacement(match, debugInfoVar) {
3000
+ return `${match}
3001
+ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
3002
+ var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
3003
+ if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
3004
+ ${debugInfoVar} = _resolved._debugInfo;
3005
+ }
3006
+ }`;
3007
+ }
3008
+ function patchRsdwClientDebugInfoRecovery(code) {
3009
+ const match = code.match(RSDW_PATCH_RE);
3010
+ if (!match) {
3011
+ return { code, debugInfoVar: null };
3012
+ }
3013
+ return {
3014
+ code: code.replace(match[1], buildPatchReplacement(match[1], match[2])),
3015
+ debugInfoVar: match[2]
3016
+ };
3017
+ }
3018
+ function performanceTracksOptimizeDepsPlugin() {
3019
+ return {
3020
+ name: "@rangojs/router:performance-tracks-optimize-deps",
3021
+ setup(build) {
3022
+ build.onLoad(
3023
+ {
3024
+ filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
3025
+ },
3026
+ async (args) => {
3027
+ const code = await readFile(args.path, "utf8");
3028
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3029
+ return {
3030
+ contents: patched.code,
3031
+ loader: "js"
3032
+ };
3033
+ }
3034
+ );
3035
+ }
3036
+ };
3037
+ }
3038
+ function performanceTracksPlugin() {
3039
+ return {
3040
+ name: "@rangojs/router:performance-tracks",
3041
+ transform(code, id) {
3042
+ if (!id.includes("react-server-dom") || !id.includes("client")) return;
3043
+ const patched = patchRsdwClientDebugInfoRecovery(code);
3044
+ if (!patched.debugInfoVar) return;
3045
+ if (process.env.INTERNAL_RANGO_DEBUG)
3046
+ console.log(
3047
+ "[perf-tracks] patched RSDW client (var:",
3048
+ patched.debugInfoVar,
3049
+ ")"
3050
+ );
3051
+ return patched.code;
3052
+ }
3053
+ };
3054
+ }
3055
+
3056
+ // src/vite/utils/shared-utils.ts
2806
3057
  var versionEsbuildPlugin = {
2807
3058
  name: "@rangojs/router-version",
2808
3059
  setup(build) {
@@ -2820,7 +3071,7 @@ var versionEsbuildPlugin = {
2820
3071
  }
2821
3072
  };
2822
3073
  var sharedEsbuildOptions = {
2823
- plugins: [versionEsbuildPlugin]
3074
+ plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
2824
3075
  };
2825
3076
  function createVirtualEntriesPlugin(entries, routerPathRef) {
2826
3077
  const virtualModules = {};
@@ -2903,11 +3154,11 @@ ${dim} \u2571${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2
2903
3154
  ${dim} ${reset}${bold}\u2551 \u2551${reset} ${bold}\u2554\u2550\u2557${reset}${dim} * \u2727. \u2571${reset}
2904
3155
  ${dim} ${reset}${bold}\u2554\u2557 \u2551 \u2551 \u2551 \u2551${reset}${dim} * \u2571${reset}
2905
3156
  ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2551 \u2551 \u2551 \u2566\u2550\u2557\u2554\u2550\u2557\u2554\u2557\u2554\u2554\u2550\u2557\u2554\u2550\u2557${reset}${dim} \u2727 \u2726${reset}
2906
- ${dim} ${reset}${bold}\u2550\u2563\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
3157
+ ${dim} ${reset}${bold}\u2551\u2551 \u2551 \u2560\u2550\u255D \u2551 \u2560\u2566\u255D\u2560\u2550\u2563\u2551\u2551\u2551\u2551 \u2566\u2551 \u2551${reset}${dim} * \u2727${reset}
2907
3158
  ${dim} ${reset}${bold}\u2551\u255A\u2550\u255D \u2554\u2550\u2550\u2550\u255D \u2569\u255A\u2550\u2569 \u2569\u255D\u255A\u255D\u255A\u2550\u255D\u255A\u2550\u255D${reset}${dim} \u2726 . *${reset}
2908
3159
  ${dim} ${reset}${bold}\u255A\u2550\u2550\u2557 \u2551${reset}${dim} * RSC Wrangler \u2727 \u2726${reset}
2909
- ${dim} * ${reset}${bold}\u2551 \u2560\u2550${reset}${dim} * \u2727. \u2571${reset}
2910
- ${bold}\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2569\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
3160
+ ${dim} * ${reset}${bold}\u2551 \u2551${reset}${dim} * \u2727. \u2571${reset}
3161
+ ${dim} ${reset}${bold}\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550${reset}${dim} \u2726 *${reset}
2911
3162
 
2912
3163
  v${version} \xB7 ${preset} \xB7 ${mode}
2913
3164
  `;
@@ -3024,8 +3275,10 @@ function createCjsToEsmPlugin() {
3024
3275
 
3025
3276
  // src/vite/router-discovery.ts
3026
3277
  import { createServer as createViteServer } from "vite";
3027
- import { resolve as resolve7 } from "node:path";
3278
+ import { resolve as resolve8 } from "node:path";
3028
3279
  import { readFileSync as readFileSync6 } from "node:fs";
3280
+ import { createRequire as createRequire2, register } from "node:module";
3281
+ import { pathToFileURL } from "node:url";
3029
3282
 
3030
3283
  // src/vite/plugins/virtual-stub-plugin.ts
3031
3284
  function createVirtualStubPlugin() {
@@ -3051,8 +3304,115 @@ function createVirtualStubPlugin() {
3051
3304
  };
3052
3305
  }
3053
3306
 
3307
+ // src/vite/plugins/cloudflare-protocol-stub.ts
3308
+ var VIRTUAL_PREFIX = "virtual:rango-cloudflare-stub-";
3309
+ var NULL_PREFIX = "\0" + VIRTUAL_PREFIX;
3310
+ var CF_PREFIX = "cloudflare:";
3311
+ var BUILD_ENV_GLOBAL_KEY = "__rango_build_env__";
3312
+ var SOURCE_EXT_RE = /\.[mc]?[jt]sx?$/;
3313
+ var IMPORT_NODE_TYPES = /* @__PURE__ */ new Set([
3314
+ "ImportDeclaration",
3315
+ "ImportExpression",
3316
+ "ExportNamedDeclaration",
3317
+ "ExportAllDeclaration"
3318
+ ]);
3319
+ var STUBS = {
3320
+ "cloudflare:workers": `
3321
+ export class DurableObject { constructor(_ctx, _env) {} }
3322
+ export class WorkerEntrypoint { constructor(_ctx, _env) {} }
3323
+ export class WorkflowEntrypoint { constructor(_ctx, _env) {} }
3324
+ export class RpcTarget {}
3325
+ export const env = globalThis[${JSON.stringify(BUILD_ENV_GLOBAL_KEY)}] ?? {};
3326
+ export default {};
3327
+ `,
3328
+ "cloudflare:email": `
3329
+ export class EmailMessage { constructor(_from, _to, _raw) {} }
3330
+ export default {};
3331
+ `,
3332
+ "cloudflare:sockets": `
3333
+ export function connect() { return {}; }
3334
+ export default {};
3335
+ `,
3336
+ "cloudflare:workflows": `
3337
+ export class NonRetryableError extends Error {
3338
+ constructor(message, name) { super(message); this.name = name ?? "NonRetryableError"; }
3339
+ }
3340
+ export default {};
3341
+ `
3342
+ };
3343
+ var FALLBACK_STUB = `export default {};
3344
+ `;
3345
+ function createCloudflareProtocolStubPlugin() {
3346
+ return {
3347
+ name: "@rangojs/router:cloudflare-protocol-stub",
3348
+ transform(code, id) {
3349
+ const cleanId = id.split("?")[0] ?? id;
3350
+ if (!SOURCE_EXT_RE.test(cleanId)) return null;
3351
+ if (!code.includes(CF_PREFIX)) return null;
3352
+ let ast;
3353
+ try {
3354
+ ast = this.parse(code);
3355
+ } catch {
3356
+ return null;
3357
+ }
3358
+ const hits = [];
3359
+ walk(ast, (node) => {
3360
+ if (!IMPORT_NODE_TYPES.has(node.type)) return;
3361
+ const source = node.source;
3362
+ if (!source || source.type !== "Literal") return;
3363
+ if (typeof source.value !== "string") return;
3364
+ if (!source.value.startsWith(CF_PREFIX)) return;
3365
+ if (typeof source.start !== "number" || typeof source.end !== "number")
3366
+ return;
3367
+ hits.push({
3368
+ start: source.start,
3369
+ end: source.end,
3370
+ value: source.value
3371
+ });
3372
+ });
3373
+ if (hits.length === 0) return null;
3374
+ hits.sort((a, b) => b.start - a.start);
3375
+ let out = code;
3376
+ for (const hit of hits) {
3377
+ const submodule = hit.value.slice(CF_PREFIX.length);
3378
+ const quote = code[hit.start] === "'" ? "'" : '"';
3379
+ out = out.slice(0, hit.start) + quote + VIRTUAL_PREFIX + submodule + quote + out.slice(hit.end);
3380
+ }
3381
+ return { code: out, map: null };
3382
+ },
3383
+ resolveId(id) {
3384
+ if (id.startsWith(VIRTUAL_PREFIX)) {
3385
+ return "\0" + id;
3386
+ }
3387
+ return null;
3388
+ },
3389
+ load(id) {
3390
+ if (!id.startsWith(NULL_PREFIX)) return null;
3391
+ const submodule = id.slice(NULL_PREFIX.length);
3392
+ const specifier = CF_PREFIX + submodule;
3393
+ return STUBS[specifier] ?? FALLBACK_STUB;
3394
+ }
3395
+ };
3396
+ }
3397
+ function walk(node, visit) {
3398
+ if (!node || typeof node !== "object") return;
3399
+ if (Array.isArray(node)) {
3400
+ for (const child of node) walk(child, visit);
3401
+ return;
3402
+ }
3403
+ const n = node;
3404
+ if (typeof n.type !== "string") return;
3405
+ visit(n);
3406
+ for (const key in n) {
3407
+ if (key === "loc" || key === "start" || key === "end" || key === "range") {
3408
+ continue;
3409
+ }
3410
+ walk(n[key], visit);
3411
+ }
3412
+ }
3413
+
3054
3414
  // src/vite/plugins/client-ref-hashing.ts
3055
- import { relative as relative2 } from "node:path";
3415
+ import { relative } from "node:path";
3056
3416
  import { createHash as createHash2 } from "node:crypto";
3057
3417
  var CLIENT_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-package-proxy/";
3058
3418
  var CLIENT_IN_SERVER_PKG_PROXY_PREFIX = "/@id/__x00__virtual:vite-rsc/client-in-server-package-proxy/";
@@ -3065,10 +3425,10 @@ function computeProductionHash(projectRoot, refKey) {
3065
3425
  const absPath = decodeURIComponent(
3066
3426
  refKey.slice(CLIENT_IN_SERVER_PKG_PROXY_PREFIX.length)
3067
3427
  );
3068
- toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3428
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3069
3429
  } else if (refKey.startsWith(FS_PREFIX)) {
3070
3430
  const absPath = refKey.slice(FS_PREFIX.length - 1);
3071
- toHash = relative2(projectRoot, absPath).replaceAll("\\", "/");
3431
+ toHash = relative(projectRoot, absPath).replaceAll("\\", "/");
3072
3432
  } else if (refKey.startsWith("/")) {
3073
3433
  toHash = refKey.slice(1);
3074
3434
  } else {
@@ -3207,10 +3567,10 @@ function createDiscoveryState(entryPath, opts) {
3207
3567
  perRouterTrieMap: /* @__PURE__ */ new Map(),
3208
3568
  perRouterPrecomputedMap: /* @__PURE__ */ new Map(),
3209
3569
  perRouterManifestDataMap: /* @__PURE__ */ new Map(),
3210
- prerenderCollectedData: null,
3211
- staticCollectedData: null,
3212
- handlerChunkInfo: null,
3213
- staticHandlerChunkInfo: null,
3570
+ prerenderManifestEntries: null,
3571
+ staticManifestEntries: null,
3572
+ handlerChunkInfoMap: /* @__PURE__ */ new Map(),
3573
+ staticHandlerChunkInfoMap: /* @__PURE__ */ new Map(),
3214
3574
  rscEntryFileName: null,
3215
3575
  resolvedPrerenderModules: void 0,
3216
3576
  resolvedStaticModules: void 0,
@@ -3293,8 +3653,17 @@ function jsonParseExpression(value) {
3293
3653
  }
3294
3654
 
3295
3655
  // src/context-var.ts
3656
+ var NON_CACHEABLE_KEYS = /* @__PURE__ */ Symbol.for(
3657
+ "rango:non-cacheable-keys"
3658
+ );
3659
+ function getNonCacheableKeys(variables) {
3660
+ if (!variables[NON_CACHEABLE_KEYS]) {
3661
+ variables[NON_CACHEABLE_KEYS] = /* @__PURE__ */ new Set();
3662
+ }
3663
+ return variables[NON_CACHEABLE_KEYS];
3664
+ }
3296
3665
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3297
- function contextSet(variables, keyOrVar, value) {
3666
+ function contextSet(variables, keyOrVar, value, options) {
3298
3667
  if (typeof keyOrVar === "string") {
3299
3668
  if (FORBIDDEN_KEYS.has(keyOrVar)) {
3300
3669
  throw new Error(
@@ -3302,12 +3671,28 @@ function contextSet(variables, keyOrVar, value) {
3302
3671
  );
3303
3672
  }
3304
3673
  variables[keyOrVar] = value;
3674
+ if (options?.cache === false) {
3675
+ getNonCacheableKeys(variables).add(keyOrVar);
3676
+ }
3305
3677
  } else {
3306
3678
  variables[keyOrVar.key] = value;
3679
+ if (options?.cache === false) {
3680
+ getNonCacheableKeys(variables).add(keyOrVar.key);
3681
+ }
3307
3682
  }
3308
3683
  }
3309
3684
 
3310
3685
  // src/vite/utils/prerender-utils.ts
3686
+ import { createHash as createHash4 } from "node:crypto";
3687
+ import {
3688
+ copyFileSync,
3689
+ existsSync as existsSync4,
3690
+ mkdirSync,
3691
+ rmSync,
3692
+ statSync,
3693
+ writeFileSync as writeFileSync2
3694
+ } from "node:fs";
3695
+ import { resolve as resolve5 } from "node:path";
3311
3696
  function escapeRegExp2(str) {
3312
3697
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3313
3698
  }
@@ -3316,13 +3701,31 @@ function encodePathParam(value) {
3316
3701
  }
3317
3702
  function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3318
3703
  let result = pattern;
3704
+ let hadOmittedOptional = false;
3319
3705
  for (const [key, value] of Object.entries(params)) {
3320
3706
  const escaped = escapeRegExp2(key);
3321
- result = result.replace(
3322
- new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3323
- encode(value)
3324
- );
3325
- result = result.replace(`*${key}`, encode(value));
3707
+ if (value === "") {
3708
+ result = result.replace(
3709
+ new RegExp(`:${escaped}(\\([^)]*\\))?(?!\\?)`),
3710
+ ""
3711
+ );
3712
+ result = result.replace(`*${key}`, "");
3713
+ } else {
3714
+ result = result.replace(
3715
+ new RegExp(`:${escaped}(\\([^)]*\\))?\\??`),
3716
+ encode(value)
3717
+ );
3718
+ result = result.replace(`*${key}`, encode(value));
3719
+ }
3720
+ }
3721
+ result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3722
+ hadOmittedOptional = true;
3723
+ return "";
3724
+ });
3725
+ if (hadOmittedOptional) {
3726
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
3727
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
3728
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
3326
3729
  }
3327
3730
  return result;
3328
3731
  }
@@ -3386,6 +3789,37 @@ function notifyOnError(registry, error, phase, routeKey, pathname, skipped) {
3386
3789
  break;
3387
3790
  }
3388
3791
  }
3792
+ function getStagedAssetDir(projectRoot) {
3793
+ return resolve5(projectRoot, "node_modules/.rangojs-router-build/rsc-assets");
3794
+ }
3795
+ function resetStagedBuildAssets(projectRoot) {
3796
+ rmSync(getStagedAssetDir(projectRoot), { recursive: true, force: true });
3797
+ }
3798
+ function stageBuildAssetModule(projectRoot, prefix, exportValue) {
3799
+ const stagedDir = getStagedAssetDir(projectRoot);
3800
+ mkdirSync(stagedDir, { recursive: true });
3801
+ const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
3802
+ const fileName = `${prefix}-${contentHash}.js`;
3803
+ const filePath = resolve5(stagedDir, fileName);
3804
+ if (!existsSync4(filePath)) {
3805
+ writeFileSync2(filePath, `export default ${exportValue};
3806
+ `);
3807
+ }
3808
+ return fileName;
3809
+ }
3810
+ function copyStagedBuildAssets(projectRoot, fileNames) {
3811
+ const stagedDir = getStagedAssetDir(projectRoot);
3812
+ const distAssetsDir = resolve5(projectRoot, "dist/rsc/assets");
3813
+ mkdirSync(distAssetsDir, { recursive: true });
3814
+ let totalBytes = 0;
3815
+ for (const fileName of new Set(fileNames)) {
3816
+ const stagedPath = resolve5(stagedDir, fileName);
3817
+ const distPath = resolve5(distAssetsDir, fileName);
3818
+ copyFileSync(stagedPath, distPath);
3819
+ totalBytes += statSync(stagedPath).size;
3820
+ }
3821
+ return totalBytes;
3822
+ }
3389
3823
 
3390
3824
  // src/vite/discovery/prerender-collection.ts
3391
3825
  async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
@@ -3401,84 +3835,126 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3401
3835
  if (!params) return pattern;
3402
3836
  return substituteRouteParams(pattern, params);
3403
3837
  };
3838
+ let resolvedRoutes = 0;
3839
+ let totalDynamic = 0;
3404
3840
  for (const { manifest } of allManifests) {
3405
3841
  if (!manifest.prerenderRoutes) continue;
3406
- const defs = manifest._prerenderDefs || {};
3407
3842
  for (const routeName of manifest.prerenderRoutes) {
3408
3843
  const pattern = manifest.routeManifest[routeName];
3409
- if (!pattern) continue;
3410
- const def = defs[routeName];
3411
- const isPassthroughRoute = !!def?.options?.passthrough;
3412
- const hasDynamic = pattern.includes(":") || pattern.includes("*");
3413
- if (!hasDynamic) {
3414
- entries.push({
3415
- urlPath: pattern.replace(/\/$/, "") || "/",
3416
- routeName,
3417
- concurrency: 1,
3418
- isPassthroughRoute
3419
- });
3420
- } else {
3421
- if (def?.getParams) {
3422
- try {
3423
- const buildVars = {};
3424
- const getParamsCtx = {
3425
- build: true,
3426
- set: ((keyOrVar, value) => {
3427
- contextSet(buildVars, keyOrVar, value);
3428
- }),
3429
- reverse: getParamsReverse
3430
- };
3431
- const paramsList = await def.getParams(getParamsCtx);
3432
- const concurrency = def.options?.concurrency ?? 1;
3433
- const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3434
- for (const params of paramsList) {
3435
- let url = substituteRouteParams(
3436
- pattern,
3437
- params,
3438
- encodePathParam
3439
- );
3440
- if (url.includes("*")) {
3441
- const wildcardValue = params["*"] ?? params.splat;
3442
- if (wildcardValue !== void 0) {
3443
- url = url.replace(/\*[^/]*$/, encodePathParam(wildcardValue));
3844
+ if (pattern && (pattern.includes(":") || pattern.includes("*"))) {
3845
+ totalDynamic++;
3846
+ }
3847
+ }
3848
+ }
3849
+ const paramsStart = performance.now();
3850
+ const progressInterval = totalDynamic > 0 ? setInterval(() => {
3851
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3852
+ console.log(
3853
+ `[rsc-router] Resolving prerender params... ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3854
+ );
3855
+ }, 5e3) : void 0;
3856
+ try {
3857
+ for (const { manifest } of allManifests) {
3858
+ if (!manifest.prerenderRoutes) continue;
3859
+ const defs = manifest._prerenderDefs || {};
3860
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
3861
+ for (const routeName of manifest.prerenderRoutes) {
3862
+ const pattern = manifest.routeManifest[routeName];
3863
+ if (!pattern) continue;
3864
+ const def = defs[routeName];
3865
+ const isPassthroughRoute = passthroughSet.has(routeName);
3866
+ const hasDynamic = pattern.includes(":") || pattern.includes("*");
3867
+ if (!hasDynamic) {
3868
+ entries.push({
3869
+ urlPath: pattern.replace(/\/$/, "") || "/",
3870
+ routeName,
3871
+ concurrency: 1,
3872
+ isPassthroughRoute
3873
+ });
3874
+ } else {
3875
+ if (def?.getParams) {
3876
+ try {
3877
+ const buildVars = {};
3878
+ const buildEnv = state.resolvedBuildEnv;
3879
+ const getParamsCtx = {
3880
+ build: true,
3881
+ dev: !state.isBuildMode,
3882
+ set: ((keyOrVar, value) => {
3883
+ contextSet(buildVars, keyOrVar, value);
3884
+ }),
3885
+ reverse: getParamsReverse,
3886
+ get env() {
3887
+ if (buildEnv !== void 0) return buildEnv;
3888
+ throw new Error(
3889
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3890
+ );
3444
3891
  }
3892
+ };
3893
+ const paramsList = await def.getParams(getParamsCtx);
3894
+ const concurrency = def.options?.concurrency ?? 1;
3895
+ const hasBuildVars = Object.keys(buildVars).length > 0 || Object.getOwnPropertySymbols(buildVars).length > 0;
3896
+ for (const params of paramsList) {
3897
+ let url = substituteRouteParams(
3898
+ pattern,
3899
+ params,
3900
+ encodePathParam
3901
+ );
3902
+ if (url.includes("*")) {
3903
+ const wildcardValue = params["*"] ?? params.splat;
3904
+ if (wildcardValue !== void 0) {
3905
+ url = url.replace(
3906
+ /\*[^/]*$/,
3907
+ encodePathParam(wildcardValue)
3908
+ );
3909
+ }
3910
+ }
3911
+ entries.push({
3912
+ urlPath: url.replace(/\/$/, "") || "/",
3913
+ routeName,
3914
+ concurrency,
3915
+ ...hasBuildVars ? { buildVars } : {},
3916
+ isPassthroughRoute
3917
+ });
3445
3918
  }
3446
- entries.push({
3447
- urlPath: url.replace(/\/$/, "") || "/",
3448
- routeName,
3449
- concurrency,
3450
- ...hasBuildVars ? { buildVars } : {},
3451
- isPassthroughRoute
3452
- });
3453
- }
3454
- } catch (err) {
3455
- if (err.name === "Skip") {
3456
- console.log(
3457
- `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3458
- );
3459
- notifyOnError(
3460
- registry,
3461
- err,
3462
- "prerender",
3463
- routeName,
3464
- void 0,
3465
- true
3919
+ resolvedRoutes++;
3920
+ } catch (err) {
3921
+ resolvedRoutes++;
3922
+ if (err.name === "Skip") {
3923
+ console.log(
3924
+ `[rsc-router] SKIP route "${routeName}" - ${err.message}`
3925
+ );
3926
+ notifyOnError(
3927
+ registry,
3928
+ err,
3929
+ "prerender",
3930
+ routeName,
3931
+ void 0,
3932
+ true
3933
+ );
3934
+ continue;
3935
+ }
3936
+ console.error(
3937
+ `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3466
3938
  );
3467
- continue;
3939
+ notifyOnError(registry, err, "prerender", routeName);
3940
+ throw err;
3468
3941
  }
3469
- console.error(
3470
- `[rsc-router] Failed to get params for prerender route "${routeName}": ${err.message}`
3942
+ } else {
3943
+ console.warn(
3944
+ `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3471
3945
  );
3472
- notifyOnError(registry, err, "prerender", routeName);
3473
- throw err;
3474
3946
  }
3475
- } else {
3476
- console.warn(
3477
- `[rsc-router] Dynamic prerender route "${routeName}" has no getParams(), skipping`
3478
- );
3479
3947
  }
3480
3948
  }
3481
3949
  }
3950
+ } finally {
3951
+ if (progressInterval) {
3952
+ clearInterval(progressInterval);
3953
+ const elapsed = ((performance.now() - paramsStart) / 1e3).toFixed(1);
3954
+ console.log(
3955
+ `[rsc-router] Resolved prerender params: ${resolvedRoutes}/${totalDynamic} routes (${elapsed}s)`
3956
+ );
3957
+ }
3482
3958
  }
3483
3959
  if (entries.length === 0) return;
3484
3960
  const maxConcurrency = Math.max(...entries.map((e) => e.concurrency));
@@ -3487,7 +3963,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3487
3963
  `[rsc-router] Pre-rendering ${entries.length} URL(s)${concurrencyNote}...`
3488
3964
  );
3489
3965
  const { hashParams } = await rscEnv.runner.import("@rangojs/router/build");
3490
- const collectedData = {};
3966
+ const manifestEntries = {};
3491
3967
  let doneCount = 0;
3492
3968
  let skipCount = 0;
3493
3969
  const startTotal = performance.now();
@@ -3505,7 +3981,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3505
3981
  entry.urlPath,
3506
3982
  {},
3507
3983
  entry.buildVars,
3508
- entry.isPassthroughRoute
3984
+ entry.isPassthroughRoute,
3985
+ state.resolvedBuildEnv
3509
3986
  );
3510
3987
  if (!result) continue;
3511
3988
  if (result.passthrough) {
@@ -3517,18 +3994,30 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3517
3994
  break;
3518
3995
  }
3519
3996
  const paramHash = hashParams(result.params || {});
3520
- collectedData[`${result.routeName}/${paramHash}`] = {
3997
+ const mainKey = `${result.routeName}/${paramHash}`;
3998
+ const mainValue = JSON.stringify({
3521
3999
  segments: result.segments,
3522
4000
  handles: result.handles
3523
- };
4001
+ });
4002
+ manifestEntries[mainKey] = stageBuildAssetModule(
4003
+ state.projectRoot,
4004
+ "__pr",
4005
+ mainValue
4006
+ );
3524
4007
  if (result.interceptSegments?.length) {
3525
- collectedData[`${result.routeName}/${paramHash}/i`] = {
4008
+ const interceptKey = `${result.routeName}/${paramHash}/i`;
4009
+ const interceptValue = JSON.stringify({
3526
4010
  segments: [...result.segments, ...result.interceptSegments],
3527
4011
  handles: {
3528
4012
  ...result.handles,
3529
4013
  ...result.interceptHandles || {}
3530
4014
  }
3531
- };
4015
+ });
4016
+ manifestEntries[interceptKey] = stageBuildAssetModule(
4017
+ state.projectRoot,
4018
+ "__pr",
4019
+ interceptValue
4020
+ );
3532
4021
  }
3533
4022
  const elapsed = (performance.now() - startUrl).toFixed(0);
3534
4023
  console.log(
@@ -3572,7 +4061,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3572
4061
  }
3573
4062
  const totalElapsed = (performance.now() - startTotal).toFixed(0);
3574
4063
  if (doneCount > 0) {
3575
- state.prerenderCollectedData = collectedData;
4064
+ state.prerenderManifestEntries = manifestEntries;
3576
4065
  }
3577
4066
  const parts = [`${doneCount} done`];
3578
4067
  if (skipCount > 0) parts.push(`${skipCount} skipped`);
@@ -3583,7 +4072,7 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3583
4072
  async function renderStaticHandlers(state, rscEnv, registry) {
3584
4073
  if (!state.opts?.enableBuildPrerender || !state.isBuildMode || !state.resolvedStaticModules?.size)
3585
4074
  return;
3586
- const collected = {};
4075
+ const manifestEntries = {};
3587
4076
  let staticDone = 0;
3588
4077
  let staticSkip = 0;
3589
4078
  let totalStaticCount = 0;
@@ -3617,10 +4106,18 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3617
4106
  const result = await routerInstance.renderStaticSegment(
3618
4107
  def.handler,
3619
4108
  def.$$id,
3620
- def.$$routePrefix
4109
+ def.$$routePrefix,
4110
+ state.resolvedBuildEnv,
4111
+ !state.isBuildMode
3621
4112
  );
3622
4113
  if (result) {
3623
- collected[def.$$id] = result;
4114
+ const hasHandles = Object.keys(result.handles).length > 0;
4115
+ const exportValue = hasHandles ? JSON.stringify(result) : JSON.stringify(result.encoded);
4116
+ manifestEntries[def.$$id] = stageBuildAssetModule(
4117
+ state.projectRoot,
4118
+ "__st",
4119
+ exportValue
4120
+ );
3624
4121
  const elapsed = (performance.now() - startHandler).toFixed(0);
3625
4122
  console.log(
3626
4123
  `[rsc-router] OK ${name.padEnd(40)} (${elapsed}ms)`
@@ -3657,7 +4154,7 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3657
4154
  }
3658
4155
  const totalStaticElapsed = (performance.now() - startStatic).toFixed(0);
3659
4156
  if (staticDone > 0) {
3660
- state.staticCollectedData = collected;
4157
+ state.staticManifestEntries = manifestEntries;
3661
4158
  }
3662
4159
  const staticParts = [`${staticDone} done`];
3663
4160
  if (staticSkip > 0) staticParts.push(`${staticSkip} skipped`);
@@ -3674,8 +4171,7 @@ async function discoverRouters(state, rscEnv) {
3674
4171
  let registry = serverMod.RouterRegistry;
3675
4172
  if (!registry || registry.size === 0) {
3676
4173
  try {
3677
- const hostMod = await rscEnv.runner.import("@rangojs/router/host");
3678
- const hostRegistry = hostMod.HostRouterRegistry;
4174
+ const hostRegistry = serverMod.HostRouterRegistry;
3679
4175
  if (hostRegistry && hostRegistry.size > 0) {
3680
4176
  console.log(
3681
4177
  `[rsc-router] Found ${hostRegistry.size} host router(s), resolving lazy handlers...`
@@ -3737,7 +4233,11 @@ async function discoverRouters(state, rscEnv) {
3737
4233
  if (!router.urlpatterns || !generateManifestFull) {
3738
4234
  continue;
3739
4235
  }
3740
- const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
4236
+ const manifest = generateManifestFull(
4237
+ router.urlpatterns,
4238
+ routerMountIndex,
4239
+ router.__basename ? { urlPrefix: router.__basename } : void 0
4240
+ );
3741
4241
  routerMountIndex++;
3742
4242
  allManifests.push({ id, manifest });
3743
4243
  const routeCount = Object.keys(manifest.routeManifest).length;
@@ -3879,8 +4379,8 @@ async function discoverRouters(state, rscEnv) {
3879
4379
  }
3880
4380
 
3881
4381
  // src/vite/discovery/route-types-writer.ts
3882
- import { dirname as dirname3, basename, join as join3, resolve as resolve5 } from "node:path";
3883
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, unlinkSync as unlinkSync2 } from "node:fs";
4382
+ import { dirname as dirname3, basename, join as join2, resolve as resolve6 } from "node:path";
4383
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "node:fs";
3884
4384
  function filterUserNamedRoutes(manifest) {
3885
4385
  const filtered = {};
3886
4386
  for (const [name, pattern] of Object.entries(manifest)) {
@@ -3900,7 +4400,7 @@ function writeCombinedRouteTypesWithTracking(state, opts) {
3900
4400
  /\.(tsx?|jsx?)$/,
3901
4401
  ""
3902
4402
  );
3903
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4403
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
3904
4404
  try {
3905
4405
  preContent.set(outPath, readFileSync4(outPath, "utf-8"));
3906
4406
  } catch {
@@ -3913,8 +4413,8 @@ function writeCombinedRouteTypesWithTracking(state, opts) {
3913
4413
  /\.(tsx?|jsx?)$/,
3914
4414
  ""
3915
4415
  );
3916
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
3917
- if (!existsSync4(outPath)) continue;
4416
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4417
+ if (!existsSync5(outPath)) continue;
3918
4418
  try {
3919
4419
  const content = readFileSync4(outPath, "utf-8");
3920
4420
  if (content !== preContent.get(outPath)) {
@@ -3928,10 +4428,10 @@ function writeRouteTypesFiles(state) {
3928
4428
  if (state.perRouterManifests.length === 0) return;
3929
4429
  try {
3930
4430
  const entryDir = dirname3(
3931
- resolve5(state.projectRoot, state.resolvedEntryPath)
4431
+ resolve6(state.projectRoot, state.resolvedEntryPath)
3932
4432
  );
3933
- const oldCombinedPath = join3(entryDir, "named-routes.gen.ts");
3934
- if (existsSync4(oldCombinedPath)) {
4433
+ const oldCombinedPath = join2(entryDir, "named-routes.gen.ts");
4434
+ if (existsSync5(oldCombinedPath)) {
3935
4435
  unlinkSync2(oldCombinedPath);
3936
4436
  console.log(
3937
4437
  `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
@@ -3955,7 +4455,7 @@ Set an explicit \`id\` on createRouter() or check the call site.`
3955
4455
  }
3956
4456
  const routerDir = dirname3(sourceFile);
3957
4457
  const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
3958
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4458
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
3959
4459
  const userRoutes = filterUserNamedRoutes(routeManifest);
3960
4460
  let effectiveSearchSchemas = routeSearchSchemas;
3961
4461
  if ((!effectiveSearchSchemas || Object.keys(effectiveSearchSchemas).length === 0) && sourceFile) {
@@ -3975,10 +4475,10 @@ Set an explicit \`id\` on createRouter() or check the call site.`
3975
4475
  userRoutes,
3976
4476
  effectiveSearchSchemas && Object.keys(effectiveSearchSchemas).length > 0 ? effectiveSearchSchemas : void 0
3977
4477
  );
3978
- const existing = existsSync4(outPath) ? readFileSync4(outPath, "utf-8") : null;
4478
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
3979
4479
  if (existing !== source) {
3980
4480
  markSelfGenWrite(state, outPath, source);
3981
- writeFileSync2(outPath, source);
4481
+ writeFileSync3(outPath, source);
3982
4482
  console.log(`[rsc-router] Generated route types -> ${outPath}`);
3983
4483
  }
3984
4484
  }
@@ -4020,21 +4520,21 @@ function supplementGenFilesWithRuntimeRoutes(state) {
4020
4520
  }
4021
4521
  const routerDir = dirname3(sourceFile);
4022
4522
  const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
4023
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
4523
+ const outPath = join2(routerDir, `${routerBasename}.named-routes.gen.ts`);
4024
4524
  const source = generateRouteTypesSource(
4025
4525
  mergedRoutes,
4026
4526
  Object.keys(mergedSearchSchemas).length > 0 ? mergedSearchSchemas : void 0
4027
4527
  );
4028
- const existing = existsSync4(outPath) ? readFileSync4(outPath, "utf-8") : null;
4528
+ const existing = existsSync5(outPath) ? readFileSync4(outPath, "utf-8") : null;
4029
4529
  if (existing !== source) {
4030
4530
  markSelfGenWrite(state, outPath, source);
4031
- writeFileSync2(outPath, source);
4531
+ writeFileSync3(outPath, source);
4032
4532
  }
4033
4533
  }
4034
4534
  }
4035
4535
 
4036
4536
  // src/vite/discovery/virtual-module-codegen.ts
4037
- import { dirname as dirname4, basename as basename2, join as join4 } from "node:path";
4537
+ import { dirname as dirname4, basename as basename2, join as join3 } from "node:path";
4038
4538
  function generateRoutesManifestModule(state) {
4039
4539
  const hasManifest = state.mergedRouteManifest && Object.keys(state.mergedRouteManifest).length > 0;
4040
4540
  if (hasManifest) {
@@ -4049,7 +4549,7 @@ function generateRoutesManifestModule(state) {
4049
4549
  /\.(tsx?|jsx?)$/,
4050
4550
  ""
4051
4551
  );
4052
- const genPath = join4(
4552
+ const genPath = join3(
4053
4553
  routerDir,
4054
4554
  `${routerBasename}.named-routes.gen.js`
4055
4555
  ).replaceAll("\\", "/");
@@ -4146,7 +4646,7 @@ function generatePerRouterModule(state, routerId) {
4146
4646
  /\.(tsx?|jsx?)$/,
4147
4647
  ""
4148
4648
  );
4149
- const genPath = join4(
4649
+ const genPath = join3(
4150
4650
  routerDir,
4151
4651
  `${routerBasename}.named-routes.gen.js`
4152
4652
  ).replaceAll("\\", "/");
@@ -4173,100 +4673,90 @@ function generatePerRouterModule(state, routerId) {
4173
4673
  }
4174
4674
 
4175
4675
  // src/vite/discovery/bundle-postprocess.ts
4176
- import { resolve as resolve6 } from "node:path";
4177
- import { createHash as createHash4 } from "node:crypto";
4178
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync } from "node:fs";
4676
+ import { resolve as resolve7 } from "node:path";
4677
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "node:fs";
4179
4678
  function postprocessBundle(state) {
4180
- const hasPrerenderData = state.prerenderCollectedData && Object.keys(state.prerenderCollectedData).length > 0;
4181
- const hasStaticData = state.staticCollectedData && Object.keys(state.staticCollectedData).length > 0;
4679
+ const hasPrerenderData = state.prerenderManifestEntries && Object.keys(state.prerenderManifestEntries).length > 0;
4680
+ const hasStaticData = state.staticManifestEntries && Object.keys(state.staticManifestEntries).length > 0;
4182
4681
  if (!hasPrerenderData && !hasStaticData) return;
4183
- const rscEntryPath = resolve6(
4682
+ const rscEntryPath = resolve7(
4184
4683
  state.projectRoot,
4185
4684
  "dist/rsc",
4186
4685
  state.rscEntryFileName ?? "index.js"
4187
4686
  );
4188
4687
  const evictionTargets = [
4189
4688
  {
4190
- info: state.handlerChunkInfo,
4689
+ infos: state.handlerChunkInfoMap.values(),
4191
4690
  fnName: "Prerender",
4192
4691
  brand: "prerenderHandler",
4193
4692
  label: "handler code from RSC bundle"
4194
4693
  },
4195
4694
  {
4196
- info: state.staticHandlerChunkInfo,
4695
+ infos: state.staticHandlerChunkInfoMap.values(),
4197
4696
  fnName: "Static",
4198
4697
  brand: "staticHandler",
4199
4698
  label: "static handler code"
4200
4699
  }
4201
4700
  ];
4202
4701
  for (const target of evictionTargets) {
4203
- if (!target.info) continue;
4204
- const chunkPath = resolve6(
4205
- state.projectRoot,
4206
- "dist/rsc",
4207
- target.info.fileName
4208
- );
4209
- try {
4210
- const code = readFileSync5(chunkPath, "utf-8");
4211
- const result = evictHandlerCode(
4212
- code,
4213
- target.info.exports,
4214
- target.fnName,
4215
- target.brand
4216
- );
4217
- if (result) {
4218
- writeFileSync3(chunkPath, result.code);
4219
- const savedKB = (result.savedBytes / 1024).toFixed(1);
4220
- console.log(
4221
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${target.info.fileName}`
4702
+ for (const info of target.infos) {
4703
+ const chunkPath = resolve7(state.projectRoot, "dist/rsc", info.fileName);
4704
+ try {
4705
+ const code = readFileSync5(chunkPath, "utf-8");
4706
+ const result = evictHandlerCode(
4707
+ code,
4708
+ info.exports,
4709
+ target.fnName,
4710
+ target.brand
4711
+ );
4712
+ if (result) {
4713
+ writeFileSync4(chunkPath, result.code);
4714
+ const savedKB = (result.savedBytes / 1024).toFixed(1);
4715
+ console.log(
4716
+ `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4717
+ );
4718
+ }
4719
+ } catch (replaceErr) {
4720
+ console.warn(
4721
+ `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4222
4722
  );
4223
4723
  }
4224
- } catch (replaceErr) {
4225
- console.warn(
4226
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4227
- );
4228
4724
  }
4229
4725
  }
4230
- state.handlerChunkInfo = null;
4231
- state.staticHandlerChunkInfo = null;
4232
- if (hasPrerenderData && existsSync5(rscEntryPath)) {
4726
+ state.handlerChunkInfoMap.clear();
4727
+ state.staticHandlerChunkInfoMap.clear();
4728
+ if (hasPrerenderData && existsSync6(rscEntryPath)) {
4233
4729
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4234
4730
  if (!rscCode.includes("__prerender-manifest.js")) {
4235
4731
  try {
4236
- const assetsDir = resolve6(state.projectRoot, "dist/rsc/assets");
4237
- mkdirSync(assetsDir, { recursive: true });
4238
- const manifestEntries = [];
4239
- let totalBytes = 0;
4240
- for (const [key, entry] of Object.entries(
4241
- state.prerenderCollectedData
4732
+ let totalBytes = copyStagedBuildAssets(
4733
+ state.projectRoot,
4734
+ Object.values(state.prerenderManifestEntries)
4735
+ );
4736
+ const manifestMap = {};
4737
+ for (const [key, assetFileName] of Object.entries(
4738
+ state.prerenderManifestEntries
4242
4739
  )) {
4243
- const entryJson = JSON.stringify(entry);
4244
- const contentHash = createHash4("sha256").update(entryJson).digest("hex").slice(0, 8);
4245
- const assetFileName = `__pr-${contentHash}.js`;
4246
- const assetPath = resolve6(assetsDir, assetFileName);
4247
- const assetCode = `export default ${entryJson};
4248
- `;
4249
- writeFileSync3(assetPath, assetCode);
4250
- totalBytes += Buffer.byteLength(assetCode);
4251
- manifestEntries.push(
4252
- `${JSON.stringify(key)}:()=>import("./assets/${assetFileName}")`
4253
- );
4740
+ manifestMap[key] = `./assets/${assetFileName}`;
4254
4741
  }
4255
- const manifestCode = `const m={${manifestEntries.join(",")}};export default m;
4256
- `;
4257
- const manifestPath = resolve6(
4742
+ const manifestCode = [
4743
+ `const m=JSON.parse('${JSON.stringify(manifestMap).replace(/'/g, "\\'")}');`,
4744
+ `export function loadPrerenderAsset(s){return import(s)}`,
4745
+ `export default m;`,
4746
+ ""
4747
+ ].join("\n");
4748
+ const manifestPath = resolve7(
4258
4749
  state.projectRoot,
4259
4750
  "dist/rsc/__prerender-manifest.js"
4260
4751
  );
4261
- writeFileSync3(manifestPath, manifestCode);
4752
+ writeFileSync4(manifestPath, manifestCode);
4262
4753
  totalBytes += Buffer.byteLength(manifestCode);
4263
- const injection = `import __pm from "./__prerender-manifest.js";
4264
- globalThis.__PRERENDER_MANIFEST = __pm;
4754
+ const injection = `globalThis.__loadPrerenderManifestModule = () => import("./__prerender-manifest.js");
4265
4755
  `;
4266
- writeFileSync3(rscEntryPath, injection + rscCode);
4756
+ writeFileSync4(rscEntryPath, injection + rscCode);
4267
4757
  const totalKB = (totalBytes / 1024).toFixed(1);
4268
4758
  console.log(
4269
- `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderCollectedData).length} entries)`
4759
+ `[rsc-router] Wrote prerender assets (${totalKB} KB total, ${Object.keys(state.prerenderManifestEntries).length} entries)`
4270
4760
  );
4271
4761
  } catch (err) {
4272
4762
  throw new Error(
@@ -4275,44 +4765,36 @@ globalThis.__PRERENDER_MANIFEST = __pm;
4275
4765
  }
4276
4766
  }
4277
4767
  }
4278
- if (hasStaticData && existsSync5(rscEntryPath)) {
4768
+ if (hasStaticData && existsSync6(rscEntryPath)) {
4279
4769
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4280
- if (!rscCode.includes("__STATIC_MANIFEST")) {
4770
+ if (!rscCode.includes("__static-manifest.js")) {
4281
4771
  try {
4282
- const assetsDir = resolve6(state.projectRoot, "dist/rsc/assets");
4283
- mkdirSync(assetsDir, { recursive: true });
4284
4772
  const manifestEntries = [];
4285
- let totalBytes = 0;
4286
- for (const [handlerId, { encoded, handles }] of Object.entries(
4287
- state.staticCollectedData
4773
+ let totalBytes = copyStagedBuildAssets(
4774
+ state.projectRoot,
4775
+ Object.values(state.staticManifestEntries)
4776
+ );
4777
+ for (const [handlerId, assetFileName] of Object.entries(
4778
+ state.staticManifestEntries
4288
4779
  )) {
4289
- const hasHandles = Object.keys(handles).length > 0;
4290
- const exportValue = hasHandles ? JSON.stringify({ encoded, handles }) : JSON.stringify(encoded);
4291
- const contentHash = createHash4("sha256").update(exportValue).digest("hex").slice(0, 8);
4292
- const assetFileName = `__st-${contentHash}.js`;
4293
- const assetPath = resolve6(assetsDir, assetFileName);
4294
- const assetCode = `export default ${exportValue};
4295
- `;
4296
- writeFileSync3(assetPath, assetCode);
4297
- totalBytes += Buffer.byteLength(assetCode);
4298
4780
  manifestEntries.push(
4299
4781
  `${JSON.stringify(handlerId)}:()=>import("./assets/${assetFileName}")`
4300
4782
  );
4301
4783
  }
4302
4784
  const manifestCode = `const m={${manifestEntries.join(",")}};globalThis.__STATIC_MANIFEST=m;export default m;
4303
4785
  `;
4304
- const manifestPath = resolve6(
4786
+ const manifestPath = resolve7(
4305
4787
  state.projectRoot,
4306
4788
  "dist/rsc/__static-manifest.js"
4307
4789
  );
4308
- writeFileSync3(manifestPath, manifestCode);
4790
+ writeFileSync4(manifestPath, manifestCode);
4309
4791
  totalBytes += Buffer.byteLength(manifestCode);
4310
4792
  const injection = `import "./__static-manifest.js";
4311
4793
  `;
4312
- writeFileSync3(rscEntryPath, injection + rscCode);
4794
+ writeFileSync4(rscEntryPath, injection + rscCode);
4313
4795
  const totalKB = (totalBytes / 1024).toFixed(1);
4314
4796
  console.log(
4315
- `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticCollectedData).length} entries)`
4797
+ `[rsc-router] Wrote static assets (${totalKB} KB total, ${Object.keys(state.staticManifestEntries).length} entries)`
4316
4798
  );
4317
4799
  } catch (err) {
4318
4800
  throw new Error(
@@ -4324,7 +4806,22 @@ globalThis.__PRERENDER_MANIFEST = __pm;
4324
4806
  }
4325
4807
 
4326
4808
  // src/vite/router-discovery.ts
4809
+ var loaderHookRegistered = false;
4810
+ function ensureCloudflareProtocolLoaderRegistered() {
4811
+ if (loaderHookRegistered) return;
4812
+ loaderHookRegistered = true;
4813
+ try {
4814
+ register(
4815
+ new URL("./plugins/cloudflare-protocol-loader-hook.mjs", import.meta.url)
4816
+ );
4817
+ } catch (err) {
4818
+ console.warn(
4819
+ `[rsc-router] Could not register Node ESM loader hook for cloudflare:* imports (${err?.message ?? err}). Falling back to Vite transform only.`
4820
+ );
4821
+ }
4822
+ }
4327
4823
  async function createTempRscServer(state, options = {}) {
4824
+ ensureCloudflareProtocolLoaderRegistered();
4328
4825
  const { default: rsc } = await import("@vitejs/plugin-rsc");
4329
4826
  return createViteServer({
4330
4827
  root: state.projectRoot,
@@ -4347,6 +4844,7 @@ async function createTempRscServer(state, options = {}) {
4347
4844
  ...options.forceBuild ? [hashClientRefs(state.projectRoot)] : [],
4348
4845
  createVersionPlugin(),
4349
4846
  createVirtualStubPlugin(),
4847
+ createCloudflareProtocolStubPlugin(),
4350
4848
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
4351
4849
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
4352
4850
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
@@ -4354,8 +4852,69 @@ async function createTempRscServer(state, options = {}) {
4354
4852
  ]
4355
4853
  });
4356
4854
  }
4855
+ async function resolveBuildEnv(option, factoryCtx) {
4856
+ if (!option) return null;
4857
+ if (option === "auto") {
4858
+ if (factoryCtx.preset !== "cloudflare") {
4859
+ throw new Error(
4860
+ '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
4861
+ );
4862
+ }
4863
+ try {
4864
+ const userRequire = createRequire2(
4865
+ resolve8(factoryCtx.root, "package.json")
4866
+ );
4867
+ const wranglerPath = userRequire.resolve("wrangler");
4868
+ const { getPlatformProxy } = await import(pathToFileURL(wranglerPath).href);
4869
+ const proxy = await getPlatformProxy();
4870
+ return {
4871
+ env: proxy.env,
4872
+ dispose: proxy.dispose
4873
+ };
4874
+ } catch (err) {
4875
+ throw new Error(
4876
+ `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
4877
+ Install it with: pnpm add -D wrangler
4878
+ ${err.message}`
4879
+ );
4880
+ }
4881
+ }
4882
+ if (typeof option === "function") {
4883
+ return await option(factoryCtx);
4884
+ }
4885
+ return { env: option };
4886
+ }
4887
+ async function acquireBuildEnv(s, command, mode) {
4888
+ const option = s.opts?.buildEnv;
4889
+ if (!option) return false;
4890
+ const result = await resolveBuildEnv(option, {
4891
+ root: s.projectRoot,
4892
+ mode,
4893
+ command,
4894
+ preset: s.opts?.preset ?? "node"
4895
+ });
4896
+ if (!result) return false;
4897
+ s.resolvedBuildEnv = result.env;
4898
+ s.buildEnvDispose = result.dispose ?? null;
4899
+ globalThis[BUILD_ENV_GLOBAL_KEY] = result.env;
4900
+ return true;
4901
+ }
4902
+ async function releaseBuildEnv(s) {
4903
+ if (s.buildEnvDispose) {
4904
+ try {
4905
+ await s.buildEnvDispose();
4906
+ } catch (err) {
4907
+ console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
4908
+ }
4909
+ s.buildEnvDispose = null;
4910
+ }
4911
+ s.resolvedBuildEnv = void 0;
4912
+ delete globalThis[BUILD_ENV_GLOBAL_KEY];
4913
+ }
4357
4914
  function createRouterDiscoveryPlugin(entryPath, opts) {
4358
4915
  const s = createDiscoveryState(entryPath, opts);
4916
+ let viteCommand = "build";
4917
+ let viteMode = "production";
4359
4918
  return {
4360
4919
  name: "@rangojs/router:discovery",
4361
4920
  config() {
@@ -4364,31 +4923,13 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4364
4923
  __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG)
4365
4924
  }
4366
4925
  };
4367
- if (opts?.enableBuildPrerender) {
4368
- config.environments = {
4369
- rsc: {
4370
- build: {
4371
- rollupOptions: {
4372
- output: {
4373
- manualChunks(id) {
4374
- if (s.resolvedPrerenderModules?.has(id)) {
4375
- return "__prerender-handlers";
4376
- }
4377
- if (s.resolvedStaticModules?.has(id)) {
4378
- return "__static-handlers";
4379
- }
4380
- }
4381
- }
4382
- }
4383
- }
4384
- }
4385
- };
4386
- }
4387
4926
  return config;
4388
4927
  },
4389
4928
  configResolved(config) {
4390
4929
  s.projectRoot = config.root;
4391
4930
  s.isBuildMode = config.command === "build";
4931
+ viteCommand = config.command;
4932
+ viteMode = config.mode;
4392
4933
  s.userResolveAlias = config.resolve.alias;
4393
4934
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
4394
4935
  s.resolvedEntryPath = opts.routerPathRef.path;
@@ -4402,12 +4943,6 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4402
4943
  s.resolvedEntryPath = entries[0];
4403
4944
  }
4404
4945
  }
4405
- if (opts?.include || opts?.exclude) {
4406
- s.scanFilter = createScanFilter(s.projectRoot, {
4407
- include: opts.include,
4408
- exclude: opts.exclude
4409
- });
4410
- }
4411
4946
  if (opts?.staticRouteTypesGeneration !== false) {
4412
4947
  s.cachedRouterFiles = findRouterFiles(s.projectRoot, s.scanFilter);
4413
4948
  writeCombinedRouteTypesWithTracking(s, { preserveIfLarger: true });
@@ -4427,8 +4962,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4427
4962
  if (globalThis.__rscRouterDiscoveryActive) return;
4428
4963
  s.devServer = server;
4429
4964
  let resolveDiscovery;
4430
- const discoveryPromise = new Promise((resolve9) => {
4431
- resolveDiscovery = resolve9;
4965
+ const discoveryPromise = new Promise((resolve10) => {
4966
+ resolveDiscovery = resolve10;
4432
4967
  });
4433
4968
  const getDevServerOrigin = () => server.resolvedUrls?.local?.[0]?.replace(/\/$/, "") || `http://localhost:${server.config.server.port || 5173}`;
4434
4969
  let prerenderTempServer = null;
@@ -4439,6 +4974,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4439
4974
  });
4440
4975
  prerenderTempServer = null;
4441
4976
  }
4977
+ releaseBuildEnv(s).catch(() => {
4978
+ });
4442
4979
  });
4443
4980
  async function getOrCreateTempServer() {
4444
4981
  if (prerenderNodeRegistry) {
@@ -4469,6 +5006,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4469
5006
  if (!rscEnv?.runner) {
4470
5007
  s.devServerOrigin = getDevServerOrigin();
4471
5008
  try {
5009
+ await acquireBuildEnv(s, viteCommand, viteMode);
4472
5010
  const tempRscEnv = await getOrCreateTempServer();
4473
5011
  if (tempRscEnv) {
4474
5012
  await discoverRouters(s, tempRscEnv);
@@ -4484,6 +5022,7 @@ ${err.stack}`
4484
5022
  return;
4485
5023
  }
4486
5024
  try {
5025
+ await acquireBuildEnv(s, viteCommand, viteMode);
4487
5026
  const serverMod = await rscEnv.runner.import(
4488
5027
  "@rangojs/router/server"
4489
5028
  );
@@ -4503,8 +5042,8 @@ ${err.stack}`
4503
5042
  resolveDiscovery();
4504
5043
  }
4505
5044
  };
4506
- s.discoveryDone = new Promise((resolve9) => {
4507
- setTimeout(() => discover().then(resolve9, resolve9), 0);
5045
+ s.discoveryDone = new Promise((resolve10) => {
5046
+ setTimeout(() => discover().then(resolve10, resolve10), 0);
4508
5047
  });
4509
5048
  let mainRegistry = null;
4510
5049
  const propagateDiscoveryState = async (rscEnv) => {
@@ -4548,7 +5087,26 @@ ${err.stack}`
4548
5087
  res.end("Missing pathname");
4549
5088
  return;
4550
5089
  }
4551
- let registry = mainRegistry;
5090
+ const rscEnv = server.environments?.rsc;
5091
+ let registry = null;
5092
+ if (rscEnv?.runner && s.resolvedEntryPath) {
5093
+ try {
5094
+ await rscEnv.runner.import(s.resolvedEntryPath);
5095
+ const serverMod = await rscEnv.runner.import(
5096
+ "@rangojs/router/server"
5097
+ );
5098
+ registry = serverMod.RouterRegistry ?? null;
5099
+ } catch (err) {
5100
+ console.warn(
5101
+ `[rsc-router] Dev prerender module refresh failed: ${err.message}`
5102
+ );
5103
+ res.statusCode = 500;
5104
+ res.end(`Prerender handler error: ${err.message}`);
5105
+ return;
5106
+ }
5107
+ } else {
5108
+ registry = mainRegistry;
5109
+ }
4552
5110
  if (!registry) {
4553
5111
  if (!prerenderNodeRegistry) {
4554
5112
  await getOrCreateTempServer();
@@ -4570,7 +5128,10 @@ ${err.stack}`
4570
5128
  pathname,
4571
5129
  {},
4572
5130
  void 0,
4573
- wantPassthrough
5131
+ wantPassthrough,
5132
+ s.resolvedBuildEnv,
5133
+ true
5134
+ // devMode: check getParams for passthrough routes
4574
5135
  );
4575
5136
  if (!result) continue;
4576
5137
  if (result.passthrough) continue;
@@ -4673,7 +5234,7 @@ ${err.stack}`
4673
5234
  if (hasCreateRouter) {
4674
5235
  const nestedRouterConflict = findNestedRouterConflict([
4675
5236
  ...s.cachedRouterFiles ?? [],
4676
- resolve7(filePath)
5237
+ resolve8(filePath)
4677
5238
  ]);
4678
5239
  if (nestedRouterConflict) {
4679
5240
  server.config.logger.error(
@@ -4703,6 +5264,10 @@ ${err.stack}`
4703
5264
  async buildStart() {
4704
5265
  if (!s.isBuildMode) return;
4705
5266
  if (s.mergedRouteManifest !== null) return;
5267
+ resetStagedBuildAssets(s.projectRoot);
5268
+ s.prerenderManifestEntries = null;
5269
+ s.staticManifestEntries = null;
5270
+ await acquireBuildEnv(s, viteCommand, viteMode);
4706
5271
  let tempServer = null;
4707
5272
  globalThis.__rscRouterDiscoveryActive = true;
4708
5273
  try {
@@ -4742,6 +5307,7 @@ ${details}`
4742
5307
  if (tempServer) {
4743
5308
  await tempServer.close();
4744
5309
  }
5310
+ await releaseBuildEnv(s);
4745
5311
  }
4746
5312
  },
4747
5313
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -4784,20 +5350,30 @@ ${details}`
4784
5350
  }
4785
5351
  if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
4786
5352
  return;
5353
+ s.handlerChunkInfoMap.clear();
5354
+ s.staticHandlerChunkInfoMap.clear();
4787
5355
  for (const [fileName, chunk] of Object.entries(bundle)) {
4788
5356
  if (chunk.type !== "chunk") continue;
4789
- if (fileName.includes("__prerender-handlers") && s.resolvedPrerenderModules?.size) {
5357
+ if (s.resolvedPrerenderModules?.size) {
4790
5358
  const handlers = extractHandlerExportsFromChunk(
4791
5359
  chunk.code,
4792
5360
  s.resolvedPrerenderModules,
4793
5361
  "Prerender",
4794
- true
5362
+ false
4795
5363
  );
4796
5364
  if (handlers.length > 0) {
4797
- s.handlerChunkInfo = { fileName, exports: handlers };
5365
+ const existing = s.handlerChunkInfoMap.get(fileName);
5366
+ if (existing) {
5367
+ existing.exports.push(...handlers);
5368
+ } else {
5369
+ s.handlerChunkInfoMap.set(fileName, {
5370
+ fileName,
5371
+ exports: handlers
5372
+ });
5373
+ }
4798
5374
  }
4799
5375
  }
4800
- if (fileName.includes("__static-handlers") && s.resolvedStaticModules?.size) {
5376
+ if (s.resolvedStaticModules?.size) {
4801
5377
  const handlers = extractHandlerExportsFromChunk(
4802
5378
  chunk.code,
4803
5379
  s.resolvedStaticModules,
@@ -4805,7 +5381,15 @@ ${details}`
4805
5381
  false
4806
5382
  );
4807
5383
  if (handlers.length > 0) {
4808
- s.staticHandlerChunkInfo = { fileName, exports: handlers };
5384
+ const existing = s.staticHandlerChunkInfoMap.get(fileName);
5385
+ if (existing) {
5386
+ existing.exports.push(...handlers);
5387
+ } else {
5388
+ s.staticHandlerChunkInfoMap.set(fileName, {
5389
+ fileName,
5390
+ exports: handlers
5391
+ });
5392
+ }
4809
5393
  }
4810
5394
  }
4811
5395
  }
@@ -4831,9 +5415,19 @@ async function rango(options) {
4831
5415
  const preset = resolvedOptions.preset ?? "node";
4832
5416
  const showBanner = resolvedOptions.banner ?? true;
4833
5417
  const plugins = [];
4834
- const rangoAliases = getPackageAliases();
4835
- const excludeDeps = getExcludeDeps();
4836
- let rscEntryPath = null;
5418
+ const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
5419
+ const excludeDeps = [
5420
+ ...getExcludeDeps(),
5421
+ // The public browser entry re-exports the RSDW browser client.
5422
+ // Excluding both keeps Vite from freezing the unpatched bundle into
5423
+ // .vite/deps before our source transforms run.
5424
+ "@vitejs/plugin-rsc/browser",
5425
+ // Keep the browser RSDW client out of Vite's dep optimizer so our
5426
+ // cjs-to-esm transform can patch the real file.
5427
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5428
+ ];
5429
+ const pkg = getPublishedPackageName();
5430
+ const nested = (spec) => `${pkg} > ${spec}`;
4837
5431
  const routerRef = { path: void 0 };
4838
5432
  const prerenderEnabled = true;
4839
5433
  if (preset === "cloudflare") {
@@ -4871,7 +5465,7 @@ async function rango(options) {
4871
5465
  // Pre-bundle rsc-html-stream to prevent discovery during first request
4872
5466
  // Exclude rsc-router modules to ensure same Context instance
4873
5467
  optimizeDeps: {
4874
- include: ["rsc-html-stream/client"],
5468
+ include: [nested("rsc-html-stream/client")],
4875
5469
  exclude: excludeDeps,
4876
5470
  esbuildOptions: sharedEsbuildOptions
4877
5471
  }
@@ -4896,8 +5490,10 @@ async function rango(options) {
4896
5490
  "react-dom/static.edge",
4897
5491
  "react/jsx-runtime",
4898
5492
  "react/jsx-dev-runtime",
4899
- "rsc-html-stream/server",
4900
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5493
+ nested("rsc-html-stream/server"),
5494
+ nested(
5495
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5496
+ )
4901
5497
  ],
4902
5498
  exclude: excludeDeps,
4903
5499
  esbuildOptions: sharedEsbuildOptions
@@ -4912,7 +5508,9 @@ async function rango(options) {
4912
5508
  "react",
4913
5509
  "react/jsx-runtime",
4914
5510
  "react/jsx-dev-runtime",
4915
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5511
+ nested(
5512
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5513
+ )
4916
5514
  ],
4917
5515
  exclude: excludeDeps,
4918
5516
  esbuildOptions: sharedEsbuildOptions
@@ -4929,6 +5527,7 @@ async function rango(options) {
4929
5527
  }
4930
5528
  });
4931
5529
  plugins.push(createVirtualEntriesPlugin(finalEntries));
5530
+ plugins.push(performanceTracksPlugin());
4932
5531
  plugins.push(
4933
5532
  rsc({
4934
5533
  entries: finalEntries,
@@ -4937,153 +5536,126 @@ async function rango(options) {
4937
5536
  );
4938
5537
  plugins.push(clientRefDedup());
4939
5538
  } else {
4940
- const nodeOptions = resolvedOptions;
4941
- routerRef.path = nodeOptions.router;
4942
- if (!routerRef.path) {
4943
- plugins.push({
4944
- name: "@rangojs/router:auto-discover",
4945
- config(userConfig) {
4946
- if (routerRef.path) return;
4947
- const root = userConfig.root ? resolve8(process.cwd(), userConfig.root) : process.cwd();
4948
- const filter = createScanFilter(root, {
4949
- include: resolvedOptions.include,
4950
- exclude: resolvedOptions.exclude
4951
- });
4952
- const candidates = findRouterFiles(root, filter);
4953
- if (candidates.length === 1) {
4954
- const abs = candidates[0];
4955
- routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
4956
- } else if (candidates.length > 1) {
4957
- const list = candidates.map(
4958
- (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
4959
- ).join("\n");
4960
- throw new Error(
4961
- `[rsc-router] Multiple routers found. Specify \`router\` to choose one:
4962
- ${list}`
4963
- );
4964
- }
5539
+ plugins.push({
5540
+ name: "@rangojs/router:auto-discover",
5541
+ config(userConfig) {
5542
+ if (routerRef.path) return;
5543
+ const root = userConfig.root ? resolve9(process.cwd(), userConfig.root) : process.cwd();
5544
+ const candidates = findRouterFiles(root);
5545
+ if (candidates.length === 1) {
5546
+ const abs = candidates[0];
5547
+ routerRef.path = (abs.startsWith(root) ? "./" + abs.slice(root.length + 1) : abs).replaceAll("\\", "/");
5548
+ } else if (candidates.length > 1) {
5549
+ const list = candidates.map(
5550
+ (f) => " - " + (f.startsWith(root) ? f.slice(root.length + 1) : f)
5551
+ ).join("\n");
5552
+ throw new Error(`[rsc-router] Multiple routers found:
5553
+ ${list}`);
4965
5554
  }
4966
- });
4967
- }
4968
- const rscOption = nodeOptions.rsc ?? true;
4969
- if (rscOption !== false) {
4970
- const { default: rsc } = await import("@vitejs/plugin-rsc");
4971
- const userEntries = typeof rscOption === "boolean" ? {} : rscOption.entries || {};
4972
- const finalEntries = {
4973
- client: userEntries.client ?? VIRTUAL_IDS.browser,
4974
- ssr: userEntries.ssr ?? VIRTUAL_IDS.ssr,
4975
- rsc: userEntries.rsc ?? VIRTUAL_IDS.rsc
4976
- };
4977
- rscEntryPath = userEntries.rsc ?? null;
4978
- let hasWarnedDuplicate = false;
4979
- plugins.push({
4980
- name: "@rangojs/router:rsc-integration",
4981
- enforce: "pre",
4982
- config() {
4983
- const useVirtualClient = finalEntries.client === VIRTUAL_IDS.browser;
4984
- const useVirtualSSR = finalEntries.ssr === VIRTUAL_IDS.ssr;
4985
- const useVirtualRSC = finalEntries.rsc === VIRTUAL_IDS.rsc;
4986
- return {
4987
- // Exclude rsc-router modules from optimization to prevent module duplication
4988
- // This ensures the same Context instance is used by both browser entry and RSC proxy modules
4989
- optimizeDeps: {
4990
- exclude: excludeDeps,
4991
- esbuildOptions: sharedEsbuildOptions
4992
- },
4993
- build: {
4994
- rollupOptions: { onwarn }
4995
- },
4996
- resolve: {
4997
- alias: rangoAliases
4998
- },
4999
- environments: {
5000
- client: {
5001
- build: {
5002
- rollupOptions: {
5003
- output: {
5004
- manualChunks: getManualChunks
5005
- }
5006
- }
5007
- },
5008
- // Always exclude rsc-router modules, conditionally add virtual entry
5009
- optimizeDeps: {
5010
- // Pre-bundle React and rsc-html-stream to prevent late discovery
5011
- // triggering ERR_OUTDATED_OPTIMIZED_DEP on cold starts
5012
- include: [
5013
- "react",
5014
- "react-dom",
5015
- "react/jsx-runtime",
5016
- "react/jsx-dev-runtime",
5017
- "rsc-html-stream/client"
5018
- ],
5019
- exclude: excludeDeps,
5020
- esbuildOptions: sharedEsbuildOptions,
5021
- ...useVirtualClient && {
5022
- // Tell Vite to scan the virtual entry for dependencies
5023
- entries: [VIRTUAL_IDS.browser]
5024
- }
5025
- }
5026
- },
5027
- ...useVirtualSSR && {
5028
- ssr: {
5029
- optimizeDeps: {
5030
- entries: [VIRTUAL_IDS.ssr],
5031
- // Pre-bundle all SSR deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
5032
- include: [
5033
- "react",
5034
- "react-dom",
5035
- "react-dom/server.edge",
5036
- "react-dom/static.edge",
5037
- "react/jsx-runtime",
5038
- "react/jsx-dev-runtime",
5039
- "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5040
- ],
5041
- exclude: excludeDeps,
5042
- esbuildOptions: sharedEsbuildOptions
5555
+ }
5556
+ });
5557
+ const finalEntries = {
5558
+ client: VIRTUAL_IDS.browser,
5559
+ ssr: VIRTUAL_IDS.ssr,
5560
+ rsc: VIRTUAL_IDS.rsc
5561
+ };
5562
+ const { default: rsc } = await import("@vitejs/plugin-rsc");
5563
+ let hasWarnedDuplicate = false;
5564
+ plugins.push({
5565
+ name: "@rangojs/router:rsc-integration",
5566
+ enforce: "pre",
5567
+ config() {
5568
+ return {
5569
+ optimizeDeps: {
5570
+ exclude: excludeDeps,
5571
+ esbuildOptions: sharedEsbuildOptions
5572
+ },
5573
+ build: {
5574
+ rollupOptions: { onwarn }
5575
+ },
5576
+ resolve: {
5577
+ alias: rangoAliases
5578
+ },
5579
+ environments: {
5580
+ client: {
5581
+ build: {
5582
+ rollupOptions: {
5583
+ output: {
5584
+ manualChunks: getManualChunks
5043
5585
  }
5044
5586
  }
5045
5587
  },
5046
- ...useVirtualRSC && {
5047
- rsc: {
5048
- optimizeDeps: {
5049
- entries: [VIRTUAL_IDS.rsc],
5050
- // Pre-bundle all RSC deps to prevent late discovery triggering ERR_OUTDATED_OPTIMIZED_DEP
5051
- include: [
5052
- "react",
5053
- "react/jsx-runtime",
5054
- "react/jsx-dev-runtime",
5055
- "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5056
- ],
5057
- esbuildOptions: sharedEsbuildOptions
5058
- }
5059
- }
5588
+ optimizeDeps: {
5589
+ include: [
5590
+ "react",
5591
+ "react-dom",
5592
+ "react/jsx-runtime",
5593
+ "react/jsx-dev-runtime",
5594
+ nested("rsc-html-stream/client")
5595
+ ],
5596
+ exclude: excludeDeps,
5597
+ esbuildOptions: sharedEsbuildOptions,
5598
+ entries: [VIRTUAL_IDS.browser]
5599
+ }
5600
+ },
5601
+ ssr: {
5602
+ optimizeDeps: {
5603
+ entries: [VIRTUAL_IDS.ssr],
5604
+ include: [
5605
+ "react",
5606
+ "react-dom",
5607
+ "react-dom/server.edge",
5608
+ "react-dom/static.edge",
5609
+ "react/jsx-runtime",
5610
+ "react/jsx-dev-runtime",
5611
+ nested(
5612
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.edge"
5613
+ )
5614
+ ],
5615
+ exclude: excludeDeps,
5616
+ esbuildOptions: sharedEsbuildOptions
5617
+ }
5618
+ },
5619
+ rsc: {
5620
+ optimizeDeps: {
5621
+ entries: [VIRTUAL_IDS.rsc],
5622
+ include: [
5623
+ "react",
5624
+ "react/jsx-runtime",
5625
+ "react/jsx-dev-runtime",
5626
+ nested(
5627
+ "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge"
5628
+ )
5629
+ ],
5630
+ esbuildOptions: sharedEsbuildOptions
5060
5631
  }
5061
5632
  }
5062
- };
5063
- },
5064
- configResolved(config) {
5065
- if (showBanner) {
5066
- const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
5067
- printBanner(mode, "node", rangoVersion);
5068
- }
5069
- const rscMinimalCount = config.plugins.filter(
5070
- (p) => p.name === "rsc:minimal"
5071
- ).length;
5072
- if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
5073
- hasWarnedDuplicate = true;
5074
- console.warn(
5075
- "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your config or use rango({ rsc: false }) for manual configuration."
5076
- );
5077
5633
  }
5634
+ };
5635
+ },
5636
+ configResolved(config) {
5637
+ if (showBanner) {
5638
+ const mode = config.command === "serve" ? process.argv.includes("preview") ? "preview" : "dev" : "build";
5639
+ printBanner(mode, "node", rangoVersion);
5078
5640
  }
5079
- });
5080
- plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
5081
- plugins.push(
5082
- rsc({
5083
- entries: finalEntries
5084
- })
5085
- );
5086
- }
5641
+ const rscMinimalCount = config.plugins.filter(
5642
+ (p) => p.name === "rsc:minimal"
5643
+ ).length;
5644
+ if (rscMinimalCount > 1 && !hasWarnedDuplicate) {
5645
+ hasWarnedDuplicate = true;
5646
+ console.warn(
5647
+ "[rsc-router] Duplicate @vitejs/plugin-rsc detected. Remove rsc() from your vite config \u2014 rango() includes it automatically."
5648
+ );
5649
+ }
5650
+ }
5651
+ });
5652
+ plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
5653
+ plugins.push(performanceTracksPlugin());
5654
+ plugins.push(
5655
+ rsc({
5656
+ entries: finalEntries
5657
+ })
5658
+ );
5087
5659
  plugins.push(clientRefDedup());
5088
5660
  }
5089
5661
  plugins.push({
@@ -5111,22 +5683,102 @@ ${list}`
5111
5683
  plugins.push(createVersionPlugin());
5112
5684
  const discoveryEntryPath = preset !== "cloudflare" ? routerRef.path : void 0;
5113
5685
  const discoveryRouterRef = preset !== "cloudflare" ? routerRef : void 0;
5114
- const injectorEntryPath = rscEntryPath ?? (preset === "cloudflare" ? void 0 : null);
5115
- if (injectorEntryPath !== null) {
5116
- plugins.push(createVersionInjectorPlugin(injectorEntryPath));
5686
+ if (preset === "cloudflare") {
5687
+ plugins.push(createVersionInjectorPlugin(void 0));
5117
5688
  }
5118
5689
  plugins.push(createCjsToEsmPlugin());
5119
5690
  plugins.push(
5120
5691
  createRouterDiscoveryPlugin(discoveryEntryPath, {
5121
5692
  routerPathRef: discoveryRouterRef,
5122
5693
  enableBuildPrerender: prerenderEnabled,
5123
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration,
5124
- include: resolvedOptions.include,
5125
- exclude: resolvedOptions.exclude
5694
+ buildEnv: options?.buildEnv,
5695
+ preset
5126
5696
  })
5127
5697
  );
5128
5698
  return plugins;
5129
5699
  }
5700
+
5701
+ // src/vite/plugins/refresh-cmd.ts
5702
+ function poke() {
5703
+ return {
5704
+ name: "vite-plugin-poke",
5705
+ apply: "serve",
5706
+ configureServer(server) {
5707
+ const stdin = process.stdin;
5708
+ const debug = process.env.RANGO_POKE_DEBUG === "1";
5709
+ const triggerReload = (source) => {
5710
+ server.hot.send({ type: "full-reload", path: "*" });
5711
+ server.config.logger.info(` browser reload (${source})`, {
5712
+ timestamp: true
5713
+ });
5714
+ };
5715
+ const toBuffer = (chunk) => {
5716
+ return typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5717
+ };
5718
+ const formatChunk = (chunk) => {
5719
+ const data = toBuffer(chunk);
5720
+ const hex = Array.from(data).map((byte) => `0x${byte.toString(16).padStart(2, "0")}`).join(" ");
5721
+ const ascii = Array.from(data).map((byte) => {
5722
+ if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
5723
+ if (byte === 10) return "\\n";
5724
+ if (byte === 13) return "\\r";
5725
+ if (byte === 9) return "\\t";
5726
+ return ".";
5727
+ }).join("");
5728
+ return `len=${data.length} hex=[${hex}] ascii="${ascii}"`;
5729
+ };
5730
+ const readCtrlR = (chunk) => {
5731
+ const data = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5732
+ return data.length === 1 && data[0] === 18;
5733
+ };
5734
+ const readSubmittedCommands = (chunk) => {
5735
+ const text = toBuffer(chunk).toString("utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5736
+ if (!text.includes("\n")) return [];
5737
+ const lines = text.split("\n");
5738
+ lines.pop();
5739
+ return lines;
5740
+ };
5741
+ if (debug) {
5742
+ server.config.logger.info(
5743
+ ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
5744
+ { timestamp: true }
5745
+ );
5746
+ }
5747
+ if (stdin.isTTY) {
5748
+ server.config.logger.info(
5749
+ " poke ready: press e + enter to reload browser (ctrl+r also works when available)",
5750
+ { timestamp: true }
5751
+ );
5752
+ }
5753
+ const onData = (data) => {
5754
+ if (debug) {
5755
+ server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5756
+ timestamp: true
5757
+ });
5758
+ }
5759
+ if (readCtrlR(data)) {
5760
+ triggerReload("ctrl+r");
5761
+ return;
5762
+ }
5763
+ for (const command of readSubmittedCommands(data)) {
5764
+ if (command === "e") {
5765
+ triggerReload("e+enter");
5766
+ return;
5767
+ }
5768
+ if (command === "\x1Br") {
5769
+ triggerReload("option+r+enter");
5770
+ return;
5771
+ }
5772
+ }
5773
+ };
5774
+ stdin.on("data", onData);
5775
+ server.httpServer?.on("close", () => {
5776
+ stdin.off("data", onData);
5777
+ });
5778
+ }
5779
+ };
5780
+ }
5130
5781
  export {
5782
+ poke,
5131
5783
  rango
5132
5784
  };