@rangojs/router 0.0.0-experimental.113 → 0.0.0-experimental.115
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/dist/bin/rango.js +73 -2
- package/dist/vite/index.js +193 -19
- package/package.json +18 -17
- package/skills/hooks/SKILL.md +3 -3
- package/skills/links/SKILL.md +10 -10
- package/skills/rango/SKILL.md +1 -0
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/view-transitions/SKILL.md +85 -3
- package/src/browser/react/use-reverse.ts +19 -12
- package/src/build/route-types/router-processing.ts +14 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/handle.ts +3 -5
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +2 -5
- package/src/missing-id-error.ts +68 -0
- package/src/reverse.ts +16 -13
- package/src/route-definition/dsl-helpers.ts +5 -2
- package/src/route-definition/helpers-types.ts +31 -19
- package/src/router/router-options.ts +24 -0
- package/src/router/segment-resolution/fresh.ts +17 -4
- package/src/router/segment-resolution/revalidation.ts +17 -4
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/types.ts +8 -0
- package/src/router.ts +2 -0
- package/src/segment-system.tsx +18 -2
- package/src/types/segments.ts +18 -1
- package/src/urls/path-helper-types.ts +9 -1
- package/src/vite/debug.ts +1 -0
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/expose-ids/export-analysis.ts +68 -12
- package/src/vite/plugins/expose-internal-ids.ts +12 -4
- package/src/vite/rango.ts +12 -0
- package/src/vite/router-discovery.ts +14 -2
- package/src/vite/utils/client-chunks.ts +156 -0
- package/src/vite/utils/shared-utils.ts +10 -3
package/dist/bin/rango.js
CHANGED
|
@@ -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,
|
|
@@ -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 {
|
|
@@ -971,15 +1040,17 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
|
|
|
971
1040
|
}
|
|
972
1041
|
}
|
|
973
1042
|
}
|
|
974
|
-
var ROUTER_CALL_PATTERN;
|
|
1043
|
+
var ROUTER_CALL_PATTERN, ROUTER_CALL_PATTERN_G;
|
|
975
1044
|
var init_router_processing = __esm({
|
|
976
1045
|
"src/build/route-types/router-processing.ts"() {
|
|
977
1046
|
"use strict";
|
|
978
1047
|
init_codegen();
|
|
1048
|
+
init_source_scan();
|
|
979
1049
|
init_include_resolution();
|
|
980
1050
|
init_per_module_writer();
|
|
981
1051
|
init_route_name();
|
|
982
1052
|
ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
|
|
1053
|
+
ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
|
|
983
1054
|
}
|
|
984
1055
|
});
|
|
985
1056
|
|
package/dist/vite/index.js
CHANGED
|
@@ -200,7 +200,8 @@ var NS = {
|
|
|
200
200
|
prerender: "rango:prerender",
|
|
201
201
|
build: "rango:build",
|
|
202
202
|
dev: "rango:dev",
|
|
203
|
-
transform: "rango:transform"
|
|
203
|
+
transform: "rango:transform",
|
|
204
|
+
chunks: "rango:chunks"
|
|
204
205
|
};
|
|
205
206
|
if (process.env.INTERNAL_RANGO_DEBUG) {
|
|
206
207
|
const existing = debugFactory.disable();
|
|
@@ -744,6 +745,83 @@ var STRICT_CREATE_CONFIGS = [
|
|
|
744
745
|
|
|
745
746
|
// src/vite/plugins/expose-ids/export-analysis.ts
|
|
746
747
|
import { parseAst } from "vite";
|
|
748
|
+
|
|
749
|
+
// src/build/route-types/source-scan.ts
|
|
750
|
+
function isLineTerminator(ch) {
|
|
751
|
+
const c = ch.charCodeAt(0);
|
|
752
|
+
return c === 10 || c === 13 || c === 8232 || c === 8233;
|
|
753
|
+
}
|
|
754
|
+
function makeCodeClassifier(code) {
|
|
755
|
+
const n = code.length;
|
|
756
|
+
let i = 0;
|
|
757
|
+
let skipStart = -1;
|
|
758
|
+
let skipEnd = -1;
|
|
759
|
+
return (q) => {
|
|
760
|
+
if (q >= skipStart && q < skipEnd) return false;
|
|
761
|
+
while (i < n && i <= q) {
|
|
762
|
+
const c = code[i];
|
|
763
|
+
const d = i + 1 < n ? code[i + 1] : "";
|
|
764
|
+
let end = -1;
|
|
765
|
+
if (c === "/" && d === "/") {
|
|
766
|
+
let j = i + 2;
|
|
767
|
+
while (j < n && !isLineTerminator(code[j])) j++;
|
|
768
|
+
end = j;
|
|
769
|
+
} else if (c === "/" && d === "*") {
|
|
770
|
+
let j = i + 2;
|
|
771
|
+
while (j < n && !(code[j] === "*" && code[j + 1] === "/")) j++;
|
|
772
|
+
end = Math.min(n, j + 2);
|
|
773
|
+
} else if (c === '"' || c === "'" || c === "`") {
|
|
774
|
+
let j = i + 1;
|
|
775
|
+
while (j < n) {
|
|
776
|
+
if (code[j] === "\\") {
|
|
777
|
+
j += 2;
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (code[j] === c) {
|
|
781
|
+
j++;
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
j++;
|
|
785
|
+
}
|
|
786
|
+
end = j;
|
|
787
|
+
}
|
|
788
|
+
if (end >= 0) {
|
|
789
|
+
if (q < end) {
|
|
790
|
+
skipStart = i;
|
|
791
|
+
skipEnd = end;
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
i = end;
|
|
795
|
+
} else {
|
|
796
|
+
i++;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return true;
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function firstCodeMatchIndex(code, pattern) {
|
|
803
|
+
const inCode = makeCodeClassifier(code);
|
|
804
|
+
pattern.lastIndex = 0;
|
|
805
|
+
let m;
|
|
806
|
+
while ((m = pattern.exec(code)) !== null) {
|
|
807
|
+
if (inCode(m.index)) return m.index;
|
|
808
|
+
if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
|
|
809
|
+
}
|
|
810
|
+
return -1;
|
|
811
|
+
}
|
|
812
|
+
function codeMatchIndices(code, pattern) {
|
|
813
|
+
const inCode = makeCodeClassifier(code);
|
|
814
|
+
const indices = [];
|
|
815
|
+
pattern.lastIndex = 0;
|
|
816
|
+
let m;
|
|
817
|
+
while ((m = pattern.exec(code)) !== null) {
|
|
818
|
+
if (inCode(m.index)) indices.push(m.index);
|
|
819
|
+
if (pattern.lastIndex <= m.index) pattern.lastIndex = m.index + 1;
|
|
820
|
+
}
|
|
821
|
+
return indices;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/vite/plugins/expose-ids/export-analysis.ts
|
|
747
825
|
function isExportOnlyFile(code, bindings) {
|
|
748
826
|
if (bindings.length === 0) return false;
|
|
749
827
|
const knownLocals = /* @__PURE__ */ new Set();
|
|
@@ -772,12 +850,30 @@ function isExportOnlyFile(code, bindings) {
|
|
|
772
850
|
}
|
|
773
851
|
return true;
|
|
774
852
|
}
|
|
775
|
-
function
|
|
776
|
-
|
|
853
|
+
function createCallPattern(fnNames) {
|
|
854
|
+
return new RegExp(
|
|
777
855
|
`\\b(?:${fnNames.map(escapeRegExp).join("|")})\\s*(?:<[^>]*>\\s*)?\\(`,
|
|
778
856
|
"g"
|
|
779
857
|
);
|
|
780
|
-
|
|
858
|
+
}
|
|
859
|
+
function countCreateCallsForNames(code, fnNames) {
|
|
860
|
+
return codeMatchIndices(code, createCallPattern(fnNames)).length;
|
|
861
|
+
}
|
|
862
|
+
function offsetToLineColumn(code, index) {
|
|
863
|
+
let line = 1;
|
|
864
|
+
let lineStart = 0;
|
|
865
|
+
const end = Math.min(index, code.length);
|
|
866
|
+
for (let i = 0; i < end; i++) {
|
|
867
|
+
if (code[i] === "\n") {
|
|
868
|
+
line++;
|
|
869
|
+
lineStart = i + 1;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return { line, column: index - lineStart + 1 };
|
|
873
|
+
}
|
|
874
|
+
function findUnsupportedCreateCallSites(code, fnNames, supportedBindings) {
|
|
875
|
+
const supported = new Set(supportedBindings.map((b) => b.callExprStart));
|
|
876
|
+
return codeMatchIndices(code, createCallPattern(fnNames)).filter((index) => !supported.has(index)).map((index) => offsetToLineColumn(code, index));
|
|
781
877
|
}
|
|
782
878
|
function getImportedFnNames(code, importedName) {
|
|
783
879
|
const importPattern = /import\s*\{([^}]*)\}\s*from\s*["']@rangojs\/router(?:\/[^"']*)?["']/g;
|
|
@@ -936,9 +1032,20 @@ function collectCreateExportBindings(code, fnNames, program) {
|
|
|
936
1032
|
}
|
|
937
1033
|
return bindings;
|
|
938
1034
|
}
|
|
939
|
-
function buildUnsupportedShapeWarning(filePath, fnName) {
|
|
940
|
-
|
|
941
|
-
|
|
1035
|
+
function buildUnsupportedShapeWarning(filePath, fnName, sites = []) {
|
|
1036
|
+
const lines = [`[rango] Unsupported ${fnName} shape in "${filePath}".`];
|
|
1037
|
+
if (sites.length === 1) {
|
|
1038
|
+
const s = sites[0];
|
|
1039
|
+
lines.push(
|
|
1040
|
+
`The ${fnName}(...) call at ${filePath}:${s.line}:${s.column} has no stable $$id injected \u2014 it is not in a supported shape.`
|
|
1041
|
+
);
|
|
1042
|
+
} else if (sites.length > 1) {
|
|
1043
|
+
lines.push(
|
|
1044
|
+
`These ${fnName}(...) calls have no stable $$id injected \u2014 they are not in a supported shape:`
|
|
1045
|
+
);
|
|
1046
|
+
for (const s of sites) lines.push(` - ${filePath}:${s.line}:${s.column}`);
|
|
1047
|
+
}
|
|
1048
|
+
lines.push(
|
|
942
1049
|
`Supported shapes are:`,
|
|
943
1050
|
` - export const X = ${fnName}(...)`,
|
|
944
1051
|
` - const X = ${fnName}(...); export { X }`,
|
|
@@ -946,7 +1053,8 @@ function buildUnsupportedShapeWarning(filePath, fnName) {
|
|
|
946
1053
|
`Potentially unsupported forms include:`,
|
|
947
1054
|
` - export let/var X = ${fnName}(...)`,
|
|
948
1055
|
` - inline ${fnName}(...) calls`
|
|
949
|
-
|
|
1056
|
+
);
|
|
1057
|
+
return lines.join("\n");
|
|
950
1058
|
}
|
|
951
1059
|
|
|
952
1060
|
// src/vite/plugins/expose-ids/loader-transform.ts
|
|
@@ -1366,13 +1474,16 @@ ${lazyImports.join(",\n")}
|
|
|
1366
1474
|
const hasCode = cfg.fnName === "createLoader" ? hasLoaderCode : cfg.fnName === "createHandle" ? hasHandleCode : hasLocationStateCode;
|
|
1367
1475
|
if (!hasCode) continue;
|
|
1368
1476
|
const fnNames = getFnNames(cfg.fnName);
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
|
|
1477
|
+
const sites = findUnsupportedCreateCallSites(
|
|
1478
|
+
code,
|
|
1479
|
+
fnNames,
|
|
1480
|
+
getBindings(code, fnNames)
|
|
1481
|
+
);
|
|
1482
|
+
if (sites.length === 0) continue;
|
|
1372
1483
|
const warnKey = `${id}::${cfg.fnName}`;
|
|
1373
1484
|
if (unsupportedShapeWarnings.has(warnKey)) continue;
|
|
1374
1485
|
unsupportedShapeWarnings.add(warnKey);
|
|
1375
|
-
this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName));
|
|
1486
|
+
this.warn(buildUnsupportedShapeWarning(filePath, cfg.fnName, sites));
|
|
1376
1487
|
}
|
|
1377
1488
|
if (hasLoaderCode && isRscEnv) {
|
|
1378
1489
|
const fnNames = getFnNames("createLoader");
|
|
@@ -2019,7 +2130,7 @@ import { resolve } from "node:path";
|
|
|
2019
2130
|
// package.json
|
|
2020
2131
|
var package_default = {
|
|
2021
2132
|
name: "@rangojs/router",
|
|
2022
|
-
version: "0.0.0-experimental.
|
|
2133
|
+
version: "0.0.0-experimental.115",
|
|
2023
2134
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
2024
2135
|
keywords: [
|
|
2025
2136
|
"react",
|
|
@@ -2677,6 +2788,7 @@ function countPublicRouteEntries(source) {
|
|
|
2677
2788
|
return count;
|
|
2678
2789
|
}
|
|
2679
2790
|
var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
|
|
2791
|
+
var ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
|
|
2680
2792
|
function isRoutableSourceFile(name) {
|
|
2681
2793
|
return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".spec.");
|
|
2682
2794
|
}
|
|
@@ -2704,7 +2816,7 @@ function findRouterFilesRecursive(dir, filter, results) {
|
|
|
2704
2816
|
if (filter && !filter(fullPath)) continue;
|
|
2705
2817
|
try {
|
|
2706
2818
|
const source = readFileSync2(fullPath, "utf-8");
|
|
2707
|
-
if (ROUTER_CALL_PATTERN.test(source)) {
|
|
2819
|
+
if (ROUTER_CALL_PATTERN.test(source) && firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0) {
|
|
2708
2820
|
routerFilesInDir.push(fullPath);
|
|
2709
2821
|
}
|
|
2710
2822
|
} catch {
|
|
@@ -3323,12 +3435,65 @@ function getManualChunks(id) {
|
|
|
3323
3435
|
return "react";
|
|
3324
3436
|
}
|
|
3325
3437
|
const packageName = getPublishedPackageName();
|
|
3326
|
-
if (normalized.includes(`node_modules/${packageName}/`) ||
|
|
3438
|
+
if (normalized.includes(`node_modules/${packageName}/`) || /\/packages\/(rsc-router|rangojs-router)\/(src|dist)\//.test(normalized)) {
|
|
3327
3439
|
return "router";
|
|
3328
3440
|
}
|
|
3329
3441
|
return void 0;
|
|
3330
3442
|
}
|
|
3331
3443
|
|
|
3444
|
+
// src/vite/utils/client-chunks.ts
|
|
3445
|
+
var debugChunks = createRangoDebugger(NS.chunks);
|
|
3446
|
+
function isSharedRuntime(meta) {
|
|
3447
|
+
return [meta.id, meta.normalizedId].some(
|
|
3448
|
+
(path6) => path6.includes("/node_modules/") || /\/@rangojs\/router\//.test(path6) || /\/packages\/(rangojs-router|rsc-router)\/(src|dist)\//.test(path6)
|
|
3449
|
+
);
|
|
3450
|
+
}
|
|
3451
|
+
function sanitizeGroup(name) {
|
|
3452
|
+
return name.replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "") || "app";
|
|
3453
|
+
}
|
|
3454
|
+
var ROUTE_ROOT_DIRS = /* @__PURE__ */ new Set([
|
|
3455
|
+
"routes",
|
|
3456
|
+
"route",
|
|
3457
|
+
"pages",
|
|
3458
|
+
"page",
|
|
3459
|
+
"app",
|
|
3460
|
+
"features",
|
|
3461
|
+
"feature",
|
|
3462
|
+
"views",
|
|
3463
|
+
"view",
|
|
3464
|
+
"handlers",
|
|
3465
|
+
"urls",
|
|
3466
|
+
"modules",
|
|
3467
|
+
"screens",
|
|
3468
|
+
"sections"
|
|
3469
|
+
]);
|
|
3470
|
+
function directoryClientChunks(meta) {
|
|
3471
|
+
if (isSharedRuntime(meta)) {
|
|
3472
|
+
return void 0;
|
|
3473
|
+
}
|
|
3474
|
+
const segments = meta.normalizedId.split("/").filter(Boolean);
|
|
3475
|
+
const dirCount = segments.length - 1;
|
|
3476
|
+
if (dirCount >= 1) {
|
|
3477
|
+
for (let i = 0; i < dirCount - 1; i++) {
|
|
3478
|
+
if (ROUTE_ROOT_DIRS.has(segments[i].toLowerCase())) {
|
|
3479
|
+
const group = `app-${sanitizeGroup(segments[i + 1])}`;
|
|
3480
|
+
debugChunks?.("split %s -> %s", meta.normalizedId, group);
|
|
3481
|
+
return group;
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
debugChunks?.(
|
|
3486
|
+
"shared %s (no route-root marker; inherits default grouping)",
|
|
3487
|
+
meta.normalizedId
|
|
3488
|
+
);
|
|
3489
|
+
return void 0;
|
|
3490
|
+
}
|
|
3491
|
+
function resolveClientChunks(option) {
|
|
3492
|
+
if (!option) return void 0;
|
|
3493
|
+
if (option === true) return directoryClientChunks;
|
|
3494
|
+
return option;
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3332
3497
|
// src/vite/utils/banner.ts
|
|
3333
3498
|
var rangoVersion = package_default.version;
|
|
3334
3499
|
var _bannerPrinted = false;
|
|
@@ -5946,8 +6111,12 @@ ${err.stack}`
|
|
|
5946
6111
|
const trimmed = source.trimStart();
|
|
5947
6112
|
const isUseClient = trimmed.startsWith('"use client"') || trimmed.startsWith("'use client'");
|
|
5948
6113
|
if (!inRecoveryMode && isUseClient) return;
|
|
5949
|
-
|
|
5950
|
-
|
|
6114
|
+
let hasUrls = source.includes("urls(");
|
|
6115
|
+
let hasCreateRouter = /\bcreateRouter\s*[<(]/.test(source);
|
|
6116
|
+
if (hasUrls) hasUrls = firstCodeMatchIndex(source, /urls\(/g) >= 0;
|
|
6117
|
+
if (hasCreateRouter) {
|
|
6118
|
+
hasCreateRouter = firstCodeMatchIndex(source, /\bcreateRouter\s*[<(]/g) >= 0;
|
|
6119
|
+
}
|
|
5951
6120
|
if (!inRecoveryMode && !hasUrls && !hasCreateRouter) return;
|
|
5952
6121
|
if (inRecoveryMode) {
|
|
5953
6122
|
debugDiscovery?.(
|
|
@@ -6259,6 +6428,9 @@ async function rango(options) {
|
|
|
6259
6428
|
const resolvedOptions = options ?? { preset: "node" };
|
|
6260
6429
|
const preset = resolvedOptions.preset ?? "node";
|
|
6261
6430
|
const showBanner = resolvedOptions.banner ?? true;
|
|
6431
|
+
const clientChunks = resolveClientChunks(
|
|
6432
|
+
resolvedOptions.clientChunks ?? true
|
|
6433
|
+
);
|
|
6262
6434
|
debugConfig?.("rango(%s) setup start", preset);
|
|
6263
6435
|
const plugins = [];
|
|
6264
6436
|
const rangoAliases = { ...getPackageAliases(), ...getVendorAliases() };
|
|
@@ -6384,7 +6556,8 @@ async function rango(options) {
|
|
|
6384
6556
|
plugins.push(
|
|
6385
6557
|
rsc({
|
|
6386
6558
|
entries: finalEntries,
|
|
6387
|
-
serverHandler: false
|
|
6559
|
+
serverHandler: false,
|
|
6560
|
+
clientChunks
|
|
6388
6561
|
})
|
|
6389
6562
|
);
|
|
6390
6563
|
plugins.push(clientRefDedup());
|
|
@@ -6514,7 +6687,8 @@ ${list}`);
|
|
|
6514
6687
|
plugins.push(performanceTracksPlugin());
|
|
6515
6688
|
plugins.push(
|
|
6516
6689
|
rsc({
|
|
6517
|
-
entries: finalEntries
|
|
6690
|
+
entries: finalEntries,
|
|
6691
|
+
clientChunks
|
|
6518
6692
|
})
|
|
6519
6693
|
);
|
|
6520
6694
|
plugins.push(clientRefDedup());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rangojs/router",
|
|
3
|
-
"version": "0.0.0-experimental.
|
|
3
|
+
"version": "0.0.0-experimental.115",
|
|
4
4
|
"description": "Django-inspired RSC router with composable URL patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -132,6 +132,16 @@
|
|
|
132
132
|
"access": "public",
|
|
133
133
|
"tag": "experimental"
|
|
134
134
|
},
|
|
135
|
+
"scripts": {
|
|
136
|
+
"build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
|
|
137
|
+
"prepublishOnly": "pnpm build",
|
|
138
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
|
|
139
|
+
"test": "playwright test",
|
|
140
|
+
"test:ui": "playwright test --ui",
|
|
141
|
+
"test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
|
|
142
|
+
"test:unit": "vitest run",
|
|
143
|
+
"test:unit:watch": "vitest"
|
|
144
|
+
},
|
|
135
145
|
"dependencies": {
|
|
136
146
|
"@types/debug": "^4.1.12",
|
|
137
147
|
"@vitejs/plugin-rsc": "^0.5.26",
|
|
@@ -142,17 +152,17 @@
|
|
|
142
152
|
},
|
|
143
153
|
"devDependencies": {
|
|
144
154
|
"@playwright/test": "^1.49.1",
|
|
155
|
+
"@shared/e2e": "workspace:*",
|
|
145
156
|
"@types/node": "^24.10.1",
|
|
146
|
-
"@types/react": "
|
|
147
|
-
"@types/react-dom": "
|
|
157
|
+
"@types/react": "catalog:",
|
|
158
|
+
"@types/react-dom": "catalog:",
|
|
148
159
|
"esbuild": "^0.27.0",
|
|
149
160
|
"jiti": "^2.6.1",
|
|
150
|
-
"react": "
|
|
151
|
-
"react-dom": "
|
|
161
|
+
"react": "catalog:",
|
|
162
|
+
"react-dom": "catalog:",
|
|
152
163
|
"tinyexec": "^0.3.2",
|
|
153
164
|
"typescript": "^5.3.0",
|
|
154
|
-
"vitest": "^4.0.0"
|
|
155
|
-
"@shared/e2e": "0.0.1"
|
|
165
|
+
"vitest": "^4.0.0"
|
|
156
166
|
},
|
|
157
167
|
"peerDependencies": {
|
|
158
168
|
"@cloudflare/vite-plugin": "^1.38.0",
|
|
@@ -168,14 +178,5 @@
|
|
|
168
178
|
"vite": {
|
|
169
179
|
"optional": true
|
|
170
180
|
}
|
|
171
|
-
},
|
|
172
|
-
"scripts": {
|
|
173
|
-
"build": "pnpm dlx esbuild src/vite/index.ts --bundle --format=esm --outfile=dist/vite/index.js --platform=node --packages=external && mkdir -p dist/vite/plugins && cp src/vite/plugins/cloudflare-protocol-loader-hook.mjs dist/vite/plugins/cloudflare-protocol-loader-hook.mjs && pnpm dlx esbuild src/bin/rango.ts --bundle --format=esm --outfile=dist/bin/rango.js --platform=node --packages=external --banner:js='#!/usr/bin/env node' && chmod +x dist/bin/rango.js",
|
|
174
|
-
"typecheck": "tsc --noEmit && tsc -p tsconfig.strict-check.json --noEmit && tsc -p tsconfig.augment-check.json --noEmit",
|
|
175
|
-
"test": "playwright test",
|
|
176
|
-
"test:ui": "playwright test --ui",
|
|
177
|
-
"test:hmr-local": "playwright test --project=dev-warmup --project=hmr-routes --project=hmr-basename --project=hmr-prerender --no-deps --workers=1",
|
|
178
|
-
"test:unit": "vitest run",
|
|
179
|
-
"test:unit:watch": "vitest"
|
|
180
181
|
}
|
|
181
|
-
}
|
|
182
|
+
}
|
package/skills/hooks/SKILL.md
CHANGED
|
@@ -868,7 +868,7 @@ function MountInfo() {
|
|
|
868
868
|
|
|
869
869
|
### useReverse(routes)
|
|
870
870
|
|
|
871
|
-
Mount-aware local reverse for client components. Import the generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse("
|
|
871
|
+
Mount-aware local reverse for client components. Import the generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse("name", params?)` — the leading dot is optional. Auto-fills params from `useParams()`; explicit params override.
|
|
872
872
|
|
|
873
873
|
> Per-module `*.gen.ts` files are **CLI opt-in and not Vite-watched** — run `rango generate <urls-file>` (or wire it into `predev`) and re-run it whenever the module's routes change. See `/links` for the full generated-file setup and exposure-boundary rules.
|
|
874
874
|
|
|
@@ -881,8 +881,8 @@ function BlogNav() {
|
|
|
881
881
|
const reverse = useReverse(blogRoutes);
|
|
882
882
|
return (
|
|
883
883
|
<nav>
|
|
884
|
-
<Link to={reverse("
|
|
885
|
-
<Link to={reverse("
|
|
884
|
+
<Link to={reverse("index")}>Blog</Link>
|
|
885
|
+
<Link to={reverse("post", { postId: "hello" })}>Post</Link>
|
|
886
886
|
</nav>
|
|
887
887
|
);
|
|
888
888
|
}
|
package/skills/links/SKILL.md
CHANGED
|
@@ -13,7 +13,7 @@ argument-hint: [ctx.reverse|href|useHref|useMount|useReverse|scopedReverse]
|
|
|
13
13
|
**On the client, two patterns:**
|
|
14
14
|
|
|
15
15
|
1. **Receive URLs as props / loader data / action return.** The default. The server has the full route manifest and handler context — generate URLs there and hand strings to client components.
|
|
16
|
-
2. **`useReverse(routes)`.** Import a generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse("
|
|
16
|
+
2. **`useReverse(routes)`.** Import a generated `routes` map from a `urls()` module's `.gen.ts` and call `reverse("name", params?)` (the leading dot is optional). Mount-aware via `useMount()`, auto-fills params from `useParams()`, fully typed from the imported map. Use this when a client component needs to generate URLs into a known module without round-tripping through the server.
|
|
17
17
|
|
|
18
18
|
`ctx.reverse()` itself is **server-only** — it depends on the full route manifest and handler context. Client components never import or call it.
|
|
19
19
|
|
|
@@ -273,7 +273,7 @@ function MountInfo() {
|
|
|
273
273
|
|
|
274
274
|
Hook that returns a typed local reverse function for a `routes` map imported from a generated `.gen.ts` next to a `urls()` module. The route map is the **exposure boundary** — `useReverse` only knows about names in that map, never the full app manifest.
|
|
275
275
|
|
|
276
|
-
>
|
|
276
|
+
> **Which map?** `useReverse` accepts any routes map. Prefer the per-module `routes` (e.g. `urls/blog.gen.ts`): it gives **mount-aware** local `.name` reverse (auto-prefixes the `include()` mount) and only that module's names enter the client bundle. You _can_ instead pass `router.named-routes.gen.ts` (`NamedRoutes`) for **global** names (`blog.post`; the leading dot is optional) — it is a plain importable map and works on the client (it is **not** server-only) — but its paths are **absolute** while `useReverse` mount-prefixes, so it is correct only at the root mount (under a non-root mount it double-prefixes), and importing it pulls every route name and pattern in the app into the client bundle (a small names-to-paths map — not components or loaders), versus the per-module map which exposes only one module's names. So the per-module map is preferred for in-module links; the named-routes map is the escape hatch for global names.
|
|
277
277
|
|
|
278
278
|
```tsx
|
|
279
279
|
"use client";
|
|
@@ -285,8 +285,8 @@ export function BlogNav() {
|
|
|
285
285
|
|
|
286
286
|
return (
|
|
287
287
|
<nav>
|
|
288
|
-
<Link to={reverse("
|
|
289
|
-
<Link to={reverse("
|
|
288
|
+
<Link to={reverse("index")}>Blog</Link>
|
|
289
|
+
<Link to={reverse("post", { postId: "hello" })}>Post</Link>
|
|
290
290
|
</nav>
|
|
291
291
|
);
|
|
292
292
|
}
|
|
@@ -294,7 +294,7 @@ export function BlogNav() {
|
|
|
294
294
|
|
|
295
295
|
### How it resolves
|
|
296
296
|
|
|
297
|
-
1. Strips
|
|
297
|
+
1. Strips an optional leading `.` and looks up the name in the imported `routes` map.
|
|
298
298
|
2. Joins the local pattern with the surrounding `useMount()` value — the include's URL pattern.
|
|
299
299
|
3. Substitutes params: explicit params from the call, then auto-filled from `useParams()` for anything still unresolved (mount params like `:tenantId` flow in this way).
|
|
300
300
|
4. Appends a query string if a search object is passed and the route has a `search` schema.
|
|
@@ -362,14 +362,14 @@ reverse(".search", {}, { q: "hello world", page: 2 });
|
|
|
362
362
|
|
|
363
363
|
### Errors
|
|
364
364
|
|
|
365
|
-
- Unknown name: throws `Unknown
|
|
365
|
+
- Unknown name: throws `Unknown route: ".not-a-route"`.
|
|
366
366
|
- Missing required param: throws `Missing param "postId" for route ".detail"`.
|
|
367
367
|
|
|
368
368
|
Both happen synchronously during `reverse()` — wrap calls in try/catch (or an ErrorBoundary if the throw happens during render) when you need to surface them as UI.
|
|
369
369
|
|
|
370
|
-
###
|
|
370
|
+
### The leading dot is optional
|
|
371
371
|
|
|
372
|
-
`
|
|
372
|
+
`reverse("post")` and `reverse(".post")` resolve **identically** — the leading dot is cosmetic. The map you import IS the scope, so there is no separate global namespace to disambiguate and the dot carries no meaning; it exists only as a readability convention and for parity with `ctx.reverse(".name")` on the server. To link into a different module, import that module's `routes`:
|
|
373
373
|
|
|
374
374
|
```tsx
|
|
375
375
|
import { routes as blogRoutes } from "../urls/blog.gen.js";
|
|
@@ -380,8 +380,8 @@ function CrossNav() {
|
|
|
380
380
|
const shop = useReverse(shopRoutes);
|
|
381
381
|
return (
|
|
382
382
|
<nav>
|
|
383
|
-
<Link to={blog("
|
|
384
|
-
<Link to={shop("
|
|
383
|
+
<Link to={blog("index")}>Blog</Link>
|
|
384
|
+
<Link to={shop("cart")}>Cart</Link>
|
|
385
385
|
</nav>
|
|
386
386
|
);
|
|
387
387
|
}
|
package/skills/rango/SKILL.md
CHANGED
|
@@ -220,6 +220,7 @@ Grouped by concern — read when you need to…
|
|
|
220
220
|
| `/tailwind` | Set up Tailwind CSS v4 with `?url` imports |
|
|
221
221
|
| `/view-transitions` | React View Transitions on layouts, routes, and parallel slots |
|
|
222
222
|
| `/breadcrumbs` | Built-in Breadcrumbs handle for breadcrumb navigation |
|
|
223
|
+
| `/react-compiler` | Enable React Compiler (opt-in) the vite-rsc way; client-only scope |
|
|
223
224
|
|
|
224
225
|
**Observability & production health**:
|
|
225
226
|
|