tanstack-router-cache 0.1.3 → 0.1.5
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/index.cjs +148 -87
- package/dist/index.js +148 -87
- package/docs/architecture.md +10 -10
- package/docs/releases.md +12 -4
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -787,12 +787,50 @@ const LIVE_ROUTER_METHODS = [
|
|
|
787
787
|
"navigate",
|
|
788
788
|
"preloadRoute"
|
|
789
789
|
];
|
|
790
|
-
|
|
790
|
+
const routerSnapshotUpdaters = /* @__PURE__ */ new WeakMap();
|
|
791
|
+
function createSnapshotStore(value) {
|
|
792
|
+
let currentValue = value;
|
|
793
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
791
794
|
return {
|
|
792
|
-
get: () =>
|
|
793
|
-
|
|
795
|
+
get: () => currentValue,
|
|
796
|
+
set: (nextValue) => {
|
|
797
|
+
if (Object.is(currentValue, nextValue)) return;
|
|
798
|
+
currentValue = nextValue;
|
|
799
|
+
for (const listener of listeners) listener(currentValue);
|
|
800
|
+
},
|
|
801
|
+
subscribe: (listener) => {
|
|
802
|
+
if (listener) listeners.add(listener);
|
|
803
|
+
return { unsubscribe: listener ? () => {
|
|
804
|
+
listeners.delete(listener);
|
|
805
|
+
} : () => void 0 };
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function createRouterSnapshotData({ matches, router, routerLocation, routerResolvedLocation }) {
|
|
810
|
+
const snapshotMatches = matches.reduce((snapshot, match) => {
|
|
811
|
+
const nextMatch = snapshotMatch(match, routerLocation);
|
|
812
|
+
if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
|
|
813
|
+
return snapshot;
|
|
814
|
+
}, []);
|
|
815
|
+
return {
|
|
816
|
+
matches: snapshotMatches,
|
|
817
|
+
state: {
|
|
818
|
+
...router.stores.__store.get(),
|
|
819
|
+
matches: snapshotMatches,
|
|
820
|
+
location: routerLocation,
|
|
821
|
+
resolvedLocation: routerResolvedLocation
|
|
822
|
+
}
|
|
794
823
|
};
|
|
795
824
|
}
|
|
825
|
+
function createMatchStore(match) {
|
|
826
|
+
return Object.assign(createSnapshotStore(match), { routeId: match.routeId });
|
|
827
|
+
}
|
|
828
|
+
function syncRouterSnapshot(routerSnapshot, input) {
|
|
829
|
+
const update = routerSnapshotUpdaters.get(routerSnapshot);
|
|
830
|
+
if (!update) return false;
|
|
831
|
+
update(input);
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
796
834
|
function getLiveRouterMethodDescriptors(router) {
|
|
797
835
|
return Object.fromEntries(LIVE_ROUTER_METHODS.flatMap((methodName) => {
|
|
798
836
|
const method = router[methodName];
|
|
@@ -828,29 +866,40 @@ function hasCurrentRouteError({ erroredRouteCounts, matches, resolvedPathname, r
|
|
|
828
866
|
function isRouterMatch(match) {
|
|
829
867
|
return Boolean(match?.id && match.routeId);
|
|
830
868
|
}
|
|
831
|
-
function snapshotMatch(match) {
|
|
869
|
+
function snapshotMatch(match, routerLocation) {
|
|
832
870
|
if (!isRouterMatch(match)) return;
|
|
871
|
+
const isLocationMatch = match.pathname ? normalizeCachedRoutePathname(match.pathname) === normalizeCachedRoutePathname(routerLocation.pathname) : false;
|
|
833
872
|
return {
|
|
834
873
|
...match,
|
|
874
|
+
...isLocationMatch ? {
|
|
875
|
+
_strictSearch: routerLocation.search,
|
|
876
|
+
search: routerLocation.search
|
|
877
|
+
} : {},
|
|
835
878
|
_nonReactive: { ...match._nonReactive }
|
|
836
879
|
};
|
|
837
880
|
}
|
|
838
|
-
function
|
|
839
|
-
return
|
|
881
|
+
function isCurrentUnmanagedCachedRoute(route, routerHref) {
|
|
882
|
+
return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.routerSnapshot && route.href === routerHref);
|
|
840
883
|
}
|
|
841
884
|
function syncReadyCachedRoute({ matchId, matches, routeId, route, routerLocation, router, routerResolvedLocation, routerHref, routerPathname, setCachedRoutes, staticData }) {
|
|
842
|
-
|
|
885
|
+
const routerSnapshotInput = {
|
|
886
|
+
matches,
|
|
887
|
+
router,
|
|
888
|
+
routerLocation,
|
|
889
|
+
routerResolvedLocation
|
|
890
|
+
};
|
|
891
|
+
let routerSnapshot = matchId ? route?.routerSnapshot : void 0;
|
|
892
|
+
if (routerSnapshot && !syncRouterSnapshot(routerSnapshot, routerSnapshotInput)) {
|
|
893
|
+
if (isCurrentUnmanagedCachedRoute(route, routerHref)) return;
|
|
894
|
+
routerSnapshot = void 0;
|
|
895
|
+
}
|
|
896
|
+
if (matchId && !routerSnapshot) routerSnapshot = createRouterSnapshot(routerSnapshotInput);
|
|
843
897
|
setCachedRoutes(routerPathname, {
|
|
844
898
|
href: routerHref,
|
|
845
899
|
matchId,
|
|
846
900
|
routeId,
|
|
847
901
|
ready: true,
|
|
848
|
-
routerSnapshot
|
|
849
|
-
matches,
|
|
850
|
-
router,
|
|
851
|
-
routerLocation,
|
|
852
|
-
routerResolvedLocation
|
|
853
|
-
}) : void 0,
|
|
902
|
+
routerSnapshot,
|
|
854
903
|
staticData
|
|
855
904
|
});
|
|
856
905
|
}
|
|
@@ -879,54 +928,43 @@ function syncCachedRouteState({ isCurrentMatchReady, isCurrentMatchResolved, del
|
|
|
879
928
|
}
|
|
880
929
|
if (route) deleteCachedRoutes([routerPathname]);
|
|
881
930
|
}
|
|
882
|
-
function createRouterSnapshot(
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
}, []);
|
|
888
|
-
const matchStores = new Map(snapshotMatches.map((match) => {
|
|
889
|
-
const store = Object.assign(createStaticStore(match), { routeId: match.routeId });
|
|
890
|
-
return [match.id, store];
|
|
891
|
-
}));
|
|
892
|
-
const snapshotState = {
|
|
893
|
-
...router.stores.__store.get(),
|
|
894
|
-
matches: snapshotMatches,
|
|
895
|
-
location: routerLocation,
|
|
896
|
-
resolvedLocation: routerResolvedLocation
|
|
897
|
-
};
|
|
931
|
+
function createRouterSnapshot(input) {
|
|
932
|
+
const { router, routerLocation, routerResolvedLocation } = input;
|
|
933
|
+
const snapshotData = createRouterSnapshotData(input);
|
|
934
|
+
const snapshotMatches = snapshotData.matches;
|
|
935
|
+
const matchStores = new Map(snapshotMatches.map((match) => [match.id, createMatchStore(match)]));
|
|
898
936
|
const routeMatchStoreCache = /* @__PURE__ */ new Map();
|
|
899
937
|
const stores = {
|
|
900
938
|
...router.stores,
|
|
901
|
-
status:
|
|
902
|
-
loadedAt:
|
|
903
|
-
isLoading:
|
|
904
|
-
isTransitioning:
|
|
905
|
-
location:
|
|
906
|
-
resolvedLocation:
|
|
907
|
-
statusCode:
|
|
908
|
-
redirect:
|
|
909
|
-
matchesId:
|
|
910
|
-
pendingIds:
|
|
911
|
-
cachedIds:
|
|
912
|
-
matches:
|
|
913
|
-
pendingMatches:
|
|
914
|
-
cachedMatches:
|
|
915
|
-
firstId:
|
|
916
|
-
hasPending:
|
|
917
|
-
matchRouteDeps:
|
|
939
|
+
status: createSnapshotStore(router.stores.status.get()),
|
|
940
|
+
loadedAt: createSnapshotStore(router.stores.loadedAt.get()),
|
|
941
|
+
isLoading: createSnapshotStore(router.stores.isLoading.get()),
|
|
942
|
+
isTransitioning: createSnapshotStore(router.stores.isTransitioning.get()),
|
|
943
|
+
location: createSnapshotStore(routerLocation),
|
|
944
|
+
resolvedLocation: createSnapshotStore(routerResolvedLocation),
|
|
945
|
+
statusCode: createSnapshotStore(router.stores.statusCode.get()),
|
|
946
|
+
redirect: createSnapshotStore(router.stores.redirect.get()),
|
|
947
|
+
matchesId: createSnapshotStore(snapshotMatches.map((match) => match.id)),
|
|
948
|
+
pendingIds: createSnapshotStore([]),
|
|
949
|
+
cachedIds: createSnapshotStore([]),
|
|
950
|
+
matches: createSnapshotStore(snapshotMatches),
|
|
951
|
+
pendingMatches: createSnapshotStore([]),
|
|
952
|
+
cachedMatches: createSnapshotStore([]),
|
|
953
|
+
firstId: createSnapshotStore(snapshotMatches[0]?.id),
|
|
954
|
+
hasPending: createSnapshotStore(snapshotMatches.some((match) => match.status === "pending")),
|
|
955
|
+
matchRouteDeps: createSnapshotStore({
|
|
918
956
|
locationHref: routerLocation.href,
|
|
919
957
|
resolvedLocationHref: routerResolvedLocation?.href,
|
|
920
|
-
status:
|
|
958
|
+
status: snapshotData.state.status
|
|
921
959
|
}),
|
|
922
|
-
__store:
|
|
960
|
+
__store: createSnapshotStore(snapshotData.state),
|
|
923
961
|
matchStores,
|
|
924
962
|
pendingMatchStores: /* @__PURE__ */ new Map(),
|
|
925
963
|
cachedMatchStores: /* @__PURE__ */ new Map(),
|
|
926
964
|
getRouteMatchStore: (routeId) => {
|
|
927
965
|
let cached = routeMatchStoreCache.get(routeId);
|
|
928
966
|
if (!cached) {
|
|
929
|
-
cached =
|
|
967
|
+
cached = createSnapshotStore(snapshotMatches.find((match) => match.routeId === routeId));
|
|
930
968
|
routeMatchStoreCache.set(routeId, cached);
|
|
931
969
|
}
|
|
932
970
|
return cached;
|
|
@@ -935,14 +973,53 @@ function createRouterSnapshot({ matches, router, routerLocation, routerResolvedL
|
|
|
935
973
|
setPending: () => void 0,
|
|
936
974
|
setCached: () => void 0
|
|
937
975
|
};
|
|
976
|
+
const updateSnapshot = (nextInput) => {
|
|
977
|
+
const nextData = createRouterSnapshotData(nextInput);
|
|
978
|
+
const nextMatches = nextData.matches;
|
|
979
|
+
const nextMatchIds = new Set(nextMatches.map((match) => match.id));
|
|
980
|
+
const nextMatchesByRouteId = new Map(nextMatches.map((match) => [match.routeId, match]));
|
|
981
|
+
for (const match of nextMatches) {
|
|
982
|
+
const store = matchStores.get(match.id);
|
|
983
|
+
if (store) {
|
|
984
|
+
store.set(match);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
matchStores.set(match.id, createMatchStore(match));
|
|
988
|
+
}
|
|
989
|
+
for (const [matchId] of matchStores) if (!nextMatchIds.has(matchId)) matchStores.delete(matchId);
|
|
990
|
+
stores.status.set(nextInput.router.stores.status.get());
|
|
991
|
+
stores.loadedAt.set(nextInput.router.stores.loadedAt.get());
|
|
992
|
+
stores.isLoading.set(nextInput.router.stores.isLoading.get());
|
|
993
|
+
stores.isTransitioning.set(nextInput.router.stores.isTransitioning.get());
|
|
994
|
+
stores.location.set(nextInput.routerLocation);
|
|
995
|
+
stores.resolvedLocation.set(nextInput.routerResolvedLocation);
|
|
996
|
+
stores.statusCode.set(nextInput.router.stores.statusCode.get());
|
|
997
|
+
stores.redirect.set(nextInput.router.stores.redirect.get());
|
|
998
|
+
stores.matchesId.set(nextMatches.map((match) => match.id));
|
|
999
|
+
stores.pendingIds.set([]);
|
|
1000
|
+
stores.cachedIds.set([]);
|
|
1001
|
+
stores.matches.set(nextMatches);
|
|
1002
|
+
stores.pendingMatches.set([]);
|
|
1003
|
+
stores.cachedMatches.set([]);
|
|
1004
|
+
stores.firstId.set(nextMatches[0]?.id);
|
|
1005
|
+
stores.hasPending.set(nextMatches.some((match) => match.status === "pending"));
|
|
1006
|
+
stores.matchRouteDeps.set({
|
|
1007
|
+
locationHref: nextInput.routerLocation.href,
|
|
1008
|
+
resolvedLocationHref: nextInput.routerResolvedLocation?.href,
|
|
1009
|
+
status: nextData.state.status
|
|
1010
|
+
});
|
|
1011
|
+
stores.__store.set(nextData.state);
|
|
1012
|
+
for (const [routeId, store] of routeMatchStoreCache) store.set(nextMatchesByRouteId.get(routeId));
|
|
1013
|
+
};
|
|
938
1014
|
const routerSnapshot = Object.create(router);
|
|
939
1015
|
Object.defineProperties(routerSnapshot, {
|
|
940
1016
|
stores: { value: stores },
|
|
941
|
-
latestLocation: {
|
|
1017
|
+
latestLocation: { get: () => stores.location.get() },
|
|
942
1018
|
getMatch: { value: (matchId) => matchStores.get(matchId)?.get() },
|
|
943
1019
|
updateMatch: { value: () => void 0 },
|
|
944
1020
|
...getLiveRouterMethodDescriptors(router)
|
|
945
1021
|
});
|
|
1022
|
+
routerSnapshotUpdaters.set(routerSnapshot, updateSnapshot);
|
|
946
1023
|
return routerSnapshot;
|
|
947
1024
|
}
|
|
948
1025
|
function getRouterCacheStaticData(childMatches, isCurrentMatchResolved) {
|
|
@@ -959,17 +1036,7 @@ function restoreCachedHref(router, href) {
|
|
|
959
1036
|
resetScroll: false
|
|
960
1037
|
}).catch(() => void 0);
|
|
961
1038
|
}
|
|
962
|
-
function
|
|
963
|
-
if (ancestorPathname === pathname) return false;
|
|
964
|
-
if (ancestorPathname === "/") return pathname.startsWith("/");
|
|
965
|
-
return pathname.startsWith(`${ancestorPathname}/`);
|
|
966
|
-
}
|
|
967
|
-
function getShouldRenderLiveOutlet({ cachedRoutes, bypassCachedPathname, visiblePathname }) {
|
|
968
|
-
if (visiblePathname === bypassCachedPathname) return true;
|
|
969
|
-
const visibleRoute = cachedRoutes[visiblePathname];
|
|
970
|
-
return !isReadyCachedRoute(visibleRoute);
|
|
971
|
-
}
|
|
972
|
-
function renderCachedRoute({ bypassCachedPathname, pathname, route, visiblePathname }) {
|
|
1039
|
+
function renderCachedRoute({ bypassCachedPathname, pathname, route, routerPathname }) {
|
|
973
1040
|
if (pathname === bypassCachedPathname) return null;
|
|
974
1041
|
const content = route.matchId && route.routerSnapshot ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CachedOutlet, {
|
|
975
1042
|
matchId: route.matchId,
|
|
@@ -977,18 +1044,18 @@ function renderCachedRoute({ bypassCachedPathname, pathname, route, visiblePathn
|
|
|
977
1044
|
}) : null;
|
|
978
1045
|
if (!content) return null;
|
|
979
1046
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OffScreen, {
|
|
980
|
-
mode:
|
|
1047
|
+
mode: routerPathname === pathname ? "visible" : "hidden",
|
|
981
1048
|
pathname,
|
|
982
1049
|
children: content
|
|
983
1050
|
}, pathname);
|
|
984
1051
|
}
|
|
985
|
-
function buildRouteCacheModes(cachedRoutes,
|
|
1052
|
+
function buildRouteCacheModes(cachedRoutes, routerPathname) {
|
|
986
1053
|
const nextModes = /* @__PURE__ */ new Map();
|
|
987
|
-
for (const pathname of Object.keys(cachedRoutes)) nextModes.set(pathname,
|
|
1054
|
+
for (const pathname of Object.keys(cachedRoutes)) nextModes.set(pathname, routerPathname === pathname ? "visible" : "hidden");
|
|
988
1055
|
return nextModes;
|
|
989
1056
|
}
|
|
990
1057
|
function syncCachedRouteActivityEvents(params) {
|
|
991
|
-
const nextModes = buildRouteCacheModes(params.cachedRoutes, params.
|
|
1058
|
+
const nextModes = buildRouteCacheModes(params.cachedRoutes, params.routerPathname);
|
|
992
1059
|
for (const [pathname, mode] of nextModes) {
|
|
993
1060
|
const previousMode = params.previousRouteCacheModes.get(pathname);
|
|
994
1061
|
if (previousMode === void 0 && mode === "hidden") continue;
|
|
@@ -1014,7 +1081,7 @@ function RouteCacheManager() {
|
|
|
1014
1081
|
const previousPathnameRef = (0, react.useRef)(void 0);
|
|
1015
1082
|
const previousHrefRef = (0, react.useRef)(void 0);
|
|
1016
1083
|
const previousRouteCacheModesRef = (0, react.useRef)(null);
|
|
1017
|
-
|
|
1084
|
+
previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
|
|
1018
1085
|
const previousVisiblePathnameRef = (0, react.useRef)(void 0);
|
|
1019
1086
|
const routerLocation = (0, _tanstack_react_router.useRouterState)({ select: (state) => toRouterLocation(state.location) });
|
|
1020
1087
|
const routerHref = routerLocation.href;
|
|
@@ -1022,7 +1089,6 @@ function RouteCacheManager() {
|
|
|
1022
1089
|
const routerResolvedLocation = (0, _tanstack_react_router.useRouterState)({ select: (state) => state.resolvedLocation ? toRouterLocation(state.resolvedLocation) : void 0 });
|
|
1023
1090
|
const resolvedPathname = normalizeCachedRoutePathname(routerResolvedLocation?.pathname ?? routerPathname);
|
|
1024
1091
|
const destinationRoute = cachedRoutes[routerPathname];
|
|
1025
|
-
const visiblePathname = routerPathname !== resolvedPathname && (isReadyCachedRoute(destinationRoute) || isAncestorPathname(routerPathname, resolvedPathname)) ? routerPathname : resolvedPathname;
|
|
1026
1092
|
const matches = (0, _tanstack_react_router.useMatches)();
|
|
1027
1093
|
const childMatches = (0, _tanstack_react_router.useChildMatches)();
|
|
1028
1094
|
const router = (0, _tanstack_react_router.useRouter)();
|
|
@@ -1090,7 +1156,7 @@ function RouteCacheManager() {
|
|
|
1090
1156
|
(0, react.useLayoutEffect)(() => {
|
|
1091
1157
|
const lastVisitedPathname = previousPathnameRef.current;
|
|
1092
1158
|
const pendingNavigation = pendingCachedNavigationRef.current;
|
|
1093
|
-
if (pendingNavigation && pendingNavigation.pathname !== routerPathname
|
|
1159
|
+
if (pendingNavigation && pendingNavigation.pathname !== routerPathname) {
|
|
1094
1160
|
eventListener.emit("cachedNavigationCancel", pendingNavigation);
|
|
1095
1161
|
pendingCachedNavigationRef.current = null;
|
|
1096
1162
|
}
|
|
@@ -1106,37 +1172,36 @@ function RouteCacheManager() {
|
|
|
1106
1172
|
}, [
|
|
1107
1173
|
destinationRoute,
|
|
1108
1174
|
eventListener,
|
|
1109
|
-
routerPathname
|
|
1110
|
-
visiblePathname
|
|
1175
|
+
routerPathname
|
|
1111
1176
|
]);
|
|
1112
1177
|
(0, react.useLayoutEffect)(() => {
|
|
1113
|
-
|
|
1178
|
+
previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
|
|
1114
1179
|
previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
|
|
1115
1180
|
cachedRoutes,
|
|
1116
1181
|
eventListener,
|
|
1117
1182
|
previousRouteCacheModes: previousRouteCacheModesRef.current,
|
|
1118
|
-
|
|
1183
|
+
routerPathname
|
|
1119
1184
|
});
|
|
1120
1185
|
}, [
|
|
1121
1186
|
cachedRoutes,
|
|
1122
1187
|
eventListener,
|
|
1123
|
-
|
|
1188
|
+
routerPathname
|
|
1124
1189
|
]);
|
|
1125
1190
|
(0, react.useLayoutEffect)(() => {
|
|
1126
|
-
if (previousVisiblePathnameRef.current ===
|
|
1127
|
-
previousVisiblePathnameRef.current =
|
|
1191
|
+
if (previousVisiblePathnameRef.current === routerPathname || !cachedRoutes[routerPathname]) {
|
|
1192
|
+
previousVisiblePathnameRef.current = routerPathname;
|
|
1128
1193
|
return;
|
|
1129
1194
|
}
|
|
1130
|
-
previousVisiblePathnameRef.current =
|
|
1131
|
-
touchCachedRoutes([
|
|
1195
|
+
previousVisiblePathnameRef.current = routerPathname;
|
|
1196
|
+
touchCachedRoutes([routerPathname]);
|
|
1132
1197
|
}, [
|
|
1133
1198
|
cachedRoutes,
|
|
1134
1199
|
touchCachedRoutes,
|
|
1135
|
-
|
|
1200
|
+
routerPathname
|
|
1136
1201
|
]);
|
|
1137
1202
|
(0, react.useEffect)(() => {
|
|
1138
1203
|
const pendingNavigation = pendingCachedNavigationRef.current;
|
|
1139
|
-
if (
|
|
1204
|
+
if (routerPathname !== pendingNavigation?.pathname) return;
|
|
1140
1205
|
let firstFrameId = 0;
|
|
1141
1206
|
let secondFrameId = 0;
|
|
1142
1207
|
firstFrameId = globalThis.requestAnimationFrame(() => {
|
|
@@ -1157,7 +1222,7 @@ function RouteCacheManager() {
|
|
|
1157
1222
|
globalThis.cancelAnimationFrame(firstFrameId);
|
|
1158
1223
|
globalThis.cancelAnimationFrame(secondFrameId);
|
|
1159
1224
|
};
|
|
1160
|
-
}, [eventListener,
|
|
1225
|
+
}, [eventListener, routerPathname]);
|
|
1161
1226
|
(0, react.useLayoutEffect)(() => {
|
|
1162
1227
|
if (!(shouldRestoreDestinationHref && destinationRoute?.href)) return;
|
|
1163
1228
|
restoreCachedHref(router, destinationRoute.href);
|
|
@@ -1170,17 +1235,13 @@ function RouteCacheManager() {
|
|
|
1170
1235
|
previousPathnameRef.current = routerPathname;
|
|
1171
1236
|
previousHrefRef.current = routerHref;
|
|
1172
1237
|
}, [routerHref, routerPathname]);
|
|
1173
|
-
const shouldRenderLiveOutlet =
|
|
1174
|
-
|
|
1175
|
-
bypassCachedPathname,
|
|
1176
|
-
visiblePathname
|
|
1177
|
-
});
|
|
1178
|
-
useRouterCacheDebug(cachedRoutes, visiblePathname);
|
|
1238
|
+
const shouldRenderLiveOutlet = routerPathname === bypassCachedPathname || !isReadyCachedRoute(destinationRoute);
|
|
1239
|
+
useRouterCacheDebug(cachedRoutes, routerPathname);
|
|
1179
1240
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [Object.entries(cachedRoutes).map(([pathname, route]) => renderCachedRoute({
|
|
1180
1241
|
bypassCachedPathname,
|
|
1181
1242
|
pathname,
|
|
1182
1243
|
route,
|
|
1183
|
-
|
|
1244
|
+
routerPathname
|
|
1184
1245
|
})), shouldRenderLiveOutlet ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_router.Outlet, {}, `live-${routerPathname}`) : null] });
|
|
1185
1246
|
}
|
|
1186
1247
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -786,12 +786,50 @@ const LIVE_ROUTER_METHODS = [
|
|
|
786
786
|
"navigate",
|
|
787
787
|
"preloadRoute"
|
|
788
788
|
];
|
|
789
|
-
|
|
789
|
+
const routerSnapshotUpdaters = /* @__PURE__ */ new WeakMap();
|
|
790
|
+
function createSnapshotStore(value) {
|
|
791
|
+
let currentValue = value;
|
|
792
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
790
793
|
return {
|
|
791
|
-
get: () =>
|
|
792
|
-
|
|
794
|
+
get: () => currentValue,
|
|
795
|
+
set: (nextValue) => {
|
|
796
|
+
if (Object.is(currentValue, nextValue)) return;
|
|
797
|
+
currentValue = nextValue;
|
|
798
|
+
for (const listener of listeners) listener(currentValue);
|
|
799
|
+
},
|
|
800
|
+
subscribe: (listener) => {
|
|
801
|
+
if (listener) listeners.add(listener);
|
|
802
|
+
return { unsubscribe: listener ? () => {
|
|
803
|
+
listeners.delete(listener);
|
|
804
|
+
} : () => void 0 };
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
function createRouterSnapshotData({ matches, router, routerLocation, routerResolvedLocation }) {
|
|
809
|
+
const snapshotMatches = matches.reduce((snapshot, match) => {
|
|
810
|
+
const nextMatch = snapshotMatch(match, routerLocation);
|
|
811
|
+
if (isRouterMatch(nextMatch)) snapshot.push(nextMatch);
|
|
812
|
+
return snapshot;
|
|
813
|
+
}, []);
|
|
814
|
+
return {
|
|
815
|
+
matches: snapshotMatches,
|
|
816
|
+
state: {
|
|
817
|
+
...router.stores.__store.get(),
|
|
818
|
+
matches: snapshotMatches,
|
|
819
|
+
location: routerLocation,
|
|
820
|
+
resolvedLocation: routerResolvedLocation
|
|
821
|
+
}
|
|
793
822
|
};
|
|
794
823
|
}
|
|
824
|
+
function createMatchStore(match) {
|
|
825
|
+
return Object.assign(createSnapshotStore(match), { routeId: match.routeId });
|
|
826
|
+
}
|
|
827
|
+
function syncRouterSnapshot(routerSnapshot, input) {
|
|
828
|
+
const update = routerSnapshotUpdaters.get(routerSnapshot);
|
|
829
|
+
if (!update) return false;
|
|
830
|
+
update(input);
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
795
833
|
function getLiveRouterMethodDescriptors(router) {
|
|
796
834
|
return Object.fromEntries(LIVE_ROUTER_METHODS.flatMap((methodName) => {
|
|
797
835
|
const method = router[methodName];
|
|
@@ -827,29 +865,40 @@ function hasCurrentRouteError({ erroredRouteCounts, matches, resolvedPathname, r
|
|
|
827
865
|
function isRouterMatch(match) {
|
|
828
866
|
return Boolean(match?.id && match.routeId);
|
|
829
867
|
}
|
|
830
|
-
function snapshotMatch(match) {
|
|
868
|
+
function snapshotMatch(match, routerLocation) {
|
|
831
869
|
if (!isRouterMatch(match)) return;
|
|
870
|
+
const isLocationMatch = match.pathname ? normalizeCachedRoutePathname(match.pathname) === normalizeCachedRoutePathname(routerLocation.pathname) : false;
|
|
832
871
|
return {
|
|
833
872
|
...match,
|
|
873
|
+
...isLocationMatch ? {
|
|
874
|
+
_strictSearch: routerLocation.search,
|
|
875
|
+
search: routerLocation.search
|
|
876
|
+
} : {},
|
|
834
877
|
_nonReactive: { ...match._nonReactive }
|
|
835
878
|
};
|
|
836
879
|
}
|
|
837
|
-
function
|
|
838
|
-
return
|
|
880
|
+
function isCurrentUnmanagedCachedRoute(route, routerHref) {
|
|
881
|
+
return Boolean(route && isRouteCacheEnabled(route.staticData) && route.ready && route.routerSnapshot && route.href === routerHref);
|
|
839
882
|
}
|
|
840
883
|
function syncReadyCachedRoute({ matchId, matches, routeId, route, routerLocation, router, routerResolvedLocation, routerHref, routerPathname, setCachedRoutes, staticData }) {
|
|
841
|
-
|
|
884
|
+
const routerSnapshotInput = {
|
|
885
|
+
matches,
|
|
886
|
+
router,
|
|
887
|
+
routerLocation,
|
|
888
|
+
routerResolvedLocation
|
|
889
|
+
};
|
|
890
|
+
let routerSnapshot = matchId ? route?.routerSnapshot : void 0;
|
|
891
|
+
if (routerSnapshot && !syncRouterSnapshot(routerSnapshot, routerSnapshotInput)) {
|
|
892
|
+
if (isCurrentUnmanagedCachedRoute(route, routerHref)) return;
|
|
893
|
+
routerSnapshot = void 0;
|
|
894
|
+
}
|
|
895
|
+
if (matchId && !routerSnapshot) routerSnapshot = createRouterSnapshot(routerSnapshotInput);
|
|
842
896
|
setCachedRoutes(routerPathname, {
|
|
843
897
|
href: routerHref,
|
|
844
898
|
matchId,
|
|
845
899
|
routeId,
|
|
846
900
|
ready: true,
|
|
847
|
-
routerSnapshot
|
|
848
|
-
matches,
|
|
849
|
-
router,
|
|
850
|
-
routerLocation,
|
|
851
|
-
routerResolvedLocation
|
|
852
|
-
}) : void 0,
|
|
901
|
+
routerSnapshot,
|
|
853
902
|
staticData
|
|
854
903
|
});
|
|
855
904
|
}
|
|
@@ -878,54 +927,43 @@ function syncCachedRouteState({ isCurrentMatchReady, isCurrentMatchResolved, del
|
|
|
878
927
|
}
|
|
879
928
|
if (route) deleteCachedRoutes([routerPathname]);
|
|
880
929
|
}
|
|
881
|
-
function createRouterSnapshot(
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
}, []);
|
|
887
|
-
const matchStores = new Map(snapshotMatches.map((match) => {
|
|
888
|
-
const store = Object.assign(createStaticStore(match), { routeId: match.routeId });
|
|
889
|
-
return [match.id, store];
|
|
890
|
-
}));
|
|
891
|
-
const snapshotState = {
|
|
892
|
-
...router.stores.__store.get(),
|
|
893
|
-
matches: snapshotMatches,
|
|
894
|
-
location: routerLocation,
|
|
895
|
-
resolvedLocation: routerResolvedLocation
|
|
896
|
-
};
|
|
930
|
+
function createRouterSnapshot(input) {
|
|
931
|
+
const { router, routerLocation, routerResolvedLocation } = input;
|
|
932
|
+
const snapshotData = createRouterSnapshotData(input);
|
|
933
|
+
const snapshotMatches = snapshotData.matches;
|
|
934
|
+
const matchStores = new Map(snapshotMatches.map((match) => [match.id, createMatchStore(match)]));
|
|
897
935
|
const routeMatchStoreCache = /* @__PURE__ */ new Map();
|
|
898
936
|
const stores = {
|
|
899
937
|
...router.stores,
|
|
900
|
-
status:
|
|
901
|
-
loadedAt:
|
|
902
|
-
isLoading:
|
|
903
|
-
isTransitioning:
|
|
904
|
-
location:
|
|
905
|
-
resolvedLocation:
|
|
906
|
-
statusCode:
|
|
907
|
-
redirect:
|
|
908
|
-
matchesId:
|
|
909
|
-
pendingIds:
|
|
910
|
-
cachedIds:
|
|
911
|
-
matches:
|
|
912
|
-
pendingMatches:
|
|
913
|
-
cachedMatches:
|
|
914
|
-
firstId:
|
|
915
|
-
hasPending:
|
|
916
|
-
matchRouteDeps:
|
|
938
|
+
status: createSnapshotStore(router.stores.status.get()),
|
|
939
|
+
loadedAt: createSnapshotStore(router.stores.loadedAt.get()),
|
|
940
|
+
isLoading: createSnapshotStore(router.stores.isLoading.get()),
|
|
941
|
+
isTransitioning: createSnapshotStore(router.stores.isTransitioning.get()),
|
|
942
|
+
location: createSnapshotStore(routerLocation),
|
|
943
|
+
resolvedLocation: createSnapshotStore(routerResolvedLocation),
|
|
944
|
+
statusCode: createSnapshotStore(router.stores.statusCode.get()),
|
|
945
|
+
redirect: createSnapshotStore(router.stores.redirect.get()),
|
|
946
|
+
matchesId: createSnapshotStore(snapshotMatches.map((match) => match.id)),
|
|
947
|
+
pendingIds: createSnapshotStore([]),
|
|
948
|
+
cachedIds: createSnapshotStore([]),
|
|
949
|
+
matches: createSnapshotStore(snapshotMatches),
|
|
950
|
+
pendingMatches: createSnapshotStore([]),
|
|
951
|
+
cachedMatches: createSnapshotStore([]),
|
|
952
|
+
firstId: createSnapshotStore(snapshotMatches[0]?.id),
|
|
953
|
+
hasPending: createSnapshotStore(snapshotMatches.some((match) => match.status === "pending")),
|
|
954
|
+
matchRouteDeps: createSnapshotStore({
|
|
917
955
|
locationHref: routerLocation.href,
|
|
918
956
|
resolvedLocationHref: routerResolvedLocation?.href,
|
|
919
|
-
status:
|
|
957
|
+
status: snapshotData.state.status
|
|
920
958
|
}),
|
|
921
|
-
__store:
|
|
959
|
+
__store: createSnapshotStore(snapshotData.state),
|
|
922
960
|
matchStores,
|
|
923
961
|
pendingMatchStores: /* @__PURE__ */ new Map(),
|
|
924
962
|
cachedMatchStores: /* @__PURE__ */ new Map(),
|
|
925
963
|
getRouteMatchStore: (routeId) => {
|
|
926
964
|
let cached = routeMatchStoreCache.get(routeId);
|
|
927
965
|
if (!cached) {
|
|
928
|
-
cached =
|
|
966
|
+
cached = createSnapshotStore(snapshotMatches.find((match) => match.routeId === routeId));
|
|
929
967
|
routeMatchStoreCache.set(routeId, cached);
|
|
930
968
|
}
|
|
931
969
|
return cached;
|
|
@@ -934,14 +972,53 @@ function createRouterSnapshot({ matches, router, routerLocation, routerResolvedL
|
|
|
934
972
|
setPending: () => void 0,
|
|
935
973
|
setCached: () => void 0
|
|
936
974
|
};
|
|
975
|
+
const updateSnapshot = (nextInput) => {
|
|
976
|
+
const nextData = createRouterSnapshotData(nextInput);
|
|
977
|
+
const nextMatches = nextData.matches;
|
|
978
|
+
const nextMatchIds = new Set(nextMatches.map((match) => match.id));
|
|
979
|
+
const nextMatchesByRouteId = new Map(nextMatches.map((match) => [match.routeId, match]));
|
|
980
|
+
for (const match of nextMatches) {
|
|
981
|
+
const store = matchStores.get(match.id);
|
|
982
|
+
if (store) {
|
|
983
|
+
store.set(match);
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
matchStores.set(match.id, createMatchStore(match));
|
|
987
|
+
}
|
|
988
|
+
for (const [matchId] of matchStores) if (!nextMatchIds.has(matchId)) matchStores.delete(matchId);
|
|
989
|
+
stores.status.set(nextInput.router.stores.status.get());
|
|
990
|
+
stores.loadedAt.set(nextInput.router.stores.loadedAt.get());
|
|
991
|
+
stores.isLoading.set(nextInput.router.stores.isLoading.get());
|
|
992
|
+
stores.isTransitioning.set(nextInput.router.stores.isTransitioning.get());
|
|
993
|
+
stores.location.set(nextInput.routerLocation);
|
|
994
|
+
stores.resolvedLocation.set(nextInput.routerResolvedLocation);
|
|
995
|
+
stores.statusCode.set(nextInput.router.stores.statusCode.get());
|
|
996
|
+
stores.redirect.set(nextInput.router.stores.redirect.get());
|
|
997
|
+
stores.matchesId.set(nextMatches.map((match) => match.id));
|
|
998
|
+
stores.pendingIds.set([]);
|
|
999
|
+
stores.cachedIds.set([]);
|
|
1000
|
+
stores.matches.set(nextMatches);
|
|
1001
|
+
stores.pendingMatches.set([]);
|
|
1002
|
+
stores.cachedMatches.set([]);
|
|
1003
|
+
stores.firstId.set(nextMatches[0]?.id);
|
|
1004
|
+
stores.hasPending.set(nextMatches.some((match) => match.status === "pending"));
|
|
1005
|
+
stores.matchRouteDeps.set({
|
|
1006
|
+
locationHref: nextInput.routerLocation.href,
|
|
1007
|
+
resolvedLocationHref: nextInput.routerResolvedLocation?.href,
|
|
1008
|
+
status: nextData.state.status
|
|
1009
|
+
});
|
|
1010
|
+
stores.__store.set(nextData.state);
|
|
1011
|
+
for (const [routeId, store] of routeMatchStoreCache) store.set(nextMatchesByRouteId.get(routeId));
|
|
1012
|
+
};
|
|
937
1013
|
const routerSnapshot = Object.create(router);
|
|
938
1014
|
Object.defineProperties(routerSnapshot, {
|
|
939
1015
|
stores: { value: stores },
|
|
940
|
-
latestLocation: {
|
|
1016
|
+
latestLocation: { get: () => stores.location.get() },
|
|
941
1017
|
getMatch: { value: (matchId) => matchStores.get(matchId)?.get() },
|
|
942
1018
|
updateMatch: { value: () => void 0 },
|
|
943
1019
|
...getLiveRouterMethodDescriptors(router)
|
|
944
1020
|
});
|
|
1021
|
+
routerSnapshotUpdaters.set(routerSnapshot, updateSnapshot);
|
|
945
1022
|
return routerSnapshot;
|
|
946
1023
|
}
|
|
947
1024
|
function getRouterCacheStaticData(childMatches, isCurrentMatchResolved) {
|
|
@@ -958,17 +1035,7 @@ function restoreCachedHref(router, href) {
|
|
|
958
1035
|
resetScroll: false
|
|
959
1036
|
}).catch(() => void 0);
|
|
960
1037
|
}
|
|
961
|
-
function
|
|
962
|
-
if (ancestorPathname === pathname) return false;
|
|
963
|
-
if (ancestorPathname === "/") return pathname.startsWith("/");
|
|
964
|
-
return pathname.startsWith(`${ancestorPathname}/`);
|
|
965
|
-
}
|
|
966
|
-
function getShouldRenderLiveOutlet({ cachedRoutes, bypassCachedPathname, visiblePathname }) {
|
|
967
|
-
if (visiblePathname === bypassCachedPathname) return true;
|
|
968
|
-
const visibleRoute = cachedRoutes[visiblePathname];
|
|
969
|
-
return !isReadyCachedRoute(visibleRoute);
|
|
970
|
-
}
|
|
971
|
-
function renderCachedRoute({ bypassCachedPathname, pathname, route, visiblePathname }) {
|
|
1038
|
+
function renderCachedRoute({ bypassCachedPathname, pathname, route, routerPathname }) {
|
|
972
1039
|
if (pathname === bypassCachedPathname) return null;
|
|
973
1040
|
const content = route.matchId && route.routerSnapshot ? /* @__PURE__ */ jsx(CachedOutlet, {
|
|
974
1041
|
matchId: route.matchId,
|
|
@@ -976,18 +1043,18 @@ function renderCachedRoute({ bypassCachedPathname, pathname, route, visiblePathn
|
|
|
976
1043
|
}) : null;
|
|
977
1044
|
if (!content) return null;
|
|
978
1045
|
return /* @__PURE__ */ jsx(OffScreen, {
|
|
979
|
-
mode:
|
|
1046
|
+
mode: routerPathname === pathname ? "visible" : "hidden",
|
|
980
1047
|
pathname,
|
|
981
1048
|
children: content
|
|
982
1049
|
}, pathname);
|
|
983
1050
|
}
|
|
984
|
-
function buildRouteCacheModes(cachedRoutes,
|
|
1051
|
+
function buildRouteCacheModes(cachedRoutes, routerPathname) {
|
|
985
1052
|
const nextModes = /* @__PURE__ */ new Map();
|
|
986
|
-
for (const pathname of Object.keys(cachedRoutes)) nextModes.set(pathname,
|
|
1053
|
+
for (const pathname of Object.keys(cachedRoutes)) nextModes.set(pathname, routerPathname === pathname ? "visible" : "hidden");
|
|
987
1054
|
return nextModes;
|
|
988
1055
|
}
|
|
989
1056
|
function syncCachedRouteActivityEvents(params) {
|
|
990
|
-
const nextModes = buildRouteCacheModes(params.cachedRoutes, params.
|
|
1057
|
+
const nextModes = buildRouteCacheModes(params.cachedRoutes, params.routerPathname);
|
|
991
1058
|
for (const [pathname, mode] of nextModes) {
|
|
992
1059
|
const previousMode = params.previousRouteCacheModes.get(pathname);
|
|
993
1060
|
if (previousMode === void 0 && mode === "hidden") continue;
|
|
@@ -1013,7 +1080,7 @@ function RouteCacheManager() {
|
|
|
1013
1080
|
const previousPathnameRef = useRef(void 0);
|
|
1014
1081
|
const previousHrefRef = useRef(void 0);
|
|
1015
1082
|
const previousRouteCacheModesRef = useRef(null);
|
|
1016
|
-
|
|
1083
|
+
previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
|
|
1017
1084
|
const previousVisiblePathnameRef = useRef(void 0);
|
|
1018
1085
|
const routerLocation = useRouterState({ select: (state) => toRouterLocation(state.location) });
|
|
1019
1086
|
const routerHref = routerLocation.href;
|
|
@@ -1021,7 +1088,6 @@ function RouteCacheManager() {
|
|
|
1021
1088
|
const routerResolvedLocation = useRouterState({ select: (state) => state.resolvedLocation ? toRouterLocation(state.resolvedLocation) : void 0 });
|
|
1022
1089
|
const resolvedPathname = normalizeCachedRoutePathname(routerResolvedLocation?.pathname ?? routerPathname);
|
|
1023
1090
|
const destinationRoute = cachedRoutes[routerPathname];
|
|
1024
|
-
const visiblePathname = routerPathname !== resolvedPathname && (isReadyCachedRoute(destinationRoute) || isAncestorPathname(routerPathname, resolvedPathname)) ? routerPathname : resolvedPathname;
|
|
1025
1091
|
const matches = useMatches();
|
|
1026
1092
|
const childMatches = useChildMatches();
|
|
1027
1093
|
const router = useRouter();
|
|
@@ -1089,7 +1155,7 @@ function RouteCacheManager() {
|
|
|
1089
1155
|
useLayoutEffect(() => {
|
|
1090
1156
|
const lastVisitedPathname = previousPathnameRef.current;
|
|
1091
1157
|
const pendingNavigation = pendingCachedNavigationRef.current;
|
|
1092
|
-
if (pendingNavigation && pendingNavigation.pathname !== routerPathname
|
|
1158
|
+
if (pendingNavigation && pendingNavigation.pathname !== routerPathname) {
|
|
1093
1159
|
eventListener.emit("cachedNavigationCancel", pendingNavigation);
|
|
1094
1160
|
pendingCachedNavigationRef.current = null;
|
|
1095
1161
|
}
|
|
@@ -1105,37 +1171,36 @@ function RouteCacheManager() {
|
|
|
1105
1171
|
}, [
|
|
1106
1172
|
destinationRoute,
|
|
1107
1173
|
eventListener,
|
|
1108
|
-
routerPathname
|
|
1109
|
-
visiblePathname
|
|
1174
|
+
routerPathname
|
|
1110
1175
|
]);
|
|
1111
1176
|
useLayoutEffect(() => {
|
|
1112
|
-
|
|
1177
|
+
previousRouteCacheModesRef.current ??= /* @__PURE__ */ new Map();
|
|
1113
1178
|
previousRouteCacheModesRef.current = syncCachedRouteActivityEvents({
|
|
1114
1179
|
cachedRoutes,
|
|
1115
1180
|
eventListener,
|
|
1116
1181
|
previousRouteCacheModes: previousRouteCacheModesRef.current,
|
|
1117
|
-
|
|
1182
|
+
routerPathname
|
|
1118
1183
|
});
|
|
1119
1184
|
}, [
|
|
1120
1185
|
cachedRoutes,
|
|
1121
1186
|
eventListener,
|
|
1122
|
-
|
|
1187
|
+
routerPathname
|
|
1123
1188
|
]);
|
|
1124
1189
|
useLayoutEffect(() => {
|
|
1125
|
-
if (previousVisiblePathnameRef.current ===
|
|
1126
|
-
previousVisiblePathnameRef.current =
|
|
1190
|
+
if (previousVisiblePathnameRef.current === routerPathname || !cachedRoutes[routerPathname]) {
|
|
1191
|
+
previousVisiblePathnameRef.current = routerPathname;
|
|
1127
1192
|
return;
|
|
1128
1193
|
}
|
|
1129
|
-
previousVisiblePathnameRef.current =
|
|
1130
|
-
touchCachedRoutes([
|
|
1194
|
+
previousVisiblePathnameRef.current = routerPathname;
|
|
1195
|
+
touchCachedRoutes([routerPathname]);
|
|
1131
1196
|
}, [
|
|
1132
1197
|
cachedRoutes,
|
|
1133
1198
|
touchCachedRoutes,
|
|
1134
|
-
|
|
1199
|
+
routerPathname
|
|
1135
1200
|
]);
|
|
1136
1201
|
useEffect(() => {
|
|
1137
1202
|
const pendingNavigation = pendingCachedNavigationRef.current;
|
|
1138
|
-
if (
|
|
1203
|
+
if (routerPathname !== pendingNavigation?.pathname) return;
|
|
1139
1204
|
let firstFrameId = 0;
|
|
1140
1205
|
let secondFrameId = 0;
|
|
1141
1206
|
firstFrameId = globalThis.requestAnimationFrame(() => {
|
|
@@ -1156,7 +1221,7 @@ function RouteCacheManager() {
|
|
|
1156
1221
|
globalThis.cancelAnimationFrame(firstFrameId);
|
|
1157
1222
|
globalThis.cancelAnimationFrame(secondFrameId);
|
|
1158
1223
|
};
|
|
1159
|
-
}, [eventListener,
|
|
1224
|
+
}, [eventListener, routerPathname]);
|
|
1160
1225
|
useLayoutEffect(() => {
|
|
1161
1226
|
if (!(shouldRestoreDestinationHref && destinationRoute?.href)) return;
|
|
1162
1227
|
restoreCachedHref(router, destinationRoute.href);
|
|
@@ -1169,17 +1234,13 @@ function RouteCacheManager() {
|
|
|
1169
1234
|
previousPathnameRef.current = routerPathname;
|
|
1170
1235
|
previousHrefRef.current = routerHref;
|
|
1171
1236
|
}, [routerHref, routerPathname]);
|
|
1172
|
-
const shouldRenderLiveOutlet =
|
|
1173
|
-
|
|
1174
|
-
bypassCachedPathname,
|
|
1175
|
-
visiblePathname
|
|
1176
|
-
});
|
|
1177
|
-
useRouterCacheDebug(cachedRoutes, visiblePathname);
|
|
1237
|
+
const shouldRenderLiveOutlet = routerPathname === bypassCachedPathname || !isReadyCachedRoute(destinationRoute);
|
|
1238
|
+
useRouterCacheDebug(cachedRoutes, routerPathname);
|
|
1178
1239
|
return /* @__PURE__ */ jsxs(Fragment, { children: [Object.entries(cachedRoutes).map(([pathname, route]) => renderCachedRoute({
|
|
1179
1240
|
bypassCachedPathname,
|
|
1180
1241
|
pathname,
|
|
1181
1242
|
route,
|
|
1182
|
-
|
|
1243
|
+
routerPathname
|
|
1183
1244
|
})), shouldRenderLiveOutlet ? /* @__PURE__ */ jsx(Outlet, {}, `live-${routerPathname}`) : null] });
|
|
1184
1245
|
}
|
|
1185
1246
|
//#endregion
|
package/docs/architecture.md
CHANGED
|
@@ -131,8 +131,8 @@ classDiagram
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
class RouterSnapshot {
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
snapshot stores
|
|
135
|
+
snapshot matches
|
|
136
136
|
live navigate
|
|
137
137
|
live invalidate
|
|
138
138
|
live preloadRoute
|
|
@@ -149,21 +149,21 @@ classDiagram
|
|
|
149
149
|
| `routeId` | TanStack route id. Used by `maxEntriesPerRouteId`. |
|
|
150
150
|
| `staticData` | Route static data. The route is cacheable when `routeCache` is `true`. |
|
|
151
151
|
| `matchId` | Match id used to render the cached route with TanStack Router's `Match`. |
|
|
152
|
-
| `routerSnapshot` |
|
|
152
|
+
| `routerSnapshot` | Router-like object with isolated snapshot stores used by the cached route tree. |
|
|
153
153
|
| `ready` | Marks that the route has a complete snapshot and can be rendered from cache. |
|
|
154
154
|
|
|
155
155
|
Pathnames are normalized by removing trailing slashes except for `/`, so `/customers/` and `/customers` share one cache key.
|
|
156
156
|
|
|
157
157
|
## Router snapshot
|
|
158
158
|
|
|
159
|
-
Cached route trees still expect TanStack Router context. Instead of
|
|
159
|
+
Cached route trees still expect TanStack Router context. Instead of connecting every cached tree directly to the live router stores, the package creates a router-like snapshot when a route becomes ready.
|
|
160
160
|
|
|
161
161
|
```mermaid
|
|
162
162
|
flowchart TD
|
|
163
163
|
liveRouter["Live router"]
|
|
164
164
|
matches["Current matches"]
|
|
165
165
|
location["Current location"]
|
|
166
|
-
|
|
166
|
+
snapshotStores["Snapshot stores"]
|
|
167
167
|
liveMethods["Bound live methods"]
|
|
168
168
|
routerSnapshot["Router snapshot"]
|
|
169
169
|
cachedOutlet["CachedOutlet"]
|
|
@@ -171,18 +171,18 @@ flowchart TD
|
|
|
171
171
|
|
|
172
172
|
liveRouter --> matches
|
|
173
173
|
liveRouter --> location
|
|
174
|
-
matches -->
|
|
175
|
-
location -->
|
|
174
|
+
matches --> snapshotStores
|
|
175
|
+
location --> snapshotStores
|
|
176
176
|
liveRouter --> liveMethods
|
|
177
|
-
|
|
177
|
+
snapshotStores --> routerSnapshot
|
|
178
178
|
liveMethods --> routerSnapshot
|
|
179
179
|
routerSnapshot --> cachedOutlet
|
|
180
180
|
cachedOutlet --> match
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
The snapshot
|
|
183
|
+
The snapshot owns isolated stores for the current matches, location, resolved location, and match stores. It also keeps selected live router methods bound to the real router, including `navigate`, `invalidate`, `preloadRoute`, and location builders.
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
When the cached entry is refreshed, the cache manager updates those snapshot stores in place. This lets route hooks read current match and search data without remounting the retained route tree. Imperative router actions still call through to the real router.
|
|
186
186
|
|
|
187
187
|
`CachedOutlet` renders that snapshot like this:
|
|
188
188
|
|
package/docs/releases.md
CHANGED
|
@@ -34,15 +34,23 @@ That updates GitHub and runs CI, but it does not publish to npm.
|
|
|
34
34
|
For a release, start from a clean `main` branch after your changes are already committed:
|
|
35
35
|
|
|
36
36
|
```sh
|
|
37
|
-
bun run release
|
|
38
|
-
bun run release:push
|
|
37
|
+
bun run release patch
|
|
39
38
|
```
|
|
40
39
|
|
|
41
40
|
Use `minor`, `major`, or an exact version when needed:
|
|
42
41
|
|
|
43
42
|
```sh
|
|
44
|
-
bun run release
|
|
45
|
-
bun run release
|
|
43
|
+
bun run release minor
|
|
44
|
+
bun run release 0.2.0
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`release` updates `package.json`, runs the package checks, commits the version bump, creates the matching tag, pushes `main` and the tag, creates the GitHub Release, waits for the publish workflow, and verifies that npm has the new package version.
|
|
48
|
+
|
|
49
|
+
The lower-level commands are still available when you need to split the release into two steps:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
bun run release:prepare patch
|
|
53
|
+
bun run release:push
|
|
46
54
|
```
|
|
47
55
|
|
|
48
56
|
`release:prepare` updates `package.json`, runs the package checks, commits the version bump, and creates the matching tag locally. `release:push` pushes `main` and that exact tag. The tag starts the npm publish workflow.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tanstack-router-cache",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Route view caching for TanStack Router.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"lint:fix": "biome lint src scripts .github/scripts rolldown.config.ts --write --skip=lint/performance/noBarrelFile --skip=lint/style/useConsistentTypeDefinitions --skip=lint/suspicious/noConsole",
|
|
51
51
|
"check": "bun run lint && bun run typecheck && bun run build && bun run pack:dry-run",
|
|
52
52
|
"pack:dry-run": "npm pack --dry-run",
|
|
53
|
+
"release": "node scripts/release.mjs release",
|
|
53
54
|
"release:prepare": "node scripts/release.mjs prepare",
|
|
54
55
|
"release:push": "node scripts/release.mjs push",
|
|
55
56
|
"prepack": "bun run build",
|