本页还没有译文
本页 69 段还没译成Deutsch,下面显示的是英文原文。Architecture Overview
还没翻译译自英文(tanstack.com)本页 69 段都还没译
TanStack Devtools is a modular system of packages organized into three layers: Framework Adapters, Core Shell, and Event Transport. This architecture lets you use pre-built devtools panels or build your own custom ones, regardless of which frontend framework you use.
block-beta
columns 1
block:framework["Framework Layer"]
columns 4
React Vue Solid Preact
end
block:core["Core Layer"]
columns 3
Workbench["Devtools Workbench"] UI["UI components + private semantic theme"] Client["Event Client"]
end
block:transport["Transport Layer"]
columns 3
ClientBus["Client Event Bus"] ServerBus["Server Event Bus"] Vite["Vite Plugin"]
end
framework --> core
core --> transportPackage Dependency Graph
graph TD
subgraph Framework["Framework Adapters"]
react["@tanstack/react-devtools"]
vue["@tanstack/vue-devtools"]
solid["@tanstack/solid-devtools"]
preact["@tanstack/preact-devtools"]
end
subgraph Core["Core Layer"]
shell["@tanstack/devtools<br/><i>Core Workbench (Solid.js)</i>"]
client["@tanstack/devtools-client<br/><i>Core devtools events</i>"]
eventClient["@tanstack/devtools-event-client<br/><i>Generic EventClient</i>"]
ui["@tanstack/devtools-ui<br/><i>Shared UI + private semantic-theme owner</i>"]
clientBus["@tanstack/devtools-event-bus/client<br/><i>ClientEventBus</i>"]
end
subgraph Build["Build Layer"]
vite["@tanstack/devtools-vite"]
serverBus["@tanstack/devtools-event-bus/server<br/><i>ServerEventBus</i>"]
end
subgraph Utilities
utils["@tanstack/devtools-utils"]
end
react --> shell
vue --> shell
solid --> shell
preact --> shell
shell --> client
shell --> ui
shell --> clientBus
client --> eventClient
vite --> client
vite --> serverBus
utils --> uiEach framework adapter depends only on @tanstack/devtools. The core shell pulls in everything it needs, so end users install just two packages: their framework adapter and the Vite plugin.
Transport Layer
The transport layer handles event delivery between plugins, the devtools UI, and (optionally) a dev server. It is composed of three pieces.
ServerEventBus (@tanstack/devtools-event-bus/server)
Runs inside the Vite dev server process (Node.js). It creates an HTTP server (or piggybacks on Vite's existing server when HTTPS is enabled) that accepts both WebSocket and SSE connections. When a message arrives from any client, the server broadcasts it to every other connected client and dispatches it on a server-side EventTarget so server-side listeners (like the Vite plugin's package-manager helpers) can react to it.
Key details:
- Default port is
4206, auto-increments if the port is in use. - Handles
/__devtools/wsfor WebSocket upgrades,/__devtools/ssefor SSE streams, and/__devtools/sendfor SSE POST fallback. - Sets
globalThis.__TANSTACK_EVENT_TARGET__so thatEventClientinstances running on the server can dispatch events onto the same target.
ClientEventBus (@tanstack/devtools-event-bus/client)
Runs in the browser. Started automatically when the core shell mounts via TanStackDevtoolsCore.mount(). Its responsibilities:
- Local dispatch -- Listens for
tanstack-dispatch-eventCustomEvents onwindow, re-dispatches them as both a type-specific CustomEvent (e.g.my-plugin:state-update) and a globaltanstack-devtools-globalevent so listeners can subscribe to individual event types or to all events. - Server forwarding -- If connected to a server bus, forwards every dispatched event over WebSocket (preferred) or SSE POST fallback.
- Cross-tab sync -- Uses
BroadcastChannel('tanstack-devtools')to replicate events across browser tabs without round-tripping through the server. - Connection handshake -- Responds to
tanstack-connectevents withtanstack-connect-success, allowingEventClientinstances to discover the bus.
EventClient (@tanstack/devtools-event-client)
The high-level, typed API that plugins use to send and receive events. Each EventClient is created with a pluginId and a type map that defines the events it can emit and listen to.
import { EventClient } from '@tanstack/devtools-event-client'
type MyEvents = {
'state-update': { count: number }
'reset': void
}
const client = new EventClient<MyEvents>({ pluginId: 'my-plugin' })When you call client.emit('state-update', { count: 42 }), the EventClient:
- Dispatches a CustomEvent on its internal
EventTarget(for same-page listeners using thewithEventTargetoption). - Dispatches a
tanstack-dispatch-eventCustomEvent on the global target (typicallywindow), with a payload of{ type: 'my-plugin:state-update', payload: { count: 42 }, pluginId: 'my-plugin' }. - The
ClientEventBuspicks up thattanstack-dispatch-event, re-dispatches it as amy-plugin:state-updateCustomEvent onwindow, and forwards it to the server bus via WebSocket.
When you call client.on('state-update', callback), the EventClient registers a listener on the global target for my-plugin:state-update events, so it receives events regardless of whether they came from a local emit or from the server bus.
Note
The server bus is optional. Without the Vite plugin, EventClient still works for same-page communication via CustomEvent dispatch on window. Events simply won't cross tab or process boundaries.
Event Flow Summary
flowchart LR
emit["EventClient.emit()"] --> dispatch["CustomEvent<br/><b>tanstack-dispatch-event</b><br/>on window"]
dispatch --> bus["ClientEventBus"]
bus --> local["Re-dispatch as<br/><b>my-plugin:state-update</b><br/>on window"]
local --> onLocal["EventClient.on()<br/>callbacks fire"]
bus --> bc["BroadcastChannel"]
bc --> otherTabs["Other tabs<br/>receive event"]
bus --> ws["WebSocket"]
ws --> server["ServerEventBus"]
server --> broadcast["Broadcast to all<br/>connected clients"]
broadcast --> remote["EventClient.on()<br/>callbacks fire<br/>in other clients"]Core Layer
@tanstack/devtools -- The Shell
The devtools shell is a Solid.js application that renders the entire devtools UI. It exposes the TanStackDevtoolsCore class with three methods:
mount(el)-- Renders the Solid.js devtools application into the given DOM element. Starts aClientEventBusand lazy-loads the UI. Wraps everything in aDevtoolsProvider(reactive store for plugins, settings, state) and aPiPProvider(Picture-in-Picture support).unmount()-- Tears down the Solid.js app and stops the event bus.setConfig(config)-- Updates configuration and plugins at runtime. Plugins are reactive: adding or removing them updates the tab bar immediately.
The shell renders:
- A trigger button (the floating rainbow palm mark, customizable or replaceable)
- A resizable Workbench panel (docked to the top or bottom of the viewport, resizable via pointer or keyboard)
- A compact 36px TanStack Devtools header with Plugins, Marketplace, SEO, and Settings destinations. The palm emblem is inline SVG, so it stays sharp and takes its colour from the theme.
- A fixed-height 44px secondary strip for plugin and SEO navigation. It scrolls horizontally when space is limited, and a pull tab on its bottom edge folds it away behind the header. Folding changes nothing else — the panel keeps its height and the destination content keeps running — so the tab is rendered only on the destinations that have a strip.
- A separate Marketplace header tab that does not disturb mounted plugin panes
- A settings panel for theme, hotkeys, position, and other preferences
- Up to three simultaneous plugin mount frames, divided into equal widths by static separators
Settings and UI state (panel size, position, active tab, theme, and whether the secondary strip is folded) are persisted in localStorage so they survive page reloads.
The core shell owns the Workbench header, navigation, mount-frame geometry, separators, and surrounding light/dark surfaces. Each external plugin owns everything inside its mount target; core styling deliberately does not reach into plugin descendants. Detaching the Workbench uses a fixed 100vh Picture-in-Picture layout and restores the stored docked height when reattached.
Workbench surfaces and gutters
Two rules keep the shell reading as one surface:
- Chrome versus canvas. The header and every secondary strip paint the brand surface (cream in light mode, near-black in dark mode) and close with a translucent ink hairline. Destination content and plugin mount frames paint the workspace surface. A plugin pane can paint any colour it likes. The resize gutter between panes is invisible at rest and paints a line on hover or keyboard focus.
- One gutter.
WORKBENCH_GUTTER(16px, orWORKBENCH_GUTTER_NARROWat 12px below 430px) is the single inline gutter. The header, the strips, and each destination's content all start there, so the left edge is one column instead of three.MainPanel withPaddinguses the same value.
Colour comes from the semantic theme only. Raw hex values in core-owned source are rejected by tests/semantic-color-usage.test.ts unless they carry a narrow, path-scoped semantic-color-exempt marker. The current exemptions are third-party network marks, the source-inspector highlight alpha, and the trigger rainbow mark. Status colours (success, warning, error, info) mark real state; identity accents do not compete with them, so a card that is both featured and active keeps the neutral outline and lets its badge say which it is.
@tanstack/devtools-ui -- Component Library
A shared Solid.js component library used by the core shell and available for use in Solid.js plugins. Provides buttons, inputs, checkboxes, a JSON tree viewer, section layouts, and other UI primitives. The @tanstack/devtools-utils package also depends on it to provide framework-specific plugin helpers.
Core packages share a private semantic resolver through @tanstack/devtools-ui/internal. It supplies TanStack light/dark colors, typography, spacing, status roles, and focus treatment to core-owned UI and the accessibility plugin. This internal subpath is an implementation boundary, not a public theming API for application plugins.
@tanstack/devtools-client -- Core Event Client
A specialized EventClient pre-configured with pluginId: 'tanstack-devtools-core' and a fixed event map for devtools-internal operations:
mounted-- Fired when the devtools UI has mounted, triggers the server to send current package.json and outdated dependency data.package-json-read/outdated-deps-read-- Carries project metadata from the Vite server to the devtools UI.install-devtools/devtools-installed-- Request/response cycle for installing a plugin package from the marketplace.add-plugin-to-devtools/plugin-added-- Request/response cycle for injecting a plugin into the user's source code.trigger-toggled-- Synchronizes the open/closed state of the devtools panel.
This client is a singleton (devtoolsEventClient) used by both the core shell and the Vite plugin to coordinate.
Framework Layer
Each framework adapter is a thin wrapper that bridges its framework's component model to the core Solid.js shell. The pattern is the same across all adapters:
- Creates a
TanStackDevtoolsCoreinstance with the user's plugins and config. - Mounts it to a DOM element using the framework's lifecycle APIs, such as
useEffectin React,onMountedin Vue,$effectin Svelte, andonMountin Solid. - Converts framework-specific plugin definitions into the core's DOM-based
render(el, props)interface. Each adapter defines its own plugin type (e.g.TanStackDevtoolsReactPlugin) that accepts framework-native components, then wraps them in arendercallback that the core calls with a target DOM element and{ theme, devtoolsOpen }. - Uses the framework's rendering API to render plugin components into the core's DOM containers:
- React --
createPortal()fromreact-dom - Vue --
<Teleport :to="'#' + plugin.id" /> - Solid --
<Portal mount={el} /> - Preact -- Same portal pattern as React
- Svelte --
mount(component, { target: el, props }) - Angular --
createComponent()with the container's environment injector
- React --
The key insight: the core shell is always Solid.js, but your plugins run in your framework. Each adapter receives the same plugin props and renders a native framework component into a DOM element created by the Solid.js shell. The adapters bridge this gap so you never need to think about Solid.js unless you want to.
What an adapter does NOT do
Adapters do not re-implement the devtools UI, manage settings, handle events, or communicate with the server. All of that lives in the core shell. Adapters are intentionally minimal -- typically a single file under 300 lines.
Build Layer
@tanstack/devtools-vite is a collection of Vite plugins that enhance the development experience and clean up production builds. It returns an array of Vite plugins, each handling a specific concern:
Source injection (@tanstack/devtools:inject-source)
Parses JSX/TSX files with oxc-parser and injects data-tsd-source attributes on every JSX element via MagicString. These attributes encode the file path, line number, and column number of each element in source code, which the source inspector feature uses to implement click-to-open-in-editor.
Server event bus (@tanstack/devtools:custom-server)
Starts a ServerEventBus on the Vite dev server. Also sets up middleware for the go-to-source editor integration and bidirectional console piping (client logs appear in the terminal, server logs appear in the browser).
Production stripping (@tanstack/devtools:remove-devtools-on-build)
On production builds, transforms any file that imports from @tanstack/*-devtools to remove the devtools imports and JSX usage entirely. This means devtools add zero bytes to your production bundle.
Console piping (@tanstack/devtools:console-pipe-transform)
Injects a small runtime into your application's entry file that intercepts console.log/warn/error/info/debug calls and forwards them to the Vite dev server via HTTP POST. The server then broadcasts them to connected SSE clients, enabling server-to-browser log forwarding.
Enhanced logging (@tanstack/devtools:better-console-logs)
Transforms console.* calls to prepend source location information (file, line, column), making it possible to click a console log and jump directly to the source.
Plugin marketplace support (@tanstack/devtools:event-client-setup)
Listens for install-devtools events from the devtools UI, runs the package manager to install the requested package, and then uses AST manipulation to inject the plugin import and configuration into the user's source code.
Connection injection (@tanstack/devtools:connection-injection)
Replaces compile-time placeholders (__TANSTACK_DEVTOOLS_PORT__, __TANSTACK_DEVTOOLS_HOST__, __TANSTACK_DEVTOOLS_PROTOCOL__) in the event bus client code with the actual values from the running dev server, so the client automatically connects to the correct server.
WebMCP Tools
@tanstack/devtools-webmcp registers WebMCP tools on the page. A browser agent calls those tools during development. The package has no runtime dependencies.
The helper uses the browser modelContext. The helper does not join the event bus. These tools do not appear in a devtools panel.
The steps are in WebMCP Tools.
Data Flow
To tie everything together, here is what happens when a plugin emits an event end-to-end:
-
Your library code calls
eventClient.emit('state-update', data). -
EventClient constructs a payload
{ type: 'my-plugin:state-update', payload: data, pluginId: 'my-plugin' }and dispatches it as atanstack-dispatch-eventCustomEvent onwindow. -
ClientEventBus receives the
tanstack-dispatch-event. It does three things:- Dispatches a CustomEvent named
my-plugin:state-updateonwindowso that anyeventClient.on('state-update', callback)listeners on this page fire immediately. - Dispatches a
tanstack-devtools-globalCustomEvent onwindowso thatonAll()andonAllPluginEvents()listeners fire. - Posts the event to the
BroadcastChannelso other tabs receive it.
- Dispatches a CustomEvent named
-
If connected to the server bus, ClientEventBus also sends the event over WebSocket to
ServerEventBus. -
ServerEventBus receives the WebSocket message and broadcasts it to all other connected clients (WebSocket and SSE). It also dispatches the event on its server-side
EventTargetso server-side listeners (e.g., the Vite plugin) can react. -
In other browser tabs/windows, the event arrives via WebSocket from the server (or via BroadcastChannel from step 3). The local
ClientEventBusdispatches it as amy-plugin:state-updateCustomEvent, and anyeventClient.on('state-update', callback)listeners fire with the data.
Without the Vite plugin and server bus, steps 4-6 are skipped, but steps 1-3 still work. This means plugins can communicate within a single page without any server infrastructure -- the server bus just adds cross-tab and cross-process capabilities.