You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@vitejs/devtools-kit

Package Overview
Dependencies
Maintainers
4
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vitejs/devtools-kit - npm Package Compare versions

Comparing version
0.0.0-alpha.28
to
0.0.0-alpha.29
+240
skills/vite-devtools-kit/references/dock-entry-types.md
# Dock Entry Types
Detailed configuration for each dock entry type.
## Common Properties
All dock entries share these properties:
```ts
interface DockEntryBase {
id: string // Unique identifier
title: string // Display title
icon: string // URL, data URI, or Iconify name
category?: string // Grouping category
defaultOrder?: number // Sort order (higher = earlier)
isHidden?: boolean // Hide from dock
}
```
## Icons
<!-- eslint-skip -->
```ts
// Iconify (recommended)
icon: 'ph:chart-bar-duotone' // Phosphor Icons
icon: 'carbon:analytics' // Carbon Icons
icon: 'mdi:view-dashboard' // Material Design
// URL
icon: 'https://example.com/logo.svg'
// Data URI
icon: 'data:image/svg+xml,<svg>...</svg>'
// Light/dark variants
icon: {
light: 'https://example.com/logo-light.svg',
dark: 'https://example.com/logo-dark.svg',
}
```
Browse icons at [Iconify](https://icon-sets.iconify.design/).
## Iframe Entries
Most common type. Displays your UI in an isolated iframe.
```ts
interface IframeEntry extends DockEntryBase {
type: 'iframe'
url: string // URL to load
frameId?: string // Share iframe between entries
clientScript?: ClientScriptEntry // Optional client script
}
// Example
ctx.docks.register({
id: 'my-plugin',
title: 'My Plugin',
icon: 'ph:house-duotone',
type: 'iframe',
url: '/.my-plugin/',
})
```
### Hosting Your Own UI
```ts
import { fileURLToPath } from 'node:url'
const clientDist = fileURLToPath(
new URL('../dist/client', import.meta.url)
)
ctx.views.hostStatic('/.my-plugin/', clientDist)
ctx.docks.register({
id: 'my-plugin',
title: 'My Plugin',
icon: 'ph:house-duotone',
type: 'iframe',
url: '/.my-plugin/',
})
```
## Action Entries
Buttons that trigger client-side scripts. Perfect for inspectors and toggles.
```ts
interface ActionEntry extends DockEntryBase {
type: 'action'
action: {
importFrom: string // Package export path
importName?: string // Export name (default: 'default')
}
}
// Registration
ctx.docks.register({
id: 'my-inspector',
title: 'Inspector',
icon: 'ph:cursor-duotone',
type: 'action',
action: {
importFrom: 'my-plugin/devtools-action',
importName: 'default',
},
})
```
### Client Script Implementation
```ts
// src/devtools-action.ts
import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client'
export default function setup(ctx: DevToolsClientScriptContext) {
let overlay: HTMLElement | null = null
ctx.current.events.on('entry:activated', () => {
overlay = document.createElement('div')
overlay.style.cssText = `
position: fixed;
inset: 0;
cursor: crosshair;
z-index: 99999;
`
overlay.onclick = (e) => {
const target = document.elementFromPoint(e.clientX, e.clientY)
console.log('Selected:', target)
}
document.body.appendChild(overlay)
})
ctx.current.events.on('entry:deactivated', () => {
overlay?.remove()
overlay = null
})
}
```
### Package Export
```json
{
"exports": {
".": "./dist/index.mjs",
"./devtools-action": "./dist/devtools-action.mjs"
}
}
```
## Custom Render Entries
Render directly into the DevTools panel DOM. Use when you need direct DOM access or framework mounting.
```ts
interface CustomRenderEntry extends DockEntryBase {
type: 'custom-render'
renderer: {
importFrom: string
importName?: string
}
}
ctx.docks.register({
id: 'my-custom',
title: 'Custom View',
icon: 'ph:code-duotone',
type: 'custom-render',
renderer: {
importFrom: 'my-plugin/devtools-renderer',
importName: 'default',
},
})
```
### Renderer Implementation
```ts
// src/devtools-renderer.ts
import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client'
export default function setup(ctx: DevToolsClientScriptContext) {
ctx.current.events.on('dom:panel:mounted', (panel) => {
// Vanilla JS
panel.innerHTML = `<div style="padding: 16px;">Hello</div>`
// Or mount Vue
// import { createApp } from 'vue'
// import App from './App.vue'
// createApp(App).mount(panel)
// Or mount React
// import { createRoot } from 'react-dom/client'
// createRoot(panel).render(<App />)
})
}
```
## Client Script Events
| Event | Payload | Description |
|-------|---------|-------------|
| `entry:activated` | - | Entry was selected |
| `entry:deactivated` | - | Entry was deselected |
| `entry:updated` | `DevToolsDockUserEntry` | Entry metadata changed |
| `dom:panel:mounted` | `HTMLDivElement` | Panel DOM ready (custom-render only) |
| `dom:iframe:mounted` | `HTMLIFrameElement` | Iframe mounted (iframe only) |
## Category Order
Default category ordering:
```ts
DEFAULT_CATEGORIES_ORDER = {
'~viteplus': -1000, // First
'default': 0,
'app': 100,
'framework': 200,
'web': 300,
'advanced': 400,
'~builtin': 1000, // Last
}
```
Use `category` to group related entries:
```ts
ctx.docks.register({
id: 'my-plugin',
title: 'My Plugin',
icon: 'ph:house-duotone',
type: 'iframe',
url: '/.my-plugin/',
category: 'framework',
})
```
# Project Structure
Recommended file organization for DevTools integrations.
## Basic Structure
```
my-devtools-plugin/
├── src/
│ ├── node/
│ │ ├── index.ts # Plugin entry (exports main plugin)
│ │ ├── rpc/
│ │ │ ├── index.ts # RPC function exports
│ │ │ └── functions/ # Individual RPC functions
│ │ │ ├── get-modules.ts
│ │ │ └── get-stats.ts
│ │ └── utils.ts # Server-side utilities
│ ├── client/
│ │ ├── main.ts # Client app entry
│ │ ├── App.vue # Root component
│ │ └── composables/
│ │ └── rpc.ts # RPC composables
│ ├── types.ts # Type augmentations
│ └── shared/
│ └── constants.ts # Shared constants
├── dist/
│ ├── index.mjs # Node plugin bundle
│ └── client/ # Built client assets
├── package.json
└── tsconfig.json
```
## Package.json Configuration
```json
{
"name": "my-devtools-plugin",
"type": "module",
"exports": {
".": {
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
},
"./devtools-action": {
"import": "./dist/devtools-action.mjs"
}
},
"files": ["dist"],
"scripts": {
"build": "tsdown src/node/index.ts && vite build src/client",
"dev": "vite src/client"
},
"dependencies": {},
"devDependencies": {
"@vitejs/devtools-kit": "^0.x.x",
"vite": "^6.x.x"
}
}
```
## Plugin Entry (src/node/index.ts)
```ts
/// <reference types="@vitejs/devtools-kit" />
import type { Plugin } from 'vite'
import { fileURLToPath } from 'node:url'
import { rpcFunctions } from './rpc'
import '../types'
const clientDist = fileURLToPath(
new URL('../../dist/client', import.meta.url)
)
export default function myPlugin(): Plugin {
return {
name: 'my-plugin',
devtools: {
setup(ctx) {
// Register all RPC functions
for (const fn of rpcFunctions) {
ctx.rpc.register(fn)
}
// Host static UI
ctx.views.hostStatic('/.my-plugin/', clientDist)
// Register dock entry
ctx.docks.register({
id: 'my-plugin',
title: 'My Plugin',
icon: 'ph:puzzle-piece-duotone',
type: 'iframe',
url: '/.my-plugin/',
})
},
},
}
}
```
## RPC Index (src/node/rpc/index.ts)
```ts
import type { RpcDefinitionsToFunctions } from '@vitejs/devtools-kit'
import { getModules } from './functions/get-modules'
import { getStats } from './functions/get-stats'
import '@vitejs/devtools-kit'
export const rpcFunctions = [
getModules,
getStats,
] as const
export type ServerFunctions = RpcDefinitionsToFunctions<typeof rpcFunctions>
declare module '@vitejs/devtools-kit' {
export interface DevToolsRpcServerFunctions extends ServerFunctions {}
}
```
## RPC Function (src/node/rpc/functions/get-modules.ts)
```ts
import type { Module } from '../../../types'
import { defineRpcFunction } from '@vitejs/devtools-kit'
export const getModules = defineRpcFunction({
name: 'my-plugin:get-modules',
type: 'query',
setup: (ctx) => {
return {
handler: async (): Promise<Module[]> => {
// Access vite config, server, etc. from ctx
const root = ctx.viteConfig.root
return []
},
}
},
})
```
## Type Augmentations (src/types.ts)
```ts
import '@vitejs/devtools-kit'
export interface Module {
id: string
size: number
imports: string[]
}
export interface MyPluginState {
modules: Module[]
selectedId: string | null
}
declare module '@vitejs/devtools-kit' {
interface DevToolsRpcSharedStates {
'my-plugin:state': MyPluginState
}
}
```
## Client Entry (src/client/main.ts)
```ts
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
```
## Client RPC Composable (src/client/composables/rpc.ts)
```ts
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
let clientPromise: Promise<Awaited<ReturnType<typeof getDevToolsRpcClient>>>
export function useRpc() {
if (!clientPromise) {
clientPromise = getDevToolsRpcClient()
}
return clientPromise
}
```
## Client App Component (src/client/App.vue)
```vue
<script setup lang="ts">
import type { Module } from '../types'
import { onMounted, shallowRef } from 'vue'
import { useRpc } from './composables/rpc'
const modules = shallowRef<Module[]>([])
const loading = shallowRef(true)
onMounted(async () => {
const rpc = await useRpc()
modules.value = await rpc.call('my-plugin:get-modules')
loading.value = false
})
</script>
<template>
<div class="p-4">
<h1 class="text-xl font-bold mb-4">
My Plugin
</h1>
<div v-if="loading">
Loading...
</div>
<ul v-else>
<li v-for="mod in modules" :key="mod.id">
{{ mod.id }} ({{ mod.size }} bytes)
</li>
</ul>
</div>
</template>
```
## Vite Config for Client (src/client/vite.config.ts)
```ts
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [vue()],
build: {
outDir: '../../dist/client',
emptyOutDir: true,
},
})
```
## Real-World Reference
See [packages/vite](https://github.com/user/vite-devtools/tree/main/packages/vite) for a complete implementation example with:
- Multiple RPC functions organized by feature
- Nuxt-based client UI
- Complex data visualization
- Build session management
# RPC Patterns
Advanced patterns for server-client communication in DevTools integrations.
## Function Types
| Type | Caching | Use Case |
|------|---------|----------|
| `query` | Can be cached | Read operations, data fetching |
| `action` | Never cached | Mutations, side effects |
| `static` | Cached indefinitely | Constants, configuration |
## Type-Safe RPC Setup
### Step 1: Define Types
```ts
// src/types.ts
import '@vitejs/devtools-kit'
interface Module {
id: string
size: number
imports: string[]
}
declare module '@vitejs/devtools-kit' {
interface DevToolsRpcServerFunctions {
'my-plugin:list-modules': () => Promise<Module[]>
'my-plugin:get-module': (id: string) => Promise<Module | null>
'my-plugin:analyze': (options: { deep: boolean }) => Promise<void>
}
interface DevToolsRpcClientFunctions {
'my-plugin:highlight': (selector: string) => void
'my-plugin:refresh': () => void
}
}
```
### Step 2: Import Types File
```ts
// src/node/plugin.ts
import '../types' // Side-effect import for type augmentation
```
### Step 3: Register Functions
```ts
import { defineRpcFunction } from '@vitejs/devtools-kit'
const listModules = defineRpcFunction({
name: 'my-plugin:list-modules',
type: 'query',
setup: () => ({
handler: async (): Promise<Module[]> => {
return Array.from(moduleMap.values())
},
}),
})
```
## Context Access in Setup
The `setup` function receives the full `DevToolsNodeContext`:
```ts
defineRpcFunction({
name: 'my-plugin:get-config',
type: 'static',
setup: (ctx) => {
// Access at setup time (runs once)
const root = ctx.viteConfig.root
const isDev = ctx.mode === 'dev'
return {
handler: async () => ({
root,
isDev,
plugins: ctx.viteConfig.plugins.map(p => p.name),
}),
}
},
})
```
## Broadcasting Patterns
### Basic Broadcast
```ts
// Notify all clients
ctx.rpc.broadcast({
method: 'my-plugin:refresh',
args: [],
})
```
### Broadcast with Data
```ts
ctx.viteServer?.watcher.on('change', (file) => {
ctx.rpc.broadcast({
method: 'my-plugin:file-changed',
args: [{ path: file, timestamp: Date.now() }],
})
})
```
### Optional Broadcast
```ts
// Won't error if no clients have registered the function
ctx.rpc.broadcast({
method: 'my-plugin:optional-update',
args: [data],
optional: true,
})
```
## Client Function Registration
```ts
// Client-side (action/renderer script)
export default function setup(ctx: DevToolsClientScriptContext) {
ctx.current.rpc.client.register({
name: 'my-plugin:highlight',
type: 'action',
handler: (selector: string) => {
const el = document.querySelector(selector)
if (el) {
el.style.outline = '3px solid red'
setTimeout(() => {
el.style.outline = ''
}, 2000)
}
},
})
}
```
## Collecting RPC Functions
Organize RPC functions in a registry pattern:
```ts
import { analyzeBundle } from './functions/analyze-bundle'
// src/node/rpc/index.ts
import { getModules } from './functions/get-modules'
import { getStats } from './functions/get-stats'
export const rpcFunctions = [
getModules,
getStats,
analyzeBundle,
] as const
// Register all in setup
for (const fn of rpcFunctions) {
ctx.rpc.register(fn)
}
```
## Type Extraction Utilities
```ts
import type {
RpcDefinitionsFilter,
RpcDefinitionsToFunctions,
} from '@vitejs/devtools-kit'
// Extract all function types
export type ServerFunctions = RpcDefinitionsToFunctions<typeof rpcFunctions>
// Extract only static functions
export type StaticFunctions = RpcDefinitionsToFunctions<
RpcDefinitionsFilter<typeof rpcFunctions, 'static'>
>
// Augment the global interface
declare module '@vitejs/devtools-kit' {
export interface DevToolsRpcServerFunctions extends ServerFunctions {}
}
```
# Shared State Patterns
Synchronized state between server and all clients.
## Basic Usage
### Server-Side
```ts
const state = await ctx.rpc.sharedState.get('my-plugin:state', {
initialValue: {
count: 0,
items: [],
settings: { theme: 'dark' },
},
})
// Read
const current = state.value()
// Mutate (syncs to all clients)
state.mutate((draft) => {
draft.count += 1
draft.items.push({ id: Date.now(), name: 'New' })
})
```
### Client-Side
```ts
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
const client = await getDevToolsRpcClient()
const state = await client.rpc.sharedState.get('my-plugin:state')
// Read
console.log(state.value())
// Subscribe
state.on('updated', (newState) => {
console.log('Updated:', newState)
})
```
## Type-Safe Shared State
```ts
// src/types.ts
interface MyPluginState {
count: number
items: Array<{ id: string, name: string }>
settings: {
theme: 'light' | 'dark'
notifications: boolean
}
}
declare module '@vitejs/devtools-kit' {
interface DevToolsRpcSharedStates {
'my-plugin:state': MyPluginState
}
}
```
## Vue Integration
```ts
// composables/useSharedState.ts
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
import { onMounted, shallowRef } from 'vue'
export function useSharedState<T>(name: string) {
const state = shallowRef<T | null>(null)
const loading = shallowRef(true)
onMounted(async () => {
const client = await getDevToolsRpcClient()
const shared = await client.rpc.sharedState.get<T>(name)
state.value = shared.value()
loading.value = false
shared.on('updated', (newState) => {
state.value = newState
})
})
return { state, loading }
}
// Usage in component
const { state, loading } = useSharedState<MyPluginState>('my-plugin:state')
```
### Full Vue Component Example
```vue
<script setup lang="ts">
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
import { onMounted, shallowRef } from 'vue'
interface PluginState {
count: number
items: string[]
}
const state = shallowRef<PluginState | null>(null)
onMounted(async () => {
const client = await getDevToolsRpcClient()
const shared = await client.rpc.sharedState.get<PluginState>('my-plugin:state')
state.value = shared.value()
shared.on('updated', (newState) => {
state.value = newState
})
})
</script>
<template>
<div v-if="state">
<p>Count: {{ state.count }}</p>
<ul>
<li v-for="item in state.items" :key="item">
{{ item }}
</li>
</ul>
</div>
</template>
```
## React Integration
```tsx
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
import { useEffect, useState } from 'react'
function useSharedState<T>(name: string, fallback: T) {
const [state, setState] = useState<T>(fallback)
const [loading, setLoading] = useState(true)
useEffect(() => {
let mounted = true
async function init() {
const client = await getDevToolsRpcClient()
const shared = await client.rpc.sharedState.get<T>(name)
if (mounted) {
setState(shared.value() ?? fallback)
setLoading(false)
shared.on('updated', (newState) => {
if (mounted)
setState(newState)
})
}
}
init()
return () => {
mounted = false
}
}, [name])
return { state, loading }
}
// Usage
function MyComponent() {
const { state, loading } = useSharedState('my-plugin:state', { count: 0 })
if (loading)
return <div>Loading...</div>
return (
<div>
Count:
{state.count}
</div>
)
}
```
## Svelte Integration
```svelte
<script lang="ts">
import { onMount } from 'svelte'
import { writable } from 'svelte/store'
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
interface PluginState {
count: number
}
const state = writable<PluginState>({ count: 0 })
onMount(async () => {
const client = await getDevToolsRpcClient()
const shared = await client.rpc.sharedState.get<PluginState>('my-plugin:state')
state.set(shared.value())
shared.on('updated', (newState) => {
state.set(newState)
})
})
</script>
<p>Count: {$state.count}</p>
```
## Best Practices
### Batch Mutations
```ts
// Good - single sync event
state.mutate((draft) => {
draft.count += 1
draft.lastUpdate = Date.now()
draft.items.push(newItem)
})
// Bad - multiple sync events
state.mutate((d) => {
d.count += 1
})
state.mutate((d) => {
d.lastUpdate = Date.now()
})
state.mutate((d) => {
d.items.push(newItem)
})
```
### Keep State Small
For large datasets, store IDs in shared state and fetch details via RPC:
<!-- eslint-skip -->
```ts
// Shared state (small)
{
moduleIds: ['a', 'b', 'c'],
selectedId: 'a'
}
// Fetch full data via RPC when needed
const module = await rpc.call('my-plugin:get-module', state.selectedId)
```
### Serializable Data Only
<!-- eslint-skip -->
```ts
// Bad - functions can't be serialized
{ count: 0, increment: () => {} }
// Bad - circular references
const obj = { child: null }
obj.child = obj
// Good - plain data
{ count: 0, items: [{ id: 1 }] }
```
### Real-Time Updates Example
```ts
const plugin: Plugin = {
devtools: {
async setup(ctx) {
const state = await ctx.rpc.sharedState.get('my-plugin:state', {
initialValue: { modules: [], lastUpdate: 0 },
})
// Update state when Vite processes modules
ctx.viteServer?.watcher.on('change', (file) => {
state.mutate((draft) => {
draft.modules.push(file)
draft.lastUpdate = Date.now()
})
})
}
}
}
```
---
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
/// <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, 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<Module[]>
}
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
+4
-3
{
"name": "@vitejs/devtools-kit",
"type": "module",
"version": "0.0.0-alpha.28",
"version": "0.0.0-alpha.29",
"description": "Vite DevTools Kit",

@@ -32,3 +32,4 @@ "author": "VoidZero Inc.",

"files": [
"dist"
"dist",
"skills"
],

@@ -41,3 +42,3 @@ "peerDependencies": {

"immer": "^11.1.3",
"@vitejs/devtools-rpc": "0.0.0-alpha.28"
"@vitejs/devtools-rpc": "0.0.0-alpha.29"
},

@@ -44,0 +45,0 @@ "devDependencies": {