@vitejs/devtools-kit 0.1.1 → 0.1.2
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitejs/devtools-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "Vite DevTools Kit",
|
|
6
6
|
"author": "VoidZero Inc.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"birpc": "^4.0.0",
|
|
40
40
|
"immer": "^11.1.4",
|
|
41
|
-
"@vitejs/devtools-rpc": "0.1.
|
|
41
|
+
"@vitejs/devtools-rpc": "0.1.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"tsdown": "^0.21.2",
|
|
@@ -18,11 +18,13 @@ A DevTools plugin extends a Vite plugin with a `devtools.setup(ctx)` hook. The c
|
|
|
18
18
|
|
|
19
19
|
| Property | Purpose |
|
|
20
20
|
|----------|---------|
|
|
21
|
-
| `ctx.docks` | Register dock entries (iframe, action, custom-render, launcher) |
|
|
21
|
+
| `ctx.docks` | Register dock entries (iframe, action, custom-render, launcher, json-render) |
|
|
22
22
|
| `ctx.views` | Host static files for UI |
|
|
23
23
|
| `ctx.rpc` | Register RPC functions, broadcast to clients |
|
|
24
24
|
| `ctx.rpc.sharedState` | Synchronized server-client state |
|
|
25
25
|
| `ctx.logs` | Emit structured log entries and toast notifications |
|
|
26
|
+
| `ctx.terminals` | Spawn and manage child processes with streaming terminal output |
|
|
27
|
+
| `ctx.createJsonRenderer` | Create server-side JSON render specs for zero-client-code UIs |
|
|
26
28
|
| `ctx.viteConfig` | Resolved Vite configuration |
|
|
27
29
|
| `ctx.viteServer` | Dev server instance (dev mode only) |
|
|
28
30
|
| `ctx.mode` | `'dev'` or `'build'` |
|
|
@@ -122,6 +124,7 @@ export default function myAnalyzer(): Plugin {
|
|
|
122
124
|
| Type | Use Case |
|
|
123
125
|
|------|----------|
|
|
124
126
|
| `iframe` | Full UI panels, dashboards (most common) |
|
|
127
|
+
| `json-render` | Server-side JSON specs — zero client code needed |
|
|
125
128
|
| `action` | Buttons that trigger client-side scripts (inspectors, toggles) |
|
|
126
129
|
| `custom-render` | Direct DOM access in panel (framework mounting) |
|
|
127
130
|
| `launcher` | Actionable setup cards for initialization tasks |
|
|
@@ -168,6 +171,44 @@ ctx.docks.register({
|
|
|
168
171
|
})
|
|
169
172
|
```
|
|
170
173
|
|
|
174
|
+
### JSON Render Entry
|
|
175
|
+
|
|
176
|
+
Build UIs entirely from server-side TypeScript — no client code needed:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const ui = ctx.createJsonRenderer({
|
|
180
|
+
root: 'root',
|
|
181
|
+
elements: {
|
|
182
|
+
root: {
|
|
183
|
+
type: 'Stack',
|
|
184
|
+
props: { direction: 'vertical', gap: 12 },
|
|
185
|
+
children: ['heading', 'info'],
|
|
186
|
+
},
|
|
187
|
+
heading: {
|
|
188
|
+
type: 'Text',
|
|
189
|
+
props: { content: 'Hello from JSON!', variant: 'heading' },
|
|
190
|
+
},
|
|
191
|
+
info: {
|
|
192
|
+
type: 'KeyValueTable',
|
|
193
|
+
props: {
|
|
194
|
+
entries: [
|
|
195
|
+
{ key: 'Version', value: '1.0.0' },
|
|
196
|
+
{ key: 'Status', value: 'Running' },
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
ctx.docks.register({
|
|
204
|
+
id: 'my-panel',
|
|
205
|
+
title: 'My Panel',
|
|
206
|
+
icon: 'ph:chart-bar-duotone',
|
|
207
|
+
type: 'json-render',
|
|
208
|
+
ui,
|
|
209
|
+
})
|
|
210
|
+
```
|
|
211
|
+
|
|
171
212
|
### Launcher Entry
|
|
172
213
|
|
|
173
214
|
```ts
|
|
@@ -188,6 +229,31 @@ const entry = ctx.docks.register({
|
|
|
188
229
|
})
|
|
189
230
|
```
|
|
190
231
|
|
|
232
|
+
## Terminals & Subprocesses
|
|
233
|
+
|
|
234
|
+
Spawn and manage child processes with streaming terminal output:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
const session = await ctx.terminals.startChildProcess(
|
|
238
|
+
{
|
|
239
|
+
command: 'vite',
|
|
240
|
+
args: ['build', '--watch'],
|
|
241
|
+
cwd: process.cwd(),
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: 'my-plugin:build-watcher',
|
|
245
|
+
title: 'Build Watcher',
|
|
246
|
+
icon: 'ph:terminal-duotone',
|
|
247
|
+
},
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
// Lifecycle controls
|
|
251
|
+
await session.terminate()
|
|
252
|
+
await session.restart()
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
A common pattern is combining with launcher docks — see [Terminals Patterns](./references/terminals-patterns.md).
|
|
256
|
+
|
|
191
257
|
## Logs & Notifications
|
|
192
258
|
|
|
193
259
|
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.
|
|
@@ -426,6 +492,7 @@ Real-world example plugins in the repo — reference their code structure and pa
|
|
|
426
492
|
|
|
427
493
|
- **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
494
|
- **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
|
|
495
|
+
- **Git UI** ([`examples/plugin-git-ui`](https://github.com/vitejs/devtools/tree/main/examples/plugin-git-ui)) — JSON render dock entry, server-side JSON specs, `$bindState` two-way binding, `$state` in action params, dynamic badge updates
|
|
429
496
|
|
|
430
497
|
## Further Reading
|
|
431
498
|
|
|
@@ -433,4 +500,6 @@ Real-world example plugins in the repo — reference their code structure and pa
|
|
|
433
500
|
- [Dock Entry Types](./references/dock-entry-types.md) - Detailed dock configuration options
|
|
434
501
|
- [Shared State Patterns](./references/shared-state-patterns.md) - Framework integration examples
|
|
435
502
|
- [Project Structure](./references/project-structure.md) - Recommended file organization
|
|
503
|
+
- [JSON Render Patterns](./references/json-render-patterns.md) - Server-side JSON specs, components, state binding
|
|
504
|
+
- [Terminals Patterns](./references/terminals-patterns.md) - Child processes, custom streams, session lifecycle
|
|
436
505
|
- [Logs Patterns](./references/logs-patterns.md) - Log entries, toast notifications, and handle patterns
|
|
@@ -201,6 +201,93 @@ export default function setup(ctx: DevToolsClientScriptContext) {
|
|
|
201
201
|
}
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
+
## JSON Render Entries
|
|
205
|
+
|
|
206
|
+
Server-side JSON specs rendered by the built-in component library. No client code needed.
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
interface JsonRenderEntry extends DockEntryBase {
|
|
210
|
+
type: 'json-render'
|
|
211
|
+
ui: JsonRenderer // Handle from ctx.createJsonRenderer()
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Registration
|
|
215
|
+
const ui = ctx.createJsonRenderer({
|
|
216
|
+
root: 'root',
|
|
217
|
+
state: { query: '' },
|
|
218
|
+
elements: {
|
|
219
|
+
root: {
|
|
220
|
+
type: 'Stack',
|
|
221
|
+
props: { direction: 'vertical', gap: 12 },
|
|
222
|
+
children: ['heading', 'info'],
|
|
223
|
+
},
|
|
224
|
+
heading: {
|
|
225
|
+
type: 'Text',
|
|
226
|
+
props: { content: 'My Panel', variant: 'heading' },
|
|
227
|
+
},
|
|
228
|
+
info: {
|
|
229
|
+
type: 'KeyValueTable',
|
|
230
|
+
props: {
|
|
231
|
+
entries: [
|
|
232
|
+
{ key: 'Status', value: 'Running' },
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
ctx.docks.register({
|
|
240
|
+
id: 'my-panel',
|
|
241
|
+
title: 'My Panel',
|
|
242
|
+
icon: 'ph:chart-bar-duotone',
|
|
243
|
+
type: 'json-render',
|
|
244
|
+
ui,
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Dynamic Updates
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
// Replace the entire spec
|
|
252
|
+
await ui.updateSpec(buildSpec(newData))
|
|
253
|
+
|
|
254
|
+
// Shallow-merge into spec.state
|
|
255
|
+
await ui.updateState({ query: 'vue' })
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Action Handling
|
|
259
|
+
|
|
260
|
+
Buttons trigger server-side RPC functions via `on.press.action`:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// In spec element
|
|
264
|
+
{
|
|
265
|
+
type: 'Button',
|
|
266
|
+
props: { label: 'Refresh', icon: 'ph:arrows-clockwise' },
|
|
267
|
+
on: { press: { action: 'my-plugin:refresh' } },
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Matching RPC function
|
|
271
|
+
ctx.rpc.register(defineRpcFunction({
|
|
272
|
+
name: 'my-plugin:refresh',
|
|
273
|
+
type: 'action',
|
|
274
|
+
setup: ctx => ({
|
|
275
|
+
handler: async () => {
|
|
276
|
+
await ui.updateSpec(buildSpec(await fetchData()))
|
|
277
|
+
},
|
|
278
|
+
}),
|
|
279
|
+
}))
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### JSON Render Use Cases
|
|
283
|
+
|
|
284
|
+
- **Build reports** — Display build stats, module lists, timing data
|
|
285
|
+
- **Configuration viewers** — Show resolved config with key-value tables
|
|
286
|
+
- **Status dashboards** — Progress bars, badges, real-time updates
|
|
287
|
+
- **Simple forms** — Text inputs with state binding + action buttons
|
|
288
|
+
|
|
289
|
+
See [JSON Render Patterns](./json-render-patterns.md) for the full component library and state binding details.
|
|
290
|
+
|
|
204
291
|
## Launcher Entries
|
|
205
292
|
|
|
206
293
|
Actionable setup cards for running initialization tasks. Shows a card with title, description, and a launch button.
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# JSON Render Patterns
|
|
2
|
+
|
|
3
|
+
Build DevTools UIs entirely from server-side TypeScript — no client code needed. Describe your UI as a JSON spec, and the DevTools client renders it with the built-in component library.
|
|
4
|
+
|
|
5
|
+
## Spec Structure
|
|
6
|
+
|
|
7
|
+
A JSON render spec has three parts: a `root` element ID, an `elements` map, and an optional `state` object for two-way bindings.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
ctx.createJsonRenderer({
|
|
11
|
+
root: 'root',
|
|
12
|
+
state: {
|
|
13
|
+
searchQuery: '',
|
|
14
|
+
},
|
|
15
|
+
elements: {
|
|
16
|
+
root: {
|
|
17
|
+
type: 'Stack',
|
|
18
|
+
props: { direction: 'vertical', gap: 12 },
|
|
19
|
+
children: ['title', 'content'],
|
|
20
|
+
},
|
|
21
|
+
title: {
|
|
22
|
+
type: 'Text',
|
|
23
|
+
props: { content: 'My Panel', variant: 'heading' },
|
|
24
|
+
},
|
|
25
|
+
content: {
|
|
26
|
+
type: 'Text',
|
|
27
|
+
props: { content: 'Hello world' },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Every element has a `type` (component name), `props`, and optionally `children` (array of element IDs) or `on` (event handlers).
|
|
34
|
+
|
|
35
|
+
## Registration
|
|
36
|
+
|
|
37
|
+
Pass the renderer handle as `ui` when registering a `json-render` dock entry:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const ui = ctx.createJsonRenderer(spec)
|
|
41
|
+
|
|
42
|
+
ctx.docks.register({
|
|
43
|
+
id: 'my-panel',
|
|
44
|
+
title: 'My Panel',
|
|
45
|
+
icon: 'ph:chart-bar-duotone',
|
|
46
|
+
type: 'json-render',
|
|
47
|
+
ui,
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Dynamic Updates
|
|
52
|
+
|
|
53
|
+
The `JsonRenderer` handle provides two methods for updating the UI reactively:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const ui = ctx.createJsonRenderer(buildSpec(initialData))
|
|
57
|
+
|
|
58
|
+
// Replace the entire spec (e.g. after fetching new data)
|
|
59
|
+
await ui.updateSpec(buildSpec(newData))
|
|
60
|
+
|
|
61
|
+
// Shallow-merge into spec.state (updates client-side state values)
|
|
62
|
+
await ui.updateState({ searchQuery: 'vue' })
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Update the dock entry badge when data changes:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
ctx.docks.update({
|
|
69
|
+
id: 'my-panel',
|
|
70
|
+
type: 'json-render',
|
|
71
|
+
title: 'My Panel',
|
|
72
|
+
icon: 'ph:chart-bar-duotone',
|
|
73
|
+
ui,
|
|
74
|
+
badge: hasWarnings ? '!' : undefined,
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Handling Actions via RPC
|
|
79
|
+
|
|
80
|
+
Buttons in the spec trigger RPC functions on the server via the `on` property:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// In the spec — Button with an action
|
|
84
|
+
const ui = ctx.createJsonRenderer({
|
|
85
|
+
root: 'refresh-btn',
|
|
86
|
+
elements: {
|
|
87
|
+
'refresh-btn': {
|
|
88
|
+
type: 'Button',
|
|
89
|
+
props: { label: 'Refresh', icon: 'ph:arrows-clockwise' },
|
|
90
|
+
on: { press: { action: 'my-plugin:refresh' } },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// On the server — register the matching RPC function
|
|
96
|
+
ctx.rpc.register(defineRpcFunction({
|
|
97
|
+
name: 'my-plugin:refresh',
|
|
98
|
+
type: 'action',
|
|
99
|
+
setup: ctx => ({
|
|
100
|
+
handler: async () => {
|
|
101
|
+
const data = await fetchData()
|
|
102
|
+
await ui.updateSpec(buildSpec(data))
|
|
103
|
+
},
|
|
104
|
+
}),
|
|
105
|
+
}))
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Pass parameters from the spec to the action handler:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
on: {
|
|
112
|
+
press: {
|
|
113
|
+
action: 'my-plugin:delete',
|
|
114
|
+
params: { id: 'some-id' },
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## State and Two-Way Binding
|
|
120
|
+
|
|
121
|
+
Use `$bindState` on TextInput `value` to create two-way binding with a state key. Use `$state` to read the bound value in action params:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const ui = ctx.createJsonRenderer({
|
|
125
|
+
root: 'root',
|
|
126
|
+
state: { message: '' },
|
|
127
|
+
elements: {
|
|
128
|
+
root: {
|
|
129
|
+
type: 'Stack',
|
|
130
|
+
props: { direction: 'horizontal', gap: 8 },
|
|
131
|
+
children: ['input', 'submit'],
|
|
132
|
+
},
|
|
133
|
+
input: {
|
|
134
|
+
type: 'TextInput',
|
|
135
|
+
props: {
|
|
136
|
+
placeholder: 'Type here...',
|
|
137
|
+
value: { $bindState: '/message' },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
submit: {
|
|
141
|
+
type: 'Button',
|
|
142
|
+
props: { label: 'Submit', variant: 'primary' },
|
|
143
|
+
on: {
|
|
144
|
+
press: {
|
|
145
|
+
action: 'my-plugin:submit',
|
|
146
|
+
params: { text: { $state: '/message' } },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The server-side handler receives the resolved state values:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
ctx.rpc.register(defineRpcFunction({
|
|
158
|
+
name: 'my-plugin:submit',
|
|
159
|
+
type: 'action',
|
|
160
|
+
setup: ctx => ({
|
|
161
|
+
handler: async (params: { text?: string }) => {
|
|
162
|
+
console.log('User submitted:', params.text)
|
|
163
|
+
},
|
|
164
|
+
}),
|
|
165
|
+
}))
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Built-in Components
|
|
169
|
+
|
|
170
|
+
### Layout
|
|
171
|
+
|
|
172
|
+
| Component | Props | Description |
|
|
173
|
+
|-----------|-------|-------------|
|
|
174
|
+
| `Stack` | `direction`, `gap`, `align`, `justify`, `padding` | Flex layout container |
|
|
175
|
+
| `Card` | `title`, `collapsible` | Container with optional title, collapsible |
|
|
176
|
+
| `Divider` | `label` | Separator line with optional label |
|
|
177
|
+
|
|
178
|
+
### Typography
|
|
179
|
+
|
|
180
|
+
| Component | Props | Description |
|
|
181
|
+
|-----------|-------|-------------|
|
|
182
|
+
| `Text` | `content`, `variant` (`heading`/`body`/`caption`/`code`) | Display text |
|
|
183
|
+
| `Icon` | `name`, `size` | Iconify icon by name |
|
|
184
|
+
| `Badge` | `text`, `variant` (`info`/`success`/`warning`/`error`/`default`) | Status label |
|
|
185
|
+
|
|
186
|
+
### Inputs
|
|
187
|
+
|
|
188
|
+
| Component | Props | Description |
|
|
189
|
+
|-----------|-------|-------------|
|
|
190
|
+
| `Button` | `label`, `icon`, `variant` (`primary`/`secondary`/`ghost`/`danger`), `disabled` | Clickable button, fires `press` event |
|
|
191
|
+
| `TextInput` | `placeholder`, `value`, `label`, `disabled` | Text input, supports `$bindState` on `value` |
|
|
192
|
+
|
|
193
|
+
### Data Display
|
|
194
|
+
|
|
195
|
+
| Component | Props | Description |
|
|
196
|
+
|-----------|-------|-------------|
|
|
197
|
+
| `KeyValueTable` | `title`, `entries` (`Array<{ key, value }>`) | Two-column key-value table |
|
|
198
|
+
| `DataTable` | `columns`, `rows`, `maxHeight` | Tabular data with configurable columns |
|
|
199
|
+
| `CodeBlock` | `code`, `language`, `filename`, `maxHeight` | Code snippet with optional filename header |
|
|
200
|
+
| `Progress` | `value`, `max`, `label` | Progress bar with percentage |
|
|
201
|
+
| `Tree` | `data`, `expandLevel` | Expandable tree for nested objects |
|
|
202
|
+
|
|
203
|
+
## Full Example
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import type { JsonRenderSpec, PluginWithDevTools } from '@vitejs/devtools-kit'
|
|
207
|
+
import { defineRpcFunction } from '@vitejs/devtools-kit'
|
|
208
|
+
|
|
209
|
+
function buildSpec(data: { modules: number, time: string, size: string }): JsonRenderSpec {
|
|
210
|
+
return {
|
|
211
|
+
root: 'root',
|
|
212
|
+
state: { filter: '' },
|
|
213
|
+
elements: {
|
|
214
|
+
'root': {
|
|
215
|
+
type: 'Stack',
|
|
216
|
+
props: { direction: 'vertical', gap: 12, padding: 8 },
|
|
217
|
+
children: ['header', 'divider', 'stats'],
|
|
218
|
+
},
|
|
219
|
+
'header': {
|
|
220
|
+
type: 'Stack',
|
|
221
|
+
props: { direction: 'horizontal', gap: 8, align: 'center', justify: 'space-between' },
|
|
222
|
+
children: ['title', 'refresh-btn'],
|
|
223
|
+
},
|
|
224
|
+
'title': {
|
|
225
|
+
type: 'Text',
|
|
226
|
+
props: { content: 'Build Report', variant: 'heading' },
|
|
227
|
+
},
|
|
228
|
+
'refresh-btn': {
|
|
229
|
+
type: 'Button',
|
|
230
|
+
props: { label: 'Refresh', icon: 'ph:arrows-clockwise' },
|
|
231
|
+
on: { press: { action: 'build-report:refresh' } },
|
|
232
|
+
},
|
|
233
|
+
'divider': {
|
|
234
|
+
type: 'Divider',
|
|
235
|
+
props: {},
|
|
236
|
+
},
|
|
237
|
+
'stats': {
|
|
238
|
+
type: 'KeyValueTable',
|
|
239
|
+
props: {
|
|
240
|
+
title: 'Summary',
|
|
241
|
+
entries: [
|
|
242
|
+
{ key: 'Total Modules', value: String(data.modules) },
|
|
243
|
+
{ key: 'Build Time', value: data.time },
|
|
244
|
+
{ key: 'Output Size', value: data.size },
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function BuildReportPlugin(): PluginWithDevTools {
|
|
253
|
+
return {
|
|
254
|
+
name: 'build-report',
|
|
255
|
+
devtools: {
|
|
256
|
+
setup(ctx) {
|
|
257
|
+
const data = { modules: 142, time: '1.2s', size: '48 KB' }
|
|
258
|
+
const ui = ctx.createJsonRenderer(buildSpec(data))
|
|
259
|
+
|
|
260
|
+
ctx.docks.register({
|
|
261
|
+
id: 'build-report',
|
|
262
|
+
title: 'Build Report',
|
|
263
|
+
icon: 'ph:chart-bar-duotone',
|
|
264
|
+
type: 'json-render',
|
|
265
|
+
ui,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
ctx.rpc.register(defineRpcFunction({
|
|
269
|
+
name: 'build-report:refresh',
|
|
270
|
+
type: 'action',
|
|
271
|
+
setup: ctx => ({
|
|
272
|
+
handler: async () => {
|
|
273
|
+
const newData = { modules: 145, time: '1.1s', size: '47 KB' }
|
|
274
|
+
await ui.updateSpec(buildSpec(newData))
|
|
275
|
+
},
|
|
276
|
+
}),
|
|
277
|
+
}))
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
> See the [Git UI example](https://github.com/vitejs/devtools/tree/main/examples/plugin-git-ui) for a more advanced plugin using json-render with per-file actions, text input with state binding, and dynamic badge updates.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Terminals & Subprocesses
|
|
2
|
+
|
|
3
|
+
Spawn and manage child processes from your plugin. Output streams in real-time to an xterm.js terminal inside DevTools.
|
|
4
|
+
|
|
5
|
+
## Starting a Child Process
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const session = await ctx.terminals.startChildProcess(
|
|
9
|
+
{
|
|
10
|
+
command: 'vite',
|
|
11
|
+
args: ['build', '--watch'],
|
|
12
|
+
cwd: process.cwd(),
|
|
13
|
+
env: { NODE_ENV: 'development' },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'my-plugin:build-watcher',
|
|
17
|
+
title: 'Build Watcher',
|
|
18
|
+
icon: 'ph:terminal-duotone',
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Execute Options
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
interface DevToolsChildProcessExecuteOptions {
|
|
27
|
+
command: string
|
|
28
|
+
args: string[]
|
|
29
|
+
cwd?: string
|
|
30
|
+
env?: Record<string, string>
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The second argument provides terminal metadata (`id`, `title`, and optional `description`/`icon`).
|
|
35
|
+
|
|
36
|
+
> Color output is enabled automatically — `FORCE_COLOR` and `COLORS` environment variables are set to `'true'` by default.
|
|
37
|
+
|
|
38
|
+
## Session Lifecycle
|
|
39
|
+
|
|
40
|
+
`startChildProcess()` returns a `DevToolsChildProcessTerminalSession` with lifecycle controls:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// Terminate the process
|
|
44
|
+
await session.terminate()
|
|
45
|
+
|
|
46
|
+
// Restart (kill + re-spawn)
|
|
47
|
+
await session.restart()
|
|
48
|
+
|
|
49
|
+
// Access the underlying Node.js ChildProcess
|
|
50
|
+
const cp = session.getChildProcess()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Session Status
|
|
54
|
+
|
|
55
|
+
| Status | Description |
|
|
56
|
+
|--------|-------------|
|
|
57
|
+
| `running` | Process is active and streaming output |
|
|
58
|
+
| `stopped` | Process exited normally |
|
|
59
|
+
| `error` | Process exited with an error |
|
|
60
|
+
|
|
61
|
+
Update a session's metadata or status at any time:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
ctx.terminals.update({
|
|
65
|
+
id: 'my-plugin:build-watcher',
|
|
66
|
+
status: 'stopped',
|
|
67
|
+
title: 'Build Watcher (done)',
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Combining with Launcher Docks
|
|
72
|
+
|
|
73
|
+
A common pattern is pairing a launcher dock entry with a terminal session. The launcher gives the user a button to start the process on demand:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
ctx.docks.register({
|
|
77
|
+
id: 'my-plugin:launcher',
|
|
78
|
+
title: 'My App',
|
|
79
|
+
icon: 'ph:rocket-launch-duotone',
|
|
80
|
+
type: 'launcher',
|
|
81
|
+
launcher: {
|
|
82
|
+
title: 'Start My App',
|
|
83
|
+
description: 'Launch the dev server',
|
|
84
|
+
onLaunch: async () => {
|
|
85
|
+
await ctx.terminals.startChildProcess(
|
|
86
|
+
{
|
|
87
|
+
command: 'vite',
|
|
88
|
+
args: ['dev'],
|
|
89
|
+
cwd: process.cwd(),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'my-plugin:dev-server',
|
|
93
|
+
title: 'Dev Server',
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Custom Terminal Sessions
|
|
102
|
+
|
|
103
|
+
For scenarios that don't involve spawning a child process (e.g. streaming logs from an external source), register a session directly with a custom `ReadableStream`:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
let controller: ReadableStreamDefaultController<string>
|
|
107
|
+
|
|
108
|
+
const stream = new ReadableStream<string>({
|
|
109
|
+
start(c) {
|
|
110
|
+
controller = c
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
ctx.terminals.register({
|
|
115
|
+
id: 'my-plugin:custom-stream',
|
|
116
|
+
title: 'Custom Output',
|
|
117
|
+
status: 'running',
|
|
118
|
+
stream,
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Push data to the terminal
|
|
122
|
+
controller.enqueue('Hello from custom stream!\n')
|
|
123
|
+
```
|