@rangojs/router 0.0.0-experimental.56cb65a7 → 0.0.0-experimental.57

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 (74) hide show
  1. package/dist/bin/rango.js +128 -46
  2. package/dist/vite/index.js +211 -47
  3. package/package.json +2 -2
  4. package/skills/cache-guide/SKILL.md +32 -0
  5. package/skills/caching/SKILL.md +8 -0
  6. package/skills/links/SKILL.md +3 -1
  7. package/skills/loader/SKILL.md +53 -43
  8. package/skills/middleware/SKILL.md +2 -0
  9. package/skills/parallel/SKILL.md +67 -0
  10. package/skills/route/SKILL.md +31 -0
  11. package/skills/router-setup/SKILL.md +87 -2
  12. package/skills/typesafety/SKILL.md +10 -0
  13. package/src/browser/app-version.ts +14 -0
  14. package/src/browser/navigation-bridge.ts +16 -3
  15. package/src/browser/navigation-client.ts +64 -40
  16. package/src/browser/navigation-store.ts +43 -8
  17. package/src/browser/partial-update.ts +37 -4
  18. package/src/browser/prefetch/fetch.ts +8 -2
  19. package/src/browser/prefetch/queue.ts +61 -29
  20. package/src/browser/prefetch/resource-ready.ts +77 -0
  21. package/src/browser/react/Link.tsx +44 -8
  22. package/src/browser/react/NavigationProvider.tsx +13 -4
  23. package/src/browser/react/context.ts +7 -2
  24. package/src/browser/react/use-router.ts +21 -8
  25. package/src/browser/rsc-router.tsx +26 -3
  26. package/src/browser/server-action-bridge.ts +8 -6
  27. package/src/browser/types.ts +27 -5
  28. package/src/build/generate-manifest.ts +3 -0
  29. package/src/build/generate-route-types.ts +3 -0
  30. package/src/build/route-types/include-resolution.ts +8 -1
  31. package/src/build/route-types/router-processing.ts +211 -72
  32. package/src/cache/cache-runtime.ts +15 -11
  33. package/src/cache/cache-scope.ts +46 -5
  34. package/src/cache/taint.ts +55 -0
  35. package/src/context-var.ts +72 -2
  36. package/src/route-definition/helpers-types.ts +6 -5
  37. package/src/route-definition/redirect.ts +9 -1
  38. package/src/router/handler-context.ts +36 -17
  39. package/src/router/intercept-resolution.ts +9 -4
  40. package/src/router/loader-resolution.ts +9 -2
  41. package/src/router/match-middleware/background-revalidation.ts +12 -1
  42. package/src/router/match-middleware/cache-lookup.ts +50 -7
  43. package/src/router/match-middleware/cache-store.ts +21 -4
  44. package/src/router/match-result.ts +11 -5
  45. package/src/router/middleware-types.ts +6 -8
  46. package/src/router/middleware.ts +2 -5
  47. package/src/router/prerender-match.ts +2 -2
  48. package/src/router/router-context.ts +1 -0
  49. package/src/router/router-interfaces.ts +25 -4
  50. package/src/router/router-options.ts +37 -11
  51. package/src/router/segment-resolution/fresh.ts +47 -16
  52. package/src/router/segment-resolution/helpers.ts +29 -24
  53. package/src/router/segment-resolution/revalidation.ts +50 -21
  54. package/src/router/types.ts +1 -0
  55. package/src/router.ts +41 -4
  56. package/src/rsc/handler.ts +11 -2
  57. package/src/rsc/manifest-init.ts +5 -1
  58. package/src/rsc/progressive-enhancement.ts +4 -0
  59. package/src/rsc/rsc-rendering.ts +5 -0
  60. package/src/rsc/server-action.ts +2 -0
  61. package/src/rsc/ssr-setup.ts +1 -1
  62. package/src/rsc/types.ts +8 -1
  63. package/src/server/context.ts +36 -0
  64. package/src/server/loader-registry.ts +9 -8
  65. package/src/server/request-context.ts +50 -12
  66. package/src/ssr/index.tsx +3 -0
  67. package/src/types/cache-types.ts +4 -4
  68. package/src/types/handler-context.ts +125 -31
  69. package/src/types/loader-types.ts +4 -5
  70. package/src/urls/pattern-types.ts +12 -0
  71. package/src/vite/discovery/discover-routers.ts +5 -1
  72. package/src/vite/plugins/performance-tracks.ts +88 -0
  73. package/src/vite/rango.ts +17 -1
  74. package/src/vite/utils/shared-utils.ts +3 -2
package/dist/bin/rango.js CHANGED
@@ -450,7 +450,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
450
450
  }
451
451
  return routeMap;
452
452
  }
453
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
453
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
454
454
  visited = visited ?? /* @__PURE__ */ new Set();
455
455
  const realPath = resolve(filePath);
456
456
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -466,7 +466,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
466
466
  return { routes: {}, searchSchemas: {} };
467
467
  }
468
468
  let block;
469
- if (variableName) {
469
+ if (inlineBlock) {
470
+ block = inlineBlock;
471
+ } else if (variableName) {
470
472
  const extracted = extractUrlsBlockForVariable(source, variableName);
471
473
  if (!extracted) return { routes: {}, searchSchemas: {} };
472
474
  block = extracted;
@@ -671,7 +673,7 @@ Router root: ${conflict.ancestor}
671
673
  Nested router: ${conflict.nested}
672
674
  Move the nested router into a sibling directory or configure it as a separate app root.`;
673
675
  }
674
- function extractUrlsVariableFromRouter(code) {
676
+ function extractUrlsFromRouter(code) {
675
677
  const sourceFile = ts5.createSourceFile(
676
678
  "router.tsx",
677
679
  code,
@@ -685,24 +687,70 @@ function extractUrlsVariableFromRouter(code) {
685
687
  const callee = node.expression;
686
688
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
687
689
  }
690
+ function isInlineBuilder(node) {
691
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
692
+ }
693
+ function isRoutesOnCreateRouter(node) {
694
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
695
+ return false;
696
+ let inner = node.expression.expression;
697
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
698
+ inner = inner.expression.expression;
699
+ }
700
+ return isCreateRouterCall(inner);
701
+ }
688
702
  function visit(node) {
689
703
  if (result) return;
690
- if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
691
- let inner = node.expression.expression;
692
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
693
- inner = inner.expression.expression;
694
- }
695
- if (isCreateRouterCall(inner)) {
696
- result = node.arguments[0].text;
697
- return;
704
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
705
+ const arg = node.arguments[0];
706
+ if (ts5.isIdentifier(arg)) {
707
+ result = { kind: "variable", name: arg.text };
708
+ } else if (isInlineBuilder(arg)) {
709
+ result = { kind: "inline", block: arg.getText(sourceFile) };
698
710
  }
711
+ return;
699
712
  }
700
713
  if (isCreateRouterCall(node)) {
701
714
  const callExpr = node;
702
- for (const arg of callExpr.arguments) {
715
+ for (const callArg of callExpr.arguments) {
716
+ if (ts5.isObjectLiteralExpression(callArg)) {
717
+ for (const prop of callArg.properties) {
718
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
719
+ if (ts5.isIdentifier(prop.initializer)) {
720
+ result = { kind: "variable", name: prop.initializer.text };
721
+ } else if (isInlineBuilder(prop.initializer)) {
722
+ result = {
723
+ kind: "inline",
724
+ block: prop.initializer.getText(sourceFile)
725
+ };
726
+ }
727
+ return;
728
+ }
729
+ }
730
+ }
731
+ }
732
+ }
733
+ ts5.forEachChild(node, visit);
734
+ }
735
+ visit(sourceFile);
736
+ return result;
737
+ }
738
+ function extractBasenameFromRouter(code) {
739
+ const sourceFile = ts5.createSourceFile(
740
+ "router.tsx",
741
+ code,
742
+ ts5.ScriptTarget.Latest,
743
+ true,
744
+ ts5.ScriptKind.TSX
745
+ );
746
+ let result;
747
+ function visit(node) {
748
+ if (result !== void 0) return;
749
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
750
+ for (const arg of node.arguments) {
703
751
  if (ts5.isObjectLiteralExpression(arg)) {
704
752
  for (const prop of arg.properties) {
705
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
753
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
706
754
  result = prop.initializer.text;
707
755
  return;
708
756
  }
@@ -715,6 +763,19 @@ function extractUrlsVariableFromRouter(code) {
715
763
  visit(sourceFile);
716
764
  return result;
717
765
  }
766
+ function applyBasenameToRoutes(result, basename2) {
767
+ const prefixed = {};
768
+ for (const [name, pattern] of Object.entries(result.routes)) {
769
+ if (pattern === "/") {
770
+ prefixed[name] = basename2;
771
+ } else if (basename2.endsWith("/") && pattern.startsWith("/")) {
772
+ prefixed[name] = basename2 + pattern.slice(1);
773
+ } else {
774
+ prefixed[name] = basename2 + pattern;
775
+ }
776
+ }
777
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
778
+ }
718
779
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
719
780
  let routerSource;
720
781
  try {
@@ -722,19 +783,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
722
783
  } catch {
723
784
  return { routes: {}, searchSchemas: {} };
724
785
  }
725
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
726
- if (!urlsVarName) {
786
+ const extraction = extractUrlsFromRouter(routerSource);
787
+ if (!extraction) {
727
788
  return { routes: {}, searchSchemas: {} };
728
789
  }
729
- const imported = resolveImportedVariable(routerSource, urlsVarName);
730
- if (imported) {
731
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
732
- if (!targetFile) {
733
- return { routes: {}, searchSchemas: {} };
790
+ const rawBasename = extractBasenameFromRouter(routerSource);
791
+ const basename2 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
792
+ let result;
793
+ if (extraction.kind === "inline") {
794
+ result = buildCombinedRouteMapWithSearch(
795
+ routerFilePath,
796
+ void 0,
797
+ void 0,
798
+ void 0,
799
+ extraction.block
800
+ );
801
+ } else {
802
+ const imported = resolveImportedVariable(routerSource, extraction.name);
803
+ if (imported) {
804
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
805
+ if (!targetFile) {
806
+ return { routes: {}, searchSchemas: {} };
807
+ }
808
+ result = buildCombinedRouteMapWithSearch(
809
+ targetFile,
810
+ imported.exportedName
811
+ );
812
+ } else {
813
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
734
814
  }
735
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
736
815
  }
737
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
816
+ if (basename2) {
817
+ result = applyBasenameToRoutes(result, basename2);
818
+ }
819
+ return result;
738
820
  }
739
821
  function detectUnresolvableIncludes(routerFilePath) {
740
822
  const realPath = resolve2(routerFilePath);
@@ -744,9 +826,20 @@ function detectUnresolvableIncludes(routerFilePath) {
744
826
  } catch {
745
827
  return [];
746
828
  }
747
- const urlsVarName = extractUrlsVariableFromRouter(source);
748
- if (!urlsVarName) return [];
749
- const imported = resolveImportedVariable(source, urlsVarName);
829
+ const extraction = extractUrlsFromRouter(source);
830
+ if (!extraction) return [];
831
+ const diagnostics = [];
832
+ if (extraction.kind === "inline") {
833
+ buildCombinedRouteMapWithSearch(
834
+ realPath,
835
+ void 0,
836
+ /* @__PURE__ */ new Set(),
837
+ diagnostics,
838
+ extraction.block
839
+ );
840
+ return diagnostics;
841
+ }
842
+ const imported = resolveImportedVariable(source, extraction.name);
750
843
  let targetFile;
751
844
  let exportedName;
752
845
  if (imported) {
@@ -766,9 +859,8 @@ function detectUnresolvableIncludes(routerFilePath) {
766
859
  exportedName = imported.exportedName;
767
860
  } else {
768
861
  targetFile = realPath;
769
- exportedName = urlsVarName;
862
+ exportedName = extraction.name;
770
863
  }
771
- const diagnostics = [];
772
864
  buildCombinedRouteMapWithSearch(
773
865
  targetFile,
774
866
  exportedName,
@@ -816,25 +908,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
816
908
  throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
817
909
  }
818
910
  for (const routerFilePath of routerFilePaths) {
819
- let routerSource;
820
- try {
821
- routerSource = readFileSync3(routerFilePath, "utf-8");
822
- } catch {
823
- continue;
824
- }
825
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
826
- if (!urlsVarName) continue;
827
- let result;
828
- const imported = resolveImportedVariable(routerSource, urlsVarName);
829
- if (imported) {
830
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
831
- if (!targetFile) continue;
832
- result = buildCombinedRouteMapWithSearch(
833
- targetFile,
834
- imported.exportedName
835
- );
836
- } else {
837
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
911
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
912
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
913
+ let routerSource;
914
+ try {
915
+ routerSource = readFileSync3(routerFilePath, "utf-8");
916
+ } catch {
917
+ continue;
918
+ }
919
+ if (!extractUrlsFromRouter(routerSource)) continue;
838
920
  }
839
921
  const routerBasename = pathBasename(routerFilePath).replace(
840
922
  /\.(tsx?|jsx?)$/,
@@ -1745,7 +1745,7 @@ import { resolve } from "node:path";
1745
1745
  // package.json
1746
1746
  var package_default = {
1747
1747
  name: "@rangojs/router",
1748
- version: "0.0.0-experimental.56cb65a7",
1748
+ version: "0.0.0-experimental.57",
1749
1749
  description: "Django-inspired RSC router with composable URL patterns",
1750
1750
  keywords: [
1751
1751
  "react",
@@ -1887,7 +1887,7 @@ var package_default = {
1887
1887
  "test:unit:watch": "vitest"
1888
1888
  },
1889
1889
  dependencies: {
1890
- "@vitejs/plugin-rsc": "^0.5.14",
1890
+ "@vitejs/plugin-rsc": "^0.5.19",
1891
1891
  "magic-string": "^0.30.17",
1892
1892
  picomatch: "^4.0.3",
1893
1893
  "rsc-html-stream": "^0.0.7"
@@ -2317,7 +2317,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
2317
2317
  }
2318
2318
  return routeMap;
2319
2319
  }
2320
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
2320
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
2321
2321
  visited = visited ?? /* @__PURE__ */ new Set();
2322
2322
  const realPath = resolve2(filePath);
2323
2323
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -2333,7 +2333,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
2333
2333
  return { routes: {}, searchSchemas: {} };
2334
2334
  }
2335
2335
  let block;
2336
- if (variableName) {
2336
+ if (inlineBlock) {
2337
+ block = inlineBlock;
2338
+ } else if (variableName) {
2337
2339
  const extracted = extractUrlsBlockForVariable(source, variableName);
2338
2340
  if (!extracted) return { routes: {}, searchSchemas: {} };
2339
2341
  block = extracted;
@@ -2452,7 +2454,7 @@ Router root: ${conflict.ancestor}
2452
2454
  Nested router: ${conflict.nested}
2453
2455
  Move the nested router into a sibling directory or configure it as a separate app root.`;
2454
2456
  }
2455
- function extractUrlsVariableFromRouter(code) {
2457
+ function extractUrlsFromRouter(code) {
2456
2458
  const sourceFile = ts5.createSourceFile(
2457
2459
  "router.tsx",
2458
2460
  code,
@@ -2466,24 +2468,70 @@ function extractUrlsVariableFromRouter(code) {
2466
2468
  const callee = node.expression;
2467
2469
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
2468
2470
  }
2471
+ function isInlineBuilder(node) {
2472
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
2473
+ }
2474
+ function isRoutesOnCreateRouter(node) {
2475
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
2476
+ return false;
2477
+ let inner = node.expression.expression;
2478
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
2479
+ inner = inner.expression.expression;
2480
+ }
2481
+ return isCreateRouterCall(inner);
2482
+ }
2469
2483
  function visit(node) {
2470
2484
  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;
2485
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
2486
+ const arg = node.arguments[0];
2487
+ if (ts5.isIdentifier(arg)) {
2488
+ result = { kind: "variable", name: arg.text };
2489
+ } else if (isInlineBuilder(arg)) {
2490
+ result = { kind: "inline", block: arg.getText(sourceFile) };
2479
2491
  }
2492
+ return;
2480
2493
  }
2481
2494
  if (isCreateRouterCall(node)) {
2482
2495
  const callExpr = node;
2483
- for (const arg of callExpr.arguments) {
2496
+ for (const callArg of callExpr.arguments) {
2497
+ if (ts5.isObjectLiteralExpression(callArg)) {
2498
+ for (const prop of callArg.properties) {
2499
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
2500
+ if (ts5.isIdentifier(prop.initializer)) {
2501
+ result = { kind: "variable", name: prop.initializer.text };
2502
+ } else if (isInlineBuilder(prop.initializer)) {
2503
+ result = {
2504
+ kind: "inline",
2505
+ block: prop.initializer.getText(sourceFile)
2506
+ };
2507
+ }
2508
+ return;
2509
+ }
2510
+ }
2511
+ }
2512
+ }
2513
+ }
2514
+ ts5.forEachChild(node, visit);
2515
+ }
2516
+ visit(sourceFile);
2517
+ return result;
2518
+ }
2519
+ function extractBasenameFromRouter(code) {
2520
+ const sourceFile = ts5.createSourceFile(
2521
+ "router.tsx",
2522
+ code,
2523
+ ts5.ScriptTarget.Latest,
2524
+ true,
2525
+ ts5.ScriptKind.TSX
2526
+ );
2527
+ let result;
2528
+ function visit(node) {
2529
+ if (result !== void 0) return;
2530
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
2531
+ for (const arg of node.arguments) {
2484
2532
  if (ts5.isObjectLiteralExpression(arg)) {
2485
2533
  for (const prop of arg.properties) {
2486
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
2534
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
2487
2535
  result = prop.initializer.text;
2488
2536
  return;
2489
2537
  }
@@ -2496,6 +2544,19 @@ function extractUrlsVariableFromRouter(code) {
2496
2544
  visit(sourceFile);
2497
2545
  return result;
2498
2546
  }
2547
+ function applyBasenameToRoutes(result, basename3) {
2548
+ const prefixed = {};
2549
+ for (const [name, pattern] of Object.entries(result.routes)) {
2550
+ if (pattern === "/") {
2551
+ prefixed[name] = basename3;
2552
+ } else if (basename3.endsWith("/") && pattern.startsWith("/")) {
2553
+ prefixed[name] = basename3 + pattern.slice(1);
2554
+ } else {
2555
+ prefixed[name] = basename3 + pattern;
2556
+ }
2557
+ }
2558
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
2559
+ }
2499
2560
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
2500
2561
  let routerSource;
2501
2562
  try {
@@ -2503,19 +2564,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
2503
2564
  } catch {
2504
2565
  return { routes: {}, searchSchemas: {} };
2505
2566
  }
2506
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
2507
- if (!urlsVarName) {
2567
+ const extraction = extractUrlsFromRouter(routerSource);
2568
+ if (!extraction) {
2508
2569
  return { routes: {}, searchSchemas: {} };
2509
2570
  }
2510
- const imported = resolveImportedVariable(routerSource, urlsVarName);
2511
- if (imported) {
2512
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2513
- if (!targetFile) {
2514
- return { routes: {}, searchSchemas: {} };
2571
+ const rawBasename = extractBasenameFromRouter(routerSource);
2572
+ const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
2573
+ let result;
2574
+ if (extraction.kind === "inline") {
2575
+ result = buildCombinedRouteMapWithSearch(
2576
+ routerFilePath,
2577
+ void 0,
2578
+ void 0,
2579
+ void 0,
2580
+ extraction.block
2581
+ );
2582
+ } else {
2583
+ const imported = resolveImportedVariable(routerSource, extraction.name);
2584
+ if (imported) {
2585
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
2586
+ if (!targetFile) {
2587
+ return { routes: {}, searchSchemas: {} };
2588
+ }
2589
+ result = buildCombinedRouteMapWithSearch(
2590
+ targetFile,
2591
+ imported.exportedName
2592
+ );
2593
+ } else {
2594
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
2515
2595
  }
2516
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
2517
2596
  }
2518
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
2597
+ if (basename3) {
2598
+ result = applyBasenameToRoutes(result, basename3);
2599
+ }
2600
+ return result;
2519
2601
  }
2520
2602
  function findRouterFiles(root, filter) {
2521
2603
  const result = [];
@@ -2540,25 +2622,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
2540
2622
  throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
2541
2623
  }
2542
2624
  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);
2625
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
2626
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
2627
+ let routerSource;
2628
+ try {
2629
+ routerSource = readFileSync2(routerFilePath, "utf-8");
2630
+ } catch {
2631
+ continue;
2632
+ }
2633
+ if (!extractUrlsFromRouter(routerSource)) continue;
2562
2634
  }
2563
2635
  const routerBasename = pathBasename(routerFilePath).replace(
2564
2636
  /\.(tsx?|jsx?)$/,
@@ -2784,6 +2856,68 @@ function createVersionPlugin() {
2784
2856
 
2785
2857
  // src/vite/utils/shared-utils.ts
2786
2858
  import * as Vite from "vite";
2859
+
2860
+ // src/vite/plugins/performance-tracks.ts
2861
+ import { readFile } from "node:fs/promises";
2862
+ var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
2863
+ function buildPatchReplacement(match, debugInfoVar) {
2864
+ return `${match}
2865
+ if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
2866
+ var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
2867
+ if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
2868
+ ${debugInfoVar} = _resolved._debugInfo;
2869
+ }
2870
+ }`;
2871
+ }
2872
+ function patchRsdwClientDebugInfoRecovery(code) {
2873
+ const match = code.match(RSDW_PATCH_RE);
2874
+ if (!match) {
2875
+ return { code, debugInfoVar: null };
2876
+ }
2877
+ return {
2878
+ code: code.replace(match[1], buildPatchReplacement(match[1], match[2])),
2879
+ debugInfoVar: match[2]
2880
+ };
2881
+ }
2882
+ function performanceTracksOptimizeDepsPlugin() {
2883
+ return {
2884
+ name: "@rangojs/router:performance-tracks-optimize-deps",
2885
+ setup(build) {
2886
+ build.onLoad(
2887
+ {
2888
+ filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
2889
+ },
2890
+ async (args) => {
2891
+ const code = await readFile(args.path, "utf8");
2892
+ const patched = patchRsdwClientDebugInfoRecovery(code);
2893
+ return {
2894
+ contents: patched.code,
2895
+ loader: "js"
2896
+ };
2897
+ }
2898
+ );
2899
+ }
2900
+ };
2901
+ }
2902
+ function performanceTracksPlugin() {
2903
+ return {
2904
+ name: "@rangojs/router:performance-tracks",
2905
+ transform(code, id) {
2906
+ if (!id.includes("react-server-dom") || !id.includes("client")) return;
2907
+ const patched = patchRsdwClientDebugInfoRecovery(code);
2908
+ if (!patched.debugInfoVar) return;
2909
+ if (process.env.INTERNAL_RANGO_DEBUG)
2910
+ console.log(
2911
+ "[perf-tracks] patched RSDW client (var:",
2912
+ patched.debugInfoVar,
2913
+ ")"
2914
+ );
2915
+ return patched.code;
2916
+ }
2917
+ };
2918
+ }
2919
+
2920
+ // src/vite/utils/shared-utils.ts
2787
2921
  var versionEsbuildPlugin = {
2788
2922
  name: "@rangojs/router-version",
2789
2923
  setup(build) {
@@ -2801,7 +2935,7 @@ var versionEsbuildPlugin = {
2801
2935
  }
2802
2936
  };
2803
2937
  var sharedEsbuildOptions = {
2804
- plugins: [versionEsbuildPlugin]
2938
+ plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
2805
2939
  };
2806
2940
  function createVirtualEntriesPlugin(entries, routerPathRef) {
2807
2941
  const virtualModules = {};
@@ -3274,8 +3408,17 @@ function jsonParseExpression(value) {
3274
3408
  }
3275
3409
 
3276
3410
  // src/context-var.ts
3411
+ var NON_CACHEABLE_KEYS = /* @__PURE__ */ Symbol.for(
3412
+ "rango:non-cacheable-keys"
3413
+ );
3414
+ function getNonCacheableKeys(variables) {
3415
+ if (!variables[NON_CACHEABLE_KEYS]) {
3416
+ variables[NON_CACHEABLE_KEYS] = /* @__PURE__ */ new Set();
3417
+ }
3418
+ return variables[NON_CACHEABLE_KEYS];
3419
+ }
3277
3420
  var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
3278
- function contextSet(variables, keyOrVar, value) {
3421
+ function contextSet(variables, keyOrVar, value, options) {
3279
3422
  if (typeof keyOrVar === "string") {
3280
3423
  if (FORBIDDEN_KEYS.has(keyOrVar)) {
3281
3424
  throw new Error(
@@ -3283,8 +3426,14 @@ function contextSet(variables, keyOrVar, value) {
3283
3426
  );
3284
3427
  }
3285
3428
  variables[keyOrVar] = value;
3429
+ if (options?.cache === false) {
3430
+ getNonCacheableKeys(variables).add(keyOrVar);
3431
+ }
3286
3432
  } else {
3287
3433
  variables[keyOrVar.key] = value;
3434
+ if (options?.cache === false) {
3435
+ getNonCacheableKeys(variables).add(keyOrVar.key);
3436
+ }
3288
3437
  }
3289
3438
  }
3290
3439
 
@@ -3776,7 +3925,11 @@ async function discoverRouters(state, rscEnv) {
3776
3925
  if (!router.urlpatterns || !generateManifestFull) {
3777
3926
  continue;
3778
3927
  }
3779
- const manifest = generateManifestFull(router.urlpatterns, routerMountIndex);
3928
+ const manifest = generateManifestFull(
3929
+ router.urlpatterns,
3930
+ routerMountIndex,
3931
+ router.__basename ? { urlPrefix: router.__basename } : void 0
3932
+ );
3780
3933
  routerMountIndex++;
3781
3934
  allManifests.push({ id, manifest });
3782
3935
  const routeCount = Object.keys(manifest.routeManifest).length;
@@ -4853,7 +5006,16 @@ async function rango(options) {
4853
5006
  const showBanner = resolvedOptions.banner ?? true;
4854
5007
  const plugins = [];
4855
5008
  const rangoAliases = getPackageAliases();
4856
- const excludeDeps = getExcludeDeps();
5009
+ const excludeDeps = [
5010
+ ...getExcludeDeps(),
5011
+ // The public browser entry re-exports the RSDW browser client.
5012
+ // Excluding both keeps Vite from freezing the unpatched bundle into
5013
+ // .vite/deps before our source transforms run.
5014
+ "@vitejs/plugin-rsc/browser",
5015
+ // Keep the browser RSDW client out of Vite's dep optimizer so our
5016
+ // cjs-to-esm transform can patch the real file.
5017
+ "@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
5018
+ ];
4857
5019
  const routerRef = { path: void 0 };
4858
5020
  const prerenderEnabled = true;
4859
5021
  if (preset === "cloudflare") {
@@ -4949,6 +5111,7 @@ async function rango(options) {
4949
5111
  }
4950
5112
  });
4951
5113
  plugins.push(createVirtualEntriesPlugin(finalEntries));
5114
+ plugins.push(performanceTracksPlugin());
4952
5115
  plugins.push(
4953
5116
  rsc({
4954
5117
  entries: finalEntries,
@@ -5067,6 +5230,7 @@ ${list}`);
5067
5230
  }
5068
5231
  });
5069
5232
  plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
5233
+ plugins.push(performanceTracksPlugin());
5070
5234
  plugins.push(
5071
5235
  rsc({
5072
5236
  entries: finalEntries
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rangojs/router",
3
- "version": "0.0.0-experimental.56cb65a7",
3
+ "version": "0.0.0-experimental.57",
4
4
  "description": "Django-inspired RSC router with composable URL patterns",
5
5
  "keywords": [
6
6
  "react",
@@ -142,7 +142,7 @@
142
142
  "test:unit:watch": "vitest"
143
143
  },
144
144
  "dependencies": {
145
- "@vitejs/plugin-rsc": "^0.5.14",
145
+ "@vitejs/plugin-rsc": "^0.5.19",
146
146
  "magic-string": "^0.30.17",
147
147
  "picomatch": "^4.0.3",
148
148
  "rsc-html-stream": "^0.0.7"