@tanstack/router-core 1.124.2 → 1.125.1

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 (45) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/Matches.d.cts +29 -0
  3. package/dist/cjs/index.cjs +1 -0
  4. package/dist/cjs/index.cjs.map +1 -1
  5. package/dist/cjs/index.d.cts +1 -1
  6. package/dist/cjs/route.cjs +0 -5
  7. package/dist/cjs/route.cjs.map +1 -1
  8. package/dist/cjs/route.d.cts +20 -6
  9. package/dist/cjs/router.cjs +208 -79
  10. package/dist/cjs/router.cjs.map +1 -1
  11. package/dist/cjs/router.d.cts +6 -1
  12. package/dist/cjs/ssr/ssr-client.cjs +38 -2
  13. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  14. package/dist/cjs/ssr/ssr-client.d.cts +1 -0
  15. package/dist/cjs/ssr/ssr-server.cjs +2 -1
  16. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  17. package/dist/cjs/utils.cjs +5 -0
  18. package/dist/cjs/utils.cjs.map +1 -1
  19. package/dist/cjs/utils.d.cts +1 -0
  20. package/dist/esm/Matches.d.ts +29 -0
  21. package/dist/esm/Matches.js.map +1 -1
  22. package/dist/esm/index.d.ts +1 -1
  23. package/dist/esm/index.js +2 -1
  24. package/dist/esm/route.d.ts +20 -6
  25. package/dist/esm/route.js +0 -5
  26. package/dist/esm/route.js.map +1 -1
  27. package/dist/esm/router.d.ts +6 -1
  28. package/dist/esm/router.js +208 -79
  29. package/dist/esm/router.js.map +1 -1
  30. package/dist/esm/ssr/ssr-client.d.ts +1 -0
  31. package/dist/esm/ssr/ssr-client.js +38 -2
  32. package/dist/esm/ssr/ssr-client.js.map +1 -1
  33. package/dist/esm/ssr/ssr-server.js +2 -1
  34. package/dist/esm/ssr/ssr-server.js.map +1 -1
  35. package/dist/esm/utils.d.ts +1 -0
  36. package/dist/esm/utils.js +5 -0
  37. package/dist/esm/utils.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/Matches.ts +38 -0
  40. package/src/index.ts +1 -0
  41. package/src/route.ts +32 -10
  42. package/src/router.ts +259 -96
  43. package/src/ssr/ssr-client.ts +49 -3
  44. package/src/ssr/ssr-server.ts +1 -0
  45. package/src/utils.ts +12 -0
@@ -112,8 +112,7 @@ class RouterCore {
112
112
  routeTree: this.routeTree,
113
113
  initRoute: (route, i) => {
114
114
  route.init({
115
- originalIndex: i,
116
- defaultSsr: this.options.defaultSsr
115
+ originalIndex: i
117
116
  });
118
117
  }
119
118
  });
@@ -123,8 +122,7 @@ class RouterCore {
123
122
  const notFoundRoute = this.options.notFoundRoute;
124
123
  if (notFoundRoute) {
125
124
  notFoundRoute.init({
126
- originalIndex: 99999999999,
127
- defaultSsr: this.options.defaultSsr
125
+ originalIndex: 99999999999
128
126
  });
129
127
  this.routesById[notFoundRoute.id] = notFoundRoute;
130
128
  }
@@ -215,7 +213,13 @@ class RouterCore {
215
213
  const match = this.getMatch(id);
216
214
  if (!match) return;
217
215
  match.abortController.abort();
218
- clearTimeout(match.pendingTimeout);
216
+ this.updateMatch(id, (prev) => {
217
+ clearTimeout(prev.pendingTimeout);
218
+ return {
219
+ ...prev,
220
+ pendingTimeout: void 0
221
+ };
222
+ });
219
223
  };
220
224
  this.cancelMatches = () => {
221
225
  var _a;
@@ -498,7 +502,10 @@ class RouterCore {
498
502
  throw redirect.redirect({ href: nextLocation.href });
499
503
  }
500
504
  }
501
- const pendingMatches = this.matchRoutes(this.latestLocation);
505
+ let pendingMatches = this.matchRoutes(this.latestLocation);
506
+ if (this.isShell) {
507
+ pendingMatches = pendingMatches.slice(0, 1);
508
+ }
502
509
  this.__store.setState((s) => ({
503
510
  ...s,
504
511
  status: "pending",
@@ -693,12 +700,41 @@ class RouterCore {
693
700
  const triggerOnReady = async () => {
694
701
  if (!rendered) {
695
702
  rendered = true;
703
+ if (!allPreload && !this.isServer) {
704
+ matches.forEach((match) => {
705
+ const {
706
+ id: matchId,
707
+ routeId,
708
+ _forcePending,
709
+ minPendingPromise
710
+ } = match;
711
+ const route = this.looseRoutesById[routeId];
712
+ const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
713
+ if (_forcePending && pendingMinMs && !minPendingPromise) {
714
+ const minPendingPromise2 = utils.createControlledPromise();
715
+ updateMatch(matchId, (prev) => ({
716
+ ...prev,
717
+ minPendingPromise: minPendingPromise2
718
+ }));
719
+ setTimeout(() => {
720
+ minPendingPromise2.resolve();
721
+ updateMatch(matchId, (prev) => ({
722
+ ...prev,
723
+ minPendingPromise: void 0
724
+ }));
725
+ }, pendingMinMs);
726
+ }
727
+ });
728
+ }
696
729
  await (onReady == null ? void 0 : onReady());
697
730
  }
698
731
  };
699
732
  const resolvePreload = (matchId) => {
700
733
  return !!(allPreload && !this.state.matches.find((d) => d.id === matchId));
701
734
  };
735
+ if (!this.isServer && this.state.matches.find((d) => d._forcePending)) {
736
+ triggerOnReady();
737
+ }
702
738
  const handleRedirectAndNotFound = (match, err) => {
703
739
  var _a, _b, _c, _d;
704
740
  if (redirect.isRedirect(err) || notFound.isNotFound(err)) {
@@ -741,6 +777,18 @@ class RouterCore {
741
777
  }
742
778
  }
743
779
  };
780
+ const shouldSkipLoader = (matchId) => {
781
+ const match = this.getMatch(matchId);
782
+ if (!this.isServer && match._dehydrated) {
783
+ return true;
784
+ }
785
+ if (this.isServer) {
786
+ if (match.ssr === false) {
787
+ return true;
788
+ }
789
+ }
790
+ return false;
791
+ };
744
792
  try {
745
793
  await new Promise((resolveAll, rejectAll) => {
746
794
  ;
@@ -781,23 +829,82 @@ class RouterCore {
781
829
  for (const [index, { id: matchId, routeId }] of matches.entries()) {
782
830
  const existingMatch = this.getMatch(matchId);
783
831
  const parentMatchId = (_a = matches[index - 1]) == null ? void 0 : _a.id;
832
+ const parentMatch = parentMatchId ? this.getMatch(parentMatchId) : void 0;
784
833
  const route = this.looseRoutesById[routeId];
785
834
  const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
835
+ if (this.isServer) {
836
+ const defaultSsr = this.options.defaultSsr ?? true;
837
+ let ssr;
838
+ if ((parentMatch == null ? void 0 : parentMatch.ssr) === false) {
839
+ ssr = false;
840
+ } else {
841
+ let tempSsr;
842
+ if (route.options.ssr === void 0) {
843
+ tempSsr = defaultSsr;
844
+ } else if (typeof route.options.ssr === "function") {
845
+ let makeMaybe = function(value, error) {
846
+ if (error) {
847
+ return { status: "error", error };
848
+ }
849
+ return { status: "success", value };
850
+ };
851
+ const { search, params } = this.getMatch(matchId);
852
+ const ssrFnContext = {
853
+ search: makeMaybe(search, existingMatch.searchError),
854
+ params: makeMaybe(params, existingMatch.paramsError),
855
+ location,
856
+ matches: matches.map((match) => ({
857
+ index: match.index,
858
+ pathname: match.pathname,
859
+ fullPath: match.fullPath,
860
+ staticData: match.staticData,
861
+ id: match.id,
862
+ routeId: match.routeId,
863
+ search: makeMaybe(match.search, match.searchError),
864
+ params: makeMaybe(match.params, match.paramsError),
865
+ ssr: match.ssr
866
+ }))
867
+ };
868
+ tempSsr = await route.options.ssr(ssrFnContext) ?? defaultSsr;
869
+ } else {
870
+ tempSsr = route.options.ssr;
871
+ }
872
+ if (tempSsr === true && (parentMatch == null ? void 0 : parentMatch.ssr) === "data-only") {
873
+ ssr = "data-only";
874
+ } else {
875
+ ssr = tempSsr;
876
+ }
877
+ }
878
+ updateMatch(matchId, (prev) => ({
879
+ ...prev,
880
+ ssr
881
+ }));
882
+ }
883
+ if (shouldSkipLoader(matchId)) {
884
+ continue;
885
+ }
786
886
  const shouldPending = !!(onReady && !this.isServer && !resolvePreload(matchId) && (route.options.loader || route.options.beforeLoad || routeNeedsPreload(route)) && typeof pendingMs === "number" && pendingMs !== Infinity && (route.options.pendingComponent ?? ((_b = this.options) == null ? void 0 : _b.defaultPendingComponent)));
787
887
  let executeBeforeLoad = true;
788
- if (
789
- // If we are in the middle of a load, either of these will be present
790
- // (not to be confused with `loadPromise`, which is always defined)
791
- existingMatch.beforeLoadPromise || existingMatch.loaderPromise
792
- ) {
793
- if (shouldPending) {
794
- setTimeout(() => {
888
+ const setupPendingTimeout = () => {
889
+ if (shouldPending && this.getMatch(matchId).pendingTimeout === void 0) {
890
+ const pendingTimeout = setTimeout(() => {
795
891
  try {
796
892
  triggerOnReady();
797
893
  } catch {
798
894
  }
799
895
  }, pendingMs);
896
+ updateMatch(matchId, (prev) => ({
897
+ ...prev,
898
+ pendingTimeout
899
+ }));
800
900
  }
901
+ };
902
+ if (
903
+ // If we are in the middle of a load, either of these will be present
904
+ // (not to be confused with `loadPromise`, which is always defined)
905
+ existingMatch.beforeLoadPromise || existingMatch.loaderPromise
906
+ ) {
907
+ setupPendingTimeout();
801
908
  await existingMatch.beforeLoadPromise;
802
909
  const match = this.getMatch(matchId);
803
910
  if (match.status === "error") {
@@ -818,16 +925,6 @@ class RouterCore {
818
925
  beforeLoadPromise: utils.createControlledPromise()
819
926
  };
820
927
  });
821
- const abortController = new AbortController();
822
- let pendingTimeout;
823
- if (shouldPending) {
824
- pendingTimeout = setTimeout(() => {
825
- try {
826
- triggerOnReady();
827
- } catch {
828
- }
829
- }, pendingMs);
830
- }
831
928
  const { paramsError, searchError } = this.getMatch(matchId);
832
929
  if (paramsError) {
833
930
  handleSerialError(index, paramsError, "PARSE_PARAMS");
@@ -835,15 +932,16 @@ class RouterCore {
835
932
  if (searchError) {
836
933
  handleSerialError(index, searchError, "VALIDATE_SEARCH");
837
934
  }
838
- const getParentMatchContext = () => parentMatchId ? this.getMatch(parentMatchId).context : this.options.context ?? {};
935
+ setupPendingTimeout();
936
+ const abortController = new AbortController();
937
+ const parentMatchContext = (parentMatch == null ? void 0 : parentMatch.context) ?? this.options.context ?? {};
839
938
  updateMatch(matchId, (prev) => ({
840
939
  ...prev,
841
940
  isFetching: "beforeLoad",
842
941
  fetchCount: prev.fetchCount + 1,
843
942
  abortController,
844
- pendingTimeout,
845
943
  context: {
846
- ...getParentMatchContext(),
944
+ ...parentMatchContext,
847
945
  ...prev.__routeContext
848
946
  }
849
947
  }));
@@ -870,7 +968,7 @@ class RouterCore {
870
968
  ...prev,
871
969
  __beforeLoadContext: beforeLoadContext,
872
970
  context: {
873
- ...getParentMatchContext(),
971
+ ...parentMatchContext,
874
972
  ...prev.__routeContext,
875
973
  ...beforeLoadContext
876
974
  },
@@ -896,10 +994,61 @@ class RouterCore {
896
994
  validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
897
995
  matchPromises.push(
898
996
  (async () => {
997
+ var _a2, _b2;
899
998
  let loaderShouldRunAsync = false;
900
999
  let loaderIsRunningAsync = false;
1000
+ const route = this.looseRoutesById[routeId];
1001
+ const executeHead = async () => {
1002
+ var _a3, _b3, _c2, _d2, _e, _f;
1003
+ const match = this.getMatch(matchId);
1004
+ if (!match) {
1005
+ return;
1006
+ }
1007
+ const assetContext = {
1008
+ matches,
1009
+ match,
1010
+ params: match.params,
1011
+ loaderData: match.loaderData
1012
+ };
1013
+ const headFnContent = await ((_b3 = (_a3 = route.options).head) == null ? void 0 : _b3.call(_a3, assetContext));
1014
+ const meta = headFnContent == null ? void 0 : headFnContent.meta;
1015
+ const links = headFnContent == null ? void 0 : headFnContent.links;
1016
+ const headScripts = headFnContent == null ? void 0 : headFnContent.scripts;
1017
+ const styles = headFnContent == null ? void 0 : headFnContent.styles;
1018
+ const scripts = await ((_d2 = (_c2 = route.options).scripts) == null ? void 0 : _d2.call(_c2, assetContext));
1019
+ const headers = await ((_f = (_e = route.options).headers) == null ? void 0 : _f.call(_e, assetContext));
1020
+ return {
1021
+ meta,
1022
+ links,
1023
+ headScripts,
1024
+ headers,
1025
+ scripts,
1026
+ styles
1027
+ };
1028
+ };
1029
+ const potentialPendingMinPromise = async () => {
1030
+ const latestMatch = this.getMatch(matchId);
1031
+ if (latestMatch.minPendingPromise) {
1032
+ await latestMatch.minPendingPromise;
1033
+ }
1034
+ };
901
1035
  const prevMatch = this.getMatch(matchId);
902
- if (prevMatch.loaderPromise) {
1036
+ if (shouldSkipLoader(matchId)) {
1037
+ if (this.isServer) {
1038
+ const head = await executeHead();
1039
+ updateMatch(matchId, (prev) => ({
1040
+ ...prev,
1041
+ ...head
1042
+ }));
1043
+ (_a2 = this.serverSsr) == null ? void 0 : _a2.onMatchSettled({
1044
+ router: this,
1045
+ match: this.getMatch(matchId)
1046
+ });
1047
+ return this.getMatch(matchId);
1048
+ } else {
1049
+ await potentialPendingMinPromise();
1050
+ }
1051
+ } else if (prevMatch.loaderPromise) {
903
1052
  if (prevMatch.status === "success" && !sync && !prevMatch.preload) {
904
1053
  return this.getMatch(matchId);
905
1054
  }
@@ -910,7 +1059,6 @@ class RouterCore {
910
1059
  }
911
1060
  } else {
912
1061
  const parentMatchPromise = matchPromises[index - 1];
913
- const route = this.looseRoutesById[routeId];
914
1062
  const getLoaderContext = () => {
915
1063
  const {
916
1064
  params,
@@ -943,55 +1091,28 @@ class RouterCore {
943
1091
  loaderPromise: utils.createControlledPromise(),
944
1092
  preload: !!preload && !this.state.matches.find((d) => d.id === matchId)
945
1093
  }));
946
- const executeHead = async () => {
947
- var _a2, _b2, _c2, _d2, _e, _f;
948
- const match = this.getMatch(matchId);
949
- if (!match) {
950
- return;
951
- }
952
- const assetContext = {
953
- matches,
954
- match,
955
- params: match.params,
956
- loaderData: match.loaderData
957
- };
958
- const headFnContent = await ((_b2 = (_a2 = route.options).head) == null ? void 0 : _b2.call(_a2, assetContext));
959
- const meta = headFnContent == null ? void 0 : headFnContent.meta;
960
- const links = headFnContent == null ? void 0 : headFnContent.links;
961
- const headScripts = headFnContent == null ? void 0 : headFnContent.scripts;
962
- const styles = headFnContent == null ? void 0 : headFnContent.styles;
963
- const scripts = await ((_d2 = (_c2 = route.options).scripts) == null ? void 0 : _d2.call(_c2, assetContext));
964
- const headers = await ((_f = (_e = route.options).headers) == null ? void 0 : _f.call(_e, assetContext));
965
- return {
966
- meta,
967
- links,
968
- headScripts,
969
- headers,
970
- scripts,
971
- styles
972
- };
973
- };
974
1094
  const runLoader = async () => {
975
- var _a2, _b2, _c2, _d2, _e;
1095
+ var _a3, _b3, _c2, _d2, _e;
976
1096
  try {
977
- const potentialPendingMinPromise = async () => {
978
- const latestMatch = this.getMatch(matchId);
979
- if (latestMatch.minPendingPromise) {
980
- await latestMatch.minPendingPromise;
981
- }
982
- };
983
1097
  try {
984
- this.loadRouteChunk(route);
1098
+ if (!this.isServer || this.isServer && this.getMatch(matchId).ssr === true) {
1099
+ this.loadRouteChunk(route);
1100
+ }
985
1101
  updateMatch(matchId, (prev) => ({
986
1102
  ...prev,
987
1103
  isFetching: "loader"
988
1104
  }));
989
- const loaderData = await ((_b2 = (_a2 = route.options).loader) == null ? void 0 : _b2.call(_a2, getLoaderContext()));
1105
+ const loaderData = await ((_b3 = (_a3 = route.options).loader) == null ? void 0 : _b3.call(_a3, getLoaderContext()));
990
1106
  handleRedirectAndNotFound(
991
1107
  this.getMatch(matchId),
992
1108
  loaderData
993
1109
  );
1110
+ updateMatch(matchId, (prev) => ({
1111
+ ...prev,
1112
+ loaderData
1113
+ }));
994
1114
  await route._lazyPromise;
1115
+ const head = await executeHead();
995
1116
  await potentialPendingMinPromise();
996
1117
  await route._componentsPromise;
997
1118
  updateMatch(matchId, (prev) => ({
@@ -1000,11 +1121,6 @@ class RouterCore {
1000
1121
  status: "success",
1001
1122
  isFetching: false,
1002
1123
  updatedAt: Date.now(),
1003
- loaderData
1004
- }));
1005
- const head = await executeHead();
1006
- updateMatch(matchId, (prev) => ({
1007
- ...prev,
1008
1124
  ...head
1009
1125
  }));
1010
1126
  } catch (e) {
@@ -1043,10 +1159,10 @@ class RouterCore {
1043
1159
  handleRedirectAndNotFound(this.getMatch(matchId), err);
1044
1160
  }
1045
1161
  };
1046
- const { status, invalid } = this.getMatch(matchId);
1162
+ const { status, invalid, _forcePending } = this.getMatch(matchId);
1047
1163
  loaderShouldRunAsync = status === "success" && (invalid || (shouldReload ?? age > staleAge));
1048
1164
  if (preload && route.options.preload === false) {
1049
- } else if (loaderShouldRunAsync && !sync) {
1165
+ } else if (loaderShouldRunAsync && !sync && !_forcePending) {
1050
1166
  loaderIsRunningAsync = true;
1051
1167
  (async () => {
1052
1168
  try {
@@ -1067,11 +1183,18 @@ class RouterCore {
1067
1183
  } else if (status !== "success" || loaderShouldRunAsync && sync) {
1068
1184
  await runLoader();
1069
1185
  } else {
1186
+ if (_forcePending) {
1187
+ await potentialPendingMinPromise();
1188
+ }
1070
1189
  const head = await executeHead();
1071
1190
  updateMatch(matchId, (prev) => ({
1072
1191
  ...prev,
1073
1192
  ...head
1074
1193
  }));
1194
+ (_b2 = this.serverSsr) == null ? void 0 : _b2.onMatchSettled({
1195
+ router: this,
1196
+ match: this.getMatch(matchId)
1197
+ });
1075
1198
  }
1076
1199
  }
1077
1200
  if (!loaderIsRunningAsync) {
@@ -1079,12 +1202,18 @@ class RouterCore {
1079
1202
  loaderPromise == null ? void 0 : loaderPromise.resolve();
1080
1203
  loadPromise == null ? void 0 : loadPromise.resolve();
1081
1204
  }
1082
- updateMatch(matchId, (prev) => ({
1083
- ...prev,
1084
- isFetching: loaderIsRunningAsync ? prev.isFetching : false,
1085
- loaderPromise: loaderIsRunningAsync ? prev.loaderPromise : void 0,
1086
- invalid: false
1087
- }));
1205
+ updateMatch(matchId, (prev) => {
1206
+ clearTimeout(prev.pendingTimeout);
1207
+ return {
1208
+ ...prev,
1209
+ isFetching: loaderIsRunningAsync ? prev.isFetching : false,
1210
+ loaderPromise: loaderIsRunningAsync ? prev.loaderPromise : void 0,
1211
+ invalid: false,
1212
+ pendingTimeout: void 0,
1213
+ _dehydrated: void 0,
1214
+ _forcePending: void 0
1215
+ };
1216
+ });
1088
1217
  return this.getMatch(matchId);
1089
1218
  })()
1090
1219
  );