askill
writing-vite-devtools-integrations

writing-vite-devtools-integrationsSafety 90Repository

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.

1k stars
20.3k downloads
Updated 2/7/2026

Package Files

Loading files...
SKILL.md

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:

PropertyPurpose
ctx.docksRegister dock entries (iframe, action, custom-render)
ctx.viewsHost static files for UI
ctx.rpcRegister RPC functions, broadcast to clients
ctx.rpc.sharedStateSynchronized server-client state
ctx.viteConfigResolved Vite configuration
ctx.viteServerDev server instance (dev mode only)
ctx.mode'dev' or 'build'

Quick Start: Minimal Plugin

/// <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

/// <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:

// Good - namespaced
'my-plugin:get-modules'
'my-plugin:state'

// Bad - may conflict
'get-modules'
'state'

Dock Entry Types

TypeUse Case
iframeFull UI panels, dashboards (most common)
actionButtons that trigger client-side scripts (inspectors, toggles)
custom-renderDirect DOM access in panel (framework mounting)

Iframe Entry

ctx.docks.register({
  id: 'my-plugin',
  title: 'My Plugin',
  icon: 'ph:house-duotone',
  type: 'iframe',
  url: '/.my-plugin/',
})

Action Entry

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

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

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)

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)

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

// 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:

// 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

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

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:

// 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:

{
  "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

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

95/100Analyzed 2/10/2026

An exceptionally well-structured and comprehensive guide for building Vite DevTools integrations. It provides clear code examples, covers advanced topics like RPC and shared state, and includes critical safety conventions.

90
95
90
95
95

Metadata

Licenseunknown
Version-
Updated2/7/2026
Publishervitejs

Tags

ci-cd