- name
- writing-vite-devtools-integrations
- description
- Creates devtools integrations that mount inside the Vite DevTools hub via @vitejs/devtools-kit. Use when building Vite plugins with devtools panels, RPC functions, dock entries, shared state, messages/notifications, terminals, command palette entries, or any hub-level integration. Applies to files importing from @vitejs/devtools-kit or containing devtools.setup hooks in Vite plugins. For building one portable devtool integration without a hub (CLI, static deploy, MCP), see the `devframe` skill instead.
# Vite DevTools Kit
**`@vitejs/devtools-kit` is the hub that unites many devtools integrations.** It owns the cross-tool surface — docking, the command palette, terminal aggregation, cross-tool toasts — and wraps the framework-neutral [Devframe](https://devfra.me/) container with the Vite-specific glue (`Plugin.devtools.setup`).
If you have a portable Devframe app already, drop it in via `createPluginFromDevframe(d)` from `@vitejs/devtools-kit/node` and the kit auto-derives the iframe dock entry. If you're authoring a Vite-specific integration that needs hub features directly, reach for `Plugin.devtools.setup`.
## Core Concepts
A DevTools plugin extends a Vite plugin with a `devtools.setup(ctx)` hook. The context is the **kit-augmented context** (`KitNodeContext` extended with Vite-specific fields) — it carries Devframe's portable surface plus the hub-only subsystems the kit owns:
| Property | Layer | Purpose |
|----------|-------|---------|
| `ctx.docks` | **kit** | Register dock entries (iframe, action, custom-render, launcher, json-render) |
| `ctx.terminals` | **kit** | Spawn and manage child processes with streaming terminal output |
| `ctx.messages` | **kit** | Emit structured message entries and toast notifications |
| `ctx.commands` | **kit** | Register executable commands with keyboard shortcuts and palette visibility |
| `ctx.rpc` | devframe | Register RPC functions, broadcast to clients |
| `ctx.rpc.sharedState` | devframe | Synchronized server-client state |
| `ctx.rpc.streaming` | devframe | Streaming channels — chunk-style server↔client data with cancellation, replay, Web Streams interop |
| `ctx.views` | devframe | Host static files for UI (`hostStatic(base, distDir)`) |
| `ctx.diagnostics` | devframe | Structured diagnostics host (nostics) — register custom error codes |
| `ctx.createJsonRenderer` | core | Create server-side JSON render specs for zero-client-code UIs |
| `ctx.viteConfig` | core | Resolved Vite configuration |
| `ctx.viteServer` | core | Dev server instance (dev mode only) |
| `ctx.mode` | devframe | `'dev'` or `'build'` |
## Quick Start: Bridge a Devframe App
If you already have a portable Devframe definition, this is the one-liner. The kit synthesises the iframe dock entry from the definition's `id` / `name` / `icon` / `basePath`, mounts the SPA via `views.hostStatic`, runs the devtool's own `setup`, then runs the optional kit-only `options.setup`.
```ts
// vite.config.ts
import { createPluginFromDevframe } from '@vitejs/devtools-kit/node'
import devtool from './my-devtool'
export default {
plugins: [
createPluginFromDevframe(devtool, {
// Optional kit-only setup for hub features:
setup(ctx) {
ctx.commands.register({
id: 'my-devtool:clear-cache',
title: 'Clear Cache',
handler: () => {/* ... */},
})
},
}),
],
}
```
## Quick Start: Minimal Hub-Native Plugin
When the integration is intrinsically tied to Vite (it inspects the resolved config, augments middleware, etc.), reach for `Plugin.devtools.setup` directly:
```ts
/// <reference types="@vitejs/devtools-kit" />
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
/// <reference types="@vitejs/devtools-kit" />
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<string, { size: number }>()
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, dock IDs, and command IDs with your plugin name:
```ts
// Good - namespaced
'my-plugin:get-modules'
'my-plugin:state'
'my-plugin:clear-cache' // command ID
// Bad - may conflict
'get-modules'
'state'
```
## Dock Entry Types
| Type | Use Case |
|------|----------|
| `iframe` | Full UI panels, dashboards (most common) |
| `json-render` | Server-side JSON specs — zero client code needed |
| `action` | Buttons that trigger client-side scripts (inspectors, toggles) |
| `custom-render` | Direct DOM access in panel (framework mounting) |
| `launcher` | Actionable setup cards for initialization tasks |
### Iframe Entry
```ts
ctx.docks.register({
id: 'my-plugin',
title: 'My Plugin',
icon: 'ph:house-duotone',
type: 'iframe',
url: '/__my-plugin/',
})
```
Iframes can also point at a **remote-hosted URL** that connects back via WebSocket, so you don't have to ship a SPA dist with your plugin:
```ts
ctx.docks.register({
id: 'my-remote-tool',
title: 'My Tool',
icon: 'ph:cloud-duotone',
type: 'iframe',
url: 'https://example.com/devtools',
remote: true, // or { transport: 'query', originLock: false }
})
```
On the hosted page, call `connectRemoteDevTools()` from `@vitejs/devtools-kit/client` to get a fully connected `DevToolsRpcClient`. Dev-mode only — auto-hidden in build mode. See [Remote Client Patterns](./references/remote-client-patterns.md).
### 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',
},
})
```
### JSON Render Entry
Build UIs entirely from server-side TypeScript — no client code needed:
```ts
const ui = ctx.createJsonRenderer({
root: 'root',
elements: {
root: {
type: 'Stack',
props: { direction: 'vertical', gap: 12 },
children: ['heading', 'info'],
},
heading: {
type: 'Text',
props: { content: 'Hello from JSON!', variant: 'heading' },
},
info: {
type: 'KeyValueTable',
props: {
entries: [
{ key: 'Version', value: '1.0.0' },
{ key: 'Status', value: 'Running' },
],
},
},
},
})
ctx.docks.register({
id: 'my-panel',
title: 'My Panel',
icon: 'ph:chart-bar-duotone',
type: 'json-render',
ui,
})
```
### Launcher Entry
```ts
const entry = ctx.docks.register({
id: 'my-setup',
title: 'My Setup',
icon: 'ph:rocket-launch-duotone',
type: 'launcher',
launcher: {
title: 'Initialize My Plugin',
description: 'Run initial setup before using the plugin',
buttonStart: 'Start Setup',
buttonLoading: 'Setting up...',
onLaunch: async () => {
// Run initialization logic
},
},
})
```
## Terminals & Subprocesses
Spawn and manage child processes with streaming terminal output:
```ts
const session = await ctx.terminals.startChildProcess(
{
command: 'vite',
args: ['build', '--watch'],
cwd: process.cwd(),
},
{
id: 'my-plugin:build-watcher',
title: 'Build Watcher',
icon: 'ph:terminal-duotone',
},
)
// Lifecycle controls
await session.terminate()
await session.restart()
```
A common pattern is combining with launcher docks — see [Terminals Patterns](./references/terminals-patterns.md).
## Commands & Command Palette
Register executable commands discoverable via `Mod+K` palette:
```ts
import { defineCommand } from '@vitejs/devtools-kit'
ctx.commands.register(defineCommand({
id: 'my-plugin:clear-cache',
title: 'Clear Build Cache',
icon: 'ph:trash-duotone',
keybindings: [{ key: 'Mod+Shift+C' }],
when: 'clientType == embedded',
handler: async () => { /* ... */ },
}))
```
Commands support sub-commands (two-level hierarchy), conditional visibility via `when` clauses, and user-customizable keyboard shortcuts.
See [Commands Patterns](./references/commands-patterns.md) and [When Clauses](./references/when-clauses.md) for full details.
## Logs & Notifications
Plugins can emit structured log entries from both server and client contexts. Logs appear in the built-in **Logs** panel and can optionally show as toast notifications.
### Fire-and-Forget
```ts
// No await needed
context.messages.add({
message: 'Plugin initialized',
level: 'info',
})
```
### With Handle
```ts
const handle = await context.messages.add({
id: 'my-build',
message: 'Building...',
level: 'info',
status: 'loading',
})
// Update later
await handle.update({
message: 'Build complete',
level: 'success',
status: 'idle',
})
// Or dismiss
await handle.dismiss()
```
### Key Fields
| Field | Type | Description |
|-------|------|-------------|
| `message` | `string` | Short title (required) |
| `level` | `'info' \| 'warn' \| 'error' \| 'success' \| 'debug'` | Severity (required) |
| `description` | `string` | Detailed description |
| `notify` | `boolean` | Show as toast notification |
| `filePosition` | `{ file, line?, column? }` | Source file location (clickable) |
| `elementPosition` | `{ selector?, boundingBox?, description? }` | DOM element position |
| `id` | `string` | Explicit id for deduplication |
| `status` | `'loading' \| 'idle'` | Shows spinner when loading |
| `category` | `string` | Grouping (e.g., `'a11y'`, `'lint'`) |
عرض على GitHub