@rangojs/router 0.0.0-experimental.60a361a0 → 0.0.0-experimental.62
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 +15 -10
- package/dist/bin/rango.js +128 -46
- package/dist/vite/index.js +267 -195
- package/package.json +2 -2
- package/skills/links/SKILL.md +3 -1
- package/skills/middleware/SKILL.md +2 -0
- package/skills/router-setup/SKILL.md +35 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/navigation-bridge.ts +16 -3
- package/src/browser/navigation-client.ts +64 -64
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/partial-update.ts +27 -5
- package/src/browser/prefetch/fetch.ts +8 -2
- package/src/browser/react/Link.tsx +44 -8
- package/src/browser/react/NavigationProvider.tsx +8 -1
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-router.ts +21 -8
- package/src/browser/rsc-router.tsx +26 -3
- package/src/browser/scroll-restoration.ts +10 -8
- package/src/browser/server-action-bridge.ts +8 -18
- package/src/browser/types.ts +20 -5
- package/src/build/generate-manifest.ts +3 -0
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +211 -72
- package/src/deps/browser.ts +0 -1
- package/src/reverse.ts +22 -1
- package/src/route-definition/redirect.ts +9 -1
- package/src/router/handler-context.ts +28 -10
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/loader-resolution.ts +3 -2
- package/src/router/match-middleware/cache-lookup.ts +4 -1
- package/src/router/middleware-types.ts +0 -6
- package/src/router/middleware.ts +0 -3
- package/src/router/prerender-match.ts +2 -2
- package/src/router/router-interfaces.ts +25 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +11 -3
- package/src/router/segment-resolution/revalidation.ts +8 -3
- package/src/router.ts +40 -4
- package/src/rsc/handler.ts +12 -29
- package/src/rsc/loader-fetch.ts +2 -7
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +5 -4
- package/src/rsc/rsc-rendering.ts +6 -4
- package/src/rsc/server-action.ts +2 -2
- package/src/rsc/ssr-setup.ts +1 -1
- package/src/rsc/types.ts +5 -4
- package/src/server/context.ts +25 -1
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +7 -10
- package/src/ssr/index.tsx +3 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +5 -9
- package/src/types/loader-types.ts +0 -1
- package/src/urls/pattern-types.ts +12 -0
- package/src/vite/discovery/discover-routers.ts +5 -1
- package/src/vite/plugins/performance-tracks.ts +64 -210
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/rango.ts +16 -4
- package/src/vite/utils/prerender-utils.ts +18 -0
- package/src/vite/utils/shared-utils.ts +3 -2
- package/src/browser/debug-channel.ts +0 -93
package/README.md
CHANGED
|
@@ -91,24 +91,29 @@ This file is a server/RSC module and should import router construction APIs from
|
|
|
91
91
|
|
|
92
92
|
```tsx
|
|
93
93
|
// src/router.tsx
|
|
94
|
-
import { createRouter
|
|
95
|
-
import { Document } from "./document";
|
|
94
|
+
import { createRouter } from "@rangojs/router";
|
|
96
95
|
|
|
97
|
-
const
|
|
98
|
-
path("/",
|
|
99
|
-
path("
|
|
96
|
+
export const router = createRouter().routes(({ path }) => [
|
|
97
|
+
path("/", HomePage, { name: "home" }),
|
|
98
|
+
path("/about", AboutPage, { name: "about" }),
|
|
100
99
|
]);
|
|
101
100
|
|
|
101
|
+
export const reverse = router.reverse;
|
|
102
|
+
// reverse("home") -> "/"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
For larger apps, extract route modules with `urls()` and compose with `include()`:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { createRouter, urls } from "@rangojs/router";
|
|
109
|
+
import { blogPatterns } from "./urls/blog";
|
|
110
|
+
|
|
102
111
|
const urlpatterns = urls(({ path, include }) => [
|
|
103
112
|
path("/", HomePage, { name: "home" }),
|
|
104
113
|
include("/blog", blogPatterns, { name: "blog" }),
|
|
105
114
|
]);
|
|
106
115
|
|
|
107
|
-
export const router = createRouter(
|
|
108
|
-
|
|
109
|
-
// Export typed reverse function for URL generation by route name
|
|
110
|
-
export const reverse = router.reverse;
|
|
111
|
-
|
|
116
|
+
export const router = createRouter().routes(urlpatterns);
|
|
112
117
|
// reverse("blog.post", { slug: "hello-world" }) -> "/blog/hello-world"
|
|
113
118
|
```
|
|
114
119
|
|
package/dist/bin/rango.js
CHANGED
|
@@ -450,7 +450,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
|
|
|
450
450
|
}
|
|
451
451
|
return routeMap;
|
|
452
452
|
}
|
|
453
|
-
function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
|
|
453
|
+
function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
|
|
454
454
|
visited = visited ?? /* @__PURE__ */ new Set();
|
|
455
455
|
const realPath = resolve(filePath);
|
|
456
456
|
const key = variableName ? `${realPath}:${variableName}` : realPath;
|
|
@@ -466,7 +466,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
|
|
|
466
466
|
return { routes: {}, searchSchemas: {} };
|
|
467
467
|
}
|
|
468
468
|
let block;
|
|
469
|
-
if (
|
|
469
|
+
if (inlineBlock) {
|
|
470
|
+
block = inlineBlock;
|
|
471
|
+
} else if (variableName) {
|
|
470
472
|
const extracted = extractUrlsBlockForVariable(source, variableName);
|
|
471
473
|
if (!extracted) return { routes: {}, searchSchemas: {} };
|
|
472
474
|
block = extracted;
|
|
@@ -671,7 +673,7 @@ Router root: ${conflict.ancestor}
|
|
|
671
673
|
Nested router: ${conflict.nested}
|
|
672
674
|
Move the nested router into a sibling directory or configure it as a separate app root.`;
|
|
673
675
|
}
|
|
674
|
-
function
|
|
676
|
+
function extractUrlsFromRouter(code) {
|
|
675
677
|
const sourceFile = ts5.createSourceFile(
|
|
676
678
|
"router.tsx",
|
|
677
679
|
code,
|
|
@@ -685,24 +687,70 @@ function extractUrlsVariableFromRouter(code) {
|
|
|
685
687
|
const callee = node.expression;
|
|
686
688
|
return ts5.isIdentifier(callee) && callee.text === "createRouter";
|
|
687
689
|
}
|
|
690
|
+
function isInlineBuilder(node) {
|
|
691
|
+
return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
|
|
692
|
+
}
|
|
693
|
+
function isRoutesOnCreateRouter(node) {
|
|
694
|
+
if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
|
|
695
|
+
return false;
|
|
696
|
+
let inner = node.expression.expression;
|
|
697
|
+
while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
|
|
698
|
+
inner = inner.expression.expression;
|
|
699
|
+
}
|
|
700
|
+
return isCreateRouterCall(inner);
|
|
701
|
+
}
|
|
688
702
|
function visit(node) {
|
|
689
703
|
if (result) return;
|
|
690
|
-
if (ts5.isCallExpression(node) &&
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
result = node.arguments[0].text;
|
|
697
|
-
return;
|
|
704
|
+
if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
|
|
705
|
+
const arg = node.arguments[0];
|
|
706
|
+
if (ts5.isIdentifier(arg)) {
|
|
707
|
+
result = { kind: "variable", name: arg.text };
|
|
708
|
+
} else if (isInlineBuilder(arg)) {
|
|
709
|
+
result = { kind: "inline", block: arg.getText(sourceFile) };
|
|
698
710
|
}
|
|
711
|
+
return;
|
|
699
712
|
}
|
|
700
713
|
if (isCreateRouterCall(node)) {
|
|
701
714
|
const callExpr = node;
|
|
702
|
-
for (const
|
|
715
|
+
for (const callArg of callExpr.arguments) {
|
|
716
|
+
if (ts5.isObjectLiteralExpression(callArg)) {
|
|
717
|
+
for (const prop of callArg.properties) {
|
|
718
|
+
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
|
|
719
|
+
if (ts5.isIdentifier(prop.initializer)) {
|
|
720
|
+
result = { kind: "variable", name: prop.initializer.text };
|
|
721
|
+
} else if (isInlineBuilder(prop.initializer)) {
|
|
722
|
+
result = {
|
|
723
|
+
kind: "inline",
|
|
724
|
+
block: prop.initializer.getText(sourceFile)
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
ts5.forEachChild(node, visit);
|
|
734
|
+
}
|
|
735
|
+
visit(sourceFile);
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
function extractBasenameFromRouter(code) {
|
|
739
|
+
const sourceFile = ts5.createSourceFile(
|
|
740
|
+
"router.tsx",
|
|
741
|
+
code,
|
|
742
|
+
ts5.ScriptTarget.Latest,
|
|
743
|
+
true,
|
|
744
|
+
ts5.ScriptKind.TSX
|
|
745
|
+
);
|
|
746
|
+
let result;
|
|
747
|
+
function visit(node) {
|
|
748
|
+
if (result !== void 0) return;
|
|
749
|
+
if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
|
|
750
|
+
for (const arg of node.arguments) {
|
|
703
751
|
if (ts5.isObjectLiteralExpression(arg)) {
|
|
704
752
|
for (const prop of arg.properties) {
|
|
705
|
-
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "
|
|
753
|
+
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
|
|
706
754
|
result = prop.initializer.text;
|
|
707
755
|
return;
|
|
708
756
|
}
|
|
@@ -715,6 +763,19 @@ function extractUrlsVariableFromRouter(code) {
|
|
|
715
763
|
visit(sourceFile);
|
|
716
764
|
return result;
|
|
717
765
|
}
|
|
766
|
+
function applyBasenameToRoutes(result, basename2) {
|
|
767
|
+
const prefixed = {};
|
|
768
|
+
for (const [name, pattern] of Object.entries(result.routes)) {
|
|
769
|
+
if (pattern === "/") {
|
|
770
|
+
prefixed[name] = basename2;
|
|
771
|
+
} else if (basename2.endsWith("/") && pattern.startsWith("/")) {
|
|
772
|
+
prefixed[name] = basename2 + pattern.slice(1);
|
|
773
|
+
} else {
|
|
774
|
+
prefixed[name] = basename2 + pattern;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return { routes: prefixed, searchSchemas: result.searchSchemas };
|
|
778
|
+
}
|
|
718
779
|
function buildCombinedRouteMapForRouterFile(routerFilePath) {
|
|
719
780
|
let routerSource;
|
|
720
781
|
try {
|
|
@@ -722,19 +783,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
|
|
|
722
783
|
} catch {
|
|
723
784
|
return { routes: {}, searchSchemas: {} };
|
|
724
785
|
}
|
|
725
|
-
const
|
|
726
|
-
if (!
|
|
786
|
+
const extraction = extractUrlsFromRouter(routerSource);
|
|
787
|
+
if (!extraction) {
|
|
727
788
|
return { routes: {}, searchSchemas: {} };
|
|
728
789
|
}
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
790
|
+
const rawBasename = extractBasenameFromRouter(routerSource);
|
|
791
|
+
const basename2 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
|
|
792
|
+
let result;
|
|
793
|
+
if (extraction.kind === "inline") {
|
|
794
|
+
result = buildCombinedRouteMapWithSearch(
|
|
795
|
+
routerFilePath,
|
|
796
|
+
void 0,
|
|
797
|
+
void 0,
|
|
798
|
+
void 0,
|
|
799
|
+
extraction.block
|
|
800
|
+
);
|
|
801
|
+
} else {
|
|
802
|
+
const imported = resolveImportedVariable(routerSource, extraction.name);
|
|
803
|
+
if (imported) {
|
|
804
|
+
const targetFile = resolveImportPath(imported.specifier, routerFilePath);
|
|
805
|
+
if (!targetFile) {
|
|
806
|
+
return { routes: {}, searchSchemas: {} };
|
|
807
|
+
}
|
|
808
|
+
result = buildCombinedRouteMapWithSearch(
|
|
809
|
+
targetFile,
|
|
810
|
+
imported.exportedName
|
|
811
|
+
);
|
|
812
|
+
} else {
|
|
813
|
+
result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
|
|
734
814
|
}
|
|
735
|
-
return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
|
|
736
815
|
}
|
|
737
|
-
|
|
816
|
+
if (basename2) {
|
|
817
|
+
result = applyBasenameToRoutes(result, basename2);
|
|
818
|
+
}
|
|
819
|
+
return result;
|
|
738
820
|
}
|
|
739
821
|
function detectUnresolvableIncludes(routerFilePath) {
|
|
740
822
|
const realPath = resolve2(routerFilePath);
|
|
@@ -744,9 +826,20 @@ function detectUnresolvableIncludes(routerFilePath) {
|
|
|
744
826
|
} catch {
|
|
745
827
|
return [];
|
|
746
828
|
}
|
|
747
|
-
const
|
|
748
|
-
if (!
|
|
749
|
-
const
|
|
829
|
+
const extraction = extractUrlsFromRouter(source);
|
|
830
|
+
if (!extraction) return [];
|
|
831
|
+
const diagnostics = [];
|
|
832
|
+
if (extraction.kind === "inline") {
|
|
833
|
+
buildCombinedRouteMapWithSearch(
|
|
834
|
+
realPath,
|
|
835
|
+
void 0,
|
|
836
|
+
/* @__PURE__ */ new Set(),
|
|
837
|
+
diagnostics,
|
|
838
|
+
extraction.block
|
|
839
|
+
);
|
|
840
|
+
return diagnostics;
|
|
841
|
+
}
|
|
842
|
+
const imported = resolveImportedVariable(source, extraction.name);
|
|
750
843
|
let targetFile;
|
|
751
844
|
let exportedName;
|
|
752
845
|
if (imported) {
|
|
@@ -766,9 +859,8 @@ function detectUnresolvableIncludes(routerFilePath) {
|
|
|
766
859
|
exportedName = imported.exportedName;
|
|
767
860
|
} else {
|
|
768
861
|
targetFile = realPath;
|
|
769
|
-
exportedName =
|
|
862
|
+
exportedName = extraction.name;
|
|
770
863
|
}
|
|
771
|
-
const diagnostics = [];
|
|
772
864
|
buildCombinedRouteMapWithSearch(
|
|
773
865
|
targetFile,
|
|
774
866
|
exportedName,
|
|
@@ -816,25 +908,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
|
|
|
816
908
|
throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
|
|
817
909
|
}
|
|
818
910
|
for (const routerFilePath of routerFilePaths) {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
routerSource
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const imported = resolveImportedVariable(routerSource, urlsVarName);
|
|
829
|
-
if (imported) {
|
|
830
|
-
const targetFile = resolveImportPath(imported.specifier, routerFilePath);
|
|
831
|
-
if (!targetFile) continue;
|
|
832
|
-
result = buildCombinedRouteMapWithSearch(
|
|
833
|
-
targetFile,
|
|
834
|
-
imported.exportedName
|
|
835
|
-
);
|
|
836
|
-
} else {
|
|
837
|
-
result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
|
|
911
|
+
const result = buildCombinedRouteMapForRouterFile(routerFilePath);
|
|
912
|
+
if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
|
|
913
|
+
let routerSource;
|
|
914
|
+
try {
|
|
915
|
+
routerSource = readFileSync3(routerFilePath, "utf-8");
|
|
916
|
+
} catch {
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (!extractUrlsFromRouter(routerSource)) continue;
|
|
838
920
|
}
|
|
839
921
|
const routerBasename = pathBasename(routerFilePath).replace(
|
|
840
922
|
/\.(tsx?|jsx?)$/,
|