@rangojs/router 0.0.0-experimental.8a4d0430 → 0.0.0-experimental.8bd1b239

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 (152) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +76 -18
  3. package/dist/bin/rango.js +138 -50
  4. package/dist/vite/index.js +853 -435
  5. package/package.json +17 -16
  6. package/skills/cache-guide/SKILL.md +32 -0
  7. package/skills/caching/SKILL.md +45 -4
  8. package/skills/handler-use/SKILL.md +362 -0
  9. package/skills/intercept/SKILL.md +20 -0
  10. package/skills/layout/SKILL.md +22 -0
  11. package/skills/links/SKILL.md +3 -1
  12. package/skills/loader/SKILL.md +53 -43
  13. package/skills/middleware/SKILL.md +34 -3
  14. package/skills/migrate-nextjs/SKILL.md +560 -0
  15. package/skills/migrate-react-router/SKILL.md +764 -0
  16. package/skills/parallel/SKILL.md +185 -0
  17. package/skills/prerender/SKILL.md +110 -68
  18. package/skills/rango/SKILL.md +24 -22
  19. package/skills/route/SKILL.md +55 -0
  20. package/skills/router-setup/SKILL.md +87 -2
  21. package/skills/typesafety/SKILL.md +10 -0
  22. package/src/__internal.ts +1 -1
  23. package/src/browser/app-version.ts +14 -0
  24. package/src/browser/event-controller.ts +5 -0
  25. package/src/browser/navigation-bridge.ts +40 -15
  26. package/src/browser/navigation-client.ts +142 -57
  27. package/src/browser/navigation-store.ts +43 -8
  28. package/src/browser/navigation-transaction.ts +11 -9
  29. package/src/browser/partial-update.ts +94 -17
  30. package/src/browser/prefetch/cache.ts +82 -12
  31. package/src/browser/prefetch/fetch.ts +98 -27
  32. package/src/browser/prefetch/policy.ts +6 -0
  33. package/src/browser/prefetch/queue.ts +92 -20
  34. package/src/browser/prefetch/resource-ready.ts +77 -0
  35. package/src/browser/react/Link.tsx +76 -9
  36. package/src/browser/react/NavigationProvider.tsx +40 -4
  37. package/src/browser/react/context.ts +7 -2
  38. package/src/browser/react/use-handle.ts +9 -58
  39. package/src/browser/react/use-navigation.ts +11 -10
  40. package/src/browser/react/use-router.ts +21 -8
  41. package/src/browser/rsc-router.tsx +134 -59
  42. package/src/browser/scroll-restoration.ts +41 -42
  43. package/src/browser/segment-reconciler.ts +36 -9
  44. package/src/browser/server-action-bridge.ts +8 -6
  45. package/src/browser/types.ts +36 -5
  46. package/src/build/generate-manifest.ts +6 -6
  47. package/src/build/generate-route-types.ts +3 -0
  48. package/src/build/route-trie.ts +50 -24
  49. package/src/build/route-types/include-resolution.ts +8 -1
  50. package/src/build/route-types/router-processing.ts +223 -74
  51. package/src/build/route-types/scan-filter.ts +8 -1
  52. package/src/cache/cache-runtime.ts +15 -11
  53. package/src/cache/cache-scope.ts +48 -7
  54. package/src/cache/cf/cf-cache-store.ts +453 -11
  55. package/src/cache/cf/index.ts +5 -1
  56. package/src/cache/document-cache.ts +17 -7
  57. package/src/cache/index.ts +1 -0
  58. package/src/cache/taint.ts +55 -0
  59. package/src/client.tsx +84 -230
  60. package/src/context-var.ts +72 -2
  61. package/src/debug.ts +2 -2
  62. package/src/handle.ts +40 -0
  63. package/src/index.rsc.ts +3 -1
  64. package/src/index.ts +46 -6
  65. package/src/prerender/store.ts +5 -4
  66. package/src/prerender.ts +138 -77
  67. package/src/reverse.ts +25 -1
  68. package/src/route-definition/dsl-helpers.ts +224 -37
  69. package/src/route-definition/helpers-types.ts +67 -19
  70. package/src/route-definition/index.ts +3 -0
  71. package/src/route-definition/redirect.ts +11 -3
  72. package/src/route-definition/resolve-handler-use.ts +149 -0
  73. package/src/route-map-builder.ts +7 -1
  74. package/src/route-types.ts +11 -0
  75. package/src/router/content-negotiation.ts +100 -1
  76. package/src/router/find-match.ts +4 -2
  77. package/src/router/handler-context.ts +82 -23
  78. package/src/router/intercept-resolution.ts +11 -4
  79. package/src/router/lazy-includes.ts +4 -1
  80. package/src/router/loader-resolution.ts +156 -21
  81. package/src/router/logging.ts +5 -2
  82. package/src/router/manifest.ts +9 -3
  83. package/src/router/match-api.ts +124 -189
  84. package/src/router/match-middleware/background-revalidation.ts +30 -2
  85. package/src/router/match-middleware/cache-lookup.ts +94 -17
  86. package/src/router/match-middleware/cache-store.ts +53 -10
  87. package/src/router/match-middleware/intercept-resolution.ts +9 -7
  88. package/src/router/match-middleware/segment-resolution.ts +61 -5
  89. package/src/router/match-result.ts +104 -10
  90. package/src/router/metrics.ts +6 -1
  91. package/src/router/middleware-types.ts +6 -8
  92. package/src/router/middleware.ts +4 -6
  93. package/src/router/navigation-snapshot.ts +182 -0
  94. package/src/router/prerender-match.ts +110 -10
  95. package/src/router/preview-match.ts +30 -102
  96. package/src/router/request-classification.ts +310 -0
  97. package/src/router/route-snapshot.ts +245 -0
  98. package/src/router/router-context.ts +6 -1
  99. package/src/router/router-interfaces.ts +36 -4
  100. package/src/router/router-options.ts +37 -11
  101. package/src/router/segment-resolution/fresh.ts +198 -20
  102. package/src/router/segment-resolution/helpers.ts +29 -24
  103. package/src/router/segment-resolution/loader-cache.ts +1 -0
  104. package/src/router/segment-resolution/revalidation.ts +438 -300
  105. package/src/router/segment-wrappers.ts +2 -0
  106. package/src/router/types.ts +1 -0
  107. package/src/router.ts +59 -6
  108. package/src/rsc/handler.ts +472 -372
  109. package/src/rsc/loader-fetch.ts +23 -3
  110. package/src/rsc/manifest-init.ts +5 -1
  111. package/src/rsc/progressive-enhancement.ts +14 -2
  112. package/src/rsc/rsc-rendering.ts +12 -1
  113. package/src/rsc/server-action.ts +8 -0
  114. package/src/rsc/ssr-setup.ts +2 -2
  115. package/src/rsc/types.ts +9 -1
  116. package/src/segment-content-promise.ts +67 -0
  117. package/src/segment-loader-promise.ts +122 -0
  118. package/src/segment-system.tsx +109 -23
  119. package/src/server/context.ts +140 -14
  120. package/src/server/handle-store.ts +19 -0
  121. package/src/server/loader-registry.ts +9 -8
  122. package/src/server/request-context.ts +185 -19
  123. package/src/ssr/index.tsx +4 -0
  124. package/src/static-handler.ts +18 -6
  125. package/src/types/cache-types.ts +4 -4
  126. package/src/types/handler-context.ts +137 -33
  127. package/src/types/loader-types.ts +36 -9
  128. package/src/types/route-entry.ts +8 -1
  129. package/src/types/segments.ts +2 -0
  130. package/src/urls/path-helper-types.ts +39 -6
  131. package/src/urls/path-helper.ts +48 -13
  132. package/src/urls/pattern-types.ts +12 -0
  133. package/src/urls/response-types.ts +16 -6
  134. package/src/use-loader.tsx +77 -5
  135. package/src/vite/discovery/bundle-postprocess.ts +30 -33
  136. package/src/vite/discovery/discover-routers.ts +5 -1
  137. package/src/vite/discovery/prerender-collection.ts +128 -74
  138. package/src/vite/discovery/state.ts +13 -6
  139. package/src/vite/index.ts +4 -0
  140. package/src/vite/plugin-types.ts +51 -79
  141. package/src/vite/plugins/expose-action-id.ts +1 -3
  142. package/src/vite/plugins/expose-id-utils.ts +12 -0
  143. package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
  144. package/src/vite/plugins/expose-internal-ids.ts +257 -40
  145. package/src/vite/plugins/performance-tracks.ts +88 -0
  146. package/src/vite/plugins/refresh-cmd.ts +88 -26
  147. package/src/vite/plugins/version-plugin.ts +13 -1
  148. package/src/vite/rango.ts +163 -211
  149. package/src/vite/router-discovery.ts +178 -45
  150. package/src/vite/utils/banner.ts +3 -3
  151. package/src/vite/utils/prerender-utils.ts +37 -5
  152. package/src/vite/utils/shared-utils.ts +3 -2
package/AGENTS.md CHANGED
@@ -3,3 +3,7 @@
3
3
  A file-system based React Server Components router.
4
4
 
5
5
  Run `/rango` to understand the API. Detailed guides for each feature are in the `skills/` directory (e.g. `node_modules/@rangojs/router/skills/loader`, `skills/caching`, `skills/middleware`, etc.).
6
+
7
+ ## Development rules
8
+
9
+ - Always commit generated files (e.g. `*.gen.ts`) alongside the source changes that produced them.
package/README.md CHANGED
@@ -91,24 +91,29 @@ This file is a server/RSC module and should import router construction APIs from
91
91
 
92
92
  ```tsx
93
93
  // src/router.tsx
94
- import { createRouter, urls } from "@rangojs/router";
95
- import { Document } from "./document";
94
+ import { createRouter } from "@rangojs/router";
96
95
 
97
- const blogPatterns = urls(({ path }) => [
98
- path("/", BlogIndexPage, { name: "index" }),
99
- path("/:slug", BlogPostPage, { name: "post" }),
96
+ export const router = createRouter().routes(({ path }) => [
97
+ path("/", HomePage, { name: "home" }),
98
+ path("/about", AboutPage, { name: "about" }),
100
99
  ]);
101
100
 
101
+ export const reverse = router.reverse;
102
+ // reverse("home") -> "/"
103
+ ```
104
+
105
+ For larger apps, extract route modules with `urls()` and compose with `include()`:
106
+
107
+ ```tsx
108
+ import { createRouter, urls } from "@rangojs/router";
109
+ import { blogPatterns } from "./urls/blog";
110
+
102
111
  const urlpatterns = urls(({ path, include }) => [
103
112
  path("/", HomePage, { name: "home" }),
104
113
  include("/blog", blogPatterns, { name: "blog" }),
105
114
  ]);
106
115
 
107
- export const router = createRouter({ document: Document }).routes(urlpatterns);
108
-
109
- // Export typed reverse function for URL generation by route name
110
- export const reverse = router.reverse;
111
-
116
+ export const router = createRouter().routes(urlpatterns);
112
117
  // reverse("blog.post", { slug: "hello-world" }) -> "/blog/hello-world"
113
118
  ```
114
119
 
@@ -711,10 +716,12 @@ export const BlogPost = Prerender(
711
716
 
712
717
  ### Passthrough for Unknown Params
713
718
 
719
+ Wrap a `Prerender` definition with `Passthrough()` to add a live handler for unknown params at runtime. The build handler runs at build time, the live handler runs at request time for params not in the prerender cache.
720
+
714
721
  ```tsx
715
- import { Prerender } from "@rangojs/router";
722
+ import { Prerender, Passthrough } from "@rangojs/router";
716
723
 
717
- export const ProductPage = Prerender(
724
+ export const ProductPageDef = Prerender(
718
725
  async () => {
719
726
  const featured = await db.getFeaturedProducts();
720
727
  return featured.map((p) => ({ id: p.id }));
@@ -723,16 +730,22 @@ export const ProductPage = Prerender(
723
730
  const product = await db.getProduct(ctx.params.id);
724
731
  return <Product data={product} />;
725
732
  },
726
- { passthrough: true },
727
733
  );
728
- ```
729
734
 
730
- With `passthrough: true`, known params are served from the build-time cache and unknown params fall through to live rendering.
735
+ // In route definition:
736
+ path(
737
+ "/products/:id",
738
+ Passthrough(ProductPageDef, async (ctx) => {
739
+ const product = await ctx.env.DB.getProduct(ctx.params.id);
740
+ return <Product data={product} />;
741
+ }),
742
+ );
743
+ ```
731
744
 
732
- Handlers can also skip individual param sets with `ctx.passthrough()`, deferring them to the live handler at runtime:
745
+ Build handlers can also skip individual param sets with `ctx.passthrough()`, deferring them to the live handler:
733
746
 
734
747
  ```tsx
735
- export const ProductPage = Prerender(
748
+ export const ProductPageDef = Prerender(
736
749
  async () => {
737
750
  const all = await db.getAllProducts();
738
751
  return all.map((p) => ({ id: p.id }));
@@ -742,10 +755,55 @@ export const ProductPage = Prerender(
742
755
  if (!product.published) return ctx.passthrough();
743
756
  return <Product data={product} />;
744
757
  },
745
- { passthrough: true },
746
758
  );
747
759
  ```
748
760
 
761
+ ### Build-Time Environment Bindings
762
+
763
+ Prerender handlers can access platform bindings (KV, D1, R2) at build time when `buildEnv` is configured in the Vite plugin:
764
+
765
+ ```ts
766
+ // vite.config.ts
767
+ import { rango } from "@rangojs/router/vite";
768
+
769
+ rango({ preset: "cloudflare", buildEnv: "auto" });
770
+ ```
771
+
772
+ With `buildEnv: "auto"`, the plugin calls `wrangler.getPlatformProxy()` to provide local bindings. Handlers then access `ctx.env` during build:
773
+
774
+ ```tsx
775
+ export const BlogPosts = Prerender<{ slug: string }>(
776
+ async (ctx) => {
777
+ const rows = await ctx.env.DB.prepare("SELECT slug FROM posts").all();
778
+ return rows.map((r) => ({ slug: r.slug }));
779
+ },
780
+ async (ctx) => {
781
+ const post = await ctx.env.DB.prepare("SELECT * FROM posts WHERE slug = ?")
782
+ .bind(ctx.params.slug)
783
+ .first();
784
+ return <BlogPost post={post} />;
785
+ },
786
+ );
787
+ ```
788
+
789
+ `buildEnv` also accepts a factory function or plain object:
790
+
791
+ ```ts
792
+ // Custom factory
793
+ rango({
794
+ buildEnv: async (ctx) => {
795
+ const { getPlatformProxy } = await import("wrangler");
796
+ const proxy = await getPlatformProxy();
797
+ return { env: proxy.env, dispose: proxy.dispose };
798
+ },
799
+ });
800
+
801
+ // Plain object (Node.js)
802
+ rango({ buildEnv: { DATABASE_URL: process.env.DATABASE_URL } });
803
+ ```
804
+
805
+ Build-time env applies to both production builds and dev on-demand prerender. Without `buildEnv`, accessing `ctx.env` in a Prerender handler throws with a clear error.
806
+
749
807
  ## Theme
750
808
 
751
809
  ### Router Configuration
package/dist/bin/rango.js CHANGED
@@ -218,7 +218,8 @@ function findTsFiles(dir, filter) {
218
218
  for (const entry of entries) {
219
219
  const fullPath = join(dir, entry.name);
220
220
  if (entry.isDirectory()) {
221
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
221
+ if (entry.name === "node_modules" || entry.name.startsWith(".") || entry.name === "dist" || entry.name === "build" || entry.name === "coverage")
222
+ continue;
222
223
  results.push(...findTsFiles(fullPath, filter));
223
224
  } else if ((entry.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx")) && !entry.name.includes(".gen.")) {
224
225
  if (filter && !filter(fullPath)) continue;
@@ -450,7 +451,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
450
451
  }
451
452
  return routeMap;
452
453
  }
453
- function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
454
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
454
455
  visited = visited ?? /* @__PURE__ */ new Set();
455
456
  const realPath = resolve(filePath);
456
457
  const key = variableName ? `${realPath}:${variableName}` : realPath;
@@ -466,7 +467,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
466
467
  return { routes: {}, searchSchemas: {} };
467
468
  }
468
469
  let block;
469
- if (variableName) {
470
+ if (inlineBlock) {
471
+ block = inlineBlock;
472
+ } else if (variableName) {
470
473
  const extracted = extractUrlsBlockForVariable(source, variableName);
471
474
  if (!extracted) return { routes: {}, searchSchemas: {} };
472
475
  block = extracted;
@@ -601,7 +604,7 @@ function countPublicRouteEntries(source) {
601
604
  return count;
602
605
  }
603
606
  function isRoutableSourceFile(name) {
604
- return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.");
607
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
605
608
  }
606
609
  function findRouterFilesRecursive(dir, filter, results) {
607
610
  let entries;
@@ -618,7 +621,8 @@ function findRouterFilesRecursive(dir, filter, results) {
618
621
  for (const entry of entries) {
619
622
  const fullPath = join2(dir, entry.name);
620
623
  if (entry.isDirectory()) {
621
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
624
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === "coverage" || entry.name === "__tests__" || entry.name === "__mocks__" || entry.name.startsWith("."))
625
+ continue;
622
626
  childDirs.push(fullPath);
623
627
  continue;
624
628
  }
@@ -670,7 +674,7 @@ Router root: ${conflict.ancestor}
670
674
  Nested router: ${conflict.nested}
671
675
  Move the nested router into a sibling directory or configure it as a separate app root.`;
672
676
  }
673
- function extractUrlsVariableFromRouter(code) {
677
+ function extractUrlsFromRouter(code) {
674
678
  const sourceFile = ts5.createSourceFile(
675
679
  "router.tsx",
676
680
  code,
@@ -684,24 +688,70 @@ function extractUrlsVariableFromRouter(code) {
684
688
  const callee = node.expression;
685
689
  return ts5.isIdentifier(callee) && callee.text === "createRouter";
686
690
  }
691
+ function isInlineBuilder(node) {
692
+ return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
693
+ }
694
+ function isRoutesOnCreateRouter(node) {
695
+ if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
696
+ return false;
697
+ let inner = node.expression.expression;
698
+ while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
699
+ inner = inner.expression.expression;
700
+ }
701
+ return isCreateRouterCall(inner);
702
+ }
687
703
  function visit(node) {
688
704
  if (result) return;
689
- if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
690
- let inner = node.expression.expression;
691
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
692
- inner = inner.expression.expression;
693
- }
694
- if (isCreateRouterCall(inner)) {
695
- result = node.arguments[0].text;
696
- return;
705
+ if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
706
+ const arg = node.arguments[0];
707
+ if (ts5.isIdentifier(arg)) {
708
+ result = { kind: "variable", name: arg.text };
709
+ } else if (isInlineBuilder(arg)) {
710
+ result = { kind: "inline", block: arg.getText(sourceFile) };
697
711
  }
712
+ return;
698
713
  }
699
714
  if (isCreateRouterCall(node)) {
700
715
  const callExpr = node;
701
- for (const arg of callExpr.arguments) {
716
+ for (const callArg of callExpr.arguments) {
717
+ if (ts5.isObjectLiteralExpression(callArg)) {
718
+ for (const prop of callArg.properties) {
719
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
720
+ if (ts5.isIdentifier(prop.initializer)) {
721
+ result = { kind: "variable", name: prop.initializer.text };
722
+ } else if (isInlineBuilder(prop.initializer)) {
723
+ result = {
724
+ kind: "inline",
725
+ block: prop.initializer.getText(sourceFile)
726
+ };
727
+ }
728
+ return;
729
+ }
730
+ }
731
+ }
732
+ }
733
+ }
734
+ ts5.forEachChild(node, visit);
735
+ }
736
+ visit(sourceFile);
737
+ return result;
738
+ }
739
+ function extractBasenameFromRouter(code) {
740
+ const sourceFile = ts5.createSourceFile(
741
+ "router.tsx",
742
+ code,
743
+ ts5.ScriptTarget.Latest,
744
+ true,
745
+ ts5.ScriptKind.TSX
746
+ );
747
+ let result;
748
+ function visit(node) {
749
+ if (result !== void 0) return;
750
+ if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
751
+ for (const arg of node.arguments) {
702
752
  if (ts5.isObjectLiteralExpression(arg)) {
703
753
  for (const prop of arg.properties) {
704
- if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls" && ts5.isIdentifier(prop.initializer)) {
754
+ if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
705
755
  result = prop.initializer.text;
706
756
  return;
707
757
  }
@@ -714,6 +764,19 @@ function extractUrlsVariableFromRouter(code) {
714
764
  visit(sourceFile);
715
765
  return result;
716
766
  }
767
+ function applyBasenameToRoutes(result, basename2) {
768
+ const prefixed = {};
769
+ for (const [name, pattern] of Object.entries(result.routes)) {
770
+ if (pattern === "/") {
771
+ prefixed[name] = basename2;
772
+ } else if (basename2.endsWith("/") && pattern.startsWith("/")) {
773
+ prefixed[name] = basename2 + pattern.slice(1);
774
+ } else {
775
+ prefixed[name] = basename2 + pattern;
776
+ }
777
+ }
778
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
779
+ }
717
780
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
718
781
  let routerSource;
719
782
  try {
@@ -721,19 +784,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
721
784
  } catch {
722
785
  return { routes: {}, searchSchemas: {} };
723
786
  }
724
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
725
- if (!urlsVarName) {
787
+ const extraction = extractUrlsFromRouter(routerSource);
788
+ if (!extraction) {
726
789
  return { routes: {}, searchSchemas: {} };
727
790
  }
728
- const imported = resolveImportedVariable(routerSource, urlsVarName);
729
- if (imported) {
730
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
731
- if (!targetFile) {
732
- return { routes: {}, searchSchemas: {} };
791
+ const rawBasename = extractBasenameFromRouter(routerSource);
792
+ const basename2 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
793
+ let result;
794
+ if (extraction.kind === "inline") {
795
+ result = buildCombinedRouteMapWithSearch(
796
+ routerFilePath,
797
+ void 0,
798
+ void 0,
799
+ void 0,
800
+ extraction.block
801
+ );
802
+ } else {
803
+ const imported = resolveImportedVariable(routerSource, extraction.name);
804
+ if (imported) {
805
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
806
+ if (!targetFile) {
807
+ return { routes: {}, searchSchemas: {} };
808
+ }
809
+ result = buildCombinedRouteMapWithSearch(
810
+ targetFile,
811
+ imported.exportedName
812
+ );
813
+ } else {
814
+ result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
733
815
  }
734
- return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
735
816
  }
736
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
817
+ if (basename2) {
818
+ result = applyBasenameToRoutes(result, basename2);
819
+ }
820
+ return result;
737
821
  }
738
822
  function detectUnresolvableIncludes(routerFilePath) {
739
823
  const realPath = resolve2(routerFilePath);
@@ -743,9 +827,20 @@ function detectUnresolvableIncludes(routerFilePath) {
743
827
  } catch {
744
828
  return [];
745
829
  }
746
- const urlsVarName = extractUrlsVariableFromRouter(source);
747
- if (!urlsVarName) return [];
748
- const imported = resolveImportedVariable(source, urlsVarName);
830
+ const extraction = extractUrlsFromRouter(source);
831
+ if (!extraction) return [];
832
+ const diagnostics = [];
833
+ if (extraction.kind === "inline") {
834
+ buildCombinedRouteMapWithSearch(
835
+ realPath,
836
+ void 0,
837
+ /* @__PURE__ */ new Set(),
838
+ diagnostics,
839
+ extraction.block
840
+ );
841
+ return diagnostics;
842
+ }
843
+ const imported = resolveImportedVariable(source, extraction.name);
749
844
  let targetFile;
750
845
  let exportedName;
751
846
  if (imported) {
@@ -765,9 +860,8 @@ function detectUnresolvableIncludes(routerFilePath) {
765
860
  exportedName = imported.exportedName;
766
861
  } else {
767
862
  targetFile = realPath;
768
- exportedName = urlsVarName;
863
+ exportedName = extraction.name;
769
864
  }
770
- const diagnostics = [];
771
865
  buildCombinedRouteMapWithSearch(
772
866
  targetFile,
773
867
  exportedName,
@@ -815,25 +909,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
815
909
  throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
816
910
  }
817
911
  for (const routerFilePath of routerFilePaths) {
818
- let routerSource;
819
- try {
820
- routerSource = readFileSync3(routerFilePath, "utf-8");
821
- } catch {
822
- continue;
823
- }
824
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
825
- if (!urlsVarName) continue;
826
- let result;
827
- const imported = resolveImportedVariable(routerSource, urlsVarName);
828
- if (imported) {
829
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
830
- if (!targetFile) continue;
831
- result = buildCombinedRouteMapWithSearch(
832
- targetFile,
833
- imported.exportedName
834
- );
835
- } else {
836
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
912
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
913
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
914
+ let routerSource;
915
+ try {
916
+ routerSource = readFileSync3(routerFilePath, "utf-8");
917
+ } catch {
918
+ continue;
919
+ }
920
+ if (!extractUrlsFromRouter(routerSource)) continue;
837
921
  }
838
922
  const routerBasename = pathBasename(routerFilePath).replace(
839
923
  /\.(tsx?|jsx?)$/,
@@ -1062,8 +1146,9 @@ function createVersionPlugin() {
1062
1146
  let isDev = false;
1063
1147
  let server = null;
1064
1148
  const clientModuleSignatures = /* @__PURE__ */ new Map();
1149
+ let versionCounter = 0;
1065
1150
  const bumpVersion = (reason) => {
1066
- currentVersion = Date.now().toString(16);
1151
+ currentVersion = Date.now().toString(16) + String(++versionCounter);
1067
1152
  console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
1068
1153
  const rscEnv = server?.environments?.rsc;
1069
1154
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
@@ -1119,6 +1204,9 @@ function createVersionPlugin() {
1119
1204
  if (!isDev) return;
1120
1205
  const isRscModule = this.environment?.name === "rsc";
1121
1206
  if (!isRscModule) return;
1207
+ if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
1208
+ return;
1209
+ }
1122
1210
  if (isCodeModule(ctx.file)) {
1123
1211
  const filePath = normalizeModuleId(ctx.file);
1124
1212
  const previousSignature = clientModuleSignatures.get(filePath);