@rangojs/router 0.0.0-experimental.60a361a0 → 0.0.0-experimental.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +15 -10
  2. package/dist/bin/rango.js +128 -46
  3. package/dist/vite/index.js +265 -195
  4. package/package.json +2 -2
  5. package/skills/links/SKILL.md +3 -1
  6. package/skills/middleware/SKILL.md +2 -0
  7. package/skills/router-setup/SKILL.md +35 -0
  8. package/src/browser/app-version.ts +14 -0
  9. package/src/browser/navigation-bridge.ts +16 -3
  10. package/src/browser/navigation-client.ts +64 -64
  11. package/src/browser/navigation-store.ts +43 -8
  12. package/src/browser/partial-update.ts +27 -5
  13. package/src/browser/prefetch/fetch.ts +8 -2
  14. package/src/browser/react/Link.tsx +44 -8
  15. package/src/browser/react/NavigationProvider.tsx +8 -1
  16. package/src/browser/react/context.ts +7 -2
  17. package/src/browser/react/use-router.ts +21 -8
  18. package/src/browser/rsc-router.tsx +26 -3
  19. package/src/browser/scroll-restoration.ts +10 -8
  20. package/src/browser/server-action-bridge.ts +8 -18
  21. package/src/browser/types.ts +20 -5
  22. package/src/build/generate-manifest.ts +3 -0
  23. package/src/build/generate-route-types.ts +3 -0
  24. package/src/build/route-types/include-resolution.ts +8 -1
  25. package/src/build/route-types/router-processing.ts +211 -72
  26. package/src/deps/browser.ts +0 -1
  27. package/src/reverse.ts +20 -1
  28. package/src/route-definition/redirect.ts +9 -1
  29. package/src/router/handler-context.ts +26 -10
  30. package/src/router/intercept-resolution.ts +9 -4
  31. package/src/router/loader-resolution.ts +3 -2
  32. package/src/router/match-middleware/cache-lookup.ts +4 -1
  33. package/src/router/middleware-types.ts +0 -6
  34. package/src/router/middleware.ts +0 -3
  35. package/src/router/prerender-match.ts +2 -2
  36. package/src/router/router-interfaces.ts +25 -4
  37. package/src/router/router-options.ts +37 -11
  38. package/src/router/segment-resolution/fresh.ts +11 -3
  39. package/src/router/segment-resolution/revalidation.ts +8 -3
  40. package/src/router.ts +40 -4
  41. package/src/rsc/handler.ts +12 -29
  42. package/src/rsc/loader-fetch.ts +2 -7
  43. package/src/rsc/manifest-init.ts +5 -1
  44. package/src/rsc/progressive-enhancement.ts +5 -4
  45. package/src/rsc/rsc-rendering.ts +6 -4
  46. package/src/rsc/server-action.ts +2 -2
  47. package/src/rsc/ssr-setup.ts +1 -1
  48. package/src/rsc/types.ts +5 -4
  49. package/src/server/context.ts +25 -1
  50. package/src/server/loader-registry.ts +9 -8
  51. package/src/server/request-context.ts +7 -10
  52. package/src/ssr/index.tsx +3 -0
  53. package/src/types/cache-types.ts +4 -4
  54. package/src/types/handler-context.ts +5 -9
  55. package/src/types/loader-types.ts +0 -1
  56. package/src/urls/pattern-types.ts +12 -0
  57. package/src/vite/discovery/discover-routers.ts +5 -1
  58. package/src/vite/plugins/performance-tracks.ts +64 -210
  59. package/src/vite/plugins/refresh-cmd.ts +88 -26
  60. package/src/vite/rango.ts +16 -4
  61. package/src/vite/utils/prerender-utils.ts +16 -0
  62. package/src/vite/utils/shared-utils.ts +3 -2
  63. 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, urls } from "@rangojs/router";
95
- import { Document } from "./document";
94
+ import { createRouter } from "@rangojs/router";
96
95
 
97
- const blogPatterns = urls(({ path }) => [
98
- path("/", BlogIndexPage, { name: "index" }),
99
- path("/:slug", BlogPostPage, { name: "post" }),
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({ document: Document }).routes(urlpatterns);
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 (variableName) {
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 extractUrlsVariableFromRouter(code) {
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) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "routes" && node.arguments.length >= 1 && ts5.isIdentifier(node.arguments[0])) {
691
- let inner = node.expression.expression;
692
- while (ts5.isCallExpression(inner) && ts5.isPropertyAccessExpression(inner.expression)) {
693
- inner = inner.expression.expression;
694
- }
695
- if (isCreateRouterCall(inner)) {
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 arg of callExpr.arguments) {
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 === "urls" && ts5.isIdentifier(prop.initializer)) {
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 urlsVarName = extractUrlsVariableFromRouter(routerSource);
726
- if (!urlsVarName) {
786
+ const extraction = extractUrlsFromRouter(routerSource);
787
+ if (!extraction) {
727
788
  return { routes: {}, searchSchemas: {} };
728
789
  }
729
- const imported = resolveImportedVariable(routerSource, urlsVarName);
730
- if (imported) {
731
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
732
- if (!targetFile) {
733
- return { routes: {}, searchSchemas: {} };
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
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
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 urlsVarName = extractUrlsVariableFromRouter(source);
748
- if (!urlsVarName) return [];
749
- const imported = resolveImportedVariable(source, urlsVarName);
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 = urlsVarName;
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
- let routerSource;
820
- try {
821
- routerSource = readFileSync3(routerFilePath, "utf-8");
822
- } catch {
823
- continue;
824
- }
825
- const urlsVarName = extractUrlsVariableFromRouter(routerSource);
826
- if (!urlsVarName) continue;
827
- let result;
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?)$/,