Skip to content

Commit 651a91e

Browse files
antfuclaudeCopilot
authored
feat: add devtools self-inspect plugin (#190)
Co-authored-by: Claude Opus 4.6 <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 39b11d8 commit 651a91e

38 files changed

+1185
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ packages/kit/skills
1919
*.tsbuildinfo
2020
docs/.vitepress/cache
2121
.turbo
22+
.context

alias.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const alias = {
1919
'@vitejs/devtools-kit/utils/shared-state': r('kit/src/utils/shared-state.ts'),
2020
'@vitejs/devtools-kit': r('kit/src/index.ts'),
2121
'@vitejs/devtools-rolldown': r('rolldown/src/index.ts'),
22+
'@vitejs/devtools-self-inspect': r('self-inspect/src/index.ts'),
2223
'@vitejs/devtools/client/inject': r('core/src/client/inject/index.ts'),
2324
'@vitejs/devtools/client/webcomponents': r('core/src/client/webcomponents/index.ts'),
2425
'@vitejs/devtools': r('core/src/index.ts'),

docs/kit/devtools-plugin.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,28 @@ export default function myAnalyzerPlugin(): Plugin {
210210
}
211211
```
212212

213+
## Debugging with Self Inspect
214+
215+
When developing or debugging a DevTools plugin, you can install `@vitejs/devtools-self-inspect` to get a live view of all registered RPC functions, dock entries, client scripts, and DevTools-enabled plugins:
216+
217+
```bash
218+
pnpm add -D @vitejs/devtools-self-inspect
219+
```
220+
221+
```ts [vite.config.ts]
222+
import { DevToolsSelfInspect } from '@vitejs/devtools-self-inspect'
223+
224+
export default defineConfig({
225+
plugins: [
226+
DevTools(),
227+
DevToolsSelfInspect(),
228+
// ...your plugins
229+
],
230+
})
231+
```
232+
233+
This adds a "Self Inspect" panel to DevTools that shows the internal state of the DevTools system — helpful for verifying that your plugin's RPC functions, docks, and client scripts are registered correctly.
234+
213235
## Next Steps
214236

215237
- **[Dock System](./dock-system)** - Learn about different dock entry types (iframe, action, custom renderer)

packages/core/playground/vite.config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import VueRouter from 'unplugin-vue-router/vite'
55
import { defineConfig } from 'vite'
66
import Tracer from 'vite-plugin-vue-tracer'
77
import { alias } from '../../../alias'
8+
import { DevTools } from '../../core/src'
9+
import { buildCSS } from '../../core/src/client/webcomponents/scripts/build-css'
810
// eslint-disable-next-line ts/ban-ts-comment
911
// @ts-ignore ignore the type error
1012
import { DevToolsRolldownUI } from '../../rolldown/src/node'
11-
import { DevTools } from '../src'
12-
import { buildCSS } from '../src/client/webcomponents/scripts/build-css'
13+
import { DevToolsSelfInspect } from '../../self-inspect/src/node'
1314

1415
declare module '@vitejs/devtools-kit' {
1516
interface DevToolsRpcSharedStates {
@@ -29,6 +30,7 @@ export default defineConfig({
2930
plugins: [
3031
VueRouter(),
3132
Vue(),
33+
DevToolsSelfInspect(),
3234
{
3335
name: 'build-css',
3436
handleHotUpdate({ file }) {

packages/self-inspect/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# @vitejs/devtools-self-inspect
2+
3+
A Vite DevTools plugin for inspecting the DevTools itself. Useful when developing or debugging DevTools plugins built with `@vitejs/devtools-kit`.
4+
5+
## Features
6+
7+
- List all registered RPC functions with their metadata (type, schema, cacheability, etc.)
8+
- List all registered dock entries
9+
- List all registered client scripts
10+
- List all Vite plugins with DevTools support and their capabilities
11+
12+
## Installation
13+
14+
```bash
15+
pnpm add -D @vitejs/devtools-self-inspect
16+
```
17+
18+
## Usage
19+
20+
Add the plugin to your Vite config:
21+
22+
```ts
23+
import DevTools from '@vitejs/devtools'
24+
import { DevToolsSelfInspect } from '@vitejs/devtools-self-inspect'
25+
// vite.config.ts
26+
import { defineConfig } from 'vite'
27+
28+
export default defineConfig({
29+
plugins: [
30+
DevTools(),
31+
DevToolsSelfInspect(),
32+
],
33+
})
34+
```
35+
36+
A "Self Inspect" panel will appear in the DevTools dock, giving you a live view of the registered RPC functions, docks, client scripts, and DevTools-enabled plugins.

packages/self-inspect/package.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
{
2+
"name": "@vitejs/devtools-self-inspect",
3+
"type": "module",
4+
"version": "0.0.0-alpha.34",
5+
"description": "DevTools for inspecting the DevTools itself",
6+
"author": "VoidZero Inc.",
7+
"license": "MIT",
8+
"homepage": "https://github.com/vitejs/devtools#readme",
9+
"repository": {
10+
"directory": "packages/self-inspect",
11+
"type": "git",
12+
"url": "git+https://github.com/vitejs/devtools.git"
13+
},
14+
"bugs": "https://github.com/vitejs/devtools/issues",
15+
"keywords": [
16+
"devtools",
17+
"self-inspect"
18+
],
19+
"sideEffects": false,
20+
"exports": {
21+
".": "./dist/index.mjs",
22+
"./package.json": "./package.json"
23+
},
24+
"types": "./dist/index.d.mts",
25+
"files": [
26+
"dist"
27+
],
28+
"scripts": {
29+
"build": "pnpm dev:prepare && nuxi build src && tsdown",
30+
"dev": "nuxi dev src",
31+
"dev:prepare": "nuxi prepare src",
32+
"prepack": "pnpm build"
33+
},
34+
"dependencies": {
35+
"@vitejs/devtools-kit": "workspace:*",
36+
"@vitejs/devtools-rpc": "workspace:*",
37+
"birpc": "catalog:deps",
38+
"pathe": "catalog:deps"
39+
},
40+
"devDependencies": {
41+
"@unocss/nuxt": "catalog:build",
42+
"@vueuse/core": "catalog:frontend",
43+
"@vueuse/nuxt": "catalog:build",
44+
"structured-clone-es": "catalog:deps",
45+
"tsdown": "catalog:build",
46+
"unocss": "catalog:build",
47+
"vite-hot-client": "catalog:frontend"
48+
}
49+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<script setup lang="ts">
2+
import { useHead } from '#app/composables/head'
3+
import { toggleDark } from '@vitejs/devtools-ui/composables/dark'
4+
import { useRefresh } from './composables/refresh'
5+
import { connect, connectionState } from './composables/rpc'
6+
import './styles/global.css'
7+
8+
useHead({
9+
title: 'DevTools Self Inspect',
10+
})
11+
12+
connect()
13+
14+
const navItems = [
15+
{ title: 'RPC Functions', to: '/rpc', icon: 'i-ph-plugs-connected-duotone' },
16+
{ title: 'Docks', to: '/docks', icon: 'i-ph-layout-duotone' },
17+
{ title: 'Client Scripts', to: '/scripts', icon: 'i-ph-code-duotone' },
18+
{ title: 'Plugins', to: '/plugins', icon: 'i-ph-puzzle-piece-duotone' },
19+
]
20+
21+
const { refresh, loading } = useRefresh()
22+
</script>
23+
24+
<template>
25+
<div v-if="connectionState.error" text-red p4>
26+
{{ connectionState.error }}
27+
</div>
28+
<VisualLoading
29+
v-else-if="!connectionState.connected"
30+
text="Connecting..."
31+
/>
32+
<div v-else h-vh flex="~ col" of-hidden>
33+
<div flex="~ items-center" border="b base" h9 shrink-0>
34+
<NuxtLink
35+
v-for="item in navItems" :key="item.to"
36+
:to="item.to"
37+
flex="~ items-center gap-1.5"
38+
px3 h-full text-sm
39+
op50 hover:op100 transition-colors
40+
border="b-2 transparent"
41+
active-class="op100! border-b-primary!"
42+
>
43+
<span :class="item.icon" text-base />
44+
<span>{{ item.title }}</span>
45+
</NuxtLink>
46+
<div flex-1 />
47+
<button
48+
mr2 p1.5 rounded
49+
hover:bg-active
50+
op50 hover:op100
51+
transition-colors
52+
title="Refresh"
53+
:disabled="loading"
54+
@click="refresh"
55+
>
56+
<span i-ph-arrow-clockwise text-sm :class="loading ? 'animate-spin' : ''" />
57+
</button>
58+
<button
59+
mr2 p1.5 rounded
60+
hover:bg-active
61+
op50 hover:op100
62+
transition-colors
63+
title="Toggle dark mode"
64+
@click="toggleDark"
65+
>
66+
<span class="i-ph-sun-duotone dark:i-ph-moon-duotone" text-sm />
67+
</button>
68+
</div>
69+
<div flex-1 of-auto>
70+
<NuxtPage />
71+
</div>
72+
</div>
73+
</template>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<script setup lang="ts">
2+
import type { ClientScriptInfo } from '../../types'
3+
import { computed } from 'vue'
4+
5+
const props = defineProps<{
6+
scripts: ClientScriptInfo[]
7+
}>()
8+
9+
const grouped = computed(() => {
10+
const groups = new Map<string, ClientScriptInfo[]>()
11+
for (const script of props.scripts) {
12+
const type = script.dockType
13+
if (!groups.has(type))
14+
groups.set(type, [])
15+
groups.get(type)!.push(script)
16+
}
17+
return Array.from(groups.entries()).sort(([a], [b]) => a.localeCompare(b))
18+
})
19+
20+
function shortPath(path: string): string {
21+
const parts = path.split('/')
22+
if (parts.length <= 3)
23+
return path
24+
return `.../${parts.slice(-3).join('/')}`
25+
}
26+
</script>
27+
28+
<template>
29+
<div v-if="scripts.length === 0" flex="~ items-center justify-center" py8 op50>
30+
No client scripts registered.
31+
</div>
32+
<div v-else flex="~ col gap-3" p4>
33+
<div text-xs op60>
34+
{{ scripts.length }} client scripts registered
35+
</div>
36+
37+
<div v-for="[type, typeScripts] in grouped" :key="type">
38+
<div flex="~ items-center gap-2" mb1 mt2>
39+
<DisplayBadge :text="type" />
40+
<DisplayNumberBadge :value="typeScripts.length" />
41+
</div>
42+
<table w-full text-sm>
43+
<thead>
44+
<tr border="b base" text-left>
45+
<th px2 py1 font-medium op60 text-xs>
46+
Dock ID
47+
</th>
48+
<th px2 py1 font-medium op60 text-xs>
49+
Dock Title
50+
</th>
51+
<th px2 py1 font-medium op60 text-xs>
52+
Import From
53+
</th>
54+
<th px2 py1 font-medium op60 text-xs>
55+
Import Name
56+
</th>
57+
</tr>
58+
</thead>
59+
<tbody>
60+
<tr v-for="script in typeScripts" :key="script.dockId" border="b base" hover:bg-active>
61+
<td px2 py1.5 font-mono text-xs>
62+
{{ script.dockId }}
63+
</td>
64+
<td px2 py1.5>
65+
{{ script.dockTitle }}
66+
</td>
67+
<td px2 py1.5 font-mono text-xs max-w-60 truncate :title="script.script.importFrom">
68+
{{ shortPath(script.script.importFrom) }}
69+
</td>
70+
<td px2 py1.5 font-mono text-xs>
71+
{{ script.script.importName ?? 'default' }}
72+
</td>
73+
</tr>
74+
</tbody>
75+
</table>
76+
</div>
77+
</div>
78+
</template>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<script setup lang="ts">
2+
import type { DevtoolsPluginInfo } from '../../types'
3+
import { computed, ref } from 'vue'
4+
5+
const props = defineProps<{
6+
plugins: DevtoolsPluginInfo[]
7+
}>()
8+
9+
const showAll = ref(false)
10+
11+
const devtoolsCount = computed(() => props.plugins.filter(p => p.hasDevtools).length)
12+
13+
const filtered = computed(() => {
14+
if (showAll.value)
15+
return props.plugins
16+
return props.plugins.filter(p => p.hasDevtools)
17+
})
18+
</script>
19+
20+
<template>
21+
<div flex="~ col gap-3" p4>
22+
<div flex="~ items-center gap-3" text-xs>
23+
<span op60>
24+
{{ devtoolsCount }} devtools plugins / {{ plugins.length }} total
25+
</span>
26+
<label flex="~ items-center gap-1.5" op60 cursor-pointer select-none>
27+
<input v-model="showAll" type="checkbox">
28+
Show all
29+
</label>
30+
</div>
31+
32+
<table w-full text-sm>
33+
<thead>
34+
<tr border="b base" text-left>
35+
<th px2 py1 font-medium op60 text-xs>
36+
Plugin Name
37+
</th>
38+
<th px2 py1 font-medium op60 text-xs text-center>
39+
DevTools
40+
</th>
41+
<th px2 py1 font-medium op60 text-xs text-center>
42+
Setup
43+
</th>
44+
<th px2 py1 font-medium op60 text-xs>
45+
Capabilities
46+
</th>
47+
</tr>
48+
</thead>
49+
<tbody>
50+
<tr
51+
v-for="plugin in filtered" :key="plugin.name"
52+
border="b base" hover:bg-active
53+
:class="!plugin.hasDevtools ? 'op40' : ''"
54+
>
55+
<td px2 py1.5 font-mono text-xs>
56+
{{ plugin.name }}
57+
</td>
58+
<td px2 py1.5 text-center>
59+
<span v-if="plugin.hasDevtools" i-ph-check text-green />
60+
<span v-else op20>-</span>
61+
</td>
62+
<td px2 py1.5 text-center>
63+
<span v-if="plugin.hasSetup" i-ph-check text-green />
64+
<span v-else op20>-</span>
65+
</td>
66+
<td px2 py1.5 flex="~ items-center gap-1">
67+
<template v-if="plugin.capabilities">
68+
<DisplayBadge v-if="plugin.capabilities.dev" text="dev" />
69+
<DisplayBadge v-if="plugin.capabilities.build" text="build" />
70+
</template>
71+
<span v-else op20>-</span>
72+
</td>
73+
</tr>
74+
</tbody>
75+
</table>
76+
</div>
77+
</template>

0 commit comments

Comments
 (0)