@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.
- package/README.md +46 -12
- package/dist/bin/rango.js +109 -15
- package/dist/vite/index.js +323 -121
- package/package.json +15 -16
- package/skills/breadcrumbs/SKILL.md +250 -0
- package/skills/caching/SKILL.md +4 -4
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/hooks/SKILL.md +33 -31
- package/skills/host-router/SKILL.md +218 -0
- package/skills/loader/SKILL.md +55 -15
- package/skills/prerender/SKILL.md +2 -2
- package/skills/rango/SKILL.md +0 -1
- package/skills/route/SKILL.md +3 -4
- package/skills/router-setup/SKILL.md +8 -3
- package/skills/typesafety/SKILL.md +25 -23
- package/src/__internal.ts +92 -0
- package/src/bin/rango.ts +18 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +95 -5
- package/src/browser/navigation-client.ts +97 -72
- package/src/browser/prefetch/cache.ts +112 -25
- package/src/browser/prefetch/fetch.ts +28 -30
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/react/Link.tsx +19 -7
- package/src/browser/rsc-router.tsx +11 -2
- package/src/browser/server-action-bridge.ts +448 -432
- package/src/browser/types.ts +24 -0
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/route-trie.ts +19 -3
- package/src/build/route-types/router-processing.ts +125 -15
- package/src/client.rsc.tsx +2 -1
- package/src/client.tsx +1 -46
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/host/index.ts +0 -3
- package/src/index.rsc.ts +5 -36
- package/src/index.ts +32 -66
- package/src/prerender/store.ts +56 -15
- package/src/route-definition/index.ts +0 -3
- package/src/router/handler-context.ts +30 -3
- package/src/router/loader-resolution.ts +1 -1
- package/src/router/match-api.ts +1 -1
- package/src/router/match-result.ts +0 -9
- package/src/router/metrics.ts +233 -13
- package/src/router/middleware-types.ts +53 -10
- package/src/router/middleware.ts +170 -81
- package/src/router/pattern-matching.ts +20 -5
- package/src/router/prerender-match.ts +4 -0
- package/src/router/revalidation.ts +27 -7
- package/src/router/router-interfaces.ts +14 -1
- package/src/router/router-options.ts +13 -8
- package/src/router/segment-resolution/fresh.ts +18 -0
- package/src/router/segment-resolution/helpers.ts +1 -1
- package/src/router/segment-resolution/revalidation.ts +22 -9
- package/src/router/trie-matching.ts +20 -2
- package/src/router.ts +29 -9
- package/src/rsc/handler.ts +106 -11
- package/src/rsc/index.ts +0 -20
- package/src/rsc/progressive-enhancement.ts +21 -8
- package/src/rsc/rsc-rendering.ts +30 -43
- package/src/rsc/server-action.ts +14 -10
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +2 -0
- package/src/search-params.ts +16 -13
- package/src/server/context.ts +8 -2
- package/src/server/request-context.ts +38 -16
- package/src/server.ts +6 -0
- package/src/theme/index.ts +4 -13
- package/src/types/handler-context.ts +12 -16
- package/src/types/route-config.ts +17 -8
- package/src/types/segments.ts +0 -5
- package/src/vite/discovery/bundle-postprocess.ts +31 -56
- package/src/vite/discovery/discover-routers.ts +18 -4
- package/src/vite/discovery/prerender-collection.ts +34 -14
- package/src/vite/discovery/state.ts +4 -7
- package/src/vite/index.ts +4 -3
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/rango.ts +11 -0
- package/src/vite/router-discovery.ts +16 -0
- package/src/vite/utils/prerender-utils.ts +60 -0
- package/skills/testing/SKILL.md +0 -226
- package/src/route-definition/route-function.ts +0 -119
- /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
|
|
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="
|
|
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` |
|
|
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`
|
|
854
|
-
| `@rangojs/router/
|
|
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 {
|
|
578
|
-
|
|
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
|
-
|
|
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));
|