@rangojs/router 0.0.0-experimental.54a3dc6a → 0.0.0-experimental.56
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 +128 -46
- package/dist/vite/index.js +211 -47
- package/package.json +2 -2
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +8 -0
- package/skills/links/SKILL.md +3 -1
- package/skills/loader/SKILL.md +53 -43
- package/skills/middleware/SKILL.md +2 -0
- package/skills/parallel/SKILL.md +67 -0
- package/skills/route/SKILL.md +31 -0
- package/skills/router-setup/SKILL.md +87 -2
- package/skills/typesafety/SKILL.md +10 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/navigation-bridge.ts +16 -3
- package/src/browser/navigation-client.ts +64 -40
- package/src/browser/navigation-store.ts +43 -8
- package/src/browser/partial-update.ts +37 -4
- package/src/browser/prefetch/fetch.ts +8 -2
- package/src/browser/prefetch/queue.ts +61 -29
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/react/Link.tsx +44 -8
- package/src/browser/react/NavigationProvider.tsx +13 -4
- 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/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +27 -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/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +46 -5
- package/src/cache/taint.ts +55 -0
- package/src/context-var.ts +72 -2
- package/src/route-definition/helpers-types.ts +6 -5
- package/src/route-definition/redirect.ts +9 -1
- package/src/router/handler-context.ts +36 -17
- package/src/router/intercept-resolution.ts +9 -4
- package/src/router/loader-resolution.ts +9 -2
- package/src/router/match-middleware/background-revalidation.ts +12 -1
- package/src/router/match-middleware/cache-lookup.ts +38 -1
- package/src/router/match-middleware/cache-store.ts +21 -4
- package/src/router/match-result.ts +11 -5
- package/src/router/middleware-types.ts +6 -8
- package/src/router/middleware.ts +2 -5
- package/src/router/prerender-match.ts +2 -2
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +25 -4
- package/src/router/router-options.ts +37 -11
- package/src/router/segment-resolution/fresh.ts +22 -8
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/revalidation.ts +16 -4
- package/src/router/types.ts +1 -0
- package/src/router.ts +41 -4
- package/src/rsc/handler.ts +11 -2
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/rsc-rendering.ts +5 -0
- package/src/rsc/server-action.ts +2 -0
- package/src/rsc/ssr-setup.ts +1 -1
- package/src/rsc/types.ts +8 -1
- package/src/server/context.ts +36 -0
- package/src/server/request-context.ts +50 -12
- package/src/ssr/index.tsx +3 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +125 -31
- package/src/types/loader-types.ts +4 -5
- package/src/urls/pattern-types.ts +12 -0
- package/src/vite/discovery/discover-routers.ts +5 -1
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/rango.ts +17 -1
- package/src/vite/utils/shared-utils.ts +3 -2
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?)$/,
|
package/dist/vite/index.js
CHANGED
|
@@ -1745,7 +1745,7 @@ import { resolve } from "node:path";
|
|
|
1745
1745
|
// package.json
|
|
1746
1746
|
var package_default = {
|
|
1747
1747
|
name: "@rangojs/router",
|
|
1748
|
-
version: "0.0.0-experimental.
|
|
1748
|
+
version: "0.0.0-experimental.56",
|
|
1749
1749
|
description: "Django-inspired RSC router with composable URL patterns",
|
|
1750
1750
|
keywords: [
|
|
1751
1751
|
"react",
|
|
@@ -1887,7 +1887,7 @@ var package_default = {
|
|
|
1887
1887
|
"test:unit:watch": "vitest"
|
|
1888
1888
|
},
|
|
1889
1889
|
dependencies: {
|
|
1890
|
-
"@vitejs/plugin-rsc": "^0.5.
|
|
1890
|
+
"@vitejs/plugin-rsc": "^0.5.19",
|
|
1891
1891
|
"magic-string": "^0.30.17",
|
|
1892
1892
|
picomatch: "^4.0.3",
|
|
1893
1893
|
"rsc-html-stream": "^0.0.7"
|
|
@@ -2317,7 +2317,7 @@ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSche
|
|
|
2317
2317
|
}
|
|
2318
2318
|
return routeMap;
|
|
2319
2319
|
}
|
|
2320
|
-
function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut) {
|
|
2320
|
+
function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock) {
|
|
2321
2321
|
visited = visited ?? /* @__PURE__ */ new Set();
|
|
2322
2322
|
const realPath = resolve2(filePath);
|
|
2323
2323
|
const key = variableName ? `${realPath}:${variableName}` : realPath;
|
|
@@ -2333,7 +2333,9 @@ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagno
|
|
|
2333
2333
|
return { routes: {}, searchSchemas: {} };
|
|
2334
2334
|
}
|
|
2335
2335
|
let block;
|
|
2336
|
-
if (
|
|
2336
|
+
if (inlineBlock) {
|
|
2337
|
+
block = inlineBlock;
|
|
2338
|
+
} else if (variableName) {
|
|
2337
2339
|
const extracted = extractUrlsBlockForVariable(source, variableName);
|
|
2338
2340
|
if (!extracted) return { routes: {}, searchSchemas: {} };
|
|
2339
2341
|
block = extracted;
|
|
@@ -2452,7 +2454,7 @@ Router root: ${conflict.ancestor}
|
|
|
2452
2454
|
Nested router: ${conflict.nested}
|
|
2453
2455
|
Move the nested router into a sibling directory or configure it as a separate app root.`;
|
|
2454
2456
|
}
|
|
2455
|
-
function
|
|
2457
|
+
function extractUrlsFromRouter(code) {
|
|
2456
2458
|
const sourceFile = ts5.createSourceFile(
|
|
2457
2459
|
"router.tsx",
|
|
2458
2460
|
code,
|
|
@@ -2466,24 +2468,70 @@ function extractUrlsVariableFromRouter(code) {
|
|
|
2466
2468
|
const callee = node.expression;
|
|
2467
2469
|
return ts5.isIdentifier(callee) && callee.text === "createRouter";
|
|
2468
2470
|
}
|
|
2471
|
+
function isInlineBuilder(node) {
|
|
2472
|
+
return ts5.isArrowFunction(node) || ts5.isFunctionExpression(node);
|
|
2473
|
+
}
|
|
2474
|
+
function isRoutesOnCreateRouter(node) {
|
|
2475
|
+
if (!ts5.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "routes")
|
|
2476
|
+
return false;
|
|
2477
|
+
let inner = node.expression.expression;
|
|
2478
|
+
while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
|
|
2479
|
+
inner = inner.expression.expression;
|
|
2480
|
+
}
|
|
2481
|
+
return isCreateRouterCall(inner);
|
|
2482
|
+
}
|
|
2469
2483
|
function visit(node) {
|
|
2470
2484
|
if (result) return;
|
|
2471
|
-
if (ts5.isCallExpression(node) &&
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
|
-
result = node.arguments[0].text;
|
|
2478
|
-
return;
|
|
2485
|
+
if (ts5.isCallExpression(node) && node.arguments.length >= 1 && isRoutesOnCreateRouter(node)) {
|
|
2486
|
+
const arg = node.arguments[0];
|
|
2487
|
+
if (ts5.isIdentifier(arg)) {
|
|
2488
|
+
result = { kind: "variable", name: arg.text };
|
|
2489
|
+
} else if (isInlineBuilder(arg)) {
|
|
2490
|
+
result = { kind: "inline", block: arg.getText(sourceFile) };
|
|
2479
2491
|
}
|
|
2492
|
+
return;
|
|
2480
2493
|
}
|
|
2481
2494
|
if (isCreateRouterCall(node)) {
|
|
2482
2495
|
const callExpr = node;
|
|
2483
|
-
for (const
|
|
2496
|
+
for (const callArg of callExpr.arguments) {
|
|
2497
|
+
if (ts5.isObjectLiteralExpression(callArg)) {
|
|
2498
|
+
for (const prop of callArg.properties) {
|
|
2499
|
+
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "urls") {
|
|
2500
|
+
if (ts5.isIdentifier(prop.initializer)) {
|
|
2501
|
+
result = { kind: "variable", name: prop.initializer.text };
|
|
2502
|
+
} else if (isInlineBuilder(prop.initializer)) {
|
|
2503
|
+
result = {
|
|
2504
|
+
kind: "inline",
|
|
2505
|
+
block: prop.initializer.getText(sourceFile)
|
|
2506
|
+
};
|
|
2507
|
+
}
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
ts5.forEachChild(node, visit);
|
|
2515
|
+
}
|
|
2516
|
+
visit(sourceFile);
|
|
2517
|
+
return result;
|
|
2518
|
+
}
|
|
2519
|
+
function extractBasenameFromRouter(code) {
|
|
2520
|
+
const sourceFile = ts5.createSourceFile(
|
|
2521
|
+
"router.tsx",
|
|
2522
|
+
code,
|
|
2523
|
+
ts5.ScriptTarget.Latest,
|
|
2524
|
+
true,
|
|
2525
|
+
ts5.ScriptKind.TSX
|
|
2526
|
+
);
|
|
2527
|
+
let result;
|
|
2528
|
+
function visit(node) {
|
|
2529
|
+
if (result !== void 0) return;
|
|
2530
|
+
if (ts5.isCallExpression(node) && ts5.isIdentifier(node.expression) && node.expression.text === "createRouter") {
|
|
2531
|
+
for (const arg of node.arguments) {
|
|
2484
2532
|
if (ts5.isObjectLiteralExpression(arg)) {
|
|
2485
2533
|
for (const prop of arg.properties) {
|
|
2486
|
-
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "
|
|
2534
|
+
if (ts5.isPropertyAssignment(prop) && ts5.isIdentifier(prop.name) && prop.name.text === "basename" && ts5.isStringLiteral(prop.initializer)) {
|
|
2487
2535
|
result = prop.initializer.text;
|
|
2488
2536
|
return;
|
|
2489
2537
|
}
|
|
@@ -2496,6 +2544,19 @@ function extractUrlsVariableFromRouter(code) {
|
|
|
2496
2544
|
visit(sourceFile);
|
|
2497
2545
|
return result;
|
|
2498
2546
|
}
|
|
2547
|
+
function applyBasenameToRoutes(result, basename3) {
|
|
2548
|
+
const prefixed = {};
|
|
2549
|
+
for (const [name, pattern] of Object.entries(result.routes)) {
|
|
2550
|
+
if (pattern === "/") {
|
|
2551
|
+
prefixed[name] = basename3;
|
|
2552
|
+
} else if (basename3.endsWith("/") && pattern.startsWith("/")) {
|
|
2553
|
+
prefixed[name] = basename3 + pattern.slice(1);
|
|
2554
|
+
} else {
|
|
2555
|
+
prefixed[name] = basename3 + pattern;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
return { routes: prefixed, searchSchemas: result.searchSchemas };
|
|
2559
|
+
}
|
|
2499
2560
|
function buildCombinedRouteMapForRouterFile(routerFilePath) {
|
|
2500
2561
|
let routerSource;
|
|
2501
2562
|
try {
|
|
@@ -2503,19 +2564,40 @@ function buildCombinedRouteMapForRouterFile(routerFilePath) {
|
|
|
2503
2564
|
} catch {
|
|
2504
2565
|
return { routes: {}, searchSchemas: {} };
|
|
2505
2566
|
}
|
|
2506
|
-
const
|
|
2507
|
-
if (!
|
|
2567
|
+
const extraction = extractUrlsFromRouter(routerSource);
|
|
2568
|
+
if (!extraction) {
|
|
2508
2569
|
return { routes: {}, searchSchemas: {} };
|
|
2509
2570
|
}
|
|
2510
|
-
const
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2571
|
+
const rawBasename = extractBasenameFromRouter(routerSource);
|
|
2572
|
+
const basename3 = rawBasename ? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "") : void 0;
|
|
2573
|
+
let result;
|
|
2574
|
+
if (extraction.kind === "inline") {
|
|
2575
|
+
result = buildCombinedRouteMapWithSearch(
|
|
2576
|
+
routerFilePath,
|
|
2577
|
+
void 0,
|
|
2578
|
+
void 0,
|
|
2579
|
+
void 0,
|
|
2580
|
+
extraction.block
|
|
2581
|
+
);
|
|
2582
|
+
} else {
|
|
2583
|
+
const imported = resolveImportedVariable(routerSource, extraction.name);
|
|
2584
|
+
if (imported) {
|
|
2585
|
+
const targetFile = resolveImportPath(imported.specifier, routerFilePath);
|
|
2586
|
+
if (!targetFile) {
|
|
2587
|
+
return { routes: {}, searchSchemas: {} };
|
|
2588
|
+
}
|
|
2589
|
+
result = buildCombinedRouteMapWithSearch(
|
|
2590
|
+
targetFile,
|
|
2591
|
+
imported.exportedName
|
|
2592
|
+
);
|
|
2593
|
+
} else {
|
|
2594
|
+
result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
|
|
2515
2595
|
}
|
|
2516
|
-
return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
|
|
2517
2596
|
}
|
|
2518
|
-
|
|
2597
|
+
if (basename3) {
|
|
2598
|
+
result = applyBasenameToRoutes(result, basename3);
|
|
2599
|
+
}
|
|
2600
|
+
return result;
|
|
2519
2601
|
}
|
|
2520
2602
|
function findRouterFiles(root, filter) {
|
|
2521
2603
|
const result = [];
|
|
@@ -2540,25 +2622,15 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
|
|
|
2540
2622
|
throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
|
|
2541
2623
|
}
|
|
2542
2624
|
for (const routerFilePath of routerFilePaths) {
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
routerSource
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
const imported = resolveImportedVariable(routerSource, urlsVarName);
|
|
2553
|
-
if (imported) {
|
|
2554
|
-
const targetFile = resolveImportPath(imported.specifier, routerFilePath);
|
|
2555
|
-
if (!targetFile) continue;
|
|
2556
|
-
result = buildCombinedRouteMapWithSearch(
|
|
2557
|
-
targetFile,
|
|
2558
|
-
imported.exportedName
|
|
2559
|
-
);
|
|
2560
|
-
} else {
|
|
2561
|
-
result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
|
|
2625
|
+
const result = buildCombinedRouteMapForRouterFile(routerFilePath);
|
|
2626
|
+
if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
|
|
2627
|
+
let routerSource;
|
|
2628
|
+
try {
|
|
2629
|
+
routerSource = readFileSync2(routerFilePath, "utf-8");
|
|
2630
|
+
} catch {
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
if (!extractUrlsFromRouter(routerSource)) continue;
|
|
2562
2634
|
}
|
|
2563
2635
|
const routerBasename = pathBasename(routerFilePath).replace(
|
|
2564
2636
|
/\.(tsx?|jsx?)$/,
|
|
@@ -2784,6 +2856,68 @@ function createVersionPlugin() {
|
|
|
2784
2856
|
|
|
2785
2857
|
// src/vite/utils/shared-utils.ts
|
|
2786
2858
|
import * as Vite from "vite";
|
|
2859
|
+
|
|
2860
|
+
// src/vite/plugins/performance-tracks.ts
|
|
2861
|
+
import { readFile } from "node:fs/promises";
|
|
2862
|
+
var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
|
|
2863
|
+
function buildPatchReplacement(match, debugInfoVar) {
|
|
2864
|
+
return `${match}
|
|
2865
|
+
if (${debugInfoVar} && 0 === ${debugInfoVar}.length && "fulfilled" === root.status) {
|
|
2866
|
+
var _resolved = "function" === typeof resolveLazy ? resolveLazy(root.value) : root.value;
|
|
2867
|
+
if ("object" === typeof _resolved && null !== _resolved && isArrayImpl(_resolved._debugInfo)) {
|
|
2868
|
+
${debugInfoVar} = _resolved._debugInfo;
|
|
2869
|
+
}
|
|
2870
|
+
}`;
|
|
2871
|
+
}
|
|
2872
|
+
function patchRsdwClientDebugInfoRecovery(code) {
|
|
2873
|
+
const match = code.match(RSDW_PATCH_RE);
|
|
2874
|
+
if (!match) {
|
|
2875
|
+
return { code, debugInfoVar: null };
|
|
2876
|
+
}
|
|
2877
|
+
return {
|
|
2878
|
+
code: code.replace(match[1], buildPatchReplacement(match[1], match[2])),
|
|
2879
|
+
debugInfoVar: match[2]
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
function performanceTracksOptimizeDepsPlugin() {
|
|
2883
|
+
return {
|
|
2884
|
+
name: "@rangojs/router:performance-tracks-optimize-deps",
|
|
2885
|
+
setup(build) {
|
|
2886
|
+
build.onLoad(
|
|
2887
|
+
{
|
|
2888
|
+
filter: /react-server-dom-webpack-client\.browser\.(development|production)\.js$/
|
|
2889
|
+
},
|
|
2890
|
+
async (args) => {
|
|
2891
|
+
const code = await readFile(args.path, "utf8");
|
|
2892
|
+
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
2893
|
+
return {
|
|
2894
|
+
contents: patched.code,
|
|
2895
|
+
loader: "js"
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
}
|
|
2902
|
+
function performanceTracksPlugin() {
|
|
2903
|
+
return {
|
|
2904
|
+
name: "@rangojs/router:performance-tracks",
|
|
2905
|
+
transform(code, id) {
|
|
2906
|
+
if (!id.includes("react-server-dom") || !id.includes("client")) return;
|
|
2907
|
+
const patched = patchRsdwClientDebugInfoRecovery(code);
|
|
2908
|
+
if (!patched.debugInfoVar) return;
|
|
2909
|
+
if (process.env.INTERNAL_RANGO_DEBUG)
|
|
2910
|
+
console.log(
|
|
2911
|
+
"[perf-tracks] patched RSDW client (var:",
|
|
2912
|
+
patched.debugInfoVar,
|
|
2913
|
+
")"
|
|
2914
|
+
);
|
|
2915
|
+
return patched.code;
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
// src/vite/utils/shared-utils.ts
|
|
2787
2921
|
var versionEsbuildPlugin = {
|
|
2788
2922
|
name: "@rangojs/router-version",
|
|
2789
2923
|
setup(build) {
|
|
@@ -2801,7 +2935,7 @@ var versionEsbuildPlugin = {
|
|
|
2801
2935
|
}
|
|
2802
2936
|
};
|
|
2803
2937
|
var sharedEsbuildOptions = {
|
|
2804
|
-
plugins: [versionEsbuildPlugin]
|
|
2938
|
+
plugins: [versionEsbuildPlugin, performanceTracksOptimizeDepsPlugin()]
|
|
2805
2939
|
};
|
|
2806
2940
|
function createVirtualEntriesPlugin(entries, routerPathRef) {
|
|
2807
2941
|
const virtualModules = {};
|
|
@@ -3274,8 +3408,17 @@ function jsonParseExpression(value) {
|
|
|
3274
3408
|
}
|
|
3275
3409
|
|
|
3276
3410
|
// src/context-var.ts
|
|
3411
|
+
var NON_CACHEABLE_KEYS = /* @__PURE__ */ Symbol.for(
|
|
3412
|
+
"rango:non-cacheable-keys"
|
|
3413
|
+
);
|
|
3414
|
+
function getNonCacheableKeys(variables) {
|
|
3415
|
+
if (!variables[NON_CACHEABLE_KEYS]) {
|
|
3416
|
+
variables[NON_CACHEABLE_KEYS] = /* @__PURE__ */ new Set();
|
|
3417
|
+
}
|
|
3418
|
+
return variables[NON_CACHEABLE_KEYS];
|
|
3419
|
+
}
|
|
3277
3420
|
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
3278
|
-
function contextSet(variables, keyOrVar, value) {
|
|
3421
|
+
function contextSet(variables, keyOrVar, value, options) {
|
|
3279
3422
|
if (typeof keyOrVar === "string") {
|
|
3280
3423
|
if (FORBIDDEN_KEYS.has(keyOrVar)) {
|
|
3281
3424
|
throw new Error(
|
|
@@ -3283,8 +3426,14 @@ function contextSet(variables, keyOrVar, value) {
|
|
|
3283
3426
|
);
|
|
3284
3427
|
}
|
|
3285
3428
|
variables[keyOrVar] = value;
|
|
3429
|
+
if (options?.cache === false) {
|
|
3430
|
+
getNonCacheableKeys(variables).add(keyOrVar);
|
|
3431
|
+
}
|
|
3286
3432
|
} else {
|
|
3287
3433
|
variables[keyOrVar.key] = value;
|
|
3434
|
+
if (options?.cache === false) {
|
|
3435
|
+
getNonCacheableKeys(variables).add(keyOrVar.key);
|
|
3436
|
+
}
|
|
3288
3437
|
}
|
|
3289
3438
|
}
|
|
3290
3439
|
|
|
@@ -3776,7 +3925,11 @@ async function discoverRouters(state, rscEnv) {
|
|
|
3776
3925
|
if (!router.urlpatterns || !generateManifestFull) {
|
|
3777
3926
|
continue;
|
|
3778
3927
|
}
|
|
3779
|
-
const manifest = generateManifestFull(
|
|
3928
|
+
const manifest = generateManifestFull(
|
|
3929
|
+
router.urlpatterns,
|
|
3930
|
+
routerMountIndex,
|
|
3931
|
+
router.__basename ? { urlPrefix: router.__basename } : void 0
|
|
3932
|
+
);
|
|
3780
3933
|
routerMountIndex++;
|
|
3781
3934
|
allManifests.push({ id, manifest });
|
|
3782
3935
|
const routeCount = Object.keys(manifest.routeManifest).length;
|
|
@@ -4853,7 +5006,16 @@ async function rango(options) {
|
|
|
4853
5006
|
const showBanner = resolvedOptions.banner ?? true;
|
|
4854
5007
|
const plugins = [];
|
|
4855
5008
|
const rangoAliases = getPackageAliases();
|
|
4856
|
-
const excludeDeps =
|
|
5009
|
+
const excludeDeps = [
|
|
5010
|
+
...getExcludeDeps(),
|
|
5011
|
+
// The public browser entry re-exports the RSDW browser client.
|
|
5012
|
+
// Excluding both keeps Vite from freezing the unpatched bundle into
|
|
5013
|
+
// .vite/deps before our source transforms run.
|
|
5014
|
+
"@vitejs/plugin-rsc/browser",
|
|
5015
|
+
// Keep the browser RSDW client out of Vite's dep optimizer so our
|
|
5016
|
+
// cjs-to-esm transform can patch the real file.
|
|
5017
|
+
"@vitejs/plugin-rsc/vendor/react-server-dom/client.browser"
|
|
5018
|
+
];
|
|
4857
5019
|
const routerRef = { path: void 0 };
|
|
4858
5020
|
const prerenderEnabled = true;
|
|
4859
5021
|
if (preset === "cloudflare") {
|
|
@@ -4949,6 +5111,7 @@ async function rango(options) {
|
|
|
4949
5111
|
}
|
|
4950
5112
|
});
|
|
4951
5113
|
plugins.push(createVirtualEntriesPlugin(finalEntries));
|
|
5114
|
+
plugins.push(performanceTracksPlugin());
|
|
4952
5115
|
plugins.push(
|
|
4953
5116
|
rsc({
|
|
4954
5117
|
entries: finalEntries,
|
|
@@ -5067,6 +5230,7 @@ ${list}`);
|
|
|
5067
5230
|
}
|
|
5068
5231
|
});
|
|
5069
5232
|
plugins.push(createVirtualEntriesPlugin(finalEntries, routerRef));
|
|
5233
|
+
plugins.push(performanceTracksPlugin());
|
|
5070
5234
|
plugins.push(
|
|
5071
5235
|
rsc({
|
|
5072
5236
|
entries: finalEntries
|
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.56",
|
|
4
4
|
"description": "Django-inspired RSC router with composable URL patterns",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
"test:unit:watch": "vitest"
|
|
143
143
|
},
|
|
144
144
|
"dependencies": {
|
|
145
|
-
"@vitejs/plugin-rsc": "^0.5.
|
|
145
|
+
"@vitejs/plugin-rsc": "^0.5.19",
|
|
146
146
|
"magic-string": "^0.30.17",
|
|
147
147
|
"picomatch": "^4.0.3",
|
|
148
148
|
"rsc-html-stream": "^0.0.7"
|