@vitejs/devtools-kit 0.1.1 → 0.1.3
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 -2
- package/dist/client.js +10 -1
- package/dist/constants.d.ts +1 -1
- package/dist/{index-WxzZW3L-.d.ts → index-DryCMVBy.d.ts} +8 -1
- package/dist/index.d.ts +1 -1
- package/package.json +2 -2
- package/skills/vite-devtools-kit/SKILL.md +83 -1
- package/skills/vite-devtools-kit/references/dock-entry-types.md +87 -0
- package/skills/vite-devtools-kit/references/json-render-patterns.md +284 -0
- package/skills/vite-devtools-kit/references/project-structure.md +9 -0
- package/skills/vite-devtools-kit/references/rpc-patterns.md +15 -0
- package/skills/vite-devtools-kit/references/shared-state-patterns.md +12 -1
- package/skills/vite-devtools-kit/references/terminals-patterns.md +123 -0
package/dist/client.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
export { DevToolsClientContext, DevToolsClientRpcHost, DevToolsRpcClient, DevToolsRpcClientCall, DevToolsRpcClientCallEvent, DevToolsRpcClientCallOptional, DevToolsRpcClientMode, DevToolsRpcClientOptions, DockClientScriptContext, DockClientType, DockEntryState, DockEntryStateEvents, DockPanelStorage, DocksContext, DocksEntriesContext, DocksPanelContext, RpcClientEvents, getDevToolsRpcClient };
|
|
1
|
+
import { _ as DevToolsRpcClientCallOptional, a as DevToolsClientRpcHost, b as getDevToolsRpcClient, c as DockEntryStateEvents, d as DocksEntriesContext, f as DocksPanelContext, g as DevToolsRpcClientCallEvent, h as DevToolsRpcClientCall, i as DevToolsClientContext, l as DockPanelStorage, m as DevToolsRpcClient, n as getDevToolsClientContext, o as DockClientType, p as RpcClientEvents, r as DockClientScriptContext, s as DockEntryState, t as CLIENT_CONTEXT_KEY, u as DocksContext, v as DevToolsRpcClientMode, y as DevToolsRpcClientOptions } from "./index-DryCMVBy.js";
|
|
2
|
+
export { CLIENT_CONTEXT_KEY, DevToolsClientContext, DevToolsClientRpcHost, DevToolsRpcClient, DevToolsRpcClientCall, DevToolsRpcClientCallEvent, DevToolsRpcClientCallOptional, DevToolsRpcClientMode, DevToolsRpcClientOptions, DockClientScriptContext, DockClientType, DockEntryState, DockEntryStateEvents, DockPanelStorage, DocksContext, DocksEntriesContext, DocksPanelContext, RpcClientEvents, getDevToolsClientContext, getDevToolsRpcClient };
|
package/dist/client.js
CHANGED
|
@@ -5,6 +5,15 @@ import { createSharedState } from "./utils/shared-state.js";
|
|
|
5
5
|
import { RpcFunctionsCollectorBase } from "@vitejs/devtools-rpc";
|
|
6
6
|
import { createRpcClient } from "@vitejs/devtools-rpc/client";
|
|
7
7
|
import { createWsRpcPreset } from "@vitejs/devtools-rpc/presets/ws/client";
|
|
8
|
+
//#region src/client/context.ts
|
|
9
|
+
const CLIENT_CONTEXT_KEY = "__VITE_DEVTOOLS_CLIENT_CONTEXT__";
|
|
10
|
+
/**
|
|
11
|
+
* Get the global DevTools client context, or `undefined` if not yet initialized.
|
|
12
|
+
*/
|
|
13
|
+
function getDevToolsClientContext() {
|
|
14
|
+
return globalThis[CLIENT_CONTEXT_KEY];
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
8
17
|
//#region src/client/rpc-shared-state.ts
|
|
9
18
|
function createRpcSharedStateClientHost(rpc) {
|
|
10
19
|
const sharedState = /* @__PURE__ */ new Map();
|
|
@@ -1749,4 +1758,4 @@ async function getDevToolsRpcClient(options = {}) {
|
|
|
1749
1758
|
return rpc;
|
|
1750
1759
|
}
|
|
1751
1760
|
//#endregion
|
|
1752
|
-
export { getDevToolsRpcClient };
|
|
1761
|
+
export { CLIENT_CONTEXT_KEY, getDevToolsClientContext, getDevToolsRpcClient };
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as DevToolsDocksUserSettings } from "./index-DryCMVBy.js";
|
|
2
2
|
//#region src/constants.d.ts
|
|
3
3
|
declare const DEVTOOLS_MOUNT_PATH = "/.devtools/";
|
|
4
4
|
declare const DEVTOOLS_MOUNT_PATH_NO_TRAILING_SLASH = "/.devtools";
|
|
@@ -676,4 +676,11 @@ interface DockClientScriptContext extends DocksContext {
|
|
|
676
676
|
logs: DevToolsLogsClient;
|
|
677
677
|
}
|
|
678
678
|
//#endregion
|
|
679
|
-
|
|
679
|
+
//#region src/client/context.d.ts
|
|
680
|
+
declare const CLIENT_CONTEXT_KEY = "__VITE_DEVTOOLS_CLIENT_CONTEXT__";
|
|
681
|
+
/**
|
|
682
|
+
* Get the global DevTools client context, or `undefined` if not yet initialized.
|
|
683
|
+
*/
|
|
684
|
+
declare function getDevToolsClientContext(): DevToolsClientContext | undefined;
|
|
685
|
+
//#endregion
|
|
686
|
+
export { DevToolsDockEntryBase as $, RpcBroadcastOptions as A, DevToolsChildProcessExecuteOptions as B, PluginWithDevTools as C, DevToolsDocksUserSettings as D, Thenable as E, DevToolsCapabilities as F, DevToolsTerminalSessionStreamChunkEvent as G, DevToolsTerminalHost as H, DevToolsNodeContext as I, DevToolsRpcServerFunctions as J, DevToolsTerminalStatus as K, DevToolsNodeUtils as L, RpcSharedStateGetOptions as M, RpcSharedStateHost as N, DevToolsNodeRpcSession as O, ConnectionMeta as P, DevToolsDockEntry as Q, DevToolsPluginOptions as R, RpcDefinitionsToFunctions as S, DevToolsLogsHost as St, PartialWithoutId as T, DevToolsTerminalSession as U, DevToolsChildProcessTerminalSession as V, DevToolsTerminalSessionBase as W, ClientScriptEntry as X, DevToolsRpcSharedStates as Y, DevToolsDockEntriesGrouped as Z, DevToolsRpcClientCallOptional as _, DevToolsLogEntryInput as _t, DevToolsClientRpcHost as a, DevToolsViewBuiltin as at, getDevToolsRpcClient as b, DevToolsLogLevel as bt, DockEntryStateEvents as c, DevToolsViewJsonRender as ct, DocksEntriesContext as d, JsonRenderElement as dt, DevToolsDockEntryCategory as et, DocksPanelContext as f, JsonRenderSpec as ft, DevToolsRpcClientCallEvent as g, DevToolsLogEntryFrom as gt, DevToolsRpcClientCall as h, DevToolsLogEntry as ht, DevToolsClientContext as i, DevToolsViewAction as it, RpcFunctionsHost as j, DevToolsNodeRpcSessionMeta as k, DockPanelStorage as l, DevToolsViewLauncher as lt, DevToolsRpcClient as m, DevToolsLogElementPosition as mt, getDevToolsClientContext as n, DevToolsDockHost as nt, DockClientType as o, DevToolsViewCustomRender as ot, RpcClientEvents as p, JsonRenderer as pt, DevToolsRpcClientFunctions as q, DockClientScriptContext as r, DevToolsDockUserEntry as rt, DockEntryState as s, DevToolsViewIframe as st, CLIENT_CONTEXT_KEY as t, DevToolsDockEntryIcon as tt, DocksContext as u, DevToolsViewLauncherStatus as ut, DevToolsRpcClientMode as v, DevToolsLogFilePosition as vt, EntriesToObject as w, RpcDefinitionsFilter as x, DevToolsLogsClient as xt, DevToolsRpcClientOptions as y, DevToolsLogHandle as yt, DevToolsViewHost as z };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as EventUnsubscribe, r as EventsMap, t as EventEmitter } from "./events-B41U-zeg.js";
|
|
2
|
-
import { $ as
|
|
2
|
+
import { $ as DevToolsDockEntryBase, A as RpcBroadcastOptions, B as DevToolsChildProcessExecuteOptions, C as PluginWithDevTools, D as DevToolsDocksUserSettings, E as Thenable, F as DevToolsCapabilities, G as DevToolsTerminalSessionStreamChunkEvent, H as DevToolsTerminalHost, I as DevToolsNodeContext, J as DevToolsRpcServerFunctions, K as DevToolsTerminalStatus, L as DevToolsNodeUtils, M as RpcSharedStateGetOptions, N as RpcSharedStateHost, O as DevToolsNodeRpcSession, P as ConnectionMeta, Q as DevToolsDockEntry, R as DevToolsPluginOptions, S as RpcDefinitionsToFunctions, St as DevToolsLogsHost, T as PartialWithoutId, U as DevToolsTerminalSession, V as DevToolsChildProcessTerminalSession, W as DevToolsTerminalSessionBase, X as ClientScriptEntry, Y as DevToolsRpcSharedStates, Z as DevToolsDockEntriesGrouped, _t as DevToolsLogEntryInput, at as DevToolsViewBuiltin, bt as DevToolsLogLevel, ct as DevToolsViewJsonRender, dt as JsonRenderElement, et as DevToolsDockEntryCategory, ft as JsonRenderSpec, gt as DevToolsLogEntryFrom, ht as DevToolsLogEntry, it as DevToolsViewAction, j as RpcFunctionsHost, k as DevToolsNodeRpcSessionMeta, lt as DevToolsViewLauncher, mt as DevToolsLogElementPosition, nt as DevToolsDockHost, ot as DevToolsViewCustomRender, pt as JsonRenderer, q as DevToolsRpcClientFunctions, rt as DevToolsDockUserEntry, st as DevToolsViewIframe, tt as DevToolsDockEntryIcon, ut as DevToolsViewLauncherStatus, vt as DevToolsLogFilePosition, w as EntriesToObject, x as RpcDefinitionsFilter, xt as DevToolsLogsClient, yt as DevToolsLogHandle, z as DevToolsViewHost } from "./index-DryCMVBy.js";
|
|
3
3
|
import * as _vitejs_devtools_rpc0 from "@vitejs/devtools-rpc";
|
|
4
4
|
|
|
5
5
|
//#region src/utils/define.d.ts
|
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.3",
|
|
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.3"
|
|
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.
|
|
@@ -294,6 +360,19 @@ export default function setup(ctx: DevToolsClientScriptContext) {
|
|
|
294
360
|
}
|
|
295
361
|
```
|
|
296
362
|
|
|
363
|
+
## Client Context
|
|
364
|
+
|
|
365
|
+
The global client context (`DevToolsClientContext`) provides access to the RPC client and is set automatically when DevTools initializes (embedded or standalone). Use `getDevToolsClientContext()` to access it from anywhere on the client side:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
import { getDevToolsClientContext } from '@vitejs/devtools-kit/client'
|
|
369
|
+
|
|
370
|
+
const ctx = getDevToolsClientContext()
|
|
371
|
+
if (ctx) {
|
|
372
|
+
const modules = await ctx.rpc.call('my-plugin:get-modules')
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
297
376
|
### Broadcasting to Clients
|
|
298
377
|
|
|
299
378
|
```ts
|
|
@@ -426,6 +505,7 @@ Real-world example plugins in the repo — reference their code structure and pa
|
|
|
426
505
|
|
|
427
506
|
- **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
507
|
- **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
|
|
508
|
+
- **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
509
|
|
|
430
510
|
## Further Reading
|
|
431
511
|
|
|
@@ -433,4 +513,6 @@ Real-world example plugins in the repo — reference their code structure and pa
|
|
|
433
513
|
- [Dock Entry Types](./references/dock-entry-types.md) - Detailed dock configuration options
|
|
434
514
|
- [Shared State Patterns](./references/shared-state-patterns.md) - Framework integration examples
|
|
435
515
|
- [Project Structure](./references/project-structure.md) - Recommended file organization
|
|
516
|
+
- [JSON Render Patterns](./references/json-render-patterns.md) - Server-side JSON specs, components, state binding
|
|
517
|
+
- [Terminals Patterns](./references/terminals-patterns.md) - Child processes, custom streams, session lifecycle
|
|
436
518
|
- [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.
|
|
@@ -187,6 +187,15 @@ export function useRpc() {
|
|
|
187
187
|
}
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
+
Alternatively, use `getDevToolsClientContext()` to access the global client context synchronously (returns `undefined` if not yet initialized):
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { getDevToolsClientContext } from '@vitejs/devtools-kit/client'
|
|
194
|
+
|
|
195
|
+
const ctx = getDevToolsClientContext()
|
|
196
|
+
// ctx?.rpc is the DevToolsRpcClient
|
|
197
|
+
```
|
|
198
|
+
|
|
190
199
|
## Client App Component (src/client/App.vue)
|
|
191
200
|
|
|
192
201
|
```vue
|
|
@@ -119,6 +119,21 @@ ctx.rpc.broadcast({
|
|
|
119
119
|
})
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
## Global Client Context
|
|
123
|
+
|
|
124
|
+
Use `getDevToolsClientContext()` to access the client context (`DevToolsClientContext`) globally. Returns `undefined` if the context has not been initialized yet.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { getDevToolsClientContext } from '@vitejs/devtools-kit/client'
|
|
128
|
+
|
|
129
|
+
const ctx = getDevToolsClientContext()
|
|
130
|
+
if (ctx) {
|
|
131
|
+
await ctx.rpc.call('my-plugin:get-modules')
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This is set automatically when DevTools initializes in embedded or standalone mode. For iframe pages, `getDevToolsRpcClient()` is still the recommended way to get the RPC client directly.
|
|
136
|
+
|
|
122
137
|
## Client Function Registration
|
|
123
138
|
|
|
124
139
|
```ts
|
|
@@ -33,7 +33,7 @@ state.mutate((draft) => {
|
|
|
33
33
|
import { getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
|
|
34
34
|
|
|
35
35
|
const client = await getDevToolsRpcClient()
|
|
36
|
-
const state = await client.
|
|
36
|
+
const state = await client.sharedState.get('my-plugin:state')
|
|
37
37
|
|
|
38
38
|
// Read
|
|
39
39
|
console.log(state.value())
|
|
@@ -44,6 +44,17 @@ state.on('updated', (newState) => {
|
|
|
44
44
|
})
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
You can also access shared state through the global client context:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { getDevToolsClientContext } from '@vitejs/devtools-kit/client'
|
|
51
|
+
|
|
52
|
+
const ctx = getDevToolsClientContext()
|
|
53
|
+
if (ctx) {
|
|
54
|
+
const state = await ctx.rpc.sharedState.get('my-plugin:state')
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
47
58
|
## Type-Safe Shared State
|
|
48
59
|
|
|
49
60
|
```ts
|
|
@@ -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
|
+
```
|