@vitejs/devtools-kit 0.0.0-alpha.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +2 -35
- package/dist/client.js +1734 -19
- package/dist/constants.d.ts +14 -0
- package/dist/constants.js +28 -0
- package/dist/events-B41U-zeg.d.ts +71 -0
- package/dist/index-Bl6l5FTi.d.ts +642 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +4 -16
- package/dist/shared-state-BFKKxNt1.d.ts +53 -0
- package/dist/utils/events.d.ts +9 -0
- package/dist/utils/events.js +40 -0
- package/dist/utils/nanoid.d.ts +4 -0
- package/dist/utils/nanoid.js +10 -0
- package/dist/utils/shared-state.d.ts +2 -0
- package/dist/utils/shared-state.js +36 -0
- package/package.json +19 -10
- package/skills/vite-devtools-kit/SKILL.md +436 -0
- package/skills/vite-devtools-kit/references/dock-entry-types.md +295 -0
- package/skills/vite-devtools-kit/references/logs-patterns.md +188 -0
- package/skills/vite-devtools-kit/references/project-structure.md +256 -0
- package/skills/vite-devtools-kit/references/rpc-patterns.md +185 -0
- package/skills/vite-devtools-kit/references/shared-state-patterns.md +292 -0
- package/dist/index-BtaHil_c.d.ts +0 -156
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { t as EventEmitter } from "./events-B41U-zeg.js";
|
|
2
|
+
import { Objectish, Patch, Patch as SharedStatePatch } from "immer";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/shared-state.d.ts
|
|
5
|
+
type ImmutablePrimitive = undefined | null | boolean | string | number | Function;
|
|
6
|
+
type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
|
|
7
|
+
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
8
|
+
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
9
|
+
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
10
|
+
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
|
|
11
|
+
/**
|
|
12
|
+
* State host that is immutable by default with explicit mutate.
|
|
13
|
+
*/
|
|
14
|
+
interface SharedState<T> {
|
|
15
|
+
/**
|
|
16
|
+
* Get the current state. Immutable.
|
|
17
|
+
*/
|
|
18
|
+
value: () => Immutable<T>;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribe to state changes.
|
|
21
|
+
*/
|
|
22
|
+
on: EventEmitter<SharedStateEvents<T>>['on'];
|
|
23
|
+
/**
|
|
24
|
+
* Mutate the state.
|
|
25
|
+
*/
|
|
26
|
+
mutate: (fn: (state: T) => void, syncId?: string) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Apply patches to the state.
|
|
29
|
+
*/
|
|
30
|
+
patch: (patches: Patch[], syncId?: string) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Sync IDs that have been applied to the state.
|
|
33
|
+
*/
|
|
34
|
+
syncIds: Set<string>;
|
|
35
|
+
}
|
|
36
|
+
interface SharedStateEvents<T> {
|
|
37
|
+
updated: (fullState: T, patches: Patch[] | undefined, syncId: string) => void;
|
|
38
|
+
}
|
|
39
|
+
interface SharedStateOptions<T> {
|
|
40
|
+
/**
|
|
41
|
+
* Initial state.
|
|
42
|
+
*/
|
|
43
|
+
initialValue: T;
|
|
44
|
+
/**
|
|
45
|
+
* Enable patches.
|
|
46
|
+
*
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
enablePatches?: boolean;
|
|
50
|
+
}
|
|
51
|
+
declare function createSharedState<T extends Objectish>(options: SharedStateOptions<T>): SharedState<T>;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { ImmutableSet as a, SharedStateOptions as c, ImmutableObject as i, SharedStatePatch as l, ImmutableArray as n, SharedState as o, ImmutableMap as r, SharedStateEvents as s, Immutable as t, createSharedState as u };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { r as EventsMap, t as EventEmitter } from "../events-B41U-zeg.js";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/events.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create event emitter.
|
|
6
|
+
*/
|
|
7
|
+
declare function createEventEmitter<Events extends EventsMap>(): EventEmitter<Events>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { createEventEmitter };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/utils/events.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create event emitter.
|
|
4
|
+
*/
|
|
5
|
+
function createEventEmitter() {
|
|
6
|
+
const _listeners = {};
|
|
7
|
+
function emit(event, ...args) {
|
|
8
|
+
const callbacks = _listeners[event] || [];
|
|
9
|
+
for (let i = 0, length = callbacks.length; i < length; i++) {
|
|
10
|
+
const callback = callbacks[i];
|
|
11
|
+
if (callback) callback(...args);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function emitOnce(event, ...args) {
|
|
15
|
+
emit(event, ...args);
|
|
16
|
+
delete _listeners[event];
|
|
17
|
+
}
|
|
18
|
+
function on(event, cb) {
|
|
19
|
+
(_listeners[event] ||= []).push(cb);
|
|
20
|
+
return () => {
|
|
21
|
+
_listeners[event] = _listeners[event]?.filter((i) => cb !== i);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function once(event, cb) {
|
|
25
|
+
const unsubscribe = on(event, ((...args) => {
|
|
26
|
+
unsubscribe();
|
|
27
|
+
return cb(...args);
|
|
28
|
+
}));
|
|
29
|
+
return unsubscribe;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
_listeners,
|
|
33
|
+
emit,
|
|
34
|
+
emitOnce,
|
|
35
|
+
on,
|
|
36
|
+
once
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { createEventEmitter };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/utils/nanoid.ts
|
|
2
|
+
const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
|
|
3
|
+
function nanoid(size = 21) {
|
|
4
|
+
let id = "";
|
|
5
|
+
let i = size;
|
|
6
|
+
while (i--) id += urlAlphabet[Math.random() * 64 | 0];
|
|
7
|
+
return id;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { nanoid };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as ImmutableSet, c as SharedStateOptions, i as ImmutableObject, l as SharedStatePatch, n as ImmutableArray, o as SharedState, r as ImmutableMap, s as SharedStateEvents, t as Immutable, u as createSharedState } from "../shared-state-BFKKxNt1.js";
|
|
2
|
+
export { Immutable, ImmutableArray, ImmutableMap, ImmutableObject, ImmutableSet, SharedState, SharedStateEvents, SharedStateOptions, SharedStatePatch, createSharedState };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createEventEmitter } from "./events.js";
|
|
2
|
+
import { nanoid } from "./nanoid.js";
|
|
3
|
+
import { applyPatches, enablePatches, produce, produceWithPatches } from "immer";
|
|
4
|
+
//#region src/utils/shared-state.ts
|
|
5
|
+
function createSharedState(options) {
|
|
6
|
+
const { enablePatches: enablePatches$1 = false } = options;
|
|
7
|
+
const events = createEventEmitter();
|
|
8
|
+
let state = options.initialValue;
|
|
9
|
+
const syncIds = /* @__PURE__ */ new Set();
|
|
10
|
+
return {
|
|
11
|
+
on: events.on,
|
|
12
|
+
value: () => state,
|
|
13
|
+
patch: (patches, syncId = nanoid()) => {
|
|
14
|
+
if (syncIds.has(syncId)) return;
|
|
15
|
+
enablePatches();
|
|
16
|
+
state = applyPatches(state, patches);
|
|
17
|
+
syncIds.add(syncId);
|
|
18
|
+
events.emit("updated", state, void 0, syncId);
|
|
19
|
+
},
|
|
20
|
+
mutate: (fn, syncId = nanoid()) => {
|
|
21
|
+
if (syncIds.has(syncId)) return;
|
|
22
|
+
syncIds.add(syncId);
|
|
23
|
+
if (enablePatches$1) {
|
|
24
|
+
const [newState, patches] = produceWithPatches(state, fn);
|
|
25
|
+
state = newState;
|
|
26
|
+
events.emit("updated", state, patches, syncId);
|
|
27
|
+
} else {
|
|
28
|
+
state = produce(state, fn);
|
|
29
|
+
events.emit("updated", state, void 0, syncId);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
syncIds
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { createSharedState };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitejs/devtools-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"description": "Vite DevTools Kit",
|
|
6
6
|
"author": "VoidZero Inc.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -21,24 +21,33 @@
|
|
|
21
21
|
"exports": {
|
|
22
22
|
".": "./dist/index.js",
|
|
23
23
|
"./client": "./dist/client.js",
|
|
24
|
+
"./constants": "./dist/constants.js",
|
|
25
|
+
"./utils/events": "./dist/utils/events.js",
|
|
26
|
+
"./utils/nanoid": "./dist/utils/nanoid.js",
|
|
27
|
+
"./utils/shared-state": "./dist/utils/shared-state.js",
|
|
24
28
|
"./package.json": "./package.json"
|
|
25
29
|
},
|
|
26
|
-
"
|
|
27
|
-
"module": "./dist/index.js",
|
|
28
|
-
"types": "./dist/index.d.ts",
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
29
31
|
"files": [
|
|
30
|
-
"dist"
|
|
32
|
+
"dist",
|
|
33
|
+
"skills"
|
|
31
34
|
],
|
|
32
35
|
"peerDependencies": {
|
|
33
|
-
"vite": "
|
|
36
|
+
"vite": "*"
|
|
34
37
|
},
|
|
35
38
|
"dependencies": {
|
|
36
|
-
"birpc": "^
|
|
37
|
-
"
|
|
39
|
+
"birpc": "^4.0.0",
|
|
40
|
+
"immer": "^11.1.4",
|
|
41
|
+
"@vitejs/devtools-rpc": "0.1.0"
|
|
38
42
|
},
|
|
39
43
|
"devDependencies": {
|
|
40
|
-
"tsdown": "^0.
|
|
41
|
-
"
|
|
44
|
+
"tsdown": "^0.21.2",
|
|
45
|
+
"ua-parser-modern": "^0.1.1",
|
|
46
|
+
"vite": "^8.0.0"
|
|
47
|
+
},
|
|
48
|
+
"inlinedDependencies": {
|
|
49
|
+
"ohash": "2.0.11",
|
|
50
|
+
"ua-parser-modern": "0.1.1"
|
|
42
51
|
},
|
|
43
52
|
"scripts": {
|
|
44
53
|
"build": "tsdown",
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: writing-vite-devtools-integrations
|
|
3
|
+
description: >
|
|
4
|
+
Creates devtools integrations for Vite using @vitejs/devtools-kit.
|
|
5
|
+
Use when building Vite plugins with devtools panels, RPC functions,
|
|
6
|
+
dock entries, shared state, logs/notifications, or any devtools-related
|
|
7
|
+
functionality. Applies to files importing from @vitejs/devtools-kit or
|
|
8
|
+
containing devtools.setup hooks in Vite plugins.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Vite DevTools Kit
|
|
12
|
+
|
|
13
|
+
Build custom developer tools that integrate with Vite DevTools using `@vitejs/devtools-kit`.
|
|
14
|
+
|
|
15
|
+
## Core Concepts
|
|
16
|
+
|
|
17
|
+
A DevTools plugin extends a Vite plugin with a `devtools.setup(ctx)` hook. The context provides:
|
|
18
|
+
|
|
19
|
+
| Property | Purpose |
|
|
20
|
+
|----------|---------|
|
|
21
|
+
| `ctx.docks` | Register dock entries (iframe, action, custom-render, launcher) |
|
|
22
|
+
| `ctx.views` | Host static files for UI |
|
|
23
|
+
| `ctx.rpc` | Register RPC functions, broadcast to clients |
|
|
24
|
+
| `ctx.rpc.sharedState` | Synchronized server-client state |
|
|
25
|
+
| `ctx.logs` | Emit structured log entries and toast notifications |
|
|
26
|
+
| `ctx.viteConfig` | Resolved Vite configuration |
|
|
27
|
+
| `ctx.viteServer` | Dev server instance (dev mode only) |
|
|
28
|
+
| `ctx.mode` | `'dev'` or `'build'` |
|
|
29
|
+
|
|
30
|
+
## Quick Start: Minimal Plugin
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
/// <reference types="@vitejs/devtools-kit" />
|
|
34
|
+
import type { Plugin } from 'vite'
|
|
35
|
+
|
|
36
|
+
export default function myPlugin(): Plugin {
|
|
37
|
+
return {
|
|
38
|
+
name: 'my-plugin',
|
|
39
|
+
devtools: {
|
|
40
|
+
setup(ctx) {
|
|
41
|
+
ctx.docks.register({
|
|
42
|
+
id: 'my-plugin',
|
|
43
|
+
title: 'My Plugin',
|
|
44
|
+
icon: 'ph:puzzle-piece-duotone',
|
|
45
|
+
type: 'iframe',
|
|
46
|
+
url: 'https://example.com/devtools',
|
|
47
|
+
})
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start: Full Integration
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
/// <reference types="@vitejs/devtools-kit" />
|
|
58
|
+
import type { Plugin } from 'vite'
|
|
59
|
+
import { fileURLToPath } from 'node:url'
|
|
60
|
+
import { defineRpcFunction } from '@vitejs/devtools-kit'
|
|
61
|
+
|
|
62
|
+
export default function myAnalyzer(): Plugin {
|
|
63
|
+
const data = new Map<string, { size: number }>()
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
name: 'my-analyzer',
|
|
67
|
+
|
|
68
|
+
// Collect data in Vite hooks
|
|
69
|
+
transform(code, id) {
|
|
70
|
+
data.set(id, { size: code.length })
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
devtools: {
|
|
74
|
+
setup(ctx) {
|
|
75
|
+
// 1. Host static UI
|
|
76
|
+
const clientPath = fileURLToPath(
|
|
77
|
+
new URL('../dist/client', import.meta.url)
|
|
78
|
+
)
|
|
79
|
+
ctx.views.hostStatic('/.my-analyzer/', clientPath)
|
|
80
|
+
|
|
81
|
+
// 2. Register dock entry
|
|
82
|
+
ctx.docks.register({
|
|
83
|
+
id: 'my-analyzer',
|
|
84
|
+
title: 'Analyzer',
|
|
85
|
+
icon: 'ph:chart-bar-duotone',
|
|
86
|
+
type: 'iframe',
|
|
87
|
+
url: '/.my-analyzer/',
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// 3. Register RPC function
|
|
91
|
+
ctx.rpc.register(
|
|
92
|
+
defineRpcFunction({
|
|
93
|
+
name: 'my-analyzer:get-data',
|
|
94
|
+
type: 'query',
|
|
95
|
+
setup: () => ({
|
|
96
|
+
handler: async () => Array.from(data.entries()),
|
|
97
|
+
}),
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Namespacing Convention
|
|
107
|
+
|
|
108
|
+
**CRITICAL**: Always prefix RPC functions, shared state keys, and dock IDs with your plugin name:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// Good - namespaced
|
|
112
|
+
'my-plugin:get-modules'
|
|
113
|
+
'my-plugin:state'
|
|
114
|
+
|
|
115
|
+
// Bad - may conflict
|
|
116
|
+
'get-modules'
|
|
117
|
+
'state'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Dock Entry Types
|
|
121
|
+
|
|
122
|
+
| Type | Use Case |
|
|
123
|
+
|------|----------|
|
|
124
|
+
| `iframe` | Full UI panels, dashboards (most common) |
|
|
125
|
+
| `action` | Buttons that trigger client-side scripts (inspectors, toggles) |
|
|
126
|
+
| `custom-render` | Direct DOM access in panel (framework mounting) |
|
|
127
|
+
| `launcher` | Actionable setup cards for initialization tasks |
|
|
128
|
+
|
|
129
|
+
### Iframe Entry
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
ctx.docks.register({
|
|
133
|
+
id: 'my-plugin',
|
|
134
|
+
title: 'My Plugin',
|
|
135
|
+
icon: 'ph:house-duotone',
|
|
136
|
+
type: 'iframe',
|
|
137
|
+
url: '/.my-plugin/',
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Action Entry
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
ctx.docks.register({
|
|
145
|
+
id: 'my-inspector',
|
|
146
|
+
title: 'Inspector',
|
|
147
|
+
icon: 'ph:cursor-duotone',
|
|
148
|
+
type: 'action',
|
|
149
|
+
action: {
|
|
150
|
+
importFrom: 'my-plugin/devtools-action',
|
|
151
|
+
importName: 'default',
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Custom Render Entry
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
ctx.docks.register({
|
|
160
|
+
id: 'my-custom',
|
|
161
|
+
title: 'Custom View',
|
|
162
|
+
icon: 'ph:code-duotone',
|
|
163
|
+
type: 'custom-render',
|
|
164
|
+
renderer: {
|
|
165
|
+
importFrom: 'my-plugin/devtools-renderer',
|
|
166
|
+
importName: 'default',
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Launcher Entry
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
const entry = ctx.docks.register({
|
|
175
|
+
id: 'my-setup',
|
|
176
|
+
title: 'My Setup',
|
|
177
|
+
icon: 'ph:rocket-launch-duotone',
|
|
178
|
+
type: 'launcher',
|
|
179
|
+
launcher: {
|
|
180
|
+
title: 'Initialize My Plugin',
|
|
181
|
+
description: 'Run initial setup before using the plugin',
|
|
182
|
+
buttonStart: 'Start Setup',
|
|
183
|
+
buttonLoading: 'Setting up...',
|
|
184
|
+
onLaunch: async () => {
|
|
185
|
+
// Run initialization logic
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Logs & Notifications
|
|
192
|
+
|
|
193
|
+
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.
|
|
194
|
+
|
|
195
|
+
### Fire-and-Forget
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
// No await needed
|
|
199
|
+
context.logs.add({
|
|
200
|
+
message: 'Plugin initialized',
|
|
201
|
+
level: 'info',
|
|
202
|
+
})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### With Handle
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const handle = await context.logs.add({
|
|
209
|
+
id: 'my-build',
|
|
210
|
+
message: 'Building...',
|
|
211
|
+
level: 'info',
|
|
212
|
+
status: 'loading',
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// Update later
|
|
216
|
+
await handle.update({
|
|
217
|
+
message: 'Build complete',
|
|
218
|
+
level: 'success',
|
|
219
|
+
status: 'idle',
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// Or dismiss
|
|
223
|
+
await handle.dismiss()
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Key Fields
|
|
227
|
+
|
|
228
|
+
| Field | Type | Description |
|
|
229
|
+
|-------|------|-------------|
|
|
230
|
+
| `message` | `string` | Short title (required) |
|
|
231
|
+
| `level` | `'info' \| 'warn' \| 'error' \| 'success' \| 'debug'` | Severity (required) |
|
|
232
|
+
| `description` | `string` | Detailed description |
|
|
233
|
+
| `notify` | `boolean` | Show as toast notification |
|
|
234
|
+
| `filePosition` | `{ file, line?, column? }` | Source file location (clickable) |
|
|
235
|
+
| `elementPosition` | `{ selector?, boundingBox?, description? }` | DOM element position |
|
|
236
|
+
| `id` | `string` | Explicit id for deduplication |
|
|
237
|
+
| `status` | `'loading' \| 'idle'` | Shows spinner when loading |
|
|
238
|
+
| `category` | `string` | Grouping (e.g., `'a11y'`, `'lint'`) |
|
|
239
|
+
| `labels` | `string[]` | Tags for filtering |
|
|
240
|
+
| `autoDismiss` | `number` | Toast auto-dismiss time in ms (default: 5000) |
|
|
241
|
+
| `autoDelete` | `number` | Auto-delete time in ms |
|
|
242
|
+
|
|
243
|
+
The `from` field is automatically set to `'server'` or `'browser'`.
|
|
244
|
+
|
|
245
|
+
### Deduplication
|
|
246
|
+
|
|
247
|
+
Re-adding with the same `id` updates the existing entry instead of creating a duplicate:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
context.logs.add({ id: 'my-scan', message: 'Scanning...', level: 'info', status: 'loading' })
|
|
251
|
+
context.logs.add({ id: 'my-scan', message: 'Scan complete', level: 'success', status: 'idle' })
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## RPC Functions
|
|
255
|
+
|
|
256
|
+
### Server-Side Definition
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { defineRpcFunction } from '@vitejs/devtools-kit'
|
|
260
|
+
|
|
261
|
+
const getModules = defineRpcFunction({
|
|
262
|
+
name: 'my-plugin:get-modules',
|
|
263
|
+
type: 'query', // 'query' | 'action' | 'static'
|
|
264
|
+
setup: ctx => ({
|
|
265
|
+
handler: async (filter?: string) => {
|
|
266
|
+
// ctx has full DevToolsNodeContext
|
|
267
|
+
return modules.filter(m => !filter || m.includes(filter))
|
|
268
|
+
},
|
|
269
|
+
}),
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// Register in setup
|
|
273
|
+
ctx.rpc.register(getModules)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Client-Side Call (iframe)
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
|
|
280
|
+
|
|
281
|
+
const rpc = await getDevToolsRpcClient()
|
|
282
|
+
const modules = await rpc.call('my-plugin:get-modules', 'src/')
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Client-Side Call (action/renderer script)
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client'
|
|
289
|
+
|
|
290
|
+
export default function setup(ctx: DevToolsClientScriptContext) {
|
|
291
|
+
ctx.current.events.on('entry:activated', async () => {
|
|
292
|
+
const data = await ctx.current.rpc.call('my-plugin:get-data')
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Broadcasting to Clients
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// Server broadcasts to all clients
|
|
301
|
+
ctx.rpc.broadcast({
|
|
302
|
+
method: 'my-plugin:on-update',
|
|
303
|
+
args: [{ changedFile: '/src/main.ts' }],
|
|
304
|
+
})
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Type Safety
|
|
308
|
+
|
|
309
|
+
Extend the DevTools Kit interfaces for full type checking:
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
// src/types.ts
|
|
313
|
+
import '@vitejs/devtools-kit'
|
|
314
|
+
|
|
315
|
+
declare module '@vitejs/devtools-kit' {
|
|
316
|
+
interface DevToolsRpcServerFunctions {
|
|
317
|
+
'my-plugin:get-modules': (filter?: string) => Promise<Module[]>
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
interface DevToolsRpcClientFunctions {
|
|
321
|
+
'my-plugin:on-update': (data: { changedFile: string }) => void
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
interface DevToolsRpcSharedStates {
|
|
325
|
+
'my-plugin:state': MyPluginState
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Shared State
|
|
331
|
+
|
|
332
|
+
### Server-Side
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
const state = await ctx.rpc.sharedState.get('my-plugin:state', {
|
|
336
|
+
initialValue: { count: 0, items: [] },
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Read
|
|
340
|
+
console.log(state.value())
|
|
341
|
+
|
|
342
|
+
// Mutate (auto-syncs to clients)
|
|
343
|
+
state.mutate((draft) => {
|
|
344
|
+
draft.count += 1
|
|
345
|
+
draft.items.push('new item')
|
|
346
|
+
})
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Client-Side
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
const client = await getDevToolsRpcClient()
|
|
353
|
+
const state = await client.rpc.sharedState.get('my-plugin:state')
|
|
354
|
+
|
|
355
|
+
// Read
|
|
356
|
+
console.log(state.value())
|
|
357
|
+
|
|
358
|
+
// Subscribe to changes
|
|
359
|
+
state.on('updated', (newState) => {
|
|
360
|
+
console.log('State updated:', newState)
|
|
361
|
+
})
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Client Scripts
|
|
365
|
+
|
|
366
|
+
For action buttons and custom renderers:
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
// src/devtools-action.ts
|
|
370
|
+
import type { DevToolsClientScriptContext } from '@vitejs/devtools-kit/client'
|
|
371
|
+
|
|
372
|
+
export default function setup(ctx: DevToolsClientScriptContext) {
|
|
373
|
+
ctx.current.events.on('entry:activated', () => {
|
|
374
|
+
console.log('Action activated')
|
|
375
|
+
// Your inspector/tool logic here
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
ctx.current.events.on('entry:deactivated', () => {
|
|
379
|
+
console.log('Action deactivated')
|
|
380
|
+
// Cleanup
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Export from package.json:
|
|
386
|
+
|
|
387
|
+
```json
|
|
388
|
+
{
|
|
389
|
+
"exports": {
|
|
390
|
+
".": "./dist/index.mjs",
|
|
391
|
+
"./devtools-action": "./dist/devtools-action.mjs"
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Debugging with Self-Inspect
|
|
397
|
+
|
|
398
|
+
Use `@vitejs/devtools-self-inspect` to debug your DevTools plugin. It shows registered RPC functions, dock entries, client scripts, and plugins in a meta-introspection UI at `/.devtools-self-inspect/`.
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
import DevTools from '@vitejs/devtools'
|
|
402
|
+
import DevToolsSelfInspect from '@vitejs/devtools-self-inspect'
|
|
403
|
+
|
|
404
|
+
export default defineConfig({
|
|
405
|
+
plugins: [
|
|
406
|
+
DevTools(),
|
|
407
|
+
DevToolsSelfInspect(),
|
|
408
|
+
],
|
|
409
|
+
})
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Best Practices
|
|
413
|
+
|
|
414
|
+
1. **Always namespace** - Prefix all identifiers with your plugin name
|
|
415
|
+
2. **Use type augmentation** - Extend `DevToolsRpcServerFunctions` for type-safe RPC
|
|
416
|
+
3. **Keep state serializable** - No functions or circular references in shared state
|
|
417
|
+
4. **Batch mutations** - Use single `mutate()` call for multiple changes
|
|
418
|
+
5. **Host static files** - Use `ctx.views.hostStatic()` for your UI assets
|
|
419
|
+
6. **Use Iconify icons** - Prefer `ph:*` (Phosphor) icons: `icon: 'ph:chart-bar-duotone'`
|
|
420
|
+
7. **Deduplicate logs** - Use explicit `id` for logs representing ongoing operations
|
|
421
|
+
8. **Use Self-Inspect** - Add `@vitejs/devtools-self-inspect` during development to debug your plugin
|
|
422
|
+
|
|
423
|
+
## Example Plugins
|
|
424
|
+
|
|
425
|
+
Real-world example plugins in the repo — reference their code structure and patterns when building new integrations:
|
|
426
|
+
|
|
427
|
+
- **A11y Checker** ([`examples/plugin-a11y-checker`](https://github.com/vitejs/devtools/tree/main/examples/plugin-a11y-checker)) — Action dock entry, client-side axe-core audits, logs with severity levels and element positions, log handle updates
|
|
428
|
+
- **File Explorer** ([`examples/plugin-file-explorer`](https://github.com/vitejs/devtools/tree/main/examples/plugin-file-explorer)) — Iframe dock entry, RPC functions (static/query/action), hosted UI panel, RPC dump for static builds, backend mode detection
|
|
429
|
+
|
|
430
|
+
## Further Reading
|
|
431
|
+
|
|
432
|
+
- [RPC Patterns](./references/rpc-patterns.md) - Advanced RPC patterns and type utilities
|
|
433
|
+
- [Dock Entry Types](./references/dock-entry-types.md) - Detailed dock configuration options
|
|
434
|
+
- [Shared State Patterns](./references/shared-state-patterns.md) - Framework integration examples
|
|
435
|
+
- [Project Structure](./references/project-structure.md) - Recommended file organization
|
|
436
|
+
- [Logs Patterns](./references/logs-patterns.md) - Log entries, toast notifications, and handle patterns
|