packages/devtools-vite/skills/devtools-vite-plugin/SKILL.md
Configure @tanstack/devtools-vite for source inspection (data-tsd-source, inspectHotkey, ignore patterns), console piping (client-to-server, server-to-client, levels), enhanced logging, server event bus (port, host, HTTPS), production stripping (removeDevtoolsOnBuild), editor integration (launch-editor, custom editor.open). Must be FIRST plugin in Vite config. Vite ^6 || ^7 only.
npx skillsauth add tanstack/devtools devtools-vite-pluginInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Configure @tanstack/devtools-vite -- the Vite plugin that enhances TanStack Devtools with source inspection, console piping, enhanced logging, a server event bus, production stripping, editor integration, and a plugin marketplace. The plugin returns an array of sub-plugins, all using enforce: 'pre', so it must be the FIRST plugin in the Vite config.
// vite.config.ts
import { devtools } from '@tanstack/devtools-vite'
export default {
plugins: [
devtools(),
// ... other plugins AFTER devtools
],
}
Install as a dev dependency:
pnpm add -D @tanstack/devtools-vite
There is also a defineDevtoolsConfig helper for type-safe config objects:
import { devtools, defineDevtoolsConfig } from '@tanstack/devtools-vite'
const config = defineDevtoolsConfig({
// fully typed options
})
export default {
plugins: [devtools(config)],
}
From packages/devtools-vite/src/index.ts:
devtools -- main plugin factory, returns Array<Plugin>defineDevtoolsConfig -- identity function for type-safe configTanStackDevtoolsViteConfig -- config type (re-exported)ConsoleLevel -- 'log' | 'warn' | 'error' | 'info' | 'debug'devtools() returns an array of Vite plugins. Each has enforce: 'pre' and only activates when its conditions are met (dev mode, serve command, etc.).
| Sub-plugin name | What it does | When active |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| @tanstack/devtools:inject-source | AST transform adding data-tsd-source attrs to JSX | dev mode + injectSource.enabled |
| @tanstack/devtools:config | Reserved for future config modifications | serve command only |
| @tanstack/devtools:custom-server | Starts ServerEventBus, registers middleware for open-source/console-pipe endpoints | dev mode |
| @tanstack/devtools:remove-devtools-on-build | Strips devtools imports/JSX from production bundles | build command or production mode + removeDevtoolsOnBuild |
| @tanstack/devtools:event-client-setup | Marketplace: listens for install/add-plugin events via devtoolsEventClient | dev mode + serve + not CI |
| @tanstack/devtools:console-pipe-transform | Injects runtime console-pipe code into entry files | dev mode + serve + consolePiping.enabled |
| @tanstack/devtools:better-console-logs | AST transform prepending source location to console.log/console.error | dev mode + enhancedLogs.enabled |
| @tanstack/devtools:inject-plugin | Detects which file imports TanStackDevtools (for marketplace injection) | dev mode + serve |
| @tanstack/devtools:connection-injection | Replaces __TANSTACK_DEVTOOLS_PORT__, __TANSTACK_DEVTOOLS_HOST__, __TANSTACK_DEVTOOLS_PROTOCOL__ placeholders | dev mode + serve |
Adds data-tsd-source="<relative-path>:<line>:<column>" attributes to every JSX opening element via oxc-parser + MagicString. This powers the "Go to Source" feature -- hold the inspect hotkey (default: Shift+Alt+Ctrl/Meta), hover over elements, click to open in editor.
Key behaviors:
<Fragment> and <React.Fragment>{...props}) -- this is because injecting the attribute would be overwritten by the spreadinjectSource.ignore.files patternsinjectSource.ignore.components patternsnode_modules, ?raw imports, /dist/, /build/Source files: packages/devtools-vite/src/inject-source.ts, packages/devtools-vite/src/matcher.ts
devtools({
injectSource: {
enabled: true,
ignore: {
files: ['node_modules', /.*\.test\.(js|ts|jsx|tsx)$/],
components: ['InternalComponent', /.*Provider$/],
},
},
})
Bidirectional console piping between client and server. Injects runtime code (IIFE) into entry files that:
Client side:
console[level] to batch and POST entries to /__tsd/console-pipe/__tsd/console-pipe/sse to receive server logs[Server] prefix[Client] prefixServer side (SSR/Nitro):
console[level] to batch and POST entries to <viteServerUrl>/__tsd/console-pipe/serverEntry file detection: looks for <html tag, StartClient, hydrateRoot, createRoot, or solid-js/web + render( in code.
Source files: packages/devtools-vite/src/virtual-console.ts, packages/devtools-vite/src/utils.ts (middleware handlers)
devtools({
consolePiping: {
enabled: true,
levels: ['log', 'warn', 'error', 'info', 'debug'],
},
})
AST transform that prepends source location info to console.log() and console.error() calls. In the browser, this renders as a clickable "Go to Source" link. On the server, it shows LOG <path>:<line>:<column> in chalk colors.
The transform inserts a spread of a conditional expression: ...(typeof window === 'undefined' ? serverLogMessage : browserLogMessage) as the first argument of the console call.
Source file: packages/devtools-vite/src/enhance-logs.ts
devtools({
enhancedLogs: {
enabled: true, // default
},
})
Removes all devtools code from production builds. The transform:
@tanstack/react-devtools, @tanstack/preact-devtools, @tanstack/solid-devtools, @tanstack/vue-devtools, @tanstack/devtoolsActive when: command !== 'serve' OR config.mode === 'production' (handles hosting providers like Cloudflare/Netlify that may not use build command but set mode to production).
Source file: packages/devtools-vite/src/remove-devtools.ts
devtools({
removeDevtoolsOnBuild: true, // default
})
A WebSocket + SSE server for devtools-to-client communication. Managed by @tanstack/devtools-event-bus/server.
Key behaviors:
__TANSTACK_DEVTOOLS_SERVER__, etc.) to survive HMR without restarting__TANSTACK_DEVTOOLS_PORT__ placeholder replacementSource file: packages/event-bus/src/server/server.ts
devtools({
eventBusConfig: {
port: 4206, // default
enabled: true, // default; set false for storybook/vitest
debug: false, // default; logs internal bus activity
},
})
Uses launch-editor to open source files in the editor. Default editor is VS Code. The editor.open callback receives (path, lineNumber, columnNumber) as strings.
The open-source flow: browser requests /__tsd/open-source?source=<encoded-path:line:col> --> Vite middleware parses source param --> calls editor.open.
Supported editors via launch-editor: VS Code, WebStorm, Sublime Text, Atom, and more. For unsupported editors, provide a custom editor.open function.
Source file: packages/devtools-vite/src/editor.ts
devtools({
editor: {
name: 'Cursor',
open: async (path, lineNumber, columnNumber) => {
// Custom editor open logic
// path is the absolute file path
// lineNumber and columnNumber are strings or undefined
},
},
})
When the dev server is running, listens for events via devtoolsEventClient:
install-devtools -- runs package manager install, then auto-injects plugin into devtools setup fileadd-plugin-to-devtools -- injects plugin import and JSX/function call into the file containing <TanStackDevtools>bump-package-version -- updates a package to a minimum versionmounted -- sends package.json and outdated deps to the UIAuto-detection of the devtools setup file: the inject-plugin sub-plugin scans transforms for files importing from @tanstack/react-devtools, @tanstack/solid-devtools, @tanstack/vue-devtools, etc., and stores the file ID.
Source files: packages/devtools-vite/src/inject-plugin.ts, packages/devtools-vite/src/package-manager.ts
All sub-plugins use enforce: 'pre'. They must transform code before framework plugins (React, Vue, Solid, etc.) process it. If devtools is not first, source injection and enhanced logs may silently fail because framework transforms remove the raw JSX before devtools can annotate it.
// WRONG
export default {
plugins: [
react(),
devtools(), // too late -- react() already transformed JSX
],
}
// CORRECT
export default {
plugins: [devtools(), react()],
}
@tanstack/devtools-vite has a peer dependency on vite ^6.0.0 || ^7.0.0. It uses Vite-specific APIs (configureServer, handleHotUpdate, transform with filter objects, Plugin type). It will not work with webpack, rspack, esbuild, or other bundlers. For non-Vite setups, use @tanstack/devtools-event-bus client directly without the Vite plugin.
Source injection, console piping, enhanced logging, the server event bus, and the marketplace only operate during development (config.mode === 'development' and command === 'serve'). In production builds, the only active sub-plugin is remove-devtools-on-build (which strips devtools code). Do not rely on any of these features being available at runtime in production.
The AST transform in inject-source.ts explicitly skips any JSX element that has a {...props} spread where props is the component's parameter name. This is intentional -- the spread would overwrite the injected data-tsd-source attribute. If source inspection doesn't work for a specific component, check if it spreads its props parameter.
// data-tsd-source will NOT be injected on <div> here
const MyComponent = (props) => {
return <div {...props}>content</div>
}
The default event bus port is 4206. When running multiple Vite dev servers concurrently (monorepo), the second server will hit EADDRINUSE. The event bus handles this by falling back to an OS-assigned port (port 0), and the actual port is injected via placeholder replacement. However, if you need predictable ports (e.g., for firewall rules), set different ports explicitly:
// Project A
devtools({ eventBusConfig: { port: 4206 } })
// Project B
devtools({ eventBusConfig: { port: 4207 } })
These are registered on the Vite dev server (not the event bus server):
| Endpoint | Method | Purpose |
| ------------------------------------------- | ------ | --------------------------------------------------------- |
| /__tsd/open-source?source=<path:line:col> | GET | Opens file in editor, returns HTML that closes the window |
| /__tsd/console-pipe | POST | Receives client console entries (batched JSON) |
| /__tsd/console-pipe/server | POST | Receives server-side console entries |
| /__tsd/console-pipe/sse | GET | SSE stream for broadcasting server logs to browser |
<TanStackDevtools> in your app (must be done before the Vite plugin provides value)packages/devtools-vite/src/plugin.ts -- Main plugin factory with all sub-plugins and config typepackages/devtools-vite/src/inject-source.ts -- AST transform for data-tsd-source injectionpackages/devtools-vite/src/enhance-logs.ts -- AST transform for enhanced console logspackages/devtools-vite/src/remove-devtools.ts -- Production stripping transformpackages/devtools-vite/src/virtual-console.ts -- Console pipe runtime code generatorpackages/devtools-vite/src/editor.ts -- Editor config type and launch-editor integrationpackages/devtools-vite/src/inject-plugin.ts -- Marketplace plugin injection into devtools setup filepackages/devtools-vite/src/utils.ts -- Middleware request handling and helperspackages/devtools-vite/src/matcher.ts -- Picomatch/RegExp pattern matcherpackages/event-bus/src/server/server.ts -- ServerEventBus implementation (WebSocket + SSE + EADDRINUSE fallback)tools
Handle devtools in production vs development. removeDevtoolsOnBuild, devDependency vs regular dependency, conditional imports, NoOp plugin variants for tree-shaking, non-Vite production exclusion patterns.
tools
Publish plugin to npm and submit to TanStack Devtools Marketplace. PluginMetadata registry format, plugin-registry.ts, pluginImport (importName, type), requires (packageName, minVersion), framework tagging, multi-framework submissions, featured plugins.
tools
Analyze library codebase for critical architecture and debugging points, add strategic event emissions. Identify middleware boundaries, state transitions, lifecycle hooks. Consolidate events (1 not 15), debounce high-frequency updates, DRY shared payload fields, guard emit() for production. Transparent server/client event bridging.
tools
Create typed EventClient for a library. Define event maps with typed payloads, pluginId auto-prepend namespacing, emit()/on()/onAll()/onAllPluginEvents() API. Connection lifecycle (5 retries, 300ms), event queuing, enabled/disabled state, SSR fallbacks, singleton pattern. Unique pluginId requirement to avoid event collisions.