@rangojs/router 0.0.0-experimental.fa8a383a → 0.0.0-experimental.fad716ff

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 (118) hide show
  1. package/README.md +76 -18
  2. package/dist/bin/rango.js +130 -47
  3. package/dist/vite/index.js +526 -168
  4. package/dist/vite/index.js.bak +5448 -0
  5. package/package.json +2 -2
  6. package/skills/cache-guide/SKILL.md +32 -0
  7. package/skills/caching/SKILL.md +8 -0
  8. package/skills/links/SKILL.md +3 -1
  9. package/skills/loader/SKILL.md +53 -43
  10. package/skills/middleware/SKILL.md +2 -0
  11. package/skills/parallel/SKILL.md +67 -0
  12. package/skills/prerender/SKILL.md +110 -68
  13. package/skills/route/SKILL.md +31 -0
  14. package/skills/router-setup/SKILL.md +87 -2
  15. package/skills/typesafety/SKILL.md +10 -0
  16. package/src/__internal.ts +1 -1
  17. package/src/browser/app-version.ts +14 -0
  18. package/src/browser/navigation-bridge.ts +16 -3
  19. package/src/browser/navigation-client.ts +64 -40
  20. package/src/browser/navigation-store.ts +43 -8
  21. package/src/browser/partial-update.ts +37 -4
  22. package/src/browser/prefetch/fetch.ts +8 -2
  23. package/src/browser/prefetch/queue.ts +61 -29
  24. package/src/browser/prefetch/resource-ready.ts +77 -0
  25. package/src/browser/react/Link.tsx +44 -8
  26. package/src/browser/react/NavigationProvider.tsx +13 -4
  27. package/src/browser/react/context.ts +7 -2
  28. package/src/browser/react/use-handle.ts +9 -58
  29. package/src/browser/react/use-router.ts +21 -8
  30. package/src/browser/rsc-router.tsx +26 -3
  31. package/src/browser/scroll-restoration.ts +10 -8
  32. package/src/browser/server-action-bridge.ts +8 -6
  33. package/src/browser/types.ts +27 -5
  34. package/src/build/generate-manifest.ts +6 -6
  35. package/src/build/generate-route-types.ts +3 -0
  36. package/src/build/route-types/include-resolution.ts +8 -1
  37. package/src/build/route-types/router-processing.ts +211 -72
  38. package/src/build/route-types/scan-filter.ts +8 -1
  39. package/src/cache/cache-runtime.ts +15 -11
  40. package/src/cache/cache-scope.ts +46 -5
  41. package/src/cache/taint.ts +55 -0
  42. package/src/client.tsx +2 -56
  43. package/src/context-var.ts +72 -2
  44. package/src/handle.ts +40 -0
  45. package/src/index.rsc.ts +3 -1
  46. package/src/index.ts +12 -0
  47. package/src/prerender/store.ts +5 -4
  48. package/src/prerender.ts +138 -77
  49. package/src/reverse.ts +22 -1
  50. package/src/route-definition/dsl-helpers.ts +42 -19
  51. package/src/route-definition/helpers-types.ts +10 -6
  52. package/src/route-definition/index.ts +3 -0
  53. package/src/route-definition/redirect.ts +9 -1
  54. package/src/route-definition/resolve-handler-use.ts +149 -0
  55. package/src/route-types.ts +11 -0
  56. package/src/router/content-negotiation.ts +100 -1
  57. package/src/router/handler-context.ts +79 -23
  58. package/src/router/intercept-resolution.ts +9 -4
  59. package/src/router/loader-resolution.ts +156 -21
  60. package/src/router/match-api.ts +124 -189
  61. package/src/router/match-middleware/background-revalidation.ts +12 -1
  62. package/src/router/match-middleware/cache-lookup.ts +72 -13
  63. package/src/router/match-middleware/cache-store.ts +21 -4
  64. package/src/router/match-middleware/segment-resolution.ts +53 -0
  65. package/src/router/match-result.ts +11 -5
  66. package/src/router/metrics.ts +6 -1
  67. package/src/router/middleware-types.ts +6 -8
  68. package/src/router/middleware.ts +2 -5
  69. package/src/router/navigation-snapshot.ts +182 -0
  70. package/src/router/prerender-match.ts +110 -10
  71. package/src/router/preview-match.ts +30 -102
  72. package/src/router/request-classification.ts +310 -0
  73. package/src/router/route-snapshot.ts +245 -0
  74. package/src/router/router-context.ts +1 -0
  75. package/src/router/router-interfaces.ts +36 -4
  76. package/src/router/router-options.ts +37 -11
  77. package/src/router/segment-resolution/fresh.ts +101 -18
  78. package/src/router/segment-resolution/helpers.ts +29 -24
  79. package/src/router/segment-resolution/revalidation.ts +122 -26
  80. package/src/router/types.ts +1 -0
  81. package/src/router.ts +54 -5
  82. package/src/rsc/handler.ts +464 -377
  83. package/src/rsc/loader-fetch.ts +23 -3
  84. package/src/rsc/manifest-init.ts +5 -1
  85. package/src/rsc/progressive-enhancement.ts +14 -2
  86. package/src/rsc/rsc-rendering.ts +10 -1
  87. package/src/rsc/server-action.ts +8 -0
  88. package/src/rsc/ssr-setup.ts +2 -2
  89. package/src/rsc/types.ts +9 -1
  90. package/src/server/context.ts +50 -1
  91. package/src/server/handle-store.ts +19 -0
  92. package/src/server/loader-registry.ts +9 -8
  93. package/src/server/request-context.ts +175 -15
  94. package/src/ssr/index.tsx +3 -0
  95. package/src/static-handler.ts +18 -6
  96. package/src/types/cache-types.ts +4 -4
  97. package/src/types/handler-context.ts +137 -33
  98. package/src/types/loader-types.ts +36 -9
  99. package/src/types/route-entry.ts +1 -1
  100. package/src/urls/path-helper-types.ts +9 -2
  101. package/src/urls/path-helper.ts +47 -12
  102. package/src/urls/pattern-types.ts +12 -0
  103. package/src/urls/response-types.ts +16 -6
  104. package/src/use-loader.tsx +73 -4
  105. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  106. package/src/vite/discovery/discover-routers.ts +5 -1
  107. package/src/vite/discovery/prerender-collection.ts +14 -1
  108. package/src/vite/discovery/state.ts +13 -4
  109. package/src/vite/index.ts +4 -0
  110. package/src/vite/plugin-types.ts +60 -5
  111. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  112. package/src/vite/plugins/expose-internal-ids.ts +118 -39
  113. package/src/vite/plugins/performance-tracks.ts +88 -0
  114. package/src/vite/plugins/refresh-cmd.ts +88 -26
  115. package/src/vite/rango.ts +19 -2
  116. package/src/vite/router-discovery.ts +178 -37
  117. package/src/vite/utils/prerender-utils.ts +18 -0
  118. package/src/vite/utils/shared-utils.ts +3 -2
@@ -910,9 +910,7 @@ function generateWholeFileStubs(cfg, bindings, code, filePath, isBuild) {
910
910
  });
911
911
  return { code: stubs.join("\n") + "\n", map: null };
912
912
  }
913
- function generateExprStubs(cfg, bindings, code, filePath, sourceId, isBuild) {
914
- if (bindings.length === 0) return null;
915
- const s = new MagicString2(code);
913
+ function stubHandlerExprs(cfg, bindings, s, filePath, isBuild) {
916
914
  let hasChanges = false;
917
915
  for (const binding of bindings) {
918
916
  const exportName = binding.exportNames[0];
@@ -924,15 +922,7 @@ function generateExprStubs(cfg, bindings, code, filePath, sourceId, isBuild) {
924
922
  );
925
923
  hasChanges = true;
926
924
  }
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
- };
925
+ return hasChanges;
936
926
  }
937
927
  function transformHandlerIds(cfg, bindings, s, filePath, isBuild) {
938
928
  let hasChanges = false;
@@ -1269,15 +1259,6 @@ ${lazyImports.join(",\n")}
1269
1259
  isBuild
1270
1260
  );
1271
1261
  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
1262
  }
1282
1263
  if (hasPrerenderHandlerCode && isRscEnv && isBuild) {
1283
1264
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
@@ -1329,15 +1310,57 @@ ${lazyImports.join(",\n")}
1329
1310
  isBuild
1330
1311
  );
1331
1312
  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;
1313
+ }
1314
+ if (!isRscEnv) {
1315
+ const allBindings = [];
1316
+ if (hasLoaderCode) {
1317
+ allBindings.push(...getBindings(code, getFnNames("createLoader")));
1318
+ }
1319
+ if (hasHandleCode) {
1320
+ allBindings.push(...getBindings(code, getFnNames("createHandle")));
1321
+ }
1322
+ if (hasLocationStateCode) {
1323
+ allBindings.push(
1324
+ ...getBindings(code, getFnNames("createLocationState"))
1325
+ );
1326
+ }
1327
+ if (hasPrerenderHandlerCode) {
1328
+ allBindings.push(
1329
+ ...getBindings(code, getFnNames(PRERENDER_CONFIG.fnName))
1330
+ );
1331
+ }
1332
+ if (hasStaticHandlerCode) {
1333
+ allBindings.push(
1334
+ ...getBindings(code, getFnNames(STATIC_CONFIG.fnName))
1335
+ );
1336
+ }
1337
+ if (allBindings.length > 0 && isExportOnlyFile(code, allBindings)) {
1338
+ const stubs = [];
1339
+ for (const binding of allBindings) {
1340
+ const name = binding.exportNames[0];
1341
+ const stubId = isBuild ? hashId(filePath, name) : `${filePath}#${name}`;
1342
+ const fnCall = code.slice(
1343
+ binding.callExprStart,
1344
+ binding.callOpenParenPos + 1
1345
+ );
1346
+ let brand = "loader";
1347
+ if (hasPrerenderHandlerCode && getFnNames(PRERENDER_CONFIG.fnName).some(
1348
+ (n) => fnCall.includes(n)
1349
+ )) {
1350
+ brand = PRERENDER_CONFIG.brand;
1351
+ } else if (hasStaticHandlerCode && getFnNames(STATIC_CONFIG.fnName).some((n) => fnCall.includes(n))) {
1352
+ brand = STATIC_CONFIG.brand;
1353
+ } else if (hasHandleCode && getFnNames("createHandle").some((n) => fnCall.includes(n))) {
1354
+ brand = "handle";
1355
+ } else if (hasLocationStateCode && getFnNames("createLocationState").some((n) => fnCall.includes(n))) {
1356
+ brand = "locationState";
1357
+ }
1358
+ stubs.push(
1359
+ `export const ${name} = { __brand: "${brand}", $$id: "${stubId}" };`
1360
+ );
1361
+ }
1362
+ return { code: stubs.join("\n") + "\n", map: null };
1363
+ }
1341
1364
  }
1342
1365
  if (hasStaticHandlerCode && isRscEnv && isBuild) {
1343
1366
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
@@ -1372,25 +1395,41 @@ ${lazyImports.join(",\n")}
1372
1395
  isBuild
1373
1396
  ) || changed;
1374
1397
  }
1375
- if (hasPrerenderHandlerCode && isRscEnv) {
1398
+ if (hasPrerenderHandlerCode) {
1376
1399
  const fnNames = getFnNames(PRERENDER_CONFIG.fnName);
1377
- changed = transformHandlerIds(
1378
- PRERENDER_CONFIG,
1379
- getBindings(code, fnNames),
1380
- s,
1381
- filePath,
1382
- isBuild
1383
- ) || changed;
1400
+ const bindings = getBindings(code, fnNames);
1401
+ if (isRscEnv) {
1402
+ changed = transformHandlerIds(
1403
+ PRERENDER_CONFIG,
1404
+ bindings,
1405
+ s,
1406
+ filePath,
1407
+ isBuild
1408
+ ) || changed;
1409
+ } else {
1410
+ changed = stubHandlerExprs(
1411
+ PRERENDER_CONFIG,
1412
+ bindings,
1413
+ s,
1414
+ filePath,
1415
+ isBuild
1416
+ ) || changed;
1417
+ }
1384
1418
  }
1385
- if (hasStaticHandlerCode && isRscEnv) {
1419
+ if (hasStaticHandlerCode) {
1386
1420
  const fnNames = getFnNames(STATIC_CONFIG.fnName);
1387
- changed = transformHandlerIds(
1388
- STATIC_CONFIG,
1389
- getBindings(code, fnNames),
1390
- s,
1391
- filePath,
1392
- isBuild
1393
- ) || changed;
1421
+ const bindings = getBindings(code, fnNames);
1422
+ if (isRscEnv) {
1423
+ changed = transformHandlerIds(
1424
+ STATIC_CONFIG,
1425
+ bindings,
1426
+ s,
1427
+ filePath,
1428
+ isBuild
1429
+ ) || changed;
1430
+ } else {
1431
+ changed = stubHandlerExprs(STATIC_CONFIG, bindings, s, filePath, isBuild) || changed;
1432
+ }
1394
1433
  }
1395
1434
  if (!changed) return;
1396
1435
  return {
@@ -1745,7 +1784,7 @@ import { resolve } from "node:path";
1745
1784
  // package.json
1746
1785
  var package_default = {
1747
1786
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.fa8a383a",
1787
+ version: "0.0.0-experimental.fad716ff",
1749
1788
  description: "Django-inspired RSC router with composable URL patterns",
1750
1789
  keywords: [
1751
1790
  "react",
@@ -1887,7 +1926,7 @@ var package_default = {
1887
1926
  "test:unit:watch": "vitest"
1888
1927
  },
1889
1928
  dependencies: {
1890
- "@vitejs/plugin-rsc": "^0.5.14",
1929
+ "@vitejs/plugin-rsc": "^0.5.19",
1891
1930
  "magic-string": "^0.30.17",
1892
1931
  picomatch: "^4.0.3",
1893
1932
  "rsc-html-stream": "^0.0.7"
@@ -2317,7 +2356,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2317
2356
  }
2318
2357
  return routeMap;
2319
2358
  }
2320
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
2359
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
2321
2360
  visited = visited ?? /* @__PURE__ */ new Set();
2322
2361
  const realPath = resolve2(filePath);
2323
2362
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -2333,7 +2372,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2333
2372
  return { routes: {}, searchSchemas: {} };
2334
2373
  }
2335
2374
  let block;
2336
- if (variableName) {
2375
+ if (inlineBlock) {
2376
+ block = inlineBlock;
2377
+ } else if (variableName) {
2337
2378
  const extracted = extractUrlsBlockForVariable(source, variableName);
2338
2379
  if (!extracted) return { routes: {}, searchSchemas: {} };
2339
2380
  block = extracted;
@@ -2452,7 +2493,7 @@ Router root: ${conflict.ancestor}
2452
2493
  Nested router: ${conflict.nested}
2453
2494
  Move the nested router into a sibling directory or configure it as a separate app root.`;
2454
2495
  }
2455
- function extractUrlsVariableFromRouter(code) {
2496
+ function extractUrlsFromRouter(code) {
2456
2497
  const sourceFile = ts5.createSourceFile(
2457
2498
  "router.tsx",
2458
2499
  code,
@@ -2466,24 +2507,70 @@ function extractUrlsVariableFromRouter(code) {
2466
2507
  const callee = node.expression;
2467
2508
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
2468
2509
  }
2510
+ function isInlineBuilder(node) {
2511
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
2512
+ }
2513
+ function isRoutesOnCreateRouter(node) {
2514
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
2515
+ return false;
2516
+ let inner = node.expression.expression;
2517
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2518
+ inner = inner.expression.expression;
2519
+ }
2520
+ return isCreateRouterCall(inner);
2521
+ }
2469
2522
  function visit(node) {
2470
2523
  if (result) return;
2471
- if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
2472
- let inner = node.expression.expression;
2473
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2474
- inner = inner.expression.expression;
2475
- }
2476
- if (isCreateRouterCall(inner)) {
2477
- result = node.arguments[0].text;
2478
- return;
2524
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
2525
+ const arg = node.arguments[0];
2526
+ if (ts5.isIdentifier(arg)) {
2527
+ result = { kind: "variable", name: arg.text };
2528
+ } else if (isInlineBuilder(arg)) {
2529
+ result = { kind: "inline", block: arg.getText(sourceFile) };
2479
2530
  }
2531
+ return;
2480
2532
  }
2481
2533
  if (isCreateRouterCall(node)) {
2482
2534
  const callExpr = node;
2483
- for (const arg of callExpr.arguments) {
2535
+ for (const callArg of callExpr.arguments) {
2536
+ if (ts5.isObjectLiteralExpression(callArg)) {
2537
+ for (const prop of callArg.properties) {
2538
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
2539
+ if (ts5.isIdentifier(prop.initializer)) {
2540
+ result = { kind: "variable", name: prop.initializer.text };
2541
+ } else if (isInlineBuilder(prop.initializer)) {
2542
+ result = {
2543
+ kind: "inline",
2544
+ block: prop.initializer.getText(sourceFile)
2545
+ };
2546
+ }
2547
+ return;
2548
+ }
2549
+ }
2550
+ }
2551
+ }
2552
+ }
2553
+ ts5.forEachChild(node, visit);
2554
+ }
2555
+ visit(sourceFile);
2556
+ return result;
2557
+ }
2558
+ function extractBasenameFromRouter(code) {
2559
+ const sourceFile = ts5.createSourceFile(
2560
+ "router.tsx",
2561
+ code,
2562
+ ts5.ScriptTarget.Latest,
2563
+ true,
2564
+ ts5.ScriptKind.TSX
2565
+ );
2566
+ let result;
2567
+ function visit(node) {
2568
+ if (result !== void 0) return;
2569
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
2570
+ for (const arg of node.arguments) {
2484
2571
  if (ts5.isObjectLiteralExpression(arg)) {
2485
2572
  for (const prop of arg.properties) {
2486
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
2573
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
2487
2574
  result = prop.initializer.text;
2488
2575
  return;
2489
2576
  }
@@ -2496,6 +2583,19 @@ function extractUrlsVariableFromRouter(code) {
2496
2583
  visit(sourceFile);
2497
2584
  return result;
2498
2585
  }
2586
+ function applyBasenameToRoutes(result, basename3) {
2587
+ const prefixed = {};
2588
+ for (const [name, pattern] of Object.entries(result.routes)) {
2589
+ if (pattern === "/") {
2590
+ prefixed[name] = basename3;
2591
+ } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2592
+ prefixed[name] = basename3 + pattern.slice(1);
2593
+ } else {
2594
+ prefixed[name] = basename3 + pattern;
2595
+ }
2596
+ }
2597
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
2598
+ }
2499
2599
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2500
2600
  let routerSource;
2501
2601
  try {
@@ -2503,19 +2603,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2503
2603
  } catch {
2504
2604
  return { routes: {}, searchSchemas: {} };
2505
2605
  }
2506
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2507
- if (!urlsVarName) {
2606
+ const extraction = extractUrlsFromRouter(routerSource);
2607
+ if (!extraction) {
2508
2608
  return { routes: {}, searchSchemas: {} };
2509
2609
  }
2510
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2511
- if (imported) {
2512
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2513
- if (!targetFile) {
2514
- return { routes: {}, searchSchemas: {} };
2610
+ const rawBasename = extractBasenameFromRouter(routerSource);
2611
+ const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2612
+ let result;
2613
+ if (extraction.kind === "inline") {
2614
+ result = buildCombinedRouteMapWithSearch(
2615
+ routerFilePath,
2616
+ void 0,
2617
+ void 0,
2618
+ void 0,
2619
+ extraction.block
2620
+ );
2621
+ } else {
2622
+ const imported = resolveImportedVariable(routerSource, extraction.name);
2623
+ if (imported) {
2624
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2625
+ if (!targetFile) {
2626
+ return { routes: {}, searchSchemas: {} };
2627
+ }
2628
+ result = buildCombinedRouteMapWithSearch(
2629
+ targetFile,
2630
+ imported.exportedName
2631
+ );
2632
+ } else {
2633
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2515
2634
  }
2516
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2517
2635
  }
2518
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2636
+ if (basename3) {
2637
+ result = applyBasenameToRoutes(result, basename3);
2638
+ }
2639
+ return result;
2519
2640
  }
2520
2641
  function findRouterFiles(root, filter) {
2521
2642
  const result = [];
@@ -2540,25 +2661,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2540
2661
  throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
2541
2662
  }
2542
2663
  for (const routerFilePath of routerFilePaths) {
2543
- let routerSource;
2544
- try {
2545
- routerSource = readFileSync2(routerFilePath, "utf-8");
2546
- } catch {
2547
- continue;
2548
- }
2549
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2550
- if (!urlsVarName) continue;
2551
- let result;
2552
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2553
- if (imported) {
2554
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2555
- if (!targetFile) continue;
2556
- result = buildCombinedRouteMapWithSearch(
2557
- targetFile,
2558
- imported.exportedName
2559
- );
2560
- } else {
2561
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2664
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
2665
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
2666
+ let routerSource;
2667
+ try {
2668
+ routerSource = readFileSync2(routerFilePath, "utf-8");
2669
+ } catch {
2670
+ continue;
2671
+ }
2672
+ if (!extractUrlsFromRouter(routerSource)) continue;
2562
2673
  }
2563
2674
  const routerBasename = pathBasename(routerFilePath).replace(
2564
2675
  /\.(tsx?|jsx?)$/,
@@ -2784,6 +2895,68 @@ function createVersionPlugin() {
2784
2895
 
2785
2896
  // src/vite/utils/shared-utils.ts
2786
2897
  import * as Vite from "vite";
2898
+
2899
+ // src/vite/plugins/performance-tracks.ts
2900
+ import { readFile } from "node:fs/promises";
2901
+ var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2902
+ function buildPatchReplacement(match, debugInfoVar) {
2903
+ return `${match}
2904
+ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
2905
+ var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
2906
+ if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
2907
+ ${debugInfoVar} = _resolved._debugInfo;
2908
+ }
2909
+ }`;
2910
+ }
2911
+ function patchRsdwClientDebugInfoRecovery(code) {
2912
+ const match = code.match(RSDW_PATCH_RE);
2913
+ if (!match) {
2914
+ return { code, debugInfoVar: null };
2915
+ }
2916
+ return {
2917
+ code: code.replace(match[1], buildPatchReplacement(match[1], match[2])),
2918
+ debugInfoVar: match[2]
2919
+ };
2920
+ }
2921
+ function performanceTracksOptimizeDepsPlugin() {
2922
+ return {
2923
+ name: "@rangojs/router:performance-tracks-optimize-deps",
2924
+ setup(build) {
2925
+ build.onLoad(
2926
+ {
2927
+ filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
2928
+ },
2929
+ async (args) => {
2930
+ const code = await readFile(args.path, "utf8");
2931
+ const patched = patchRsdwClientDebugInfoRecovery(code);
2932
+ return {
2933
+ contents: patched.code,
2934
+ loader: "js"
2935
+ };
2936
+ }
2937
+ );
2938
+ }
2939
+ };
2940
+ }
2941
+ function performanceTracksPlugin() {
2942
+ return {
2943
+ name: "@rangojs/router:performance-tracks",
2944
+ transform(code, id) {
2945
+ if (!id.includes("react-server-dom") || !id.includes("client")) return;
2946
+ const patched = patchRsdwClientDebugInfoRecovery(code);
2947
+ if (!patched.debugInfoVar) return;
2948
+ if (process.env.INTERNAL_RANGO_DEBUG)
2949
+ console.log(
2950
+ "[perf-tracks] patched RSDW client (var:",
2951
+ patched.debugInfoVar,
2952
+ ")"
2953
+ );
2954
+ return patched.code;
2955
+ }
2956
+ };
2957
+ }
2958
+
2959
+ // src/vite/utils/shared-utils.ts
2787
2960
  var versionEsbuildPlugin = {
2788
2961
  name: "@rangojs/router-version",
2789
2962
  setup(build) {
@@ -2801,7 +2974,7 @@ var versionEsbuildPlugin = {
2801
2974
  }
2802
2975
  };
2803
2976
  var sharedEsbuildOptions = {
2804
- plugins: [versionEsbuildPlugin]
2977
+ plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
2805
2978
  };
2806
2979
  function createVirtualEntriesPlugin(entries, routerPathRef) {
2807
2980
  const virtualModules = {};
@@ -3007,6 +3180,8 @@ function createCjsToEsmPlugin() {
3007
3180
  import { createServer as createViteServer } from "vite";
3008
3181
  import { resolve as resolve8 } from "node:path";
3009
3182
  import { readFileSync as readFileSync6 } from "node:fs";
3183
+ import { createRequire } from "node:module";
3184
+ import { pathToFileURL } from "node:url";
3010
3185
 
3011
3186
  // src/vite/plugins/virtual-stub-plugin.ts
3012
3187
  function createVirtualStubPlugin() {
@@ -3190,8 +3365,8 @@ function createDiscoveryState(entryPath, opts) {
3190
3365
  perRouterManifestDataMap: /* @__PURE__ */ new Map(),
3191
3366
  prerenderManifestEntries: null,
3192
3367
  staticManifestEntries: null,
3193
- handlerChunkInfo: null,
3194
- staticHandlerChunkInfo: null,
3368
+ handlerChunkInfoMap: /* @__PURE__ */ new Map(),
3369
+ staticHandlerChunkInfoMap: /* @__PURE__ */ new Map(),
3195
3370
  rscEntryFileName: null,
3196
3371
  resolvedPrerenderModules: void 0,
3197
3372
  resolvedStaticModules: void 0,
@@ -3274,8 +3449,17 @@ function jsonParseExpression(value) {
3274
3449
  }
3275
3450
 
3276
3451
  // src/context-var.ts
3452
+ var NON_CACHEABLE_KEYS = /* @__PURE__ */ Symbol.for(
3453
+ "rango:non-cacheable-keys"
3454
+ );
3455
+ function getNonCacheableKeys(variables) {
3456
+ if (!variables[NON_CACHEABLE_KEYS]) {
3457
+ variables[NON_CACHEABLE_KEYS] = /* @__PURE__ */ new Set();
3458
+ }
3459
+ return variables[NON_CACHEABLE_KEYS];
3460
+ }
3277
3461
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3278
- function contextSet(variables, keyOrVar, value) {
3462
+ function contextSet(variables, keyOrVar, value, options) {
3279
3463
  if (typeof keyOrVar === "string") {
3280
3464
  if (FORBIDDEN_KEYS.has(keyOrVar)) {
3281
3465
  throw new Error(
@@ -3283,8 +3467,14 @@ function contextSet(variables, keyOrVar, value) {
3283
3467
  );
3284
3468
  }
3285
3469
  variables[keyOrVar] = value;
3470
+ if (options?.cache === false) {
3471
+ getNonCacheableKeys(variables).add(keyOrVar);
3472
+ }
3286
3473
  } else {
3287
3474
  variables[keyOrVar.key] = value;
3475
+ if (options?.cache === false) {
3476
+ getNonCacheableKeys(variables).add(keyOrVar.key);
3477
+ }
3288
3478
  }
3289
3479
  }
3290
3480
 
@@ -3307,6 +3497,7 @@ function encodePathParam(value) {
3307
3497
  }
3308
3498
  function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3309
3499
  let result = pattern;
3500
+ let hadOmittedOptional = false;
3310
3501
  for (const [key, value] of Object.entries(params)) {
3311
3502
  const escaped = escapeRegExp2(key);
3312
3503
  result = result.replace(
@@ -3315,6 +3506,15 @@ function substituteRouteParams(pattern, params, encode = encodeURIComponent) {
3315
3506
  );
3316
3507
  result = result.replace(`*${key}`, encode(value));
3317
3508
  }
3509
+ result = result.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?\?/g, () => {
3510
+ hadOmittedOptional = true;
3511
+ return "";
3512
+ });
3513
+ if (hadOmittedOptional) {
3514
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
3515
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
3516
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
3517
+ }
3318
3518
  return result;
3319
3519
  }
3320
3520
  async function runWithConcurrency(items, concurrency, fn) {
@@ -3426,11 +3626,12 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3426
3626
  for (const { manifest } of allManifests) {
3427
3627
  if (!manifest.prerenderRoutes) continue;
3428
3628
  const defs = manifest._prerenderDefs || {};
3629
+ const passthroughSet = new Set(manifest.passthroughRoutes || []);
3429
3630
  for (const routeName of manifest.prerenderRoutes) {
3430
3631
  const pattern = manifest.routeManifest[routeName];
3431
3632
  if (!pattern) continue;
3432
3633
  const def = defs[routeName];
3433
- const isPassthroughRoute = !!def?.options?.passthrough;
3634
+ const isPassthroughRoute = passthroughSet.has(routeName);
3434
3635
  const hasDynamic = pattern.includes(":") || pattern.includes("*");
3435
3636
  if (!hasDynamic) {
3436
3637
  entries.push({
@@ -3443,12 +3644,20 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3443
3644
  if (def?.getParams) {
3444
3645
  try {
3445
3646
  const buildVars = {};
3647
+ const buildEnv = state.resolvedBuildEnv;
3446
3648
  const getParamsCtx = {
3447
3649
  build: true,
3650
+ dev: !state.isBuildMode,
3448
3651
  set: ((keyOrVar, value) => {
3449
3652
  contextSet(buildVars, keyOrVar, value);
3450
3653
  }),
3451
- reverse: getParamsReverse
3654
+ reverse: getParamsReverse,
3655
+ get env() {
3656
+ if (buildEnv !== void 0) return buildEnv;
3657
+ throw new Error(
3658
+ "[rsc-router] ctx.env is not available during build-time getParams(). Configure buildEnv in your rango() plugin options to enable build-time env access."
3659
+ );
3660
+ }
3452
3661
  };
3453
3662
  const paramsList = await def.getParams(getParamsCtx);
3454
3663
  const concurrency = def.options?.concurrency ?? 1;
@@ -3527,7 +3736,8 @@ async function expandPrerenderRoutes(state, rscEnv, registry, allManifests) {
3527
3736
  entry.urlPath,
3528
3737
  {},
3529
3738
  entry.buildVars,
3530
- entry.isPassthroughRoute
3739
+ entry.isPassthroughRoute,
3740
+ state.resolvedBuildEnv
3531
3741
  );
3532
3742
  if (!result) continue;
3533
3743
  if (result.passthrough) {
@@ -3651,7 +3861,9 @@ async function renderStaticHandlers(state, rscEnv, registry) {
3651
3861
  const result = await routerInstance.renderStaticSegment(
3652
3862
  def.handler,
3653
3863
  def.$$id,
3654
- def.$$routePrefix
3864
+ def.$$routePrefix,
3865
+ state.resolvedBuildEnv,
3866
+ !state.isBuildMode
3655
3867
  );
3656
3868
  if (result) {
3657
3869
  const hasHandles = Object.keys(result.handles).length > 0;
@@ -3776,7 +3988,11 @@ async function discoverRouters(state, rscEnv) {
3776
3988
  if (!router.urlpatterns || !generateManifestFull) {
3777
3989
  continue;
3778
3990
  }
3779
- const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
3991
+ const manifest = generateManifestFull(
3992
+ router.urlpatterns,
3993
+ routerMountIndex,
3994
+ router.__basename ? { urlPrefix: router.__basename } : void 0
3995
+ );
3780
3996
  routerMountIndex++;
3781
3997
  allManifests.push({ id, manifest });
3782
3998
  const routeCount = Object.keys(manifest.routeManifest).length;
@@ -4225,48 +4441,45 @@ function postprocessBundle(state) {
4225
4441
  );
4226
4442
  const evictionTargets = [
4227
4443
  {
4228
- info: state.handlerChunkInfo,
4444
+ infos: state.handlerChunkInfoMap.values(),
4229
4445
  fnName: "Prerender",
4230
4446
  brand: "prerenderHandler",
4231
4447
  label: "handler code from RSC bundle"
4232
4448
  },
4233
4449
  {
4234
- info: state.staticHandlerChunkInfo,
4450
+ infos: state.staticHandlerChunkInfoMap.values(),
4235
4451
  fnName: "Static",
4236
4452
  brand: "staticHandler",
4237
4453
  label: "static handler code"
4238
4454
  }
4239
4455
  ];
4240
4456
  for (const target of evictionTargets) {
4241
- if (!target.info) continue;
4242
- const chunkPath = resolve7(
4243
- state.projectRoot,
4244
- "dist/rsc",
4245
- target.info.fileName
4246
- );
4247
- try {
4248
- const code = readFileSync5(chunkPath, "utf-8");
4249
- const result = evictHandlerCode(
4250
- code,
4251
- target.info.exports,
4252
- target.fnName,
4253
- target.brand
4254
- );
4255
- if (result) {
4256
- writeFileSync4(chunkPath, result.code);
4257
- const savedKB = (result.savedBytes / 1024).toFixed(1);
4258
- console.log(
4259
- `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${target.info.fileName}`
4457
+ for (const info of target.infos) {
4458
+ const chunkPath = resolve7(state.projectRoot, "dist/rsc", info.fileName);
4459
+ try {
4460
+ const code = readFileSync5(chunkPath, "utf-8");
4461
+ const result = evictHandlerCode(
4462
+ code,
4463
+ info.exports,
4464
+ target.fnName,
4465
+ target.brand
4466
+ );
4467
+ if (result) {
4468
+ writeFileSync4(chunkPath, result.code);
4469
+ const savedKB = (result.savedBytes / 1024).toFixed(1);
4470
+ console.log(
4471
+ `[rsc-router] Evicted ${target.label} (${savedKB} KB saved): ${info.fileName}`
4472
+ );
4473
+ }
4474
+ } catch (replaceErr) {
4475
+ console.warn(
4476
+ `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4260
4477
  );
4261
4478
  }
4262
- } catch (replaceErr) {
4263
- console.warn(
4264
- `[rsc-router] Failed to evict ${target.label}: ${replaceErr.message}`
4265
- );
4266
4479
  }
4267
4480
  }
4268
- state.handlerChunkInfo = null;
4269
- state.staticHandlerChunkInfo = null;
4481
+ state.handlerChunkInfoMap.clear();
4482
+ state.staticHandlerChunkInfoMap.clear();
4270
4483
  if (hasPrerenderData && existsSync6(rscEntryPath)) {
4271
4484
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4272
4485
  if (!rscCode.includes("__prerender-manifest.js")) {
@@ -4309,7 +4522,7 @@ function postprocessBundle(state) {
4309
4522
  }
4310
4523
  if (hasStaticData && existsSync6(rscEntryPath)) {
4311
4524
  const rscCode = readFileSync5(rscEntryPath, "utf-8");
4312
- if (!rscCode.includes("__STATIC_MANIFEST")) {
4525
+ if (!rscCode.includes("__static-manifest.js")) {
4313
4526
  try {
4314
4527
  const manifestEntries = [];
4315
4528
  let totalBytes = copyStagedBuildAssets(
@@ -4378,8 +4591,67 @@ async function createTempRscServer(state, options = {}) {
4378
4591
  ]
4379
4592
  });
4380
4593
  }
4594
+ async function resolveBuildEnv(option, factoryCtx) {
4595
+ if (!option) return null;
4596
+ if (option === "auto") {
4597
+ if (factoryCtx.preset !== "cloudflare") {
4598
+ throw new Error(
4599
+ '[rsc-router] buildEnv: "auto" is only supported with preset: "cloudflare". Use a factory function or plain object for other presets.'
4600
+ );
4601
+ }
4602
+ try {
4603
+ const userRequire = createRequire(
4604
+ resolve8(factoryCtx.root, "package.json")
4605
+ );
4606
+ const wranglerPath = userRequire.resolve("wrangler");
4607
+ const { getPlatformProxy } = await import(pathToFileURL(wranglerPath).href);
4608
+ const proxy = await getPlatformProxy();
4609
+ return {
4610
+ env: proxy.env,
4611
+ dispose: proxy.dispose
4612
+ };
4613
+ } catch (err) {
4614
+ throw new Error(
4615
+ `[rsc-router] buildEnv: "auto" requires wrangler to be installed.
4616
+ Install it with: pnpm add -D wrangler
4617
+ ${err.message}`
4618
+ );
4619
+ }
4620
+ }
4621
+ if (typeof option === "function") {
4622
+ return await option(factoryCtx);
4623
+ }
4624
+ return { env: option };
4625
+ }
4626
+ async function acquireBuildEnv(s, command, mode) {
4627
+ const option = s.opts?.buildEnv;
4628
+ if (!option) return false;
4629
+ const result = await resolveBuildEnv(option, {
4630
+ root: s.projectRoot,
4631
+ mode,
4632
+ command,
4633
+ preset: s.opts?.preset ?? "node"
4634
+ });
4635
+ if (!result) return false;
4636
+ s.resolvedBuildEnv = result.env;
4637
+ s.buildEnvDispose = result.dispose ?? null;
4638
+ return true;
4639
+ }
4640
+ async function releaseBuildEnv(s) {
4641
+ if (s.buildEnvDispose) {
4642
+ try {
4643
+ await s.buildEnvDispose();
4644
+ } catch (err) {
4645
+ console.warn(`[rsc-router] buildEnv dispose failed: ${err.message}`);
4646
+ }
4647
+ s.buildEnvDispose = null;
4648
+ }
4649
+ s.resolvedBuildEnv = void 0;
4650
+ }
4381
4651
  function createRouterDiscoveryPlugin(entryPath, opts) {
4382
4652
  const s = createDiscoveryState(entryPath, opts);
4653
+ let viteCommand = "build";
4654
+ let viteMode = "production";
4383
4655
  return {
4384
4656
  name: "@rangojs/router:discovery",
4385
4657
  config() {
@@ -4388,31 +4660,13 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4388
4660
  __RANGO_DEBUG__: JSON.stringify(!!process.env.INTERNAL_RANGO_DEBUG)
4389
4661
  }
4390
4662
  };
4391
- if (opts?.enableBuildPrerender) {
4392
- config.environments = {
4393
- rsc: {
4394
- build: {
4395
- rollupOptions: {
4396
- output: {
4397
- manualChunks(id) {
4398
- if (s.resolvedPrerenderModules?.has(id)) {
4399
- return "__prerender-handlers";
4400
- }
4401
- if (s.resolvedStaticModules?.has(id)) {
4402
- return "__static-handlers";
4403
- }
4404
- }
4405
- }
4406
- }
4407
- }
4408
- }
4409
- };
4410
- }
4411
4663
  return config;
4412
4664
  },
4413
4665
  configResolved(config) {
4414
4666
  s.projectRoot = config.root;
4415
4667
  s.isBuildMode = config.command === "build";
4668
+ viteCommand = config.command;
4669
+ viteMode = config.mode;
4416
4670
  s.userResolveAlias = config.resolve.alias;
4417
4671
  if (!s.resolvedEntryPath && opts?.routerPathRef?.path) {
4418
4672
  s.resolvedEntryPath = opts.routerPathRef.path;
@@ -4457,6 +4711,8 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4457
4711
  });
4458
4712
  prerenderTempServer = null;
4459
4713
  }
4714
+ releaseBuildEnv(s).catch(() => {
4715
+ });
4460
4716
  });
4461
4717
  async function getOrCreateTempServer() {
4462
4718
  if (prerenderNodeRegistry) {
@@ -4487,6 +4743,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
4487
4743
  if (!rscEnv?.runner) {
4488
4744
  s.devServerOrigin = getDevServerOrigin();
4489
4745
  try {
4746
+ await acquireBuildEnv(s, viteCommand, viteMode);
4490
4747
  const tempRscEnv = await getOrCreateTempServer();
4491
4748
  if (tempRscEnv) {
4492
4749
  await discoverRouters(s, tempRscEnv);
@@ -4502,6 +4759,7 @@ ${err.stack}`
4502
4759
  return;
4503
4760
  }
4504
4761
  try {
4762
+ await acquireBuildEnv(s, viteCommand, viteMode);
4505
4763
  const serverMod = await rscEnv.runner.import(
4506
4764
  "@rangojs/router/server"
4507
4765
  );
@@ -4566,7 +4824,26 @@ ${err.stack}`
4566
4824
  res.end("Missing pathname");
4567
4825
  return;
4568
4826
  }
4569
- let registry = mainRegistry;
4827
+ const rscEnv = server.environments?.rsc;
4828
+ let registry = null;
4829
+ if (rscEnv?.runner && s.resolvedEntryPath) {
4830
+ try {
4831
+ await rscEnv.runner.import(s.resolvedEntryPath);
4832
+ const serverMod = await rscEnv.runner.import(
4833
+ "@rangojs/router/server"
4834
+ );
4835
+ registry = serverMod.RouterRegistry ?? null;
4836
+ } catch (err) {
4837
+ console.warn(
4838
+ `[rsc-router] Dev prerender module refresh failed: ${err.message}`
4839
+ );
4840
+ res.statusCode = 500;
4841
+ res.end(`Prerender handler error: ${err.message}`);
4842
+ return;
4843
+ }
4844
+ } else {
4845
+ registry = mainRegistry;
4846
+ }
4570
4847
  if (!registry) {
4571
4848
  if (!prerenderNodeRegistry) {
4572
4849
  await getOrCreateTempServer();
@@ -4588,7 +4865,10 @@ ${err.stack}`
4588
4865
  pathname,
4589
4866
  {},
4590
4867
  void 0,
4591
- wantPassthrough
4868
+ wantPassthrough,
4869
+ s.resolvedBuildEnv,
4870
+ true
4871
+ // devMode: check getParams for passthrough routes
4592
4872
  );
4593
4873
  if (!result) continue;
4594
4874
  if (result.passthrough) continue;
@@ -4724,6 +5004,7 @@ ${err.stack}`
4724
5004
  resetStagedBuildAssets(s.projectRoot);
4725
5005
  s.prerenderManifestEntries = null;
4726
5006
  s.staticManifestEntries = null;
5007
+ await acquireBuildEnv(s, viteCommand, viteMode);
4727
5008
  let tempServer = null;
4728
5009
  globalThis.__rscRouterDiscoveryActive = true;
4729
5010
  try {
@@ -4763,6 +5044,7 @@ ${details}`
4763
5044
  if (tempServer) {
4764
5045
  await tempServer.close();
4765
5046
  }
5047
+ await releaseBuildEnv(s);
4766
5048
  }
4767
5049
  },
4768
5050
  // Virtual module: provides the pre-generated route manifest as a JS module
@@ -4805,20 +5087,30 @@ ${details}`
4805
5087
  }
4806
5088
  if (!s.resolvedPrerenderModules?.size && !s.resolvedStaticModules?.size)
4807
5089
  return;
5090
+ s.handlerChunkInfoMap.clear();
5091
+ s.staticHandlerChunkInfoMap.clear();
4808
5092
  for (const [fileName, chunk] of Object.entries(bundle)) {
4809
5093
  if (chunk.type !== "chunk") continue;
4810
- if (fileName.includes("__prerender-handlers") && s.resolvedPrerenderModules?.size) {
5094
+ if (s.resolvedPrerenderModules?.size) {
4811
5095
  const handlers = extractHandlerExportsFromChunk(
4812
5096
  chunk.code,
4813
5097
  s.resolvedPrerenderModules,
4814
5098
  "Prerender",
4815
- true
5099
+ false
4816
5100
  );
4817
5101
  if (handlers.length > 0) {
4818
- s.handlerChunkInfo = { fileName, exports: handlers };
5102
+ const existing = s.handlerChunkInfoMap.get(fileName);
5103
+ if (existing) {
5104
+ existing.exports.push(...handlers);
5105
+ } else {
5106
+ s.handlerChunkInfoMap.set(fileName, {
5107
+ fileName,
5108
+ exports: handlers
5109
+ });
5110
+ }
4819
5111
  }
4820
5112
  }
4821
- if (fileName.includes("__static-handlers") && s.resolvedStaticModules?.size) {
5113
+ if (s.resolvedStaticModules?.size) {
4822
5114
  const handlers = extractHandlerExportsFromChunk(
4823
5115
  chunk.code,
4824
5116
  s.resolvedStaticModules,
@@ -4826,7 +5118,15 @@ ${details}`
4826
5118
  false
4827
5119
  );
4828
5120
  if (handlers.length > 0) {
4829
- s.staticHandlerChunkInfo = { fileName, exports: handlers };
5121
+ const existing = s.staticHandlerChunkInfoMap.get(fileName);
5122
+ if (existing) {
5123
+ existing.exports.push(...handlers);
5124
+ } else {
5125
+ s.staticHandlerChunkInfoMap.set(fileName, {
5126
+ fileName,
5127
+ exports: handlers
5128
+ });
5129
+ }
4830
5130
  }
4831
5131
  }
4832
5132
  }
@@ -4853,7 +5153,16 @@ async function rango(options) {
4853
5153
  const showBanner = resolvedOptions.banner ?? true;
4854
5154
  const plugins = [];
4855
5155
  const rangoAliases = getPackageAliases();
4856
- const excludeDeps = getExcludeDeps();
5156
+ const excludeDeps = [
5157
+ ...getExcludeDeps(),
5158
+ // The public browser entry re-exports the RSDW browser client.
5159
+ // Excluding both keeps Vite from freezing the unpatched bundle into
5160
+ // .vite/deps before our source transforms run.
5161
+ "@vitejs/plugin-rsc/browser",
5162
+ // Keep the browser RSDW client out of Vite's dep optimizer so our
5163
+ // cjs-to-esm transform can patch the real file.
5164
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5165
+ ];
4857
5166
  const routerRef = { path: void 0 };
4858
5167
  const prerenderEnabled = true;
4859
5168
  if (preset === "cloudflare") {
@@ -4949,6 +5258,7 @@ async function rango(options) {
4949
5258
  }
4950
5259
  });
4951
5260
  plugins.push(createVirtualEntriesPlugin(finalEntries));
5261
+ plugins.push(performanceTracksPlugin());
4952
5262
  plugins.push(
4953
5263
  rsc({
4954
5264
  entries: finalEntries,
@@ -5067,6 +5377,7 @@ ${list}`);
5067
5377
  }
5068
5378
  });
5069
5379
  plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
5380
+ plugins.push(performanceTracksPlugin());
5070
5381
  plugins.push(
5071
5382
  rsc({
5072
5383
  entries: finalEntries
@@ -5107,7 +5418,8 @@ ${list}`);
5107
5418
  createRouterDiscoveryPlugin(discoveryEntryPath, {
5108
5419
  routerPathRef: discoveryRouterRef,
5109
5420
  enableBuildPrerender: prerenderEnabled,
5110
- staticRouteTypesGeneration: resolvedOptions.staticRouteTypesGeneration
5421
+ buildEnv: options?.buildEnv,
5422
+ preset
5111
5423
  })
5112
5424
  );
5113
5425
  return plugins;
@@ -5120,29 +5432,75 @@ function poke() {
5120
5432
  apply: "serve",
5121
5433
  configureServer(server) {
5122
5434
  const stdin = process.stdin;
5123
- const previousRawMode = stdin.isTTY ? stdin.isRaw : null;
5435
+ const debug = process.env.RANGO_POKE_DEBUG === "1";
5436
+ const triggerReload = (source) => {
5437
+ server.hot.send({ type: "full-reload", path: "*" });
5438
+ server.config.logger.info(` browser reload (${source})`, {
5439
+ timestamp: true
5440
+ });
5441
+ };
5442
+ const toBuffer = (chunk) => {
5443
+ return typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5444
+ };
5445
+ const formatChunk = (chunk) => {
5446
+ const data = toBuffer(chunk);
5447
+ const hex = Array.from(data).map((byte) => `0x${byte.toString(16).padStart(2, "0")}`).join(" ");
5448
+ const ascii = Array.from(data).map((byte) => {
5449
+ if (byte >= 32 && byte <= 126) return String.fromCharCode(byte);
5450
+ if (byte === 10) return "\\n";
5451
+ if (byte === 13) return "\\r";
5452
+ if (byte === 9) return "\\t";
5453
+ return ".";
5454
+ }).join("");
5455
+ return `len=${data.length} hex=[${hex}] ascii="${ascii}"`;
5456
+ };
5457
+ const readCtrlR = (chunk) => {
5458
+ const data = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
5459
+ return data.length === 1 && data[0] === 18;
5460
+ };
5461
+ const readSubmittedCommands = (chunk) => {
5462
+ const text = toBuffer(chunk).toString("utf8").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
5463
+ if (!text.includes("\n")) return [];
5464
+ const lines = text.split("\n");
5465
+ lines.pop();
5466
+ return lines;
5467
+ };
5468
+ if (debug) {
5469
+ server.config.logger.info(
5470
+ ` poke debug enabled (isTTY=${stdin.isTTY ? "yes" : "no"}, isRaw=${stdin.isTTY ? stdin.isRaw ? "yes" : "no" : "n/a"})`,
5471
+ { timestamp: true }
5472
+ );
5473
+ }
5124
5474
  if (stdin.isTTY) {
5125
- stdin.setRawMode(true);
5475
+ server.config.logger.info(
5476
+ " poke ready: press e + enter to reload browser (ctrl+r also works when available)",
5477
+ { timestamp: true }
5478
+ );
5126
5479
  }
5127
5480
  const onData = (data) => {
5128
- if (data.length !== 1) return;
5129
- if (data[0] === 3) {
5130
- process.emit("SIGINT", "SIGINT");
5131
- return;
5132
- }
5133
- if (data[0] === 18) {
5134
- server.hot.send({ type: "full-reload", path: "*" });
5135
- server.config.logger.info(" browser reload (ctrl+r)", {
5481
+ if (debug) {
5482
+ server.config.logger.info(` poke stdin ${formatChunk(data)}`, {
5136
5483
  timestamp: true
5137
5484
  });
5138
5485
  }
5486
+ if (readCtrlR(data)) {
5487
+ triggerReload("ctrl+r");
5488
+ return;
5489
+ }
5490
+ for (const command of readSubmittedCommands(data)) {
5491
+ if (command === "e") {
5492
+ triggerReload("e+enter");
5493
+ return;
5494
+ }
5495
+ if (command === "\x1Br") {
5496
+ triggerReload("option+r+enter");
5497
+ return;
5498
+ }
5499
+ }
5139
5500
  };
5140
5501
  stdin.on("data", onData);
5141
5502
  server.httpServer?.on("close", () => {
5142
5503
  stdin.off("data", onData);
5143
- if (stdin.isTTY && previousRawMode !== null) {
5144
- stdin.setRawMode(previousRawMode);
5145
- }
5146
5504
  });
5147
5505
  }
5148
5506
  };