@tanstack/router-plugin 1.168.23 → 1.168.24
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/cjs/core/hmr/handle-route-update.cjs +2 -67
- package/dist/cjs/core/hmr/handle-route-update.cjs.map +1 -1
- package/dist/esm/core/hmr/handle-route-update.js +2 -67
- package/dist/esm/core/hmr/handle-route-update.js.map +1 -1
- package/package.json +8 -7
- package/skills/router-plugin/SKILL.md +20 -3
- package/src/core/hmr/handle-route-update.ts +8 -148
|
@@ -12,13 +12,6 @@ function handleRouteUpdate(routeId, newRoute) {
|
|
|
12
12
|
generatedRouteOptionKeys.forEach((key) => {
|
|
13
13
|
if (key in oldRoute.options) generatedRouteOptions[key] = oldRoute.options[key];
|
|
14
14
|
});
|
|
15
|
-
const removedKeys = /* @__PURE__ */ new Set();
|
|
16
|
-
Object.keys(oldRoute.options).forEach((key) => {
|
|
17
|
-
if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) {
|
|
18
|
-
removedKeys.add(key);
|
|
19
|
-
delete oldRoute.options[key];
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
15
|
const preserveComponentIdentity = "shellComponent" in oldRoute.options === "shellComponent" in newRoute.options;
|
|
23
16
|
const componentKeys = "__TSR_COMPONENT_TYPES__";
|
|
24
17
|
if (preserveComponentIdentity) componentKeys.forEach((key) => {
|
|
@@ -30,42 +23,11 @@ function handleRouteUpdate(routeId, newRoute) {
|
|
|
30
23
|
};
|
|
31
24
|
oldRoute.options = nextOptions;
|
|
32
25
|
oldRoute.update(nextOptions);
|
|
33
|
-
oldRoute.
|
|
34
|
-
oldRoute._lazyPromise = void 0;
|
|
26
|
+
router._replaceRouteChunk(oldRoute, newRoute.lazyFn);
|
|
35
27
|
router.setRoutes(router.buildRouteTree());
|
|
36
28
|
syncHotRouteExport(oldRoute);
|
|
37
29
|
router.resolvePathCache.clear();
|
|
38
|
-
|
|
39
|
-
const activeMatch = router.stores.matches.get().find(filter);
|
|
40
|
-
const pendingMatch = router.stores.pendingMatches.get().find(filter);
|
|
41
|
-
const cachedMatches = router.stores.cachedMatches.get().filter(filter);
|
|
42
|
-
if (activeMatch || pendingMatch || cachedMatches.length > 0) {
|
|
43
|
-
if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) {
|
|
44
|
-
const matchIds = [
|
|
45
|
-
activeMatch?.id,
|
|
46
|
-
pendingMatch?.id,
|
|
47
|
-
...cachedMatches.map((match) => match.id)
|
|
48
|
-
].filter(Boolean);
|
|
49
|
-
router.batch(() => {
|
|
50
|
-
for (const matchId of matchIds) {
|
|
51
|
-
const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId);
|
|
52
|
-
if (store) store.set((prev) => {
|
|
53
|
-
const next = { ...prev };
|
|
54
|
-
if (removedKeys.has("loader")) next.loaderData = void 0;
|
|
55
|
-
if (removedKeys.has("beforeLoad")) {
|
|
56
|
-
next.__beforeLoadContext = void 0;
|
|
57
|
-
next.context = rebuildMatchContextWithoutBeforeLoad(next);
|
|
58
|
-
}
|
|
59
|
-
return next;
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
router.invalidate({
|
|
65
|
-
filter,
|
|
66
|
-
sync: true
|
|
67
|
-
});
|
|
68
|
-
}
|
|
30
|
+
router._refreshRoute?.();
|
|
69
31
|
function syncHotRouteExport(liveRoute) {
|
|
70
32
|
newRoute.options = liveRoute.options;
|
|
71
33
|
newRoute.parentRoute = liveRoute.parentRoute;
|
|
@@ -74,33 +36,6 @@ function handleRouteUpdate(routeId, newRoute) {
|
|
|
74
36
|
newRoute._fullPath = liveRoute._fullPath;
|
|
75
37
|
newRoute._to = liveRoute._to;
|
|
76
38
|
}
|
|
77
|
-
function getStoreMatch(matchId) {
|
|
78
|
-
return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get();
|
|
79
|
-
}
|
|
80
|
-
function getMatchList(matchId) {
|
|
81
|
-
const pendingMatches = router.stores.pendingMatches.get();
|
|
82
|
-
if (pendingMatches.some((match) => match.id === matchId)) return pendingMatches;
|
|
83
|
-
const activeMatches = router.stores.matches.get();
|
|
84
|
-
if (activeMatches.some((match) => match.id === matchId)) return activeMatches;
|
|
85
|
-
const cachedMatches = router.stores.cachedMatches.get();
|
|
86
|
-
if (cachedMatches.some((match) => match.id === matchId)) return cachedMatches;
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
function getParentMatch(match) {
|
|
90
|
-
const matchList = getMatchList(match.id);
|
|
91
|
-
const matchIndex = matchList.findIndex((item) => item.id === match.id);
|
|
92
|
-
if (matchIndex <= 0) return;
|
|
93
|
-
const parentMatch = matchList[matchIndex - 1];
|
|
94
|
-
return getStoreMatch(parentMatch.id) || parentMatch;
|
|
95
|
-
}
|
|
96
|
-
function rebuildMatchContextWithoutBeforeLoad(match) {
|
|
97
|
-
const parentMatch = getParentMatch(match);
|
|
98
|
-
const getParentContext = router.getParentContext;
|
|
99
|
-
return {
|
|
100
|
-
...(getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context) ?? {},
|
|
101
|
-
...match.__routeContext ?? {}
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
39
|
}
|
|
105
40
|
var handleRouteUpdateStr = handleRouteUpdate.toString();
|
|
106
41
|
function getHandleRouteUpdateCode(stableRouteOptionKeys) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-route-update.cjs","names":[],"sources":["../../../../src/core/hmr/handle-route-update.ts"],"sourcesContent":["import type {\n AnyRoute,\n AnyRouteMatch,\n AnyRouter,\n RouterWritableStore,\n} from '@tanstack/router-core'\n\ntype AnyRouteWithPrivateProps = AnyRoute & {\n options: Record<string, unknown>\n parentRoute: AnyRoute\n _componentsPromise?: Promise<void>\n _lazyPromise?: Promise<void>\n update: (options: Record<string, unknown>) => unknown\n _path: string\n _id: string\n _fullPath: string\n _to: string\n}\n\ntype AnyRouterWithPrivateMaps = AnyRouter & {\n routesById: Record<string, AnyRoute>\n buildRouteTree: () => Parameters<AnyRouter['setRoutes']>[0]\n setRoutes: AnyRouter['setRoutes']\n stores: AnyRouter['stores'] & {\n cachedMatchStores: Map<\n string,\n Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>\n >\n pendingMatchStores: Map<\n string,\n Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>\n >\n matchStores: Map<\n string,\n Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>\n >\n }\n}\n\ntype AnyRouteMatchWithPrivateProps = AnyRouteMatch & {\n __beforeLoadContext?: unknown\n __routeContext?: Record<string, unknown>\n context?: Record<string, unknown>\n}\n\nfunction handleRouteUpdate(\n routeId: string,\n newRoute: AnyRouteWithPrivateProps,\n) {\n const router = window.__TSR_ROUTER__ as AnyRouterWithPrivateMaps\n const oldRoute = router.routesById[routeId] as\n | AnyRouteWithPrivateProps\n | undefined\n\n if (!oldRoute) {\n return\n }\n\n // Generated route-tree options are not present on the freshly imported route\n // module, but they must stay on the live route before rebuilding indexes.\n const generatedRouteOptionKeys = new Set(['id', 'path', 'getParentRoute'])\n const generatedRouteOptions: Record<string, unknown> = {}\n generatedRouteOptionKeys.forEach((key) => {\n if (key in oldRoute.options) {\n generatedRouteOptions[key] = oldRoute.options[key]\n }\n })\n\n const removedKeys = new Set<string>()\n Object.keys(oldRoute.options).forEach((key) => {\n if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) {\n removedKeys.add(key)\n delete oldRoute.options[key]\n }\n })\n\n const oldHasShellComponent = 'shellComponent' in oldRoute.options\n const newHasShellComponent = 'shellComponent' in newRoute.options\n const preserveComponentIdentity =\n oldHasShellComponent === newHasShellComponent\n\n // Keys whose identity must remain stable to prevent React from\n // unmounting/remounting the component tree. React Fast Refresh already\n // handles hot-updating the function bodies of these components — our job\n // is only to update non-component route options (loader, head, etc.).\n // For code-split (splittable) routes, the lazyRouteComponent wrapper is\n // already cached in the bundler hot data so its identity is stable.\n // For unsplittable routes (e.g. root routes), the component is a plain\n // function reference that gets recreated on every module re-execution,\n // so we must explicitly preserve the old reference.\n // Preserve component identity so React doesn't remount.\n // React Fast Refresh patches the function bodies in-place.\n const componentKeys = '__TSR_COMPONENT_TYPES__' as unknown as Array<string>\n if (preserveComponentIdentity) {\n componentKeys.forEach((key) => {\n if (key in oldRoute.options && key in newRoute.options) {\n newRoute.options[key] = oldRoute.options[key]\n }\n })\n }\n\n const nextOptions = {\n ...newRoute.options,\n ...generatedRouteOptions,\n }\n\n oldRoute.options = nextOptions\n oldRoute.update(nextOptions)\n oldRoute._componentsPromise = undefined\n oldRoute._lazyPromise = undefined\n\n router.setRoutes(router.buildRouteTree())\n syncHotRouteExport(oldRoute)\n router.resolvePathCache.clear()\n\n const filter = (m: AnyRouteMatch) => m.routeId === oldRoute.id\n const activeMatch = router.stores.matches.get().find(filter)\n const pendingMatch = router.stores.pendingMatches.get().find(filter)\n const cachedMatches = router.stores.cachedMatches.get().filter(filter)\n\n if (activeMatch || pendingMatch || cachedMatches.length > 0) {\n // Clear stale match data for removed route options BEFORE invalidating.\n // Without this, router.invalidate() -> matchRoutes() reuses the existing\n // match from the store (via ...existingMatch spread) and the stale\n // loaderData / __beforeLoadContext survives the reload cycle.\n //\n // We must update the store directly (not via router.updateMatch) because\n // updateMatch wraps in startTransition which may defer the state update,\n // and we need the clear to be visible before invalidate reads the store.\n if (removedKeys.has('loader') || removedKeys.has('beforeLoad')) {\n const matchIds = [\n activeMatch?.id,\n pendingMatch?.id,\n ...cachedMatches.map((match) => match.id),\n ].filter(Boolean) as Array<string>\n router.batch(() => {\n for (const matchId of matchIds) {\n const store =\n router.stores.pendingMatchStores.get(matchId) ||\n router.stores.matchStores.get(matchId) ||\n router.stores.cachedMatchStores.get(matchId)\n if (store) {\n store.set((prev) => {\n const next: AnyRouteMatchWithPrivateProps = { ...prev }\n\n if (removedKeys.has('loader')) {\n next.loaderData = undefined\n }\n if (removedKeys.has('beforeLoad')) {\n next.__beforeLoadContext = undefined\n next.context = rebuildMatchContextWithoutBeforeLoad(next)\n }\n\n return next\n })\n }\n }\n })\n }\n\n router.invalidate({ filter, sync: true })\n }\n\n function syncHotRouteExport(liveRoute: AnyRouteWithPrivateProps) {\n // routeTree.gen.ts mutates the original module export with generated\n // routing state. Mirror that state onto the fresh HMR export too, so\n // aliased route imports keep working after the module is hot-reloaded.\n newRoute.options = liveRoute.options\n newRoute.parentRoute = liveRoute.parentRoute\n newRoute._path = liveRoute._path\n newRoute._id = liveRoute._id\n newRoute._fullPath = liveRoute._fullPath\n newRoute._to = liveRoute._to\n }\n\n function getStoreMatch(matchId: string) {\n return (\n router.stores.pendingMatchStores.get(matchId)?.get() ||\n router.stores.matchStores.get(matchId)?.get() ||\n router.stores.cachedMatchStores.get(matchId)?.get()\n )\n }\n\n function getMatchList(matchId: string) {\n const pendingMatches = router.stores.pendingMatches.get()\n if (pendingMatches.some((match) => match.id === matchId)) {\n return pendingMatches\n }\n\n const activeMatches = router.stores.matches.get()\n if (activeMatches.some((match) => match.id === matchId)) {\n return activeMatches\n }\n\n const cachedMatches = router.stores.cachedMatches.get()\n if (cachedMatches.some((match) => match.id === matchId)) {\n return cachedMatches\n }\n\n return []\n }\n\n function getParentMatch(match: AnyRouteMatch) {\n const matchList = getMatchList(match.id)\n const matchIndex = matchList.findIndex((item) => item.id === match.id)\n\n if (matchIndex <= 0) {\n return undefined\n }\n\n const parentMatch = matchList[matchIndex - 1]!\n return getStoreMatch(parentMatch.id) || parentMatch\n }\n\n function rebuildMatchContextWithoutBeforeLoad(\n match: AnyRouteMatchWithPrivateProps,\n ) {\n const parentMatch = getParentMatch(match)\n const getParentContext = (\n router as unknown as {\n getParentContext?: (\n parentMatch?: AnyRouteMatch,\n ) => Record<string, unknown> | undefined\n }\n ).getParentContext\n const parentContext = getParentContext\n ? getParentContext.call(router, parentMatch)\n : (parentMatch?.context ?? router.options.context)\n\n return {\n ...(parentContext ?? {}),\n ...(match.__routeContext ?? {}),\n }\n }\n}\n\nconst handleRouteUpdateStr = handleRouteUpdate.toString()\n\nexport function getHandleRouteUpdateCode(stableRouteOptionKeys: Array<string>) {\n return handleRouteUpdateStr.replace(\n /['\"]__TSR_COMPONENT_TYPES__['\"]/,\n JSON.stringify(stableRouteOptionKeys),\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"handle-route-update.cjs","names":[],"sources":["../../../../src/core/hmr/handle-route-update.ts"],"sourcesContent":["import type { AnyRoute, AnyRouter } from '@tanstack/router-core'\n\ntype AnyRouteWithPrivateProps = AnyRoute & {\n options: Record<string, unknown>\n parentRoute: AnyRoute\n _lazy?: Promise<void> | true\n update: (options: Record<string, unknown>) => unknown\n _path: string\n _id: string\n _fullPath: string\n _to: string\n}\n\ntype AnyRouterWithPrivateState = AnyRouter & {\n routesById: Record<string, AnyRoute>\n buildRouteTree: () => Parameters<AnyRouter['setRoutes']>[0]\n setRoutes: AnyRouter['setRoutes']\n _refreshRoute?: () => Promise<void>\n _replaceRouteChunk: (route: AnyRoute, lazyFn: AnyRoute['lazyFn']) => void\n}\n\nfunction handleRouteUpdate(\n routeId: string,\n newRoute: AnyRouteWithPrivateProps,\n) {\n const router = window.__TSR_ROUTER__ as AnyRouterWithPrivateState\n const oldRoute = router.routesById[routeId] as\n | AnyRouteWithPrivateProps\n | undefined\n\n if (!oldRoute) {\n return\n }\n\n // Generated route-tree options are not present on the freshly imported route\n // module, but they must stay on the live route before rebuilding indexes.\n const generatedRouteOptionKeys = new Set(['id', 'path', 'getParentRoute'])\n const generatedRouteOptions: Record<string, unknown> = {}\n generatedRouteOptionKeys.forEach((key) => {\n if (key in oldRoute.options) {\n generatedRouteOptions[key] = oldRoute.options[key]\n }\n })\n\n const oldHasShellComponent = 'shellComponent' in oldRoute.options\n const newHasShellComponent = 'shellComponent' in newRoute.options\n const preserveComponentIdentity =\n oldHasShellComponent === newHasShellComponent\n\n // Keys whose identity must remain stable to prevent React from\n // unmounting/remounting the component tree. React Fast Refresh already\n // handles hot-updating the function bodies of these components — our job\n // is only to update non-component route options (loader, head, etc.).\n // For code-split (splittable) routes, the lazyRouteComponent wrapper is\n // already cached in the bundler hot data so its identity is stable.\n // For unsplittable routes (e.g. root routes), the component is a plain\n // function reference that gets recreated on every module re-execution,\n // so we must explicitly preserve the old reference.\n // Preserve component identity so React doesn't remount.\n // React Fast Refresh patches the function bodies in-place.\n const componentKeys = '__TSR_COMPONENT_TYPES__' as unknown as Array<string>\n if (preserveComponentIdentity) {\n componentKeys.forEach((key) => {\n if (key in oldRoute.options && key in newRoute.options) {\n newRoute.options[key] = oldRoute.options[key]\n }\n })\n }\n\n const nextOptions = {\n ...newRoute.options,\n ...generatedRouteOptions,\n }\n\n oldRoute.options = nextOptions\n oldRoute.update(nextOptions)\n router._replaceRouteChunk(oldRoute, newRoute.lazyFn)\n\n router.setRoutes(router.buildRouteTree())\n syncHotRouteExport(oldRoute)\n router.resolvePathCache.clear()\n void router._refreshRoute?.()\n\n function syncHotRouteExport(liveRoute: AnyRouteWithPrivateProps) {\n // routeTree.gen.ts mutates the original module export with generated\n // routing state. Mirror that state onto the fresh HMR export too, so\n // aliased route imports keep working after the module is hot-reloaded.\n newRoute.options = liveRoute.options\n newRoute.parentRoute = liveRoute.parentRoute\n newRoute._path = liveRoute._path\n newRoute._id = liveRoute._id\n newRoute._fullPath = liveRoute._fullPath\n newRoute._to = liveRoute._to\n }\n}\n\nconst handleRouteUpdateStr = handleRouteUpdate.toString()\n\nexport function getHandleRouteUpdateCode(stableRouteOptionKeys: Array<string>) {\n return handleRouteUpdateStr.replace(\n /['\"]__TSR_COMPONENT_TYPES__['\"]/,\n JSON.stringify(stableRouteOptionKeys),\n )\n}\n"],"mappings":";AAqBA,SAAS,kBACP,SACA,UACA;CACA,MAAM,SAAS,OAAO;CACtB,MAAM,WAAW,OAAO,WAAW;CAInC,IAAI,CAAC,UACH;CAKF,MAAM,2BAA2B,IAAI,IAAI;EAAC;EAAM;EAAQ;CAAgB,CAAC;CACzE,MAAM,wBAAiD,CAAC;CACxD,yBAAyB,SAAS,QAAQ;EACxC,IAAI,OAAO,SAAS,SAClB,sBAAsB,OAAO,SAAS,QAAQ;CAElD,CAAC;CAID,MAAM,4BAFuB,oBAAoB,SAAS,YAC7B,oBAAoB,SAAS;CAe1D,MAAM,gBAAgB;CACtB,IAAI,2BACF,cAAc,SAAS,QAAQ;EAC7B,IAAI,OAAO,SAAS,WAAW,OAAO,SAAS,SAC7C,SAAS,QAAQ,OAAO,SAAS,QAAQ;CAE7C,CAAC;CAGH,MAAM,cAAc;EAClB,GAAG,SAAS;EACZ,GAAG;CACL;CAEA,SAAS,UAAU;CACnB,SAAS,OAAO,WAAW;CAC3B,OAAO,mBAAmB,UAAU,SAAS,MAAM;CAEnD,OAAO,UAAU,OAAO,eAAe,CAAC;CACxC,mBAAmB,QAAQ;CAC3B,OAAO,iBAAiB,MAAM;CAC9B,OAAY,gBAAgB;CAE5B,SAAS,mBAAmB,WAAqC;EAI/D,SAAS,UAAU,UAAU;EAC7B,SAAS,cAAc,UAAU;EACjC,SAAS,QAAQ,UAAU;EAC3B,SAAS,MAAM,UAAU;EACzB,SAAS,YAAY,UAAU;EAC/B,SAAS,MAAM,UAAU;CAC3B;AACF;AAEA,IAAM,uBAAuB,kBAAkB,SAAS;AAExD,SAAgB,yBAAyB,uBAAsC;CAC7E,OAAO,qBAAqB,QAC1B,mCACA,KAAK,UAAU,qBAAqB,CACtC;AACF"}
|
|
@@ -12,13 +12,6 @@ function handleRouteUpdate(routeId, newRoute) {
|
|
|
12
12
|
generatedRouteOptionKeys.forEach((key) => {
|
|
13
13
|
if (key in oldRoute.options) generatedRouteOptions[key] = oldRoute.options[key];
|
|
14
14
|
});
|
|
15
|
-
const removedKeys = /* @__PURE__ */ new Set();
|
|
16
|
-
Object.keys(oldRoute.options).forEach((key) => {
|
|
17
|
-
if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) {
|
|
18
|
-
removedKeys.add(key);
|
|
19
|
-
delete oldRoute.options[key];
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
15
|
const preserveComponentIdentity = "shellComponent" in oldRoute.options === "shellComponent" in newRoute.options;
|
|
23
16
|
const componentKeys = "__TSR_COMPONENT_TYPES__";
|
|
24
17
|
if (preserveComponentIdentity) componentKeys.forEach((key) => {
|
|
@@ -30,42 +23,11 @@ function handleRouteUpdate(routeId, newRoute) {
|
|
|
30
23
|
};
|
|
31
24
|
oldRoute.options = nextOptions;
|
|
32
25
|
oldRoute.update(nextOptions);
|
|
33
|
-
oldRoute.
|
|
34
|
-
oldRoute._lazyPromise = void 0;
|
|
26
|
+
router._replaceRouteChunk(oldRoute, newRoute.lazyFn);
|
|
35
27
|
router.setRoutes(router.buildRouteTree());
|
|
36
28
|
syncHotRouteExport(oldRoute);
|
|
37
29
|
router.resolvePathCache.clear();
|
|
38
|
-
|
|
39
|
-
const activeMatch = router.stores.matches.get().find(filter);
|
|
40
|
-
const pendingMatch = router.stores.pendingMatches.get().find(filter);
|
|
41
|
-
const cachedMatches = router.stores.cachedMatches.get().filter(filter);
|
|
42
|
-
if (activeMatch || pendingMatch || cachedMatches.length > 0) {
|
|
43
|
-
if (removedKeys.has("loader") || removedKeys.has("beforeLoad")) {
|
|
44
|
-
const matchIds = [
|
|
45
|
-
activeMatch?.id,
|
|
46
|
-
pendingMatch?.id,
|
|
47
|
-
...cachedMatches.map((match) => match.id)
|
|
48
|
-
].filter(Boolean);
|
|
49
|
-
router.batch(() => {
|
|
50
|
-
for (const matchId of matchIds) {
|
|
51
|
-
const store = router.stores.pendingMatchStores.get(matchId) || router.stores.matchStores.get(matchId) || router.stores.cachedMatchStores.get(matchId);
|
|
52
|
-
if (store) store.set((prev) => {
|
|
53
|
-
const next = { ...prev };
|
|
54
|
-
if (removedKeys.has("loader")) next.loaderData = void 0;
|
|
55
|
-
if (removedKeys.has("beforeLoad")) {
|
|
56
|
-
next.__beforeLoadContext = void 0;
|
|
57
|
-
next.context = rebuildMatchContextWithoutBeforeLoad(next);
|
|
58
|
-
}
|
|
59
|
-
return next;
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
router.invalidate({
|
|
65
|
-
filter,
|
|
66
|
-
sync: true
|
|
67
|
-
});
|
|
68
|
-
}
|
|
30
|
+
router._refreshRoute?.();
|
|
69
31
|
function syncHotRouteExport(liveRoute) {
|
|
70
32
|
newRoute.options = liveRoute.options;
|
|
71
33
|
newRoute.parentRoute = liveRoute.parentRoute;
|
|
@@ -74,33 +36,6 @@ function handleRouteUpdate(routeId, newRoute) {
|
|
|
74
36
|
newRoute._fullPath = liveRoute._fullPath;
|
|
75
37
|
newRoute._to = liveRoute._to;
|
|
76
38
|
}
|
|
77
|
-
function getStoreMatch(matchId) {
|
|
78
|
-
return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get();
|
|
79
|
-
}
|
|
80
|
-
function getMatchList(matchId) {
|
|
81
|
-
const pendingMatches = router.stores.pendingMatches.get();
|
|
82
|
-
if (pendingMatches.some((match) => match.id === matchId)) return pendingMatches;
|
|
83
|
-
const activeMatches = router.stores.matches.get();
|
|
84
|
-
if (activeMatches.some((match) => match.id === matchId)) return activeMatches;
|
|
85
|
-
const cachedMatches = router.stores.cachedMatches.get();
|
|
86
|
-
if (cachedMatches.some((match) => match.id === matchId)) return cachedMatches;
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
function getParentMatch(match) {
|
|
90
|
-
const matchList = getMatchList(match.id);
|
|
91
|
-
const matchIndex = matchList.findIndex((item) => item.id === match.id);
|
|
92
|
-
if (matchIndex <= 0) return;
|
|
93
|
-
const parentMatch = matchList[matchIndex - 1];
|
|
94
|
-
return getStoreMatch(parentMatch.id) || parentMatch;
|
|
95
|
-
}
|
|
96
|
-
function rebuildMatchContextWithoutBeforeLoad(match) {
|
|
97
|
-
const parentMatch = getParentMatch(match);
|
|
98
|
-
const getParentContext = router.getParentContext;
|
|
99
|
-
return {
|
|
100
|
-
...(getParentContext ? getParentContext.call(router, parentMatch) : parentMatch?.context ?? router.options.context) ?? {},
|
|
101
|
-
...match.__routeContext ?? {}
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
39
|
}
|
|
105
40
|
var handleRouteUpdateStr = handleRouteUpdate.toString();
|
|
106
41
|
function getHandleRouteUpdateCode(stableRouteOptionKeys) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handle-route-update.js","names":[],"sources":["../../../../src/core/hmr/handle-route-update.ts"],"sourcesContent":["import type {\n AnyRoute,\n AnyRouteMatch,\n AnyRouter,\n RouterWritableStore,\n} from '@tanstack/router-core'\n\ntype AnyRouteWithPrivateProps = AnyRoute & {\n options: Record<string, unknown>\n parentRoute: AnyRoute\n _componentsPromise?: Promise<void>\n _lazyPromise?: Promise<void>\n update: (options: Record<string, unknown>) => unknown\n _path: string\n _id: string\n _fullPath: string\n _to: string\n}\n\ntype AnyRouterWithPrivateMaps = AnyRouter & {\n routesById: Record<string, AnyRoute>\n buildRouteTree: () => Parameters<AnyRouter['setRoutes']>[0]\n setRoutes: AnyRouter['setRoutes']\n stores: AnyRouter['stores'] & {\n cachedMatchStores: Map<\n string,\n Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>\n >\n pendingMatchStores: Map<\n string,\n Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>\n >\n matchStores: Map<\n string,\n Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>\n >\n }\n}\n\ntype AnyRouteMatchWithPrivateProps = AnyRouteMatch & {\n __beforeLoadContext?: unknown\n __routeContext?: Record<string, unknown>\n context?: Record<string, unknown>\n}\n\nfunction handleRouteUpdate(\n routeId: string,\n newRoute: AnyRouteWithPrivateProps,\n) {\n const router = window.__TSR_ROUTER__ as AnyRouterWithPrivateMaps\n const oldRoute = router.routesById[routeId] as\n | AnyRouteWithPrivateProps\n | undefined\n\n if (!oldRoute) {\n return\n }\n\n // Generated route-tree options are not present on the freshly imported route\n // module, but they must stay on the live route before rebuilding indexes.\n const generatedRouteOptionKeys = new Set(['id', 'path', 'getParentRoute'])\n const generatedRouteOptions: Record<string, unknown> = {}\n generatedRouteOptionKeys.forEach((key) => {\n if (key in oldRoute.options) {\n generatedRouteOptions[key] = oldRoute.options[key]\n }\n })\n\n const removedKeys = new Set<string>()\n Object.keys(oldRoute.options).forEach((key) => {\n if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) {\n removedKeys.add(key)\n delete oldRoute.options[key]\n }\n })\n\n const oldHasShellComponent = 'shellComponent' in oldRoute.options\n const newHasShellComponent = 'shellComponent' in newRoute.options\n const preserveComponentIdentity =\n oldHasShellComponent === newHasShellComponent\n\n // Keys whose identity must remain stable to prevent React from\n // unmounting/remounting the component tree. React Fast Refresh already\n // handles hot-updating the function bodies of these components — our job\n // is only to update non-component route options (loader, head, etc.).\n // For code-split (splittable) routes, the lazyRouteComponent wrapper is\n // already cached in the bundler hot data so its identity is stable.\n // For unsplittable routes (e.g. root routes), the component is a plain\n // function reference that gets recreated on every module re-execution,\n // so we must explicitly preserve the old reference.\n // Preserve component identity so React doesn't remount.\n // React Fast Refresh patches the function bodies in-place.\n const componentKeys = '__TSR_COMPONENT_TYPES__' as unknown as Array<string>\n if (preserveComponentIdentity) {\n componentKeys.forEach((key) => {\n if (key in oldRoute.options && key in newRoute.options) {\n newRoute.options[key] = oldRoute.options[key]\n }\n })\n }\n\n const nextOptions = {\n ...newRoute.options,\n ...generatedRouteOptions,\n }\n\n oldRoute.options = nextOptions\n oldRoute.update(nextOptions)\n oldRoute._componentsPromise = undefined\n oldRoute._lazyPromise = undefined\n\n router.setRoutes(router.buildRouteTree())\n syncHotRouteExport(oldRoute)\n router.resolvePathCache.clear()\n\n const filter = (m: AnyRouteMatch) => m.routeId === oldRoute.id\n const activeMatch = router.stores.matches.get().find(filter)\n const pendingMatch = router.stores.pendingMatches.get().find(filter)\n const cachedMatches = router.stores.cachedMatches.get().filter(filter)\n\n if (activeMatch || pendingMatch || cachedMatches.length > 0) {\n // Clear stale match data for removed route options BEFORE invalidating.\n // Without this, router.invalidate() -> matchRoutes() reuses the existing\n // match from the store (via ...existingMatch spread) and the stale\n // loaderData / __beforeLoadContext survives the reload cycle.\n //\n // We must update the store directly (not via router.updateMatch) because\n // updateMatch wraps in startTransition which may defer the state update,\n // and we need the clear to be visible before invalidate reads the store.\n if (removedKeys.has('loader') || removedKeys.has('beforeLoad')) {\n const matchIds = [\n activeMatch?.id,\n pendingMatch?.id,\n ...cachedMatches.map((match) => match.id),\n ].filter(Boolean) as Array<string>\n router.batch(() => {\n for (const matchId of matchIds) {\n const store =\n router.stores.pendingMatchStores.get(matchId) ||\n router.stores.matchStores.get(matchId) ||\n router.stores.cachedMatchStores.get(matchId)\n if (store) {\n store.set((prev) => {\n const next: AnyRouteMatchWithPrivateProps = { ...prev }\n\n if (removedKeys.has('loader')) {\n next.loaderData = undefined\n }\n if (removedKeys.has('beforeLoad')) {\n next.__beforeLoadContext = undefined\n next.context = rebuildMatchContextWithoutBeforeLoad(next)\n }\n\n return next\n })\n }\n }\n })\n }\n\n router.invalidate({ filter, sync: true })\n }\n\n function syncHotRouteExport(liveRoute: AnyRouteWithPrivateProps) {\n // routeTree.gen.ts mutates the original module export with generated\n // routing state. Mirror that state onto the fresh HMR export too, so\n // aliased route imports keep working after the module is hot-reloaded.\n newRoute.options = liveRoute.options\n newRoute.parentRoute = liveRoute.parentRoute\n newRoute._path = liveRoute._path\n newRoute._id = liveRoute._id\n newRoute._fullPath = liveRoute._fullPath\n newRoute._to = liveRoute._to\n }\n\n function getStoreMatch(matchId: string) {\n return (\n router.stores.pendingMatchStores.get(matchId)?.get() ||\n router.stores.matchStores.get(matchId)?.get() ||\n router.stores.cachedMatchStores.get(matchId)?.get()\n )\n }\n\n function getMatchList(matchId: string) {\n const pendingMatches = router.stores.pendingMatches.get()\n if (pendingMatches.some((match) => match.id === matchId)) {\n return pendingMatches\n }\n\n const activeMatches = router.stores.matches.get()\n if (activeMatches.some((match) => match.id === matchId)) {\n return activeMatches\n }\n\n const cachedMatches = router.stores.cachedMatches.get()\n if (cachedMatches.some((match) => match.id === matchId)) {\n return cachedMatches\n }\n\n return []\n }\n\n function getParentMatch(match: AnyRouteMatch) {\n const matchList = getMatchList(match.id)\n const matchIndex = matchList.findIndex((item) => item.id === match.id)\n\n if (matchIndex <= 0) {\n return undefined\n }\n\n const parentMatch = matchList[matchIndex - 1]!\n return getStoreMatch(parentMatch.id) || parentMatch\n }\n\n function rebuildMatchContextWithoutBeforeLoad(\n match: AnyRouteMatchWithPrivateProps,\n ) {\n const parentMatch = getParentMatch(match)\n const getParentContext = (\n router as unknown as {\n getParentContext?: (\n parentMatch?: AnyRouteMatch,\n ) => Record<string, unknown> | undefined\n }\n ).getParentContext\n const parentContext = getParentContext\n ? getParentContext.call(router, parentMatch)\n : (parentMatch?.context ?? router.options.context)\n\n return {\n ...(parentContext ?? {}),\n ...(match.__routeContext ?? {}),\n }\n }\n}\n\nconst handleRouteUpdateStr = handleRouteUpdate.toString()\n\nexport function getHandleRouteUpdateCode(stableRouteOptionKeys: Array<string>) {\n return handleRouteUpdateStr.replace(\n /['\"]__TSR_COMPONENT_TYPES__['\"]/,\n JSON.stringify(stableRouteOptionKeys),\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"handle-route-update.js","names":[],"sources":["../../../../src/core/hmr/handle-route-update.ts"],"sourcesContent":["import type { AnyRoute, AnyRouter } from '@tanstack/router-core'\n\ntype AnyRouteWithPrivateProps = AnyRoute & {\n options: Record<string, unknown>\n parentRoute: AnyRoute\n _lazy?: Promise<void> | true\n update: (options: Record<string, unknown>) => unknown\n _path: string\n _id: string\n _fullPath: string\n _to: string\n}\n\ntype AnyRouterWithPrivateState = AnyRouter & {\n routesById: Record<string, AnyRoute>\n buildRouteTree: () => Parameters<AnyRouter['setRoutes']>[0]\n setRoutes: AnyRouter['setRoutes']\n _refreshRoute?: () => Promise<void>\n _replaceRouteChunk: (route: AnyRoute, lazyFn: AnyRoute['lazyFn']) => void\n}\n\nfunction handleRouteUpdate(\n routeId: string,\n newRoute: AnyRouteWithPrivateProps,\n) {\n const router = window.__TSR_ROUTER__ as AnyRouterWithPrivateState\n const oldRoute = router.routesById[routeId] as\n | AnyRouteWithPrivateProps\n | undefined\n\n if (!oldRoute) {\n return\n }\n\n // Generated route-tree options are not present on the freshly imported route\n // module, but they must stay on the live route before rebuilding indexes.\n const generatedRouteOptionKeys = new Set(['id', 'path', 'getParentRoute'])\n const generatedRouteOptions: Record<string, unknown> = {}\n generatedRouteOptionKeys.forEach((key) => {\n if (key in oldRoute.options) {\n generatedRouteOptions[key] = oldRoute.options[key]\n }\n })\n\n const oldHasShellComponent = 'shellComponent' in oldRoute.options\n const newHasShellComponent = 'shellComponent' in newRoute.options\n const preserveComponentIdentity =\n oldHasShellComponent === newHasShellComponent\n\n // Keys whose identity must remain stable to prevent React from\n // unmounting/remounting the component tree. React Fast Refresh already\n // handles hot-updating the function bodies of these components — our job\n // is only to update non-component route options (loader, head, etc.).\n // For code-split (splittable) routes, the lazyRouteComponent wrapper is\n // already cached in the bundler hot data so its identity is stable.\n // For unsplittable routes (e.g. root routes), the component is a plain\n // function reference that gets recreated on every module re-execution,\n // so we must explicitly preserve the old reference.\n // Preserve component identity so React doesn't remount.\n // React Fast Refresh patches the function bodies in-place.\n const componentKeys = '__TSR_COMPONENT_TYPES__' as unknown as Array<string>\n if (preserveComponentIdentity) {\n componentKeys.forEach((key) => {\n if (key in oldRoute.options && key in newRoute.options) {\n newRoute.options[key] = oldRoute.options[key]\n }\n })\n }\n\n const nextOptions = {\n ...newRoute.options,\n ...generatedRouteOptions,\n }\n\n oldRoute.options = nextOptions\n oldRoute.update(nextOptions)\n router._replaceRouteChunk(oldRoute, newRoute.lazyFn)\n\n router.setRoutes(router.buildRouteTree())\n syncHotRouteExport(oldRoute)\n router.resolvePathCache.clear()\n void router._refreshRoute?.()\n\n function syncHotRouteExport(liveRoute: AnyRouteWithPrivateProps) {\n // routeTree.gen.ts mutates the original module export with generated\n // routing state. Mirror that state onto the fresh HMR export too, so\n // aliased route imports keep working after the module is hot-reloaded.\n newRoute.options = liveRoute.options\n newRoute.parentRoute = liveRoute.parentRoute\n newRoute._path = liveRoute._path\n newRoute._id = liveRoute._id\n newRoute._fullPath = liveRoute._fullPath\n newRoute._to = liveRoute._to\n }\n}\n\nconst handleRouteUpdateStr = handleRouteUpdate.toString()\n\nexport function getHandleRouteUpdateCode(stableRouteOptionKeys: Array<string>) {\n return handleRouteUpdateStr.replace(\n /['\"]__TSR_COMPONENT_TYPES__['\"]/,\n JSON.stringify(stableRouteOptionKeys),\n )\n}\n"],"mappings":";AAqBA,SAAS,kBACP,SACA,UACA;CACA,MAAM,SAAS,OAAO;CACtB,MAAM,WAAW,OAAO,WAAW;CAInC,IAAI,CAAC,UACH;CAKF,MAAM,2BAA2B,IAAI,IAAI;EAAC;EAAM;EAAQ;CAAgB,CAAC;CACzE,MAAM,wBAAiD,CAAC;CACxD,yBAAyB,SAAS,QAAQ;EACxC,IAAI,OAAO,SAAS,SAClB,sBAAsB,OAAO,SAAS,QAAQ;CAElD,CAAC;CAID,MAAM,4BAFuB,oBAAoB,SAAS,YAC7B,oBAAoB,SAAS;CAe1D,MAAM,gBAAgB;CACtB,IAAI,2BACF,cAAc,SAAS,QAAQ;EAC7B,IAAI,OAAO,SAAS,WAAW,OAAO,SAAS,SAC7C,SAAS,QAAQ,OAAO,SAAS,QAAQ;CAE7C,CAAC;CAGH,MAAM,cAAc;EAClB,GAAG,SAAS;EACZ,GAAG;CACL;CAEA,SAAS,UAAU;CACnB,SAAS,OAAO,WAAW;CAC3B,OAAO,mBAAmB,UAAU,SAAS,MAAM;CAEnD,OAAO,UAAU,OAAO,eAAe,CAAC;CACxC,mBAAmB,QAAQ;CAC3B,OAAO,iBAAiB,MAAM;CAC9B,OAAY,gBAAgB;CAE5B,SAAS,mBAAmB,WAAqC;EAI/D,SAAS,UAAU,UAAU;EAC7B,SAAS,cAAc,UAAU;EACjC,SAAS,QAAQ,UAAU;EAC3B,SAAS,MAAM,UAAU;EACzB,SAAS,YAAY,UAAU;EAC/B,SAAS,MAAM,UAAU;CAC3B;AACF;AAEA,IAAM,uBAAuB,kBAAkB,SAAS;AAExD,SAAgB,yBAAyB,uBAAsC;CAC7E,OAAO,qBAAqB,QAC1B,mCACA,KAAK,UAAU,qBAAqB,CACtC;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/router-plugin",
|
|
3
|
-
"version": "1.168.
|
|
3
|
+
"version": "1.168.24",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -106,11 +106,12 @@
|
|
|
106
106
|
"chokidar": "^5.0.0",
|
|
107
107
|
"unplugin": "^3.0.0",
|
|
108
108
|
"zod": "^4.4.3",
|
|
109
|
-
"@tanstack/router-core": "1.171.
|
|
110
|
-
"@tanstack/router-
|
|
111
|
-
"@tanstack/router-
|
|
109
|
+
"@tanstack/router-core": "1.171.16",
|
|
110
|
+
"@tanstack/router-generator": "1.167.22",
|
|
111
|
+
"@tanstack/router-utils": "1.162.2"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
|
+
"@rsbuild/core": "^2.1.0",
|
|
114
115
|
"@types/babel__core": "^7.20.5",
|
|
115
116
|
"@types/babel__template": "^7.4.4",
|
|
116
117
|
"@types/node": ">=20"
|
|
@@ -120,7 +121,7 @@
|
|
|
120
121
|
"vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0",
|
|
121
122
|
"vite-plugin-solid": "^2.11.10 || ^3.0.0-0",
|
|
122
123
|
"webpack": ">=5.92.0",
|
|
123
|
-
"@tanstack/react-router": "^1.170.
|
|
124
|
+
"@tanstack/react-router": "^1.170.19"
|
|
124
125
|
},
|
|
125
126
|
"peerDependenciesMeta": {
|
|
126
127
|
"@rsbuild/core": {
|
|
@@ -147,12 +148,12 @@
|
|
|
147
148
|
"test:unit:dev": "vitest --watch",
|
|
148
149
|
"test:eslint": "eslint ./src",
|
|
149
150
|
"test:types": "pnpm run \"/^test:types:ts[0-9]{2}$/\"",
|
|
150
|
-
"test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js",
|
|
151
151
|
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js",
|
|
152
152
|
"test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js",
|
|
153
153
|
"test:types:ts58": "node ../../node_modules/typescript58/lib/tsc.js",
|
|
154
154
|
"test:types:ts59": "node ../../node_modules/typescript59/lib/tsc.js",
|
|
155
|
-
"test:types:ts60": "
|
|
155
|
+
"test:types:ts60": "tsc6",
|
|
156
|
+
"test:types:ts70": "tsc",
|
|
156
157
|
"test:build": "publint --strict && attw --ignore-rules no-resolution --pack .",
|
|
157
158
|
"build": "vite build"
|
|
158
159
|
}
|
|
@@ -5,9 +5,10 @@ description: >-
|
|
|
5
5
|
code splitting. Supports Vite, Webpack, Rspack, and esbuild.
|
|
6
6
|
Configures autoCodeSplitting, routesDirectory, target framework,
|
|
7
7
|
and code split groupings.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
metadata:
|
|
9
|
+
type: core
|
|
10
|
+
library: tanstack-router
|
|
11
|
+
library_version: '1.168.23'
|
|
11
12
|
sources:
|
|
12
13
|
- TanStack/router:packages/router-plugin/src
|
|
13
14
|
- TanStack/router:docs/router/routing/file-based-routing.md
|
|
@@ -170,6 +171,18 @@ The composed plugin assembles up to 3 sub-plugins:
|
|
|
170
171
|
2. **Code Splitter** (when `autoCodeSplitting: true`) — Splits route files into lazy-loaded chunks using virtual modules
|
|
171
172
|
3. **HMR** (dev mode, when code splitter is off) — Hot-reloads route changes without full refresh
|
|
172
173
|
|
|
174
|
+
## Route Refactor Workflow
|
|
175
|
+
|
|
176
|
+
When moving, renaming, adding, or deleting file routes:
|
|
177
|
+
|
|
178
|
+
1. Change the source files under `routesDirectory`. Keep the exported route identifier named `Route`.
|
|
179
|
+
2. Let the bundler plugin regenerate the tree, or run `pnpm exec tsr generate` when the project uses the CLI.
|
|
180
|
+
3. Inspect the generated diff for the expected route IDs, parents, paths, and imports. Never repair `routeTree.gen.ts` by hand.
|
|
181
|
+
4. Update links, redirects, `from` narrowing, params, preload calls, and tests that reference the old route.
|
|
182
|
+
5. Run route-generation tests, type tests, and a production build. A passing editor typecheck does not prove the plugin generated or split the new route correctly.
|
|
183
|
+
|
|
184
|
+
Commit `routeTree.gen.ts`; it is generated source used by the application at runtime.
|
|
185
|
+
|
|
173
186
|
## Individual Plugin Exports
|
|
174
187
|
|
|
175
188
|
For advanced use, each sub-plugin is exported separately from the Vite entry:
|
|
@@ -227,6 +240,10 @@ function AboutPage() {
|
|
|
227
240
|
}
|
|
228
241
|
```
|
|
229
242
|
|
|
243
|
+
### 4. HIGH: Editing the generated route tree
|
|
244
|
+
|
|
245
|
+
Changes to `routeTree.gen.ts` are overwritten and can leave source routes, generated types, and runtime routing out of sync. Fix route filenames or plugin configuration, regenerate, and verify the generated diff instead.
|
|
246
|
+
|
|
230
247
|
## Cross-References
|
|
231
248
|
|
|
232
249
|
- [router-core/code-splitting](../../../router-core/skills/router-core/code-splitting/SKILL.md) — manual code splitting concepts
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AnyRoute,
|
|
3
|
-
AnyRouteMatch,
|
|
4
|
-
AnyRouter,
|
|
5
|
-
RouterWritableStore,
|
|
6
|
-
} from '@tanstack/router-core'
|
|
1
|
+
import type { AnyRoute, AnyRouter } from '@tanstack/router-core'
|
|
7
2
|
|
|
8
3
|
type AnyRouteWithPrivateProps = AnyRoute & {
|
|
9
4
|
options: Record<string, unknown>
|
|
10
5
|
parentRoute: AnyRoute
|
|
11
|
-
|
|
12
|
-
_lazyPromise?: Promise<void>
|
|
6
|
+
_lazy?: Promise<void> | true
|
|
13
7
|
update: (options: Record<string, unknown>) => unknown
|
|
14
8
|
_path: string
|
|
15
9
|
_id: string
|
|
@@ -17,37 +11,19 @@ type AnyRouteWithPrivateProps = AnyRoute & {
|
|
|
17
11
|
_to: string
|
|
18
12
|
}
|
|
19
13
|
|
|
20
|
-
type
|
|
14
|
+
type AnyRouterWithPrivateState = AnyRouter & {
|
|
21
15
|
routesById: Record<string, AnyRoute>
|
|
22
16
|
buildRouteTree: () => Parameters<AnyRouter['setRoutes']>[0]
|
|
23
17
|
setRoutes: AnyRouter['setRoutes']
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
string,
|
|
27
|
-
Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>
|
|
28
|
-
>
|
|
29
|
-
pendingMatchStores: Map<
|
|
30
|
-
string,
|
|
31
|
-
Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>
|
|
32
|
-
>
|
|
33
|
-
matchStores: Map<
|
|
34
|
-
string,
|
|
35
|
-
Pick<RouterWritableStore<AnyRouteMatch>, 'get' | 'set'>
|
|
36
|
-
>
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
type AnyRouteMatchWithPrivateProps = AnyRouteMatch & {
|
|
41
|
-
__beforeLoadContext?: unknown
|
|
42
|
-
__routeContext?: Record<string, unknown>
|
|
43
|
-
context?: Record<string, unknown>
|
|
18
|
+
_refreshRoute?: () => Promise<void>
|
|
19
|
+
_replaceRouteChunk: (route: AnyRoute, lazyFn: AnyRoute['lazyFn']) => void
|
|
44
20
|
}
|
|
45
21
|
|
|
46
22
|
function handleRouteUpdate(
|
|
47
23
|
routeId: string,
|
|
48
24
|
newRoute: AnyRouteWithPrivateProps,
|
|
49
25
|
) {
|
|
50
|
-
const router = window.__TSR_ROUTER__ as
|
|
26
|
+
const router = window.__TSR_ROUTER__ as AnyRouterWithPrivateState
|
|
51
27
|
const oldRoute = router.routesById[routeId] as
|
|
52
28
|
| AnyRouteWithPrivateProps
|
|
53
29
|
| undefined
|
|
@@ -66,14 +42,6 @@ function handleRouteUpdate(
|
|
|
66
42
|
}
|
|
67
43
|
})
|
|
68
44
|
|
|
69
|
-
const removedKeys = new Set<string>()
|
|
70
|
-
Object.keys(oldRoute.options).forEach((key) => {
|
|
71
|
-
if (!generatedRouteOptionKeys.has(key) && !(key in newRoute.options)) {
|
|
72
|
-
removedKeys.add(key)
|
|
73
|
-
delete oldRoute.options[key]
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
45
|
const oldHasShellComponent = 'shellComponent' in oldRoute.options
|
|
78
46
|
const newHasShellComponent = 'shellComponent' in newRoute.options
|
|
79
47
|
const preserveComponentIdentity =
|
|
@@ -106,60 +74,12 @@ function handleRouteUpdate(
|
|
|
106
74
|
|
|
107
75
|
oldRoute.options = nextOptions
|
|
108
76
|
oldRoute.update(nextOptions)
|
|
109
|
-
oldRoute.
|
|
110
|
-
oldRoute._lazyPromise = undefined
|
|
77
|
+
router._replaceRouteChunk(oldRoute, newRoute.lazyFn)
|
|
111
78
|
|
|
112
79
|
router.setRoutes(router.buildRouteTree())
|
|
113
80
|
syncHotRouteExport(oldRoute)
|
|
114
81
|
router.resolvePathCache.clear()
|
|
115
|
-
|
|
116
|
-
const filter = (m: AnyRouteMatch) => m.routeId === oldRoute.id
|
|
117
|
-
const activeMatch = router.stores.matches.get().find(filter)
|
|
118
|
-
const pendingMatch = router.stores.pendingMatches.get().find(filter)
|
|
119
|
-
const cachedMatches = router.stores.cachedMatches.get().filter(filter)
|
|
120
|
-
|
|
121
|
-
if (activeMatch || pendingMatch || cachedMatches.length > 0) {
|
|
122
|
-
// Clear stale match data for removed route options BEFORE invalidating.
|
|
123
|
-
// Without this, router.invalidate() -> matchRoutes() reuses the existing
|
|
124
|
-
// match from the store (via ...existingMatch spread) and the stale
|
|
125
|
-
// loaderData / __beforeLoadContext survives the reload cycle.
|
|
126
|
-
//
|
|
127
|
-
// We must update the store directly (not via router.updateMatch) because
|
|
128
|
-
// updateMatch wraps in startTransition which may defer the state update,
|
|
129
|
-
// and we need the clear to be visible before invalidate reads the store.
|
|
130
|
-
if (removedKeys.has('loader') || removedKeys.has('beforeLoad')) {
|
|
131
|
-
const matchIds = [
|
|
132
|
-
activeMatch?.id,
|
|
133
|
-
pendingMatch?.id,
|
|
134
|
-
...cachedMatches.map((match) => match.id),
|
|
135
|
-
].filter(Boolean) as Array<string>
|
|
136
|
-
router.batch(() => {
|
|
137
|
-
for (const matchId of matchIds) {
|
|
138
|
-
const store =
|
|
139
|
-
router.stores.pendingMatchStores.get(matchId) ||
|
|
140
|
-
router.stores.matchStores.get(matchId) ||
|
|
141
|
-
router.stores.cachedMatchStores.get(matchId)
|
|
142
|
-
if (store) {
|
|
143
|
-
store.set((prev) => {
|
|
144
|
-
const next: AnyRouteMatchWithPrivateProps = { ...prev }
|
|
145
|
-
|
|
146
|
-
if (removedKeys.has('loader')) {
|
|
147
|
-
next.loaderData = undefined
|
|
148
|
-
}
|
|
149
|
-
if (removedKeys.has('beforeLoad')) {
|
|
150
|
-
next.__beforeLoadContext = undefined
|
|
151
|
-
next.context = rebuildMatchContextWithoutBeforeLoad(next)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return next
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
router.invalidate({ filter, sync: true })
|
|
162
|
-
}
|
|
82
|
+
void router._refreshRoute?.()
|
|
163
83
|
|
|
164
84
|
function syncHotRouteExport(liveRoute: AnyRouteWithPrivateProps) {
|
|
165
85
|
// routeTree.gen.ts mutates the original module export with generated
|
|
@@ -172,66 +92,6 @@ function handleRouteUpdate(
|
|
|
172
92
|
newRoute._fullPath = liveRoute._fullPath
|
|
173
93
|
newRoute._to = liveRoute._to
|
|
174
94
|
}
|
|
175
|
-
|
|
176
|
-
function getStoreMatch(matchId: string) {
|
|
177
|
-
return (
|
|
178
|
-
router.stores.pendingMatchStores.get(matchId)?.get() ||
|
|
179
|
-
router.stores.matchStores.get(matchId)?.get() ||
|
|
180
|
-
router.stores.cachedMatchStores.get(matchId)?.get()
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function getMatchList(matchId: string) {
|
|
185
|
-
const pendingMatches = router.stores.pendingMatches.get()
|
|
186
|
-
if (pendingMatches.some((match) => match.id === matchId)) {
|
|
187
|
-
return pendingMatches
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const activeMatches = router.stores.matches.get()
|
|
191
|
-
if (activeMatches.some((match) => match.id === matchId)) {
|
|
192
|
-
return activeMatches
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const cachedMatches = router.stores.cachedMatches.get()
|
|
196
|
-
if (cachedMatches.some((match) => match.id === matchId)) {
|
|
197
|
-
return cachedMatches
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return []
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function getParentMatch(match: AnyRouteMatch) {
|
|
204
|
-
const matchList = getMatchList(match.id)
|
|
205
|
-
const matchIndex = matchList.findIndex((item) => item.id === match.id)
|
|
206
|
-
|
|
207
|
-
if (matchIndex <= 0) {
|
|
208
|
-
return undefined
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const parentMatch = matchList[matchIndex - 1]!
|
|
212
|
-
return getStoreMatch(parentMatch.id) || parentMatch
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function rebuildMatchContextWithoutBeforeLoad(
|
|
216
|
-
match: AnyRouteMatchWithPrivateProps,
|
|
217
|
-
) {
|
|
218
|
-
const parentMatch = getParentMatch(match)
|
|
219
|
-
const getParentContext = (
|
|
220
|
-
router as unknown as {
|
|
221
|
-
getParentContext?: (
|
|
222
|
-
parentMatch?: AnyRouteMatch,
|
|
223
|
-
) => Record<string, unknown> | undefined
|
|
224
|
-
}
|
|
225
|
-
).getParentContext
|
|
226
|
-
const parentContext = getParentContext
|
|
227
|
-
? getParentContext.call(router, parentMatch)
|
|
228
|
-
: (parentMatch?.context ?? router.options.context)
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
...(parentContext ?? {}),
|
|
232
|
-
...(match.__routeContext ?? {}),
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
95
|
}
|
|
236
96
|
|
|
237
97
|
const handleRouteUpdateStr = handleRouteUpdate.toString()
|