From 5b92b2efc06484db04343437417df14352c53d50 Mon Sep 17 00:00:00 2001 From: Alem Tuzlak Date: Thu, 14 Aug 2025 14:58:35 +0200 Subject: [PATCH] feat: enhance devtools to use event-bus --- packages/router-core/package.json | 3 +- packages/router-core/src/eventClient.ts | 9 + packages/router-core/src/index.ts | 1 + packages/router-core/src/router.ts | 154 +++++++++--------- packages/router-devtools-core/package.json | 5 +- .../src/TanStackRouterDevtoolsPanelCore.tsx | 13 +- pnpm-lock.yaml | 12 ++ 7 files changed, 116 insertions(+), 81 deletions(-) create mode 100644 packages/router-core/src/eventClient.ts diff --git a/packages/router-core/package.json b/packages/router-core/package.json index b3971dac40..96b070386a 100644 --- a/packages/router-core/package.json +++ b/packages/router-core/package.json @@ -79,6 +79,7 @@ "node": ">=12" }, "dependencies": { + "@tanstack/devtools-event-client": "0.2.1", "@tanstack/history": "workspace:*", "@tanstack/store": "^0.7.0", "cookie-es": "^1.2.2", @@ -90,4 +91,4 @@ "devDependencies": { "esbuild": "^0.25.0" } -} +} \ No newline at end of file diff --git a/packages/router-core/src/eventClient.ts b/packages/router-core/src/eventClient.ts new file mode 100644 index 0000000000..93c3d054d9 --- /dev/null +++ b/packages/router-core/src/eventClient.ts @@ -0,0 +1,9 @@ +import { EventClient } from "@tanstack/devtools-event-client"; + +interface RouterEventMap { + "tanstack-router:state-change": any, +} + +export const routerEventClient = new EventClient({ + pluginId: "tanstack-router" +}) \ No newline at end of file diff --git a/packages/router-core/src/index.ts b/packages/router-core/src/index.ts index 1b6f4dea87..68dca5ebcb 100644 --- a/packages/router-core/src/index.ts +++ b/packages/router-core/src/index.ts @@ -412,3 +412,4 @@ export type { ValidateUseSearchResult, ValidateUseParamsResult, } from './typePrimitives' +export { routerEventClient } from "./eventClient" \ No newline at end of file diff --git a/packages/router-core/src/router.ts b/packages/router-core/src/router.ts index cc5776882e..efdab838e4 100644 --- a/packages/router-core/src/router.ts +++ b/packages/router-core/src/router.ts @@ -34,6 +34,7 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams' import { rootRouteId } from './root' import { isRedirect, redirect } from './redirect' import { createLRUCache } from './lru-cache' +import { routerEventClient } from './eventClient' import type { ParsePathnameCache, Segment } from './path' import type { SearchParser, SearchSerializer } from './searchParams' import type { AnyRedirect, ResolvedRedirect } from './redirect' @@ -108,10 +109,10 @@ export type DefaultRemountDepsFn = ( opts: MakeRemountDepsOptionsUnion, ) => any -export interface DefaultRouterOptionsExtensions {} +export interface DefaultRouterOptionsExtensions { } export interface RouterOptionsExtensions - extends DefaultRouterOptionsExtensions {} + extends DefaultRouterOptionsExtensions { } export interface RouterOptions< TRouteTree extends AnyRoute, @@ -518,12 +519,12 @@ export type InferRouterContext = export type RouterContextOptions = AnyContext extends InferRouterContext - ? { - context?: InferRouterContext - } - : { - context: InferRouterContext - } + ? { + context?: InferRouterContext + } + : { + context: InferRouterContext + } export type RouterConstructorOptions< TRouteTree extends AnyRoute, @@ -695,14 +696,14 @@ export type AnyRouter = RouterCore export interface ViewTransitionOptions { types: - | Array - | ((locationChangeInfo: { - fromLocation?: ParsedLocation - toLocation: ParsedLocation - pathChanged: boolean - hrefChanged: boolean - hashChanged: boolean - }) => Array) + | Array + | ((locationChangeInfo: { + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + hashChanged: boolean + }) => Array) } export function defaultSerializeError(err: unknown) { @@ -713,7 +714,7 @@ export function defaultSerializeError(err: unknown) { } if (process.env.NODE_ENV === 'development') { - ;(obj as any).stack = err.stack + ; (obj as any).stack = err.stack } return obj @@ -748,12 +749,12 @@ export type CreateRouterFn = < options: undefined extends number ? 'strictNullChecks must be enabled in tsconfig.json' : RouterConstructorOptions< - TRouteTree, - TTrailingSlashOption, - TDefaultStructuralSharingOption, - TRouterHistory, - TDehydrated - >, + TRouteTree, + TTrailingSlashOption, + TDefaultStructuralSharingOption, + TRouterHistory, + TDehydrated + >, ) => RouterCore< TRouteTree, TTrailingSlashOption, @@ -827,6 +828,9 @@ export class RouterCore< parseSearch: options.parseSearch ?? defaultParseSearch, }) + this.__store.subscribe((state) => { + routerEventClient.emit("state-change", state.currentVal) + }) if (typeof document !== 'undefined') { self.__TSR_ROUTER__ = this } @@ -868,11 +872,11 @@ export class RouterCore< this.pathParamsDecodeCharMap = this.options.pathParamsAllowedCharacters ? new Map( - this.options.pathParamsAllowedCharacters.map((char) => [ - encodeURIComponent(char), - char, - ]), - ) + this.options.pathParamsAllowedCharacters.map((char) => [ + encodeURIComponent(char), + char, + ]), + ) : undefined if ( @@ -898,8 +902,8 @@ export class RouterCore< this.options.history ?? ((this.isServer ? createMemoryHistory({ - initialEntries: [this.basepath || '/'], - }) + initialEntries: [this.basepath || '/'], + }) : createBrowserHistory()) as TRouterHistory) this.updateLatestLocation() } @@ -1085,7 +1089,7 @@ export class RouterCore< foundRoute ? foundRoute.path !== '/' && routeParams['**'] : // Or if we didn't find a route and we have left over path - trimPathRight(next.pathname) + trimPathRight(next.pathname) ) { // If the user has defined an (old) 404 route, use it if (this.options.notFoundRoute) { @@ -1259,9 +1263,9 @@ export class RouterCore< } else { const status = route.options.loader || - route.options.beforeLoad || - route.lazyFn || - routeNeedsPreload(route) + route.options.beforeLoad || + route.lazyFn || + routeNeedsPreload(route) ? 'pending' : 'success' @@ -1480,9 +1484,9 @@ export class RouterCore< : (dest.params ?? true) === true ? fromParams : { - ...fromParams, - ...functionalUpdate(dest.params as any, fromParams), - } + ...fromParams, + ...functionalUpdate(dest.params as any, fromParams), + } // Interpolate the path first to get the actual resolved path, then match against that const interpolatedNextTo = interpolatePath({ @@ -1667,7 +1671,7 @@ export class RouterCore< '__hashScrollIntoViewOptions', ] as const ignoredProps.forEach((prop) => { - ;(next.state as any)[prop] = this.latestLocation.state[prop] + ; (next.state as any)[prop] = this.latestLocation.state[prop] }) const isEqual = deepEqual(next.state, this.latestLocation.state) ignoredProps.forEach((prop) => { @@ -1781,7 +1785,7 @@ export class RouterCore< try { new URL(`${href}`) reloadDocument = true - } catch {} + } catch { } } if (reloadDocument) { @@ -1936,18 +1940,18 @@ export class RouterCore< this.clearExpiredCache() }) - // - ;( - [ - [exitingMatches, 'onLeave'], - [enteringMatches, 'onEnter'], - [stayingMatches, 'onStay'], - ] as const - ).forEach(([matches, hook]) => { - matches.forEach((match) => { - this.looseRoutesById[match.routeId]!.options[hook]?.(match) + // + ; ( + [ + [exitingMatches, 'onLeave'], + [enteringMatches, 'onEnter'], + [stayingMatches, 'onStay'], + ] as const + ).forEach(([matches, hook]) => { + matches.forEach((match) => { + this.looseRoutesById[match.routeId]!.options[hook]?.(match) + }) }) - }) }) }, }) @@ -2035,11 +2039,11 @@ export class RouterCore< const resolvedViewTransitionTypes = typeof shouldViewTransition.types === 'function' ? shouldViewTransition.types( - getLocationChangeInfo({ - resolvedLocation: prevLocation, - location: next, - }), - ) + getLocationChangeInfo({ + resolvedLocation: prevLocation, + location: next, + }), + ) : shouldViewTransition.types startViewTransitionParams = { @@ -2148,7 +2152,7 @@ export class RouterCore< })) if (!(err as any).routeId) { - ;(err as any).routeId = match.routeId + ; (err as any).routeId = match.routeId } match._nonReactive.loadPromise?.resolve() @@ -2185,7 +2189,7 @@ export class RouterCore< try { await new Promise((resolveAll, rejectAll) => { - ;(async () => { + ; (async () => { try { const handleSerialError = ( index: number, @@ -2326,7 +2330,7 @@ export class RouterCore< // Update the match and prematurely resolve the loadMatches promise so that // the pending component can start rendering triggerOnReady() - } catch {} + } catch { } }, pendingMs) match._nonReactive.pendingTimeout = pendingTimeout } @@ -2696,19 +2700,19 @@ export class RouterCore< // Do nothing } else if (loaderShouldRunAsync && !sync) { loaderIsRunningAsync = true - ;(async () => { - try { - await runLoader() - const match = this.getMatch(matchId)! - match._nonReactive.loaderPromise?.resolve() - match._nonReactive.loadPromise?.resolve() - match._nonReactive.loaderPromise = undefined - } catch (err) { - if (isRedirect(err)) { - await this.navigate(err.options) + ; (async () => { + try { + await runLoader() + const match = this.getMatch(matchId)! + match._nonReactive.loaderPromise?.resolve() + match._nonReactive.loadPromise?.resolve() + match._nonReactive.loaderPromise = undefined + } catch (err) { + if (isRedirect(err)) { + await this.navigate(err.options) + } } - } - })() + })() } else if ( status !== 'success' || (loaderShouldRunAsync && sync) @@ -2978,9 +2982,9 @@ export class RouterCore< ...location, to: location.to ? this.resolvePathWithBase( - (location.from || '') as string, - location.to as string, - ) + (location.from || '') as string, + location.to as string, + ) : undefined, params: location.params || {}, leaveParams: true, @@ -3101,9 +3105,9 @@ export class RouterCore< } } -export class SearchParamError extends Error {} +export class SearchParamError extends Error { } -export class PathParamError extends Error {} +export class PathParamError extends Error { } const normalize = (str: string) => str.endsWith('/') && str.length > 1 ? str.slice(0, -1) : str diff --git a/packages/router-devtools-core/package.json b/packages/router-devtools-core/package.json index 0d47d48b9a..a4ffc369c3 100644 --- a/packages/router-devtools-core/package.json +++ b/packages/router-devtools-core/package.json @@ -62,6 +62,7 @@ "node": ">=12" }, "dependencies": { + "@tanstack/devtools-event-client": "^0.2.1", "clsx": "^2.1.1", "goober": "^2.1.16", "solid-js": "^1.9.5" @@ -70,10 +71,10 @@ "vite-plugin-solid": "^2.11.6" }, "peerDependencies": { - "tiny-invariant": "^1.3.3", "@tanstack/router-core": "workspace:^", "csstype": "^3.0.10", - "solid-js": ">=1.9.5" + "solid-js": ">=1.9.5", + "tiny-invariant": "^1.3.3" }, "peerDependenciesMeta": { "csstype": { diff --git a/packages/router-devtools-core/src/TanStackRouterDevtoolsPanelCore.tsx b/packages/router-devtools-core/src/TanStackRouterDevtoolsPanelCore.tsx index ea68e6c1fa..7842af948d 100644 --- a/packages/router-devtools-core/src/TanStackRouterDevtoolsPanelCore.tsx +++ b/packages/router-devtools-core/src/TanStackRouterDevtoolsPanelCore.tsx @@ -1,9 +1,12 @@ +import { routerEventClient } from "@tanstack/router-core" import { render } from 'solid-js/web' import { createSignal, lazy } from 'solid-js' import { DevtoolsOnCloseContext, ShadowDomTargetContext } from './context' -import type { JSX } from 'solid-js' +import type { JSX, Signal } from 'solid-js' import type { AnyRouter } from '@tanstack/router-core' + + interface TanStackRouterDevtoolsPanelCoreOptions { /** * The standard React style object used to style a component with inline styles @@ -38,7 +41,7 @@ interface TanStackRouterDevtoolsPanelCoreOptions { } class TanStackRouterDevtoolsPanelCore { - #router: any + #router: Signal #routerState: any #style: any #className: any @@ -74,6 +77,10 @@ class TanStackRouterDevtoolsPanelCore { const dispose = render(() => { const [router] = this.#router const [routerState] = this.#routerState + routerEventClient.on("state-change", e => { + router().__store.setState(e.payload) + }) + const [style] = this.#style const [className] = this.#className const shadowDOMTarget = this.#shadowDOMTarget @@ -94,7 +101,7 @@ class TanStackRouterDevtoolsPanelCore { {}, + onCloseClick: () => { }, }} > =18'} hasBin: true + '@tanstack/devtools-event-client@0.2.1': + resolution: {integrity: sha512-78AAfg9KfSDE+pQLRJo20bxX3A40ea14kFqNmzuMb7zFJNfR5L87kAEOju6QXu7cbk9r2r7EEV2a8PgHOsD7Kg==} + engines: {node: '>=18'} + '@tanstack/query-core@5.66.0': resolution: {integrity: sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw==} @@ -19264,6 +19274,8 @@ snapshots: - typescript - vite + '@tanstack/devtools-event-client@0.2.1': {} + '@tanstack/query-core@5.66.0': {} '@tanstack/query-devtools@5.67.2': {}