@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1b930379

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 (84) hide show
  1. package/README.md +46 -12
  2. package/dist/bin/rango.js +109 -15
  3. package/dist/vite/index.js +323 -121
  4. package/package.json +15 -16
  5. package/skills/breadcrumbs/SKILL.md +250 -0
  6. package/skills/caching/SKILL.md +4 -4
  7. package/skills/document-cache/SKILL.md +2 -2
  8. package/skills/hooks/SKILL.md +33 -31
  9. package/skills/host-router/SKILL.md +218 -0
  10. package/skills/loader/SKILL.md +55 -15
  11. package/skills/prerender/SKILL.md +2 -2
  12. package/skills/rango/SKILL.md +0 -1
  13. package/skills/route/SKILL.md +3 -4
  14. package/skills/router-setup/SKILL.md +8 -3
  15. package/skills/typesafety/SKILL.md +25 -23
  16. package/src/__internal.ts +92 -0
  17. package/src/bin/rango.ts +18 -0
  18. package/src/browser/link-interceptor.ts +4 -0
  19. package/src/browser/navigation-bridge.ts +95 -5
  20. package/src/browser/navigation-client.ts +97 -72
  21. package/src/browser/prefetch/cache.ts +112 -25
  22. package/src/browser/prefetch/fetch.ts +28 -30
  23. package/src/browser/prefetch/policy.ts +6 -0
  24. package/src/browser/react/Link.tsx +19 -7
  25. package/src/browser/rsc-router.tsx +11 -2
  26. package/src/browser/server-action-bridge.ts +448 -432
  27. package/src/browser/types.ts +24 -0
  28. package/src/build/generate-route-types.ts +2 -0
  29. package/src/build/route-trie.ts +19 -3
  30. package/src/build/route-types/router-processing.ts +125 -15
  31. package/src/client.rsc.tsx +2 -1
  32. package/src/client.tsx +1 -46
  33. package/src/handles/breadcrumbs.ts +66 -0
  34. package/src/handles/index.ts +1 -0
  35. package/src/host/index.ts +0 -3
  36. package/src/index.rsc.ts +5 -36
  37. package/src/index.ts +32 -66
  38. package/src/prerender/store.ts +56 -15
  39. package/src/route-definition/index.ts +0 -3
  40. package/src/router/handler-context.ts +30 -3
  41. package/src/router/loader-resolution.ts +1 -1
  42. package/src/router/match-api.ts +1 -1
  43. package/src/router/match-result.ts +0 -9
  44. package/src/router/metrics.ts +233 -13
  45. package/src/router/middleware-types.ts +53 -10
  46. package/src/router/middleware.ts +170 -81
  47. package/src/router/pattern-matching.ts +20 -5
  48. package/src/router/prerender-match.ts +4 -0
  49. package/src/router/revalidation.ts +27 -7
  50. package/src/router/router-interfaces.ts +14 -1
  51. package/src/router/router-options.ts +13 -8
  52. package/src/router/segment-resolution/fresh.ts +18 -0
  53. package/src/router/segment-resolution/helpers.ts +1 -1
  54. package/src/router/segment-resolution/revalidation.ts +22 -9
  55. package/src/router/trie-matching.ts +20 -2
  56. package/src/router.ts +29 -9
  57. package/src/rsc/handler.ts +106 -11
  58. package/src/rsc/index.ts +0 -20
  59. package/src/rsc/progressive-enhancement.ts +21 -8
  60. package/src/rsc/rsc-rendering.ts +30 -43
  61. package/src/rsc/server-action.ts +14 -10
  62. package/src/rsc/ssr-setup.ts +128 -0
  63. package/src/rsc/types.ts +2 -0
  64. package/src/search-params.ts +16 -13
  65. package/src/server/context.ts +8 -2
  66. package/src/server/request-context.ts +38 -16
  67. package/src/server.ts +6 -0
  68. package/src/theme/index.ts +4 -13
  69. package/src/types/handler-context.ts +12 -16
  70. package/src/types/route-config.ts +17 -8
  71. package/src/types/segments.ts +0 -5
  72. package/src/vite/discovery/bundle-postprocess.ts +31 -56
  73. package/src/vite/discovery/discover-routers.ts +18 -4
  74. package/src/vite/discovery/prerender-collection.ts +34 -14
  75. package/src/vite/discovery/state.ts +4 -7
  76. package/src/vite/index.ts +4 -3
  77. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  78. package/src/vite/plugins/refresh-cmd.ts +65 -0
  79. package/src/vite/rango.ts +11 -0
  80. package/src/vite/router-discovery.ts +16 -0
  81. package/src/vite/utils/prerender-utils.ts +60 -0
  82. package/skills/testing/SKILL.md +0 -226
  83. package/src/route-definition/route-function.ts +0 -119
  84. /package/{CLAUDE.md → AGENTS.md} +0 -0
package/README.md CHANGED
@@ -45,6 +45,30 @@ For Cloudflare Workers:
45
45
  npm install @cloudflare/vite-plugin
46
46
  ```
47
47
 
48
+ ## Import Paths
49
+
50
+ Use these import paths consistently:
51
+
52
+ - `@rangojs/router` — server/RSC router APIs, route DSL, `createRouter`, `urls`, `redirect`, `Prerender`, `Static`, shared types
53
+ - `@rangojs/router/client` — hooks and components such as `Link`, `Outlet`, `href`, `useNavigation`, `useLoader`, `useAction`, `useLocationState`
54
+ - `@rangojs/router/cache` — public cache APIs such as `CFCacheStore`, `MemorySegmentCacheStore`, `createDocumentCacheMiddleware`
55
+ - `@rangojs/router/host`, `@rangojs/router/theme`, `@rangojs/router/vite` — specialized public subpaths
56
+ - `@rangojs/router/rsc`, `@rangojs/router/ssr` — advanced server-only integration subpaths for custom request/HTML pipelines
57
+
58
+ Use only subpaths that are explicitly exported from the package. Avoid deep imports such as `@rangojs/router/cache/cf`.
59
+
60
+ `@rangojs/router` is conditionally resolved. Server-only root APIs such as
61
+ `createRouter()`, `urls()`, `redirect()`, `Prerender()`, and `cookies()` rely on
62
+ the `react-server` export condition and are meant to run in router definitions,
63
+ handlers, and other RSC/server modules. Outside that environment the root entry
64
+ falls back to stub implementations that throw guidance errors.
65
+
66
+ If you hit a root-entrypoint stub error:
67
+
68
+ - hooks and components like `Link`, `Outlet`, `useLoader`, `useNavigation`, and `MetaTags` belong in `@rangojs/router/client`
69
+ - cache APIs like `CFCacheStore` and `createDocumentCacheMiddleware` belong in `@rangojs/router/cache`
70
+ - host-router APIs belong in `@rangojs/router/host`
71
+
48
72
  ## Quick Start
49
73
 
50
74
  ### Vite Config
@@ -62,6 +86,9 @@ export default defineConfig({
62
86
 
63
87
  ### Router
64
88
 
89
+ This file is a server/RSC module and should import router construction APIs from
90
+ `@rangojs/router`.
91
+
65
92
  ```tsx
66
93
  // src/router.tsx
67
94
  import { createRouter, urls } from "@rangojs/router";
@@ -248,7 +275,8 @@ All handler typing styles are supported, but they solve different problems:
248
275
  Example of a scoped local name inside a mounted module:
249
276
 
250
277
  ```tsx
251
- import type { Handler, ScopedRouteMap } from "@rangojs/router";
278
+ import type { Handler } from "@rangojs/router";
279
+ import type { ScopedRouteMap } from "@rangojs/router/__internal";
252
280
 
253
281
  type BlogRoutes = ScopedRouteMap<"blog">;
254
282
 
@@ -488,7 +516,7 @@ function Nav() {
488
516
  return (
489
517
  <nav>
490
518
  <Link to={href("/")}>Home</Link>
491
- <Link to={href("/blog")} prefetch="hybrid">
519
+ <Link to={href("/blog")} prefetch="adaptive">
492
520
  Blog
493
521
  </Link>
494
522
  <Link to={href("/about")}>About</Link>
@@ -842,16 +870,22 @@ module, use `scopedReverse<typeof localPatterns>(ctx.reverse)` or
842
870
 
843
871
  ## Subpath Exports
844
872
 
845
- | Export | Description |
846
- | ------------------------ | --------------------------------------------------------------------------------- |
847
- | `@rangojs/router` | Core: `createRouter`, `urls`, `createLoader`, `Handler`, `Prerender`, `Meta` |
848
- | `@rangojs/router/client` | Client: `Link`, `Outlet`, `href`, `useNavigation`, `useLoader`, `MetaTags` |
849
- | `@rangojs/router/cache` | Cache: `CFCacheStore`, `MemorySegmentCacheStore`, `createDocumentCacheMiddleware` |
850
- | `@rangojs/router/theme` | Theme: `useTheme`, `ThemeProvider`, `ThemeScript` |
851
- | `@rangojs/router/host` | Host routing: `createHostRouter`, `defineHosts` |
852
- | `@rangojs/router/vite` | Vite plugin: `rango()` |
853
- | `@rangojs/router/server` | Server utilities |
854
- | `@rangojs/router/build` | Build utilities |
873
+ | Export | Description |
874
+ | ------------------------ | -------------------------------------------------------------------------------------------------------- |
875
+ | `@rangojs/router` | Server/RSC core and shared types: `createRouter`, `urls`, `createLoader`, `Handler`, `Prerender`, `Meta` |
876
+ | `@rangojs/router/client` | Client: `Link`, `Outlet`, `href`, `useNavigation`, `useLoader`, `MetaTags` |
877
+ | `@rangojs/router/cache` | Cache: `CFCacheStore`, `MemorySegmentCacheStore`, `createDocumentCacheMiddleware` |
878
+ | `@rangojs/router/theme` | Theme: `useTheme`, `ThemeProvider`, `ThemeScript` |
879
+ | `@rangojs/router/host` | Host routing: `createHostRouter`, `defineHosts` |
880
+ | `@rangojs/router/vite` | Vite plugin: `rango()` |
881
+ | `@rangojs/router/rsc` | Advanced server pipeline APIs: `createRSCHandler`, request-context access |
882
+ | `@rangojs/router/ssr` | Advanced SSR bridge APIs: `createSSRHandler` |
883
+ | `@rangojs/router/server` | Internal build/runtime utilities for advanced integrations |
884
+ | `@rangojs/router/build` | Build utilities |
885
+
886
+ The root entrypoint is not a generic client/runtime barrel. If you need hooks
887
+ or components, import from `@rangojs/router/client`; if you need cache or host
888
+ APIs, use their dedicated subpaths.
855
889
 
856
890
  ## Examples
857
891
 
package/dist/bin/rango.js CHANGED
@@ -574,8 +574,20 @@ var init_per_module_writer = __esm({
574
574
  });
575
575
 
576
576
  // src/build/route-types/router-processing.ts
577
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, unlinkSync } from "node:fs";
578
- import { join as join2, dirname as dirname2, resolve as resolve2, basename as pathBasename } from "node:path";
577
+ import {
578
+ readFileSync as readFileSync3,
579
+ writeFileSync as writeFileSync2,
580
+ existsSync as existsSync3,
581
+ unlinkSync,
582
+ readdirSync as readdirSync2
583
+ } from "node:fs";
584
+ import {
585
+ join as join2,
586
+ dirname as dirname2,
587
+ resolve as resolve2,
588
+ sep,
589
+ basename as pathBasename
590
+ } from "node:path";
579
591
  import ts5 from "typescript";
580
592
  function countPublicRouteEntries(source) {
581
593
  const matches = source.matchAll(/^\s+(?:"([^"]+)"|([a-zA-Z_$][^:]*)):\s*["{]/gm) ?? [];
@@ -588,6 +600,76 @@ function countPublicRouteEntries(source) {
588
600
  }
589
601
  return count;
590
602
  }
603
+ function isRoutableSourceFile(name) {
604
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.");
605
+ }
606
+ function findRouterFilesRecursive(dir, filter, results) {
607
+ let entries;
608
+ try {
609
+ entries = readdirSync2(dir, { withFileTypes: true });
610
+ } catch (err) {
611
+ console.warn(
612
+ `[rsc-router] Failed to scan directory ${dir}: ${err.message}`
613
+ );
614
+ return;
615
+ }
616
+ const childDirs = [];
617
+ const routerFilesInDir = [];
618
+ for (const entry of entries) {
619
+ const fullPath = join2(dir, entry.name);
620
+ if (entry.isDirectory()) {
621
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
622
+ childDirs.push(fullPath);
623
+ continue;
624
+ }
625
+ if (!isRoutableSourceFile(entry.name)) continue;
626
+ if (filter && !filter(fullPath)) continue;
627
+ try {
628
+ const source = readFileSync3(fullPath, "utf-8");
629
+ if (ROUTER_CALL_PATTERN.test(source)) {
630
+ routerFilesInDir.push(fullPath);
631
+ }
632
+ } catch {
633
+ continue;
634
+ }
635
+ }
636
+ if (routerFilesInDir.length > 0) {
637
+ results.push(...routerFilesInDir);
638
+ return;
639
+ }
640
+ for (const childDir of childDirs) {
641
+ findRouterFilesRecursive(childDir, filter, results);
642
+ }
643
+ }
644
+ function findNestedRouterConflict(routerFiles) {
645
+ const routerDirs = [
646
+ ...new Set(routerFiles.map((filePath) => dirname2(resolve2(filePath))))
647
+ ].sort((a, b) => a.length - b.length);
648
+ for (let i = 0; i < routerDirs.length; i++) {
649
+ const ancestorDir = routerDirs[i];
650
+ const prefix = ancestorDir.endsWith(sep) ? ancestorDir : `${ancestorDir}${sep}`;
651
+ for (let j = i + 1; j < routerDirs.length; j++) {
652
+ const nestedDir = routerDirs[j];
653
+ if (!nestedDir.startsWith(prefix)) continue;
654
+ const ancestorFile = routerFiles.find(
655
+ (filePath) => dirname2(resolve2(filePath)) === ancestorDir
656
+ );
657
+ const nestedFile = routerFiles.find(
658
+ (filePath) => dirname2(resolve2(filePath)) === nestedDir
659
+ );
660
+ if (ancestorFile && nestedFile) {
661
+ return { ancestor: ancestorFile, nested: nestedFile };
662
+ }
663
+ }
664
+ }
665
+ return null;
666
+ }
667
+ function formatNestedRouterConflictError(conflict, prefix = "[rsc-router]") {
668
+ return `${prefix} Nested router roots are not supported.
669
+ Router root: ${conflict.ancestor}
670
+ Nested router: ${conflict.nested}
671
+ Move the nested router into a sibling directory or configure it as a separate app root.`;
672
+ }
591
673
  function extractUrlsVariableFromRouter(code) {
592
674
  const sourceFile = ts5.createSourceFile(
593
675
  "router.tsx",
@@ -711,19 +793,8 @@ function detectUnresolvableIncludesForUrlsFile(filePath) {
711
793
  return diagnostics;
712
794
  }
713
795
  function findRouterFiles(root, filter) {
714
- const files = findTsFiles(root, filter);
715
796
  const result = [];
716
- for (const filePath of files) {
717
- if (filePath.includes(".gen.")) continue;
718
- try {
719
- const source = readFileSync3(filePath, "utf-8");
720
- if (/\bcreateRouter\s*[<(]/.test(source)) {
721
- result.push(filePath);
722
- }
723
- } catch {
724
- continue;
725
- }
726
- }
797
+ findRouterFilesRecursive(root, filter, result);
727
798
  return result;
728
799
  }
729
800
  function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
@@ -739,6 +810,10 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
739
810
  }
740
811
  const routerFilePaths = knownRouterFiles ?? findRouterFiles(root);
741
812
  if (routerFilePaths.length === 0) return;
813
+ const nestedRouterConflict = findNestedRouterConflict(routerFilePaths);
814
+ if (nestedRouterConflict) {
815
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
816
+ }
742
817
  for (const routerFilePath of routerFilePaths) {
743
818
  let routerSource;
744
819
  try {
@@ -798,14 +873,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
798
873
  }
799
874
  }
800
875
  }
876
+ var ROUTER_CALL_PATTERN;
801
877
  var init_router_processing = __esm({
802
878
  "src/build/route-types/router-processing.ts"() {
803
879
  "use strict";
804
880
  init_codegen();
805
- init_scan_filter();
806
881
  init_include_resolution();
807
882
  init_per_module_writer();
808
883
  init_route_name();
884
+ ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
809
885
  }
810
886
  });
811
887
 
@@ -1428,6 +1504,15 @@ function runStaticGeneration(args, mode) {
1428
1504
  formatDiagnostics(uniqueDiagnostics);
1429
1505
  console.warn("");
1430
1506
  }
1507
+ const nestedRouterConflict = findNestedRouterConflict(routerFiles);
1508
+ if (nestedRouterConflict) {
1509
+ console.error(
1510
+ `
1511
+ ${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}
1512
+ `
1513
+ );
1514
+ process.exit(1);
1515
+ }
1431
1516
  for (const urlsFile of urlsFiles) {
1432
1517
  writePerModuleRouteTypesForFile(urlsFile);
1433
1518
  }
@@ -1471,6 +1556,15 @@ async function runRuntimeDiscovery(args, configFile) {
1471
1556
  console.error("[rango] No router files found in the provided paths");
1472
1557
  process.exit(1);
1473
1558
  }
1559
+ const nestedRouterConflict = findNestedRouterConflict(routerEntries);
1560
+ if (nestedRouterConflict) {
1561
+ console.error(
1562
+ `
1563
+ ${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}
1564
+ `
1565
+ );
1566
+ process.exit(1);
1567
+ }
1474
1568
  let discoverAndWriteRouteTypes2;
1475
1569
  try {
1476
1570
  const mod = await Promise.resolve().then(() => (init_runtime_discovery(), runtime_discovery_exports));