WR

writing-vite-devtools-integrations

Develops plugins that plug into the Vite DevTools hub.

Install

mkdir -p .claude/skills/writing-vite-devtools-integrations && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4720" && unzip -o skill.zip -d .claude/skills/writing-vite-devtools-integrations && rm skill.zip

Installs to .claude/skills/writing-vite-devtools-integrations

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

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.
306 chars · catalog description✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Registers custom dock entries in Vite UI
  • Manages streaming terminal process output
  • Emits structured toasts and notifications
  • Binds command palette keyboard shortcuts
  • Integrates server-client RPC state

How it works

It hooks into the Vite devtools hub via the setup(ctx) method, extending the kit's context to manage terminal streams and custom dashboard panels.

Inputs & outputs

You give it
Vite plugin setup context object
You get back
Registered hub functionality or UI hook

When to use writing-vite-devtools-integrations

  • Create a custom devtools panel
  • Add an RPC function to a Vite plugin
  • Manage shared state in Vite devtools

About this skill

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

PropertyLayerPurpose
ctx.dockskitRegister dock entries (iframe, action, custom-render, launcher, json-render)
ctx.terminalskitSpawn and manage child processes with streaming terminal output
ctx.messageskitEmit structured message entries and toast notifications
ctx.commandskitRegister executable commands with keyboard shortcuts and palette visibility
ctx.rpcdevframeRegister RPC functions, broadcast to clients
ctx.rpc.sharedStatedevframeSynchronized server-client state
ctx.rpc.streamingdevframeStreaming channels — chunk-style server↔client data with cancellation, replay, Web Streams interop
ctx.viewsdevframeHost static files for UI (hostStatic(base, distDir))
ctx.diagnosticsdevframeStructured diagnostics host (nostics) — register custom error codes
ctx.createJsonRendererkitCreate server-side JSON render specs for zero-client-code UIs
ctx.viteConfigcoreResolved Vite configuration
ctx.viteServercoreDev server instance (dev mode only)
ctx.modedevframe'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.

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

/// <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, dock IDs, and command IDs with your plugin name:

// Good - namespaced
'my-plugin:get-modules'
'my-plugin:state'
'my-plugin:clear-cache'  // command ID

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

Dock Entry Types

TypeUse Case
iframeFull UI panels, dashboards (most common)
json-renderServer-side JSON specs — zero client code needed
actionButtons that trigger client-side scripts (inspectors, toggles)
custom-renderDirect DOM access in panel (framework mounting)
launcherActionable setup cards for initialization tasks

Iframe Entry

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:

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.

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',
  },
})

JSON Render Entry

Build UIs entirely from server-side TypeScript — no client code needed:

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

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:

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.

Commands & Command Palette

Register executable commands discoverable via Mod+K palette:

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 and When Clauses 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

// No await needed
context.messages.add({
  message: 'Plugin initialized',
  level: 'info',
})

With Handle

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

FieldTypeDescription
messagestringShort title (required)
level'info' | 'warn' | 'error' | 'success' | 'debug'Severity (required)
descriptionstringDetailed description
`no

Content truncated.

When not to use it

  • Creating standalone apps without a Vite integration
  • Developing non-Vite-based devtooling

Prerequisites

@vitejs/devtools-kitVite plugin environment

Limitations

  • Restricted to the Vite DevTools lifecycle
  • Requires Vite-specific plugin architecture
  • Dependent on kit-augmented context

How it compares

It provides a standardized hook into the Vite devtools ecosystem instead of manual iframe embedding or generic plugin logic.

Compared to similar skills

writing-vite-devtools-integrations side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
writing-vite-devtools-integrations (this skill)12moNo flagsAdvanced
browser-extension-builder66moNo flagsIntermediate
stitch-loop23moNo flagsAdvanced
e2e04moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry