@rangojs/router 0.0.0-experimental.fb4fdc18 → 0.0.0-experimental.fce7fbd1

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 (214) hide show
  1. package/README.md +9 -9
  2. package/dist/bin/rango.js +147 -57
  3. package/dist/testing/vitest.js +48 -0
  4. package/dist/vite/index.js +914 -485
  5. package/package.json +55 -11
  6. package/skills/bundle-analysis/SKILL.md +159 -0
  7. package/skills/cache-guide/SKILL.md +220 -30
  8. package/skills/caching/SKILL.md +116 -8
  9. package/skills/composability/SKILL.md +27 -2
  10. package/skills/document-cache/SKILL.md +78 -55
  11. package/skills/handler-use/SKILL.md +3 -1
  12. package/skills/hooks/SKILL.md +214 -18
  13. package/skills/host-router/SKILL.md +45 -20
  14. package/skills/intercept/SKILL.md +26 -4
  15. package/skills/layout/SKILL.md +6 -7
  16. package/skills/links/SKILL.md +173 -17
  17. package/skills/loader/SKILL.md +149 -6
  18. package/skills/middleware/SKILL.md +13 -9
  19. package/skills/migrate-nextjs/SKILL.md +1 -1
  20. package/skills/mime-routes/SKILL.md +27 -0
  21. package/skills/observability/SKILL.md +137 -0
  22. package/skills/parallel/SKILL.md +5 -6
  23. package/skills/prerender/SKILL.md +14 -33
  24. package/skills/rango/SKILL.md +242 -26
  25. package/skills/react-compiler/SKILL.md +168 -0
  26. package/skills/response-routes/SKILL.md +58 -9
  27. package/skills/route/SKILL.md +13 -4
  28. package/skills/router-setup/SKILL.md +3 -3
  29. package/skills/server-actions/SKILL.md +53 -41
  30. package/skills/testing/SKILL.md +599 -0
  31. package/skills/typesafety/SKILL.md +310 -26
  32. package/skills/use-cache/SKILL.md +34 -5
  33. package/skills/view-transitions/SKILL.md +294 -0
  34. package/src/__augment-tests__/augment.ts +81 -0
  35. package/src/__augment-tests__/augmented.check.ts +117 -0
  36. package/src/browser/action-coordinator.ts +53 -36
  37. package/src/browser/event-controller.ts +42 -66
  38. package/src/browser/history-state.ts +21 -0
  39. package/src/browser/index.ts +3 -3
  40. package/src/browser/navigation-bridge.ts +6 -6
  41. package/src/browser/navigation-client.ts +12 -15
  42. package/src/browser/navigation-store.ts +7 -8
  43. package/src/browser/navigation-transaction.ts +10 -28
  44. package/src/browser/partial-update.ts +9 -19
  45. package/src/browser/react/NavigationProvider.tsx +29 -40
  46. package/src/browser/react/index.ts +3 -0
  47. package/src/browser/react/location-state-shared.ts +175 -4
  48. package/src/browser/react/location-state.ts +39 -13
  49. package/src/browser/react/use-handle.ts +17 -9
  50. package/src/browser/react/use-params.ts +3 -4
  51. package/src/browser/react/use-reverse.ts +106 -0
  52. package/src/browser/react/use-router.ts +14 -1
  53. package/src/browser/response-adapter.ts +25 -0
  54. package/src/browser/rsc-router.tsx +30 -16
  55. package/src/browser/scroll-restoration.ts +22 -14
  56. package/src/browser/segment-structure-assert.ts +2 -2
  57. package/src/browser/server-action-bridge.ts +23 -30
  58. package/src/browser/types.ts +2 -0
  59. package/src/build/collect-fallback-refs.ts +107 -0
  60. package/src/build/generate-manifest.ts +60 -35
  61. package/src/build/generate-route-types.ts +2 -0
  62. package/src/build/index.ts +2 -0
  63. package/src/build/route-types/codegen.ts +4 -4
  64. package/src/build/route-types/include-resolution.ts +1 -1
  65. package/src/build/route-types/per-module-writer.ts +7 -4
  66. package/src/build/route-types/router-processing.ts +55 -14
  67. package/src/build/route-types/scan-filter.ts +1 -1
  68. package/src/build/route-types/source-scan.ts +118 -0
  69. package/src/build/runtime-discovery.ts +9 -20
  70. package/src/cache/cache-scope.ts +28 -42
  71. package/src/cache/cf/cf-cache-store.ts +49 -6
  72. package/src/client.rsc.tsx +3 -0
  73. package/src/client.tsx +10 -8
  74. package/src/context-var.ts +5 -5
  75. package/src/decode-loader-results.ts +36 -0
  76. package/src/errors.ts +30 -1
  77. package/src/handle.ts +26 -13
  78. package/src/host/index.ts +2 -2
  79. package/src/host/router.ts +129 -57
  80. package/src/host/types.ts +31 -2
  81. package/src/host/utils.ts +1 -1
  82. package/src/href-client.ts +140 -20
  83. package/src/index.rsc.ts +6 -4
  84. package/src/index.ts +13 -6
  85. package/src/loader-store.ts +500 -0
  86. package/src/loader.rsc.ts +2 -5
  87. package/src/loader.ts +3 -10
  88. package/src/missing-id-error.ts +68 -0
  89. package/src/prerender.ts +4 -4
  90. package/src/response-utils.ts +9 -0
  91. package/src/reverse.ts +65 -41
  92. package/src/route-content-wrapper.tsx +6 -28
  93. package/src/route-definition/dsl-helpers.ts +238 -263
  94. package/src/route-definition/helper-factories.ts +29 -139
  95. package/src/route-definition/helpers-types.ts +37 -14
  96. package/src/route-definition/use-item-types.ts +32 -0
  97. package/src/route-types.ts +19 -41
  98. package/src/router/basename.ts +14 -0
  99. package/src/router/content-negotiation.ts +15 -2
  100. package/src/router/error-handling.ts +1 -1
  101. package/src/router/handler-context.ts +4 -42
  102. package/src/router/intercept-resolution.ts +4 -18
  103. package/src/router/lazy-includes.ts +2 -2
  104. package/src/router/loader-resolution.ts +16 -2
  105. package/src/router/match-handlers.ts +62 -20
  106. package/src/router/match-middleware/cache-lookup.ts +44 -91
  107. package/src/router/match-middleware/cache-store.ts +3 -2
  108. package/src/router/match-result.ts +32 -30
  109. package/src/router/metrics.ts +1 -1
  110. package/src/router/middleware-types.ts +1 -1
  111. package/src/router/middleware.ts +46 -78
  112. package/src/router/prerender-match.ts +1 -1
  113. package/src/router/preview-match.ts +3 -1
  114. package/src/router/request-classification.ts +4 -28
  115. package/src/router/revalidation.ts +43 -1
  116. package/src/router/router-interfaces.ts +45 -28
  117. package/src/router/router-options.ts +40 -1
  118. package/src/router/router-registry.ts +2 -5
  119. package/src/router/segment-resolution/fresh.ts +19 -6
  120. package/src/router/segment-resolution/revalidation.ts +19 -6
  121. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  122. package/src/router/substitute-pattern-params.ts +56 -0
  123. package/src/router/telemetry.ts +99 -0
  124. package/src/router/types.ts +8 -0
  125. package/src/router.ts +37 -21
  126. package/src/rsc/handler-context.ts +2 -2
  127. package/src/rsc/handler.ts +20 -65
  128. package/src/rsc/helpers.ts +22 -2
  129. package/src/rsc/index.ts +1 -1
  130. package/src/rsc/origin-guard.ts +28 -10
  131. package/src/rsc/response-route-handler.ts +32 -52
  132. package/src/rsc/rsc-rendering.ts +27 -53
  133. package/src/rsc/runtime-warnings.ts +9 -10
  134. package/src/rsc/server-action.ts +13 -37
  135. package/src/rsc/ssr-setup.ts +16 -0
  136. package/src/rsc/types.ts +2 -2
  137. package/src/search-params.ts +4 -4
  138. package/src/segment-system.tsx +121 -65
  139. package/src/serialize.ts +243 -0
  140. package/src/server/context.ts +118 -51
  141. package/src/server/cookie-store.ts +28 -4
  142. package/src/server/request-context.ts +10 -0
  143. package/src/static-handler.ts +1 -1
  144. package/src/testing/cache-status.ts +166 -0
  145. package/src/testing/collect-handle.ts +63 -0
  146. package/src/testing/dispatch.ts +440 -0
  147. package/src/testing/dom.entry.ts +22 -0
  148. package/src/testing/e2e/fixture.ts +154 -0
  149. package/src/testing/e2e/index.ts +149 -0
  150. package/src/testing/e2e/matchers.ts +51 -0
  151. package/src/testing/e2e/page-helpers.ts +272 -0
  152. package/src/testing/e2e/parity.ts +306 -0
  153. package/src/testing/e2e/server.ts +183 -0
  154. package/src/testing/flight-matchers.ts +104 -0
  155. package/src/testing/flight-runtime.d.ts +21 -0
  156. package/src/testing/flight.entry.ts +22 -0
  157. package/src/testing/flight.ts +182 -0
  158. package/src/testing/generated-routes.ts +223 -0
  159. package/src/testing/index.ts +105 -0
  160. package/src/testing/internal/context.ts +193 -0
  161. package/src/testing/render-route.tsx +536 -0
  162. package/src/testing/run-loader.ts +296 -0
  163. package/src/testing/run-middleware.ts +170 -0
  164. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  165. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  166. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  167. package/src/testing/vitest-stubs/version.ts +5 -0
  168. package/src/testing/vitest.ts +183 -0
  169. package/src/types/global-namespace.ts +39 -26
  170. package/src/types/handler-context.ts +56 -11
  171. package/src/types/index.ts +1 -0
  172. package/src/types/segments.ts +18 -1
  173. package/src/urls/include-helper.ts +10 -53
  174. package/src/urls/index.ts +0 -3
  175. package/src/urls/path-helper-types.ts +11 -3
  176. package/src/urls/path-helper.ts +17 -52
  177. package/src/urls/pattern-types.ts +36 -19
  178. package/src/urls/response-types.ts +20 -19
  179. package/src/urls/type-extraction.ts +26 -116
  180. package/src/urls/urls-function.ts +1 -5
  181. package/src/use-loader.tsx +413 -42
  182. package/src/vite/debug.ts +1 -0
  183. package/src/vite/discovery/bundle-postprocess.ts +6 -6
  184. package/src/vite/discovery/discover-routers.ts +70 -48
  185. package/src/vite/discovery/discovery-errors.ts +194 -0
  186. package/src/vite/discovery/prerender-collection.ts +19 -25
  187. package/src/vite/discovery/route-types-writer.ts +40 -84
  188. package/src/vite/discovery/state.ts +33 -0
  189. package/src/vite/discovery/virtual-module-codegen.ts +13 -23
  190. package/src/vite/index.ts +2 -0
  191. package/src/vite/plugin-types.ts +67 -0
  192. package/src/vite/plugins/cjs-to-esm.ts +3 -7
  193. package/src/vite/plugins/client-ref-hashing.ts +12 -1
  194. package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -1
  195. package/src/vite/plugins/expose-action-id.ts +2 -2
  196. package/src/vite/plugins/expose-id-utils.ts +12 -8
  197. package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
  198. package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
  199. package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
  200. package/src/vite/plugins/expose-internal-ids.ts +47 -67
  201. package/src/vite/plugins/performance-tracks.ts +12 -16
  202. package/src/vite/plugins/use-cache-transform.ts +13 -11
  203. package/src/vite/plugins/version-injector.ts +2 -12
  204. package/src/vite/plugins/version-plugin.ts +59 -2
  205. package/src/vite/plugins/virtual-entries.ts +2 -2
  206. package/src/vite/rango.ts +67 -15
  207. package/src/vite/router-discovery.ts +208 -63
  208. package/src/vite/utils/ast-handler-extract.ts +15 -15
  209. package/src/vite/utils/bundle-analysis.ts +4 -2
  210. package/src/vite/utils/client-chunks.ts +190 -0
  211. package/src/vite/utils/forward-user-plugins.ts +193 -0
  212. package/src/vite/utils/manifest-utils.ts +21 -5
  213. package/src/vite/utils/shared-utils.ts +107 -26
  214. package/src/browser/action-response-classifier.ts +0 -99
package/README.md CHANGED
@@ -602,7 +602,7 @@ export function Nav({ home, post }: { home: string; post: string }) {
602
602
  }
603
603
  ```
604
604
 
605
- For client-side navigation to static paths (no named-route lookup), use `href()` — see below. For URLs tied to named routes, always generate on the server and pass the string in.
605
+ For client-side navigation to static paths (no named-route lookup), use `href()` — see below. For URLs tied to named routes, you have two options: import the per-module generated `routes` map and use `useReverse(routes)` for in-module names (see [`/links` skill](./skills/links/SKILL.md)), or generate the URL on the server and pass the string in for cross-module URLs.
606
606
 
607
607
  ### `href()` for Path Validation (Client Components)
608
608
 
@@ -943,9 +943,9 @@ import { createHostRouter } from "@rangojs/router/host";
943
943
 
944
944
  const hostRouter = createHostRouter();
945
945
 
946
- hostRouter.host(["*.localhost"]).map(() => import("./apps/admin/handler.js"));
947
- hostRouter.host(["localhost"]).map(() => import("./apps/site/handler.js"));
948
- hostRouter.fallback().map(() => import("./apps/site/handler.js"));
946
+ hostRouter.host(["*.localhost"]).lazy(() => import("./apps/admin/handler.js"));
947
+ hostRouter.host(["localhost"]).lazy(() => import("./apps/site/handler.js"));
948
+ hostRouter.fallback().lazy(() => import("./apps/site/handler.js"));
949
949
 
950
950
  export default {
951
951
  async fetch(request, env, ctx) {
@@ -954,7 +954,7 @@ export default {
954
954
  };
955
955
  ```
956
956
 
957
- Each sub-app has its own `createRouter()` and `urls()`. The host router lazily imports the matched app's handler. Patterns are matched in registration order — register more specific patterns (subdomains) before catch-alls.
957
+ Use `.lazy(() => import("./sub-app"))` to mount a lazily-imported sub-app (a module whose `default` export is a handler or nested host router), and `.map((request) => Response)` for an inline request handler. Only `.lazy()` mounts are imported during build-time discovery; `.map(() => import(...))` is a type error. Each sub-app has its own `createRouter()` and `urls()`. Patterns are matched in registration order — register more specific patterns (subdomains) before catch-alls.
958
958
 
959
959
  ## Meta Tags
960
960
 
@@ -993,16 +993,16 @@ Auto-detects file type:
993
993
 
994
994
  ## Type Safety
995
995
 
996
- The Vite plugin automatically generates a `router.named-routes.gen.ts` file that globally registers route names, patterns, and search schemas via `RSCRouter.GeneratedRouteMap`. This powers server-side named-route typing such as `Handler<"name">`, `ctx.reverse()`, `getRequestContext().reverse()`, and `RouteParams<"name">` without any manual route registration. The gen file is updated on dev server startup, HMR, and production builds.
996
+ The Vite plugin automatically generates a `router.named-routes.gen.ts` file that globally registers route names, patterns, and search schemas via `Rango.GeneratedRouteMap`. This powers server-side named-route typing such as `Handler<"name">`, `ctx.reverse()`, `getRequestContext().reverse()`, and `RouteParams<"name">` without any manual route registration. The gen file is updated on dev server startup, HMR, and production builds.
997
997
 
998
- Use the generated map by default. Augment `RSCRouter.RegisteredRoutes` only when you need the richer `typeof router.routeMap` shape globally, especially for response-aware and path-based utilities.
998
+ Use the generated map by default. Augment `Rango.RegisteredRoutes` only when you need the richer `typeof router.routeMap` shape globally, especially for response-aware and path-based utilities.
999
999
 
1000
1000
  ```typescript
1001
1001
  // router.tsx
1002
1002
  const router = createRouter<AppBindings>({}).routes(urlpatterns);
1003
1003
 
1004
1004
  declare global {
1005
- namespace RSCRouter {
1005
+ namespace Rango {
1006
1006
  interface Env extends AppEnv {}
1007
1007
  interface Vars extends AppVars {}
1008
1008
  interface RegisteredRoutes extends typeof router.routeMap {}
@@ -1014,7 +1014,7 @@ Quick rule of thumb:
1014
1014
 
1015
1015
  - `GeneratedRouteMap` (auto-generated) — use for server-side named-route typing: `Handler<"name">`, `ctx.reverse()`, `Prerender<"name">`
1016
1016
  - `typeof router.routeMap` — use when you need route entries with response metadata
1017
- - `RegisteredRoutes` (manual augmentation) — use to expose `typeof router.routeMap` globally for `href()`, `PathResponse`, `ValidPaths`, and other path/response-aware utilities
1017
+ - `RegisteredRoutes` (manual augmentation) — use to expose `typeof router.routeMap` globally for `href()`, `Rango.Path`, `Rango.PathResponse`, and other path/response-aware utilities
1018
1018
 
1019
1019
  For extracted reusable loaders or middleware, prefer global dotted names on
1020
1020
  `ctx.reverse()` by default. If you want type-safe local names for a specific
package/dist/bin/rango.js CHANGED
@@ -139,7 +139,7 @@ function generatePerModuleTypesSource(routes) {
139
139
  const valid = routes.filter(({ name }) => {
140
140
  if (!name || /["'\\`\n\r]/.test(name)) {
141
141
  console.warn(
142
- `[rsc-router] Skipping route with invalid name: ${JSON.stringify(name)}`
142
+ `[rango] Skipping route with invalid name: ${JSON.stringify(name)}`
143
143
  );
144
144
  return false;
145
145
  }
@@ -149,7 +149,7 @@ function generatePerModuleTypesSource(routes) {
149
149
  for (const { name, pattern, params, search } of valid) {
150
150
  if (deduped.has(name)) {
151
151
  console.warn(
152
- `[rsc-router] Duplicate route name "${name}" \u2014 keeping first definition`
152
+ `[rango] Duplicate route name "${name}" \u2014 keeping first definition`
153
153
  );
154
154
  continue;
155
155
  }
@@ -186,7 +186,7 @@ ${objectBody}
186
186
  } as const;
187
187
 
188
188
  declare global {
189
- namespace RSCRouter {
189
+ namespace Rango {
190
190
  interface GeneratedRouteMap extends Readonly<typeof NamedRoutes> {}
191
191
  }
192
192
  }
@@ -211,7 +211,7 @@ function findTsFiles(dir, filter) {
211
211
  entries = readdirSync(dir, { withFileTypes: true });
212
212
  } catch (err) {
213
213
  console.warn(
214
- `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
214
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
215
215
  );
216
216
  return results;
217
217
  }
@@ -456,7 +456,7 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
456
456
  const realPath = resolve(filePath);
457
457
  const key = variableName ? `${realPath}:${variableName}` : realPath;
458
458
  if (visited.has(key)) {
459
- console.warn(`[rsc-router] Circular include detected, skipping: ${key}`);
459
+ console.warn(`[rango] Circular include detected, skipping: ${key}`);
460
460
  return { routes: {}, searchSchemas: {} };
461
461
  }
462
462
  visited.add(key);
@@ -543,12 +543,12 @@ function writePerModuleRouteTypesForFile(filePath) {
543
543
  } else {
544
544
  routes = extractRoutesFromSource(source);
545
545
  }
546
- const genPath = filePath.replace(/\.(tsx?)$/, ".gen.ts");
546
+ const genPath = filePath.replace(/\.(tsx?|jsx?)$/, ".gen.ts");
547
547
  if (routes.length === 0) {
548
548
  if (varNames.length > 0 && !existsSync2(genPath)) {
549
549
  writeFileSync(genPath, generatePerModuleTypesSource([]));
550
550
  console.log(
551
- `[rsc-router] Generated route types (placeholder) -> ${genPath}`
551
+ `[rango] Generated route types (placeholder) -> ${genPath}`
552
552
  );
553
553
  }
554
554
  return;
@@ -557,11 +557,11 @@ function writePerModuleRouteTypesForFile(filePath) {
557
557
  const existing = existsSync2(genPath) ? readFileSync2(genPath, "utf-8") : null;
558
558
  if (existing !== genSource) {
559
559
  writeFileSync(genPath, genSource);
560
- console.log(`[rsc-router] Generated route types -> ${genPath}`);
560
+ console.log(`[rango] Generated route types -> ${genPath}`);
561
561
  }
562
562
  } catch (err) {
563
563
  console.warn(
564
- `[rsc-router] Failed to generate route types for ${filePath}: ${err.message}`
564
+ `[rango] Failed to generate route types for ${filePath}: ${err.message}`
565
565
  );
566
566
  }
567
567
  }
@@ -576,6 +576,75 @@ var init_per_module_writer = __esm({
576
576
  }
577
577
  });
578
578
 
579
+ // src/build/route-types/source-scan.ts
580
+ function isLineTerminator(ch) {
581
+ const c = ch.charCodeAt(0);
582
+ return c === 10 || c === 13 || c === 8232 || c === 8233;
583
+ }
584
+ function makeCodeClassifier(code) {
585
+ const n = code.length;
586
+ let i = 0;
587
+ let skipStart = -1;
588
+ let skipEnd = -1;
589
+ return (q) => {
590
+ if (q >= skipStart && q < skipEnd) return false;
591
+ while (i < n && i <= q) {
592
+ const c = code[i];
593
+ const d = i + 1 < n ? code[i + 1] : "";
594
+ let end = -1;
595
+ if (c === "/" && d === "/") {
596
+ let j = i + 2;
597
+ while (j < n && !isLineTerminator(code[j])) j++;
598
+ end = j;
599
+ } else if (c === "/" && d === "*") {
600
+ let j = i + 2;
601
+ while (j < n && !(code[j] === "*" && code[j + 1] === "/")) j++;
602
+ end = Math.min(n, j + 2);
603
+ } else if (c === '"' || c === "'" || c === "`") {
604
+ let j = i + 1;
605
+ while (j < n) {
606
+ if (code[j] === "\\") {
607
+ j += 2;
608
+ continue;
609
+ }
610
+ if (code[j] === c) {
611
+ j++;
612
+ break;
613
+ }
614
+ j++;
615
+ }
616
+ end = j;
617
+ }
618
+ if (end >= 0) {
619
+ if (q < end) {
620
+ skipStart = i;
621
+ skipEnd = end;
622
+ return false;
623
+ }
624
+ i = end;
625
+ } else {
626
+ i++;
627
+ }
628
+ }
629
+ return true;
630
+ };
631
+ }
632
+ function firstCodeMatchIndex(code, pattern) {
633
+ const inCode = makeCodeClassifier(code);
634
+ pattern.lastIndex = 0;
635
+ let m;
636
+ while ((m = pattern.exec(code)) !== null) {
637
+ if (inCode(m.index)) return m.index;
638
+ if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
639
+ }
640
+ return -1;
641
+ }
642
+ var init_source_scan = __esm({
643
+ "src/build/route-types/source-scan.ts"() {
644
+ "use strict";
645
+ }
646
+ });
647
+
579
648
  // src/build/route-types/router-processing.ts
580
649
  import {
581
650
  readFileSync as readFileSync3,
@@ -612,7 +681,7 @@ function findRouterFilesRecursive(dir, filter, results) {
612
681
  entries = readdirSync2(dir, { withFileTypes: true });
613
682
  } catch (err) {
614
683
  console.warn(
615
- `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
684
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
616
685
  );
617
686
  return;
618
687
  }
@@ -630,7 +699,7 @@ function findRouterFilesRecursive(dir, filter, results) {
630
699
  if (filter && !filter(fullPath)) continue;
631
700
  try {
632
701
  const source = readFileSync3(fullPath, "utf-8");
633
- if (ROUTER_CALL_PATTERN.test(source)) {
702
+ if (ROUTER_CALL_PATTERN.test(source) && firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0) {
634
703
  routerFilesInDir.push(fullPath);
635
704
  }
636
705
  } catch {
@@ -668,7 +737,7 @@ function findNestedRouterConflict(routerFiles) {
668
737
  }
669
738
  return null;
670
739
  }
671
- function formatNestedRouterConflictError(conflict, prefix = "[rsc-router]") {
740
+ function formatNestedRouterConflictError(conflict, prefix = "[rango]") {
672
741
  return `${prefix} Nested router roots are not supported.
673
742
  Router root: ${conflict.ancestor}
674
743
  Nested router: ${conflict.nested}
@@ -764,19 +833,38 @@ function extractBasenameFromRouter(code) {
764
833
  visit(sourceFile);
765
834
  return result;
766
835
  }
767
- function applyBasenameToRoutes(result, basename2) {
836
+ function applyBasenameToRoutes(result, basename) {
768
837
  const prefixed = {};
769
838
  for (const [name, pattern] of Object.entries(result.routes)) {
770
839
  if (pattern === "/") {
771
- prefixed[name] = basename2;
772
- } else if (basename2.endsWith("/") && pattern.startsWith("/")) {
773
- prefixed[name] = basename2 + pattern.slice(1);
840
+ prefixed[name] = basename;
841
+ } else if (basename.endsWith("/") && pattern.startsWith("/")) {
842
+ prefixed[name] = basename + pattern.slice(1);
774
843
  } else {
775
- prefixed[name] = basename2 + pattern;
844
+ prefixed[name] = basename + pattern;
776
845
  }
777
846
  }
778
847
  return { routes: prefixed, searchSchemas: result.searchSchemas };
779
848
  }
849
+ function genFileTsPath(sourceFile) {
850
+ const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
851
+ return join2(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
852
+ }
853
+ function resolveSearchSchemas(publicRouteNames, runtimeSchemas, sourceFile) {
854
+ if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
855
+ return runtimeSchemas;
856
+ }
857
+ const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
858
+ if (Object.keys(staticParsed.searchSchemas).length === 0) {
859
+ return runtimeSchemas;
860
+ }
861
+ const filtered = {};
862
+ for (const name of publicRouteNames) {
863
+ const schema = staticParsed.searchSchemas[name];
864
+ if (schema) filtered[name] = schema;
865
+ }
866
+ return Object.keys(filtered).length > 0 ? filtered : runtimeSchemas;
867
+ }
780
868
  function buildCombinedRouteMapForRouterFile(routerFilePath) {
781
869
  let routerSource;
782
870
  try {
@@ -789,7 +877,7 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
789
877
  return { routes: {}, searchSchemas: {} };
790
878
  }
791
879
  const rawBasename = extractBasenameFromRouter(routerSource);
792
- const basename2 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
880
+ const basename = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
793
881
  let result;
794
882
  if (extraction.kind === "inline") {
795
883
  result = buildCombinedRouteMapWithSearch(
@@ -814,8 +902,8 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
814
902
  result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
815
903
  }
816
904
  }
817
- if (basename2) {
818
- result = applyBasenameToRoutes(result, basename2);
905
+ if (basename) {
906
+ result = applyBasenameToRoutes(result, basename);
819
907
  }
820
908
  return result;
821
909
  }
@@ -897,7 +985,7 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
897
985
  if (existsSync3(oldCombinedPath)) {
898
986
  unlinkSync(oldCombinedPath);
899
987
  console.log(
900
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`
988
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
901
989
  );
902
990
  }
903
991
  } catch {
@@ -919,18 +1007,12 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
919
1007
  }
920
1008
  if (!extractUrlsFromRouter(routerSource)) continue;
921
1009
  }
922
- const routerBasename = pathBasename(routerFilePath).replace(
923
- /\.(tsx?|jsx?)$/,
924
- ""
925
- );
926
- const outPath = join2(
927
- dirname2(routerFilePath),
928
- `${routerBasename}.named-routes.gen.ts`
929
- );
1010
+ const outPath = genFileTsPath(routerFilePath);
930
1011
  const existing = existsSync3(outPath) ? readFileSync3(outPath, "utf-8") : null;
931
1012
  if (Object.keys(result.routes).length === 0) {
932
1013
  if (!existing) {
933
1014
  const emptySource = generateRouteTypesSource({});
1015
+ opts?.onWrite?.(outPath, emptySource);
934
1016
  writeFileSync2(outPath, emptySource);
935
1017
  }
936
1018
  continue;
@@ -950,22 +1032,25 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
950
1032
  continue;
951
1033
  }
952
1034
  }
1035
+ opts?.onWrite?.(outPath, source);
953
1036
  writeFileSync2(outPath, source);
954
1037
  console.log(
955
- `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
1038
+ `[rango] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
956
1039
  );
957
1040
  }
958
1041
  }
959
1042
  }
960
- var ROUTER_CALL_PATTERN;
1043
+ var ROUTER_CALL_PATTERN, ROUTER_CALL_PATTERN_G;
961
1044
  var init_router_processing = __esm({
962
1045
  "src/build/route-types/router-processing.ts"() {
963
1046
  "use strict";
964
1047
  init_codegen();
1048
+ init_source_scan();
965
1049
  init_include_resolution();
966
1050
  init_per_module_writer();
967
1051
  init_route_name();
968
1052
  ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
1053
+ ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
969
1054
  }
970
1055
  });
971
1056
 
@@ -1003,7 +1088,7 @@ import {
1003
1088
  import { createElement, StrictMode } from "react";
1004
1089
  import { hydrateRoot } from "react-dom/client";
1005
1090
  import { rscStream } from "@rangojs/router/internal/deps/html-stream-client";
1006
- import { initBrowserApp, RSCRouter } from "@rangojs/router/browser";
1091
+ import { initBrowserApp, Rango } from "@rangojs/router/browser";
1007
1092
 
1008
1093
  async function initializeApp() {
1009
1094
  const deps = {
@@ -1018,7 +1103,7 @@ async function initializeApp() {
1018
1103
 
1019
1104
  hydrateRoot(
1020
1105
  document,
1021
- createElement(StrictMode, null, createElement(RSCRouter))
1106
+ createElement(StrictMode, null, createElement(Rango))
1022
1107
  );
1023
1108
  }
1024
1109
 
@@ -1050,7 +1135,8 @@ export const renderHTML = createSSRHandler({
1050
1135
  // src/vite/plugins/version-plugin.ts
1051
1136
  var version_plugin_exports = {};
1052
1137
  __export(version_plugin_exports, {
1053
- createVersionPlugin: () => createVersionPlugin
1138
+ createVersionPlugin: () => createVersionPlugin,
1139
+ isViteDepCachePath: () => isViteDepCachePath
1054
1140
  });
1055
1141
  import { parseAst } from "vite";
1056
1142
  function isCodeModule(id) {
@@ -1062,7 +1148,7 @@ function normalizeModuleId(id) {
1062
1148
  function getClientModuleSignature(source) {
1063
1149
  let program;
1064
1150
  try {
1065
- program = parseAst(source, { jsx: true });
1151
+ program = parseAst(source, { lang: "tsx" });
1066
1152
  } catch {
1067
1153
  return void 0;
1068
1154
  }
@@ -1145,11 +1231,12 @@ function createVersionPlugin() {
1145
1231
  let currentVersion = buildVersion;
1146
1232
  let isDev = false;
1147
1233
  let server = null;
1234
+ let resolvedCacheDir;
1148
1235
  const clientModuleSignatures = /* @__PURE__ */ new Map();
1149
1236
  let versionCounter = 0;
1150
1237
  const bumpVersion = (reason) => {
1151
1238
  currentVersion = Date.now().toString(16) + String(++versionCounter);
1152
- console.log(`[rsc-router] ${reason}, version updated: ${currentVersion}`);
1239
+ console.log(`[rango] ${reason}, version updated: ${currentVersion}`);
1153
1240
  const rscEnv = server?.environments?.rsc;
1154
1241
  const versionMod = rscEnv?.moduleGraph?.getModuleById(
1155
1242
  "\0" + VIRTUAL_IDS.version
@@ -1163,6 +1250,7 @@ function createVersionPlugin() {
1163
1250
  enforce: "pre",
1164
1251
  configResolved(config) {
1165
1252
  isDev = config.command === "serve";
1253
+ resolvedCacheDir = config.cacheDir ? String(config.cacheDir).replace(/\\/g, "/") : void 0;
1166
1254
  },
1167
1255
  configureServer(devServer) {
1168
1256
  server = devServer;
@@ -1204,6 +1292,7 @@ function createVersionPlugin() {
1204
1292
  if (!isDev) return;
1205
1293
  const isRscModule = this.environment?.name === "rsc";
1206
1294
  if (!isRscModule) return;
1295
+ if (isViteDepCachePath(ctx.file, resolvedCacheDir)) return;
1207
1296
  if (ctx.modules.length === 1 && ctx.modules[0].id === "\0" + VIRTUAL_IDS.version) {
1208
1297
  return;
1209
1298
  }
@@ -1233,6 +1322,17 @@ function createVersionPlugin() {
1233
1322
  }
1234
1323
  };
1235
1324
  }
1325
+ function isViteDepCachePath(filePath, cacheDir) {
1326
+ if (!filePath) return false;
1327
+ const normalized = filePath.replace(/\\/g, "/");
1328
+ if (cacheDir) {
1329
+ const normalizedCacheDir = cacheDir.replace(/\\/g, "/").replace(/\/+$/, "");
1330
+ if (normalized === normalizedCacheDir || normalized.startsWith(normalizedCacheDir + "/")) {
1331
+ return true;
1332
+ }
1333
+ }
1334
+ return /\/node_modules\/\.vite[^/]*\//.test(normalized) || normalized.includes("/.vite-isolated/");
1335
+ }
1236
1336
  var init_version_plugin = __esm({
1237
1337
  "src/vite/plugins/version-plugin.ts"() {
1238
1338
  "use strict";
@@ -1278,7 +1378,7 @@ var runtime_discovery_exports = {};
1278
1378
  __export(runtime_discovery_exports, {
1279
1379
  discoverAndWriteRouteTypes: () => discoverAndWriteRouteTypes
1280
1380
  });
1281
- import { dirname as dirname3, join as join3, basename, resolve as resolve3 } from "node:path";
1381
+ import { resolve as resolve3 } from "node:path";
1282
1382
  import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
1283
1383
  async function discoverAndWriteRouteTypes(opts) {
1284
1384
  let createViteServer;
@@ -1388,22 +1488,12 @@ This means createRouter() stack trace parsing matched an internal frame.
1388
1488
  Set an explicit \`id\` on createRouter() or check the call site.`
1389
1489
  );
1390
1490
  }
1391
- if (!routeSearchSchemas || Object.keys(routeSearchSchemas).length === 0) {
1392
- const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
1393
- if (Object.keys(staticParsed.searchSchemas).length > 0) {
1394
- const filtered = {};
1395
- for (const name of Object.keys(routeManifest)) {
1396
- const schema = staticParsed.searchSchemas[name];
1397
- if (schema) filtered[name] = schema;
1398
- }
1399
- if (Object.keys(filtered).length > 0) {
1400
- routeSearchSchemas = filtered;
1401
- }
1402
- }
1403
- }
1404
- const routerDir = dirname3(sourceFile);
1405
- const routerBasename = basename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
1406
- const outPath = join3(routerDir, `${routerBasename}.named-routes.gen.ts`);
1491
+ routeSearchSchemas = resolveSearchSchemas(
1492
+ Object.keys(routeManifest),
1493
+ routeSearchSchemas,
1494
+ sourceFile
1495
+ );
1496
+ const outPath = genFileTsPath(sourceFile);
1407
1497
  const source = generateRouteTypesSource(
1408
1498
  routeManifest,
1409
1499
  routeSearchSchemas && Object.keys(routeSearchSchemas).length > 0 ? routeSearchSchemas : void 0
@@ -1440,7 +1530,7 @@ var init_runtime_discovery = __esm({
1440
1530
 
1441
1531
  // src/bin/rango.ts
1442
1532
  init_generate_route_types();
1443
- import { resolve as resolve4, dirname as dirname4 } from "node:path";
1533
+ import { resolve as resolve4, dirname as dirname3 } from "node:path";
1444
1534
  import { readFileSync as readFileSync5, statSync, existsSync as existsSync5 } from "node:fs";
1445
1535
  var [command, ...rawArgs] = process.argv.slice(2);
1446
1536
  if (command === "generate") {
@@ -1511,12 +1601,12 @@ Examples:
1511
1601
  );
1512
1602
  }
1513
1603
  function findProjectRoot(fromPath) {
1514
- let dir = dirname4(resolve4(fromPath));
1515
- while (dir !== dirname4(dir)) {
1604
+ let dir = dirname3(resolve4(fromPath));
1605
+ while (dir !== dirname3(dir)) {
1516
1606
  if (existsSync5(resolve4(dir, "package.json")) || existsSync5(resolve4(dir, "vite.config.ts")) || existsSync5(resolve4(dir, "vite.config.js"))) {
1517
1607
  return dir;
1518
1608
  }
1519
- dir = dirname4(dir);
1609
+ dir = dirname3(dir);
1520
1610
  }
1521
1611
  return process.cwd();
1522
1612
  }
@@ -0,0 +1,48 @@
1
+ // src/testing/vitest.ts
2
+ import { fileURLToPath } from "node:url";
3
+ function here(relativeFromRoot) {
4
+ return fileURLToPath(new URL(`../../${relativeFromRoot}`, import.meta.url));
5
+ }
6
+ function rangoTestAliases(opts = {}) {
7
+ const aliases = [
8
+ // Real impls (index.rsc.ts) for the bare specifier ONLY — exact regex so
9
+ // subpaths (/testing, /client, /cache, ...) are untouched. React stays the
10
+ // client build, so createContext and "use client" modules work.
11
+ { find: /^@rangojs\/router$/, replacement: here("src/index.rsc.ts") },
12
+ {
13
+ find: "@rangojs/router:version",
14
+ replacement: here("src/testing/vitest-stubs/version.ts")
15
+ },
16
+ {
17
+ find: /^@vitejs\/plugin-rsc\/rsc$/,
18
+ replacement: here("src/testing/vitest-stubs/plugin-rsc.ts")
19
+ }
20
+ ];
21
+ if (opts.cloudflare) {
22
+ aliases.push(
23
+ {
24
+ find: "cloudflare:workers",
25
+ replacement: here("src/testing/vitest-stubs/cloudflare-workers.ts")
26
+ },
27
+ {
28
+ find: "cloudflare:email",
29
+ replacement: here("src/testing/vitest-stubs/cloudflare-email.ts")
30
+ }
31
+ );
32
+ }
33
+ return aliases;
34
+ }
35
+ var rangoInlineDeps = [/@rangojs[/\\]router/];
36
+ function rangoTestConfig(opts = {}) {
37
+ return {
38
+ alias: rangoTestAliases(opts),
39
+ // fresh copy so the shared rangoInlineDeps const is never aliased into (or
40
+ // mutated through) a consumer's resolved config
41
+ server: { deps: { inline: [...rangoInlineDeps] } }
42
+ };
43
+ }
44
+ export {
45
+ rangoInlineDeps,
46
+ rangoTestAliases,
47
+ rangoTestConfig
48
+ };