UNPKG

@tanstack/router-plugin

Version:

Modern and scalable routing for React applications

1 lines 11 kB
{"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 routesByPath: Record<string, AnyRoute>\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 // 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 const removedKeys = new Set<string>()\n Object.keys(oldRoute.options).forEach((key) => {\n if (!(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 // 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 oldRoute.options = newRoute.options\n oldRoute.update(newRoute.options)\n oldRoute._componentsPromise = undefined\n oldRoute._lazyPromise = undefined\n\n router.routesById[oldRoute.id] = oldRoute\n router.routesByPath[oldRoute.fullPath] = oldRoute\n\n router.processedTree.matchCache.clear()\n router.processedTree.flatCache?.clear()\n router.processedTree.singleCache.clear()\n router.resolvePathCache.clear()\n walkReplaceSegmentTree(oldRoute, router.processedTree.segmentTree)\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 walkReplaceSegmentTree(\n route: AnyRouteWithPrivateProps,\n node: AnyRouter['processedTree']['segmentTree'],\n ) {\n if (node.route?.id === route.id) node.route = route\n if (node.index) walkReplaceSegmentTree(route, node.index)\n node.static?.forEach((child) => walkReplaceSegmentTree(route, child))\n node.staticInsensitive?.forEach((child) =>\n walkReplaceSegmentTree(route, child),\n )\n node.dynamic?.forEach((child) => walkReplaceSegmentTree(route, child))\n node.optional?.forEach((child) => walkReplaceSegmentTree(route, child))\n node.wildcard?.forEach((child) => walkReplaceSegmentTree(route, child))\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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"}