--- name: writing-vite-devtools-integrations description: > Creates devtools integrations for Vite using @vitejs/devtools-kit. Use when building Vite plugins with devtools panels, RPC functions, dock entries, shared state, or any devtools-related functionality. Applies to files importing from @vitejs/devtools-kit or containing devtools.setup hooks in Vite plugins. --- # Vite DevTools Kit Build custom developer tools that integrate with Vite DevTools using `@vitejs/devtools-kit`. ## Core Concepts A DevTools plugin extends a Vite plugin with a `devtools.setup(ctx)` hook. The context provides: | Property | Purpose | | --------------------- | ----------------------------------------------------- | | `ctx.docks` | Register dock entries (iframe, action, custom-render) | | `ctx.views` | Host static files for UI | | `ctx.rpc` | Register RPC functions, broadcast to clients | | `ctx.rpc.sharedState` | Synchronized server-client state | | `ctx.viteConfig` | Resolved Vite configuration | | `ctx.viteServer` | Dev server instance (dev mode only) | | `ctx.mode` | `'dev'` or `'build'` | ## Quick Start: Minimal Plugin ```ts /// import type { Plugin } from 'vite' export default function myPlugin(): Plugin { return { name: 'my-plugin', devtools: { setup(ctx) { ctx.docks.register({ id: 'my-plugin', title: 'My Plugin', icon: 'ph:puzzle-piece-duotone', type: 'iframe', url: 'https://example.com/devtools', }) }, }, } } ``` ## Quick Start: Full Integration ```ts /// import type { Plugin } from 'vite' import { fileURLToPath } from 'node:url' import { defineRpcFunction } from '@vitejs/devtools-kit' export default function myAnalyzer(): Plugin { const data = new Map() return { name: 'my-analyzer', // Collect data in Vite hooks transform(code, id) { data.set(id, { size: code.length }) }, devtools: { setup(ctx) { // 1. Host static UI const clientPath = fileURLToPath(new URL('../dist/client', import.meta.url)) ctx.views.hostStatic('/.my-analyzer/', clientPath) // 2. Register dock entry ctx.docks.register({ id: 'my-analyzer', title: 'Analyzer', icon: 'ph:chart-bar-duotone', type: 'iframe', url: '/.my-analyzer/', }) // 3. Register RPC function ctx.rpc.register( defineRpcFunction({ name: 'my-analyzer:get-data', type: 'query', setup: () => ({ handler: async () => Array.from(data.entries()), }), }), ) }, }, } } ``` ## Namespacing Convention **CRITICAL**: Always prefix RPC functions, shared state keys, and dock IDs with your plugin name: ```ts // Good - namespaced 'my-plugin:get-modules' 'my-plugin:state' // Bad - may conflict 'get-modules' 'state' ``` ## Dock Entry Types | Type | Use Case | | --------------- | -------------------------------------------------------------- | | `iframe` | Full UI panels, dashboards (most common) | | `action` | Buttons that trigger client-side scripts (inspectors, toggles) | | `custom-render` | Direct DOM access in panel (framework mounting) | ### Iframe Entry ```ts ctx.docks.register({ id: 'my-plugin', title: 'My Plugin', icon: 'ph:house-duotone', type: 'iframe', url: '/.my-plugin/', }) ``` ### Action Entry ```ts ctx.docks.register({ id: 'my-inspector', title: 'Inspector', icon: 'ph:cursor-duotone', type: 'action', action: { importFrom: 'my-plugin/devtools-action', importName: 'default', }, }) ``` ### Custom Render Entry ```ts ctx.docks.register({ id: 'my-custom', title: 'Custom View', icon: 'ph:code-duotone', type: 'custom-render', renderer: { importFrom: 'my-plugin/devtools-renderer', importName: 'default', }, }) ``` ## RPC Functions ### Server-Side Definition ```ts import { defineRpcFunction } from '@vitejs/devtools-kit' const getModules = defineRpcFunction({ name: 'my-plugin:get-modules', type: 'query', // 'query' | 'action' | 'static' setup: ctx => ({ handler: async (filter?: string) => { // ctx has full DevToolsNodeContext return modules.filter(m => !filter || m.includes(filter)) }, }), }) // Register in setup ctx.rpc.register(getModules) ``` ### Client-Side Call (iframe) ```ts import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client' const rpc = await getDevToolsRpcClient() const modules = await rpc.call('my-plugin:get-modules', 'src/') ``` ### Client-Side Call (action/renderer script) ```ts import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client' export default function setup(ctx: DevToolsClientScriptContext) { ctx.current.events.on('entry:activated', async () => { const data = await ctx.current.rpc.call('my-plugin:get-data') }) } ``` ### Broadcasting to Clients ```ts // Server broadcasts to all clients ctx.rpc.broadcast({ method: 'my-plugin:on-update', args: [{ changedFile: '/src/main.ts' }], }) ``` ## Type Safety Extend the DevTools Kit interfaces for full type checking: ```ts // src/types.ts import '@vitejs/devtools-kit' declare module '@vitejs/devtools-kit' { interface DevToolsRpcServerFunctions { 'my-plugin:get-modules': (filter?: string) => Promise } interface DevToolsRpcClientFunctions { 'my-plugin:on-update': (data: { changedFile: string }) => void } interface DevToolsRpcSharedStates { 'my-plugin:state': MyPluginState } } ``` ## Shared State ### Server-Side ```ts const state = await ctx.rpc.sharedState.get('my-plugin:state', { initialValue: { count: 0, items: [] }, }) // Read console.log(state.value()) // Mutate (auto-syncs to clients) state.mutate(draft => { draft.count += 1 draft.items.push('new item') }) ``` ### Client-Side ```ts const client = await getDevToolsRpcClient() const state = await client.rpc.sharedState.get('my-plugin:state') // Read console.log(state.value()) // Subscribe to changes state.on('updated', newState => { console.log('State updated:', newState) }) ``` ## Client Scripts For action buttons and custom renderers: ```ts // src/devtools-action.ts import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client' export default function setup(ctx: DevToolsClientScriptContext) { ctx.current.events.on('entry:activated', () => { console.log('Action activated') // Your inspector/tool logic here }) ctx.current.events.on('entry:deactivated', () => { console.log('Action deactivated') // Cleanup }) } ``` Export from package.json: ```json { "exports": { ".": "./dist/index.mjs", "./devtools-action": "./dist/devtools-action.mjs" } } ``` ## Best Practices 1. **Always namespace** - Prefix all identifiers with your plugin name 2. **Use type augmentation** - Extend `DevToolsRpcServerFunctions` for type-safe RPC 3. **Keep state serializable** - No functions or circular references in shared state 4. **Batch mutations** - Use single `mutate()` call for multiple changes 5. **Host static files** - Use `ctx.views.hostStatic()` for your UI assets 6. **Use Iconify icons** - Prefer `ph:*` (Phosphor) icons: `icon: 'ph:chart-bar-duotone'` ## Further Reading - [RPC Patterns](./references/rpc-patterns.md) - Advanced RPC patterns and type utilities - [Dock Entry Types](./references/dock-entry-types.md) - Detailed dock configuration options - [Shared State Patterns](./references/shared-state-patterns.md) - Framework integration examples - [Project Structure](./references/project-structure.md) - Recommended file organization