@tanstack/router-plugin 1.168.16 → 1.168.17

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.
@@ -33,6 +33,7 @@ function handleRouteUpdate(routeId, newRoute) {
33
33
  oldRoute._componentsPromise = void 0;
34
34
  oldRoute._lazyPromise = void 0;
35
35
  router.setRoutes(router.buildRouteTree());
36
+ syncHotRouteExport(oldRoute);
36
37
  router.resolvePathCache.clear();
37
38
  const filter = (m) => m.routeId === oldRoute.id;
38
39
  const activeMatch = router.stores.matches.get().find(filter);
@@ -65,6 +66,14 @@ function handleRouteUpdate(routeId, newRoute) {
65
66
  sync: true
66
67
  });
67
68
  }
69
+ function syncHotRouteExport(liveRoute) {
70
+ newRoute.options = liveRoute.options;
71
+ newRoute.parentRoute = liveRoute.parentRoute;
72
+ newRoute._path = liveRoute._path;
73
+ newRoute._id = liveRoute._id;
74
+ newRoute._fullPath = liveRoute._fullPath;
75
+ newRoute._to = liveRoute._to;
76
+ }
68
77
  function getStoreMatch(matchId) {
69
78
  return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get();
70
79
  }
@@ -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 _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 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 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 {\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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"}
@@ -18,13 +18,30 @@ function createViteHmrStatement(stableRouteOptionKeys, opts = {}) {
18
18
  if (import.meta.hot) {
19
19
  const hot = import.meta.hot
20
20
  const hotData = hot.data ??= {}
21
+ const handleRouteUpdate = ${handleRouteUpdateCode}
22
+ const initialRouteId = ${routeIdFallback} ?? hotData['tsr-route-id']
23
+ if (initialRouteId) {
24
+ hotData['tsr-route-id'] = initialRouteId
25
+ }
26
+ const existingRoute =
27
+ typeof window !== 'undefined' && initialRouteId
28
+ ? window.__TSR_ROUTER__?.routesById?.[initialRouteId]
29
+ : undefined
30
+ if (initialRouteId && existingRoute && existingRoute !== Route) {
31
+ handleRouteUpdate(initialRouteId, Route)
32
+ hotData['tsr-route-update-handled'] = Route
33
+ }
21
34
  hot.accept((newModule) => {
22
35
  if (Route && newModule && newModule.Route) {
23
36
  const routeId = hotData['tsr-route-id'] ?? ${routeIdFallback}
24
37
  if (routeId) {
25
38
  hotData['tsr-route-id'] = routeId
26
39
  }
27
- (${handleRouteUpdateCode})(routeId, newModule.Route)
40
+ if (hotData['tsr-route-update-handled'] === newModule.Route) {
41
+ delete hotData['tsr-route-update-handled']
42
+ return
43
+ }
44
+ handleRouteUpdate(routeId, newModule.Route)
28
45
  }
29
46
  })
30
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vite-adapter.cjs","names":[],"sources":["../../../../src/core/hmr/vite-adapter.ts"],"sourcesContent":["import * as template from '@babel/template'\nimport { getHandleRouteUpdateCode } from './handle-route-update'\nimport type * as t from '@babel/types'\n\n/**\n * Emits HMR accept code for Vite / native ESM HMR: `import.meta.hot.accept`\n * with a callback that receives the freshly re-imported module.\n *\n * `targetFramework` is currently unused — Vite's framework-specific fast-refresh\n * plugins handle component body patching via their own accept boundaries — but\n * we take it for API symmetry with `createWebpackHmrStatement`.\n */\nexport function createViteHmrStatement(\n stableRouteOptionKeys: Array<string>,\n opts: {\n routeId?: string\n } = {},\n): Array<t.Statement> {\n const handleRouteUpdateCode = getHandleRouteUpdateCode(stableRouteOptionKeys)\n // The replacement Route object can be uninitialized; keep a generated id as\n // fallback for the existing router route we need to patch.\n const routeIdFallback =\n typeof opts.routeId === 'string' ? JSON.stringify(opts.routeId) : 'Route.id'\n\n return [\n template.statement(\n `\nif (import.meta.hot) {\n const hot = import.meta.hot\n const hotData = hot.data ??= {}\n hot.accept((newModule) => {\n if (Route && newModule && newModule.Route) {\n const routeId = hotData['tsr-route-id'] ?? ${routeIdFallback}\n if (routeId) {\n hotData['tsr-route-id'] = routeId\n }\n (${handleRouteUpdateCode})(routeId, newModule.Route)\n }\n })\n}\n`,\n {\n syntacticPlaceholders: true,\n },\n )(),\n ]\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,uBACd,uBACA,OAEI,CAAC,GACe;CACpB,MAAM,wBAAwB,4BAAA,yBAAyB,qBAAqB;CAG5E,MAAM,kBACJ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,KAAK,OAAO,IAAI;CAEpE,OAAO,CACL,gBAAS,UACP;;;;;;mDAM6C,gBAAgB;;;;SAI1D,sBAAsB;;;;GAKzB,EACE,uBAAuB,KACzB,CACF,EAAE,CACJ;AACF"}
1
+ {"version":3,"file":"vite-adapter.cjs","names":[],"sources":["../../../../src/core/hmr/vite-adapter.ts"],"sourcesContent":["import * as template from '@babel/template'\nimport { getHandleRouteUpdateCode } from './handle-route-update'\nimport type * as t from '@babel/types'\n\n/**\n * Emits HMR accept code for Vite / native ESM HMR: `import.meta.hot.accept`\n * with a callback that receives the freshly re-imported module.\n *\n * `targetFramework` is currently unused — Vite's framework-specific fast-refresh\n * plugins handle component body patching via their own accept boundaries — but\n * we take it for API symmetry with `createWebpackHmrStatement`.\n */\nexport function createViteHmrStatement(\n stableRouteOptionKeys: Array<string>,\n opts: {\n routeId?: string\n } = {},\n): Array<t.Statement> {\n const handleRouteUpdateCode = getHandleRouteUpdateCode(stableRouteOptionKeys)\n // The replacement Route object can be uninitialized; keep a generated id as\n // fallback for the existing router route we need to patch.\n const routeIdFallback =\n typeof opts.routeId === 'string' ? JSON.stringify(opts.routeId) : 'Route.id'\n\n return [\n template.statement(\n `\nif (import.meta.hot) {\n const hot = import.meta.hot\n const hotData = hot.data ??= {}\n const handleRouteUpdate = ${handleRouteUpdateCode}\n const initialRouteId = ${routeIdFallback} ?? hotData['tsr-route-id']\n if (initialRouteId) {\n hotData['tsr-route-id'] = initialRouteId\n }\n const existingRoute =\n typeof window !== 'undefined' && initialRouteId\n ? window.__TSR_ROUTER__?.routesById?.[initialRouteId]\n : undefined\n if (initialRouteId && existingRoute && existingRoute !== Route) {\n handleRouteUpdate(initialRouteId, Route)\n hotData['tsr-route-update-handled'] = Route\n }\n hot.accept((newModule) => {\n if (Route && newModule && newModule.Route) {\n const routeId = hotData['tsr-route-id'] ?? ${routeIdFallback}\n if (routeId) {\n hotData['tsr-route-id'] = routeId\n }\n if (hotData['tsr-route-update-handled'] === newModule.Route) {\n delete hotData['tsr-route-update-handled']\n return\n }\n handleRouteUpdate(routeId, newModule.Route)\n }\n })\n}\n`,\n {\n syntacticPlaceholders: true,\n },\n )(),\n ]\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,uBACd,uBACA,OAEI,CAAC,GACe;CACpB,MAAM,wBAAwB,4BAAA,yBAAyB,qBAAqB;CAG5E,MAAM,kBACJ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,KAAK,OAAO,IAAI;CAEpE,OAAO,CACL,gBAAS,UACP;;;;8BAIwB,sBAAsB;2BACzB,gBAAgB;;;;;;;;;;;;;;mDAcQ,gBAAgB;;;;;;;;;;;;GAa7D,EACE,uBAAuB,KACzB,CACF,EAAE,CACJ;AACF"}
@@ -33,6 +33,7 @@ function handleRouteUpdate(routeId, newRoute) {
33
33
  oldRoute._componentsPromise = void 0;
34
34
  oldRoute._lazyPromise = void 0;
35
35
  router.setRoutes(router.buildRouteTree());
36
+ syncHotRouteExport(oldRoute);
36
37
  router.resolvePathCache.clear();
37
38
  const filter = (m) => m.routeId === oldRoute.id;
38
39
  const activeMatch = router.stores.matches.get().find(filter);
@@ -65,6 +66,14 @@ function handleRouteUpdate(routeId, newRoute) {
65
66
  sync: true
66
67
  });
67
68
  }
69
+ function syncHotRouteExport(liveRoute) {
70
+ newRoute.options = liveRoute.options;
71
+ newRoute.parentRoute = liveRoute.parentRoute;
72
+ newRoute._path = liveRoute._path;
73
+ newRoute._id = liveRoute._id;
74
+ newRoute._fullPath = liveRoute._fullPath;
75
+ newRoute._to = liveRoute._to;
76
+ }
68
77
  function getStoreMatch(matchId) {
69
78
  return router.stores.pendingMatchStores.get(matchId)?.get() || router.stores.matchStores.get(matchId)?.get() || router.stores.cachedMatchStores.get(matchId)?.get();
70
79
  }
@@ -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 _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 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 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 {\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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"}
@@ -16,13 +16,30 @@ function createViteHmrStatement(stableRouteOptionKeys, opts = {}) {
16
16
  if (import.meta.hot) {
17
17
  const hot = import.meta.hot
18
18
  const hotData = hot.data ??= {}
19
+ const handleRouteUpdate = ${handleRouteUpdateCode}
20
+ const initialRouteId = ${routeIdFallback} ?? hotData['tsr-route-id']
21
+ if (initialRouteId) {
22
+ hotData['tsr-route-id'] = initialRouteId
23
+ }
24
+ const existingRoute =
25
+ typeof window !== 'undefined' && initialRouteId
26
+ ? window.__TSR_ROUTER__?.routesById?.[initialRouteId]
27
+ : undefined
28
+ if (initialRouteId && existingRoute && existingRoute !== Route) {
29
+ handleRouteUpdate(initialRouteId, Route)
30
+ hotData['tsr-route-update-handled'] = Route
31
+ }
19
32
  hot.accept((newModule) => {
20
33
  if (Route && newModule && newModule.Route) {
21
34
  const routeId = hotData['tsr-route-id'] ?? ${routeIdFallback}
22
35
  if (routeId) {
23
36
  hotData['tsr-route-id'] = routeId
24
37
  }
25
- (${handleRouteUpdateCode})(routeId, newModule.Route)
38
+ if (hotData['tsr-route-update-handled'] === newModule.Route) {
39
+ delete hotData['tsr-route-update-handled']
40
+ return
41
+ }
42
+ handleRouteUpdate(routeId, newModule.Route)
26
43
  }
27
44
  })
28
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vite-adapter.js","names":[],"sources":["../../../../src/core/hmr/vite-adapter.ts"],"sourcesContent":["import * as template from '@babel/template'\nimport { getHandleRouteUpdateCode } from './handle-route-update'\nimport type * as t from '@babel/types'\n\n/**\n * Emits HMR accept code for Vite / native ESM HMR: `import.meta.hot.accept`\n * with a callback that receives the freshly re-imported module.\n *\n * `targetFramework` is currently unused — Vite's framework-specific fast-refresh\n * plugins handle component body patching via their own accept boundaries — but\n * we take it for API symmetry with `createWebpackHmrStatement`.\n */\nexport function createViteHmrStatement(\n stableRouteOptionKeys: Array<string>,\n opts: {\n routeId?: string\n } = {},\n): Array<t.Statement> {\n const handleRouteUpdateCode = getHandleRouteUpdateCode(stableRouteOptionKeys)\n // The replacement Route object can be uninitialized; keep a generated id as\n // fallback for the existing router route we need to patch.\n const routeIdFallback =\n typeof opts.routeId === 'string' ? JSON.stringify(opts.routeId) : 'Route.id'\n\n return [\n template.statement(\n `\nif (import.meta.hot) {\n const hot = import.meta.hot\n const hotData = hot.data ??= {}\n hot.accept((newModule) => {\n if (Route && newModule && newModule.Route) {\n const routeId = hotData['tsr-route-id'] ?? ${routeIdFallback}\n if (routeId) {\n hotData['tsr-route-id'] = routeId\n }\n (${handleRouteUpdateCode})(routeId, newModule.Route)\n }\n })\n}\n`,\n {\n syntacticPlaceholders: true,\n },\n )(),\n ]\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,uBACd,uBACA,OAEI,CAAC,GACe;CACpB,MAAM,wBAAwB,yBAAyB,qBAAqB;CAG5E,MAAM,kBACJ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,KAAK,OAAO,IAAI;CAEpE,OAAO,CACL,SAAS,UACP;;;;;;mDAM6C,gBAAgB;;;;SAI1D,sBAAsB;;;;GAKzB,EACE,uBAAuB,KACzB,CACF,EAAE,CACJ;AACF"}
1
+ {"version":3,"file":"vite-adapter.js","names":[],"sources":["../../../../src/core/hmr/vite-adapter.ts"],"sourcesContent":["import * as template from '@babel/template'\nimport { getHandleRouteUpdateCode } from './handle-route-update'\nimport type * as t from '@babel/types'\n\n/**\n * Emits HMR accept code for Vite / native ESM HMR: `import.meta.hot.accept`\n * with a callback that receives the freshly re-imported module.\n *\n * `targetFramework` is currently unused — Vite's framework-specific fast-refresh\n * plugins handle component body patching via their own accept boundaries — but\n * we take it for API symmetry with `createWebpackHmrStatement`.\n */\nexport function createViteHmrStatement(\n stableRouteOptionKeys: Array<string>,\n opts: {\n routeId?: string\n } = {},\n): Array<t.Statement> {\n const handleRouteUpdateCode = getHandleRouteUpdateCode(stableRouteOptionKeys)\n // The replacement Route object can be uninitialized; keep a generated id as\n // fallback for the existing router route we need to patch.\n const routeIdFallback =\n typeof opts.routeId === 'string' ? JSON.stringify(opts.routeId) : 'Route.id'\n\n return [\n template.statement(\n `\nif (import.meta.hot) {\n const hot = import.meta.hot\n const hotData = hot.data ??= {}\n const handleRouteUpdate = ${handleRouteUpdateCode}\n const initialRouteId = ${routeIdFallback} ?? hotData['tsr-route-id']\n if (initialRouteId) {\n hotData['tsr-route-id'] = initialRouteId\n }\n const existingRoute =\n typeof window !== 'undefined' && initialRouteId\n ? window.__TSR_ROUTER__?.routesById?.[initialRouteId]\n : undefined\n if (initialRouteId && existingRoute && existingRoute !== Route) {\n handleRouteUpdate(initialRouteId, Route)\n hotData['tsr-route-update-handled'] = Route\n }\n hot.accept((newModule) => {\n if (Route && newModule && newModule.Route) {\n const routeId = hotData['tsr-route-id'] ?? ${routeIdFallback}\n if (routeId) {\n hotData['tsr-route-id'] = routeId\n }\n if (hotData['tsr-route-update-handled'] === newModule.Route) {\n delete hotData['tsr-route-update-handled']\n return\n }\n handleRouteUpdate(routeId, newModule.Route)\n }\n })\n}\n`,\n {\n syntacticPlaceholders: true,\n },\n )(),\n ]\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,uBACd,uBACA,OAEI,CAAC,GACe;CACpB,MAAM,wBAAwB,yBAAyB,qBAAqB;CAG5E,MAAM,kBACJ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,KAAK,OAAO,IAAI;CAEpE,OAAO,CACL,SAAS,UACP;;;;8BAIwB,sBAAsB;2BACzB,gBAAgB;;;;;;;;;;;;;;mDAcQ,gBAAgB;;;;;;;;;;;;GAa7D,EACE,uBAAuB,KACzB,CACF,EAAE,CACJ;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-plugin",
3
- "version": "1.168.16",
3
+ "version": "1.168.17",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -106,8 +106,8 @@
106
106
  "chokidar": "^5.0.0",
107
107
  "unplugin": "^3.0.0",
108
108
  "zod": "^4.4.3",
109
- "@tanstack/router-core": "1.171.11",
110
- "@tanstack/router-generator": "1.167.15",
109
+ "@tanstack/router-core": "1.171.12",
110
+ "@tanstack/router-generator": "1.167.16",
111
111
  "@tanstack/router-utils": "1.162.2"
112
112
  },
113
113
  "devDependencies": {
@@ -120,7 +120,7 @@
120
120
  "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0",
121
121
  "vite-plugin-solid": "^2.11.10 || ^3.0.0-0",
122
122
  "webpack": ">=5.92.0",
123
- "@tanstack/react-router": "^1.170.13"
123
+ "@tanstack/react-router": "^1.170.14"
124
124
  },
125
125
  "peerDependenciesMeta": {
126
126
  "@rsbuild/core": {
@@ -7,6 +7,7 @@ import type {
7
7
 
8
8
  type AnyRouteWithPrivateProps = AnyRoute & {
9
9
  options: Record<string, unknown>
10
+ parentRoute: AnyRoute
10
11
  _componentsPromise?: Promise<void>
11
12
  _lazyPromise?: Promise<void>
12
13
  update: (options: Record<string, unknown>) => unknown
@@ -109,6 +110,7 @@ function handleRouteUpdate(
109
110
  oldRoute._lazyPromise = undefined
110
111
 
111
112
  router.setRoutes(router.buildRouteTree())
113
+ syncHotRouteExport(oldRoute)
112
114
  router.resolvePathCache.clear()
113
115
 
114
116
  const filter = (m: AnyRouteMatch) => m.routeId === oldRoute.id
@@ -159,6 +161,18 @@ function handleRouteUpdate(
159
161
  router.invalidate({ filter, sync: true })
160
162
  }
161
163
 
164
+ function syncHotRouteExport(liveRoute: AnyRouteWithPrivateProps) {
165
+ // routeTree.gen.ts mutates the original module export with generated
166
+ // routing state. Mirror that state onto the fresh HMR export too, so
167
+ // aliased route imports keep working after the module is hot-reloaded.
168
+ newRoute.options = liveRoute.options
169
+ newRoute.parentRoute = liveRoute.parentRoute
170
+ newRoute._path = liveRoute._path
171
+ newRoute._id = liveRoute._id
172
+ newRoute._fullPath = liveRoute._fullPath
173
+ newRoute._to = liveRoute._to
174
+ }
175
+
162
176
  function getStoreMatch(matchId: string) {
163
177
  return (
164
178
  router.stores.pendingMatchStores.get(matchId)?.get() ||
@@ -28,13 +28,30 @@ export function createViteHmrStatement(
28
28
  if (import.meta.hot) {
29
29
  const hot = import.meta.hot
30
30
  const hotData = hot.data ??= {}
31
+ const handleRouteUpdate = ${handleRouteUpdateCode}
32
+ const initialRouteId = ${routeIdFallback} ?? hotData['tsr-route-id']
33
+ if (initialRouteId) {
34
+ hotData['tsr-route-id'] = initialRouteId
35
+ }
36
+ const existingRoute =
37
+ typeof window !== 'undefined' && initialRouteId
38
+ ? window.__TSR_ROUTER__?.routesById?.[initialRouteId]
39
+ : undefined
40
+ if (initialRouteId && existingRoute && existingRoute !== Route) {
41
+ handleRouteUpdate(initialRouteId, Route)
42
+ hotData['tsr-route-update-handled'] = Route
43
+ }
31
44
  hot.accept((newModule) => {
32
45
  if (Route && newModule && newModule.Route) {
33
46
  const routeId = hotData['tsr-route-id'] ?? ${routeIdFallback}
34
47
  if (routeId) {
35
48
  hotData['tsr-route-id'] = routeId
36
49
  }
37
- (${handleRouteUpdateCode})(routeId, newModule.Route)
50
+ if (hotData['tsr-route-update-handled'] === newModule.Route) {
51
+ delete hotData['tsr-route-update-handled']
52
+ return
53
+ }
54
+ handleRouteUpdate(routeId, newModule.Route)
38
55
  }
39
56
  })
40
57
  }