@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 CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as DevToolsRpcClientOptions, a as DockEntryState, c as DocksContext, d as RpcClientEvents, f as DevToolsRpcClient, g as DevToolsRpcClientMode, h as DevToolsRpcClientCallOptional, i as DockClientType, l as DocksEntriesContext, m as DevToolsRpcClientCallEvent, n as DevToolsClientContext, o as DockEntryStateEvents, p as DevToolsRpcClientCall, r as DevToolsClientRpcHost, s as DockPanelStorage, t as DockClientScriptContext, u as DocksPanelContext, v as getDevToolsRpcClient } from "./index-WxzZW3L-.js";
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 };
@@ -1,4 +1,4 @@
1
- import { T as DevToolsDocksUserSettings } from "./index-WxzZW3L-.js";
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
- export { DevToolsDockEntryIcon as $, RpcSharedStateGetOptions as A, DevToolsTerminalHost as B, PartialWithoutId as C, DevToolsNodeRpcSessionMeta as D, DevToolsNodeRpcSession as E, DevToolsNodeUtils as F, DevToolsRpcClientFunctions as G, DevToolsTerminalSessionBase as H, DevToolsPluginOptions as I, ClientScriptEntry as J, DevToolsRpcServerFunctions as K, DevToolsViewHost as L, ConnectionMeta as M, DevToolsCapabilities as N, RpcBroadcastOptions as O, DevToolsNodeContext as P, DevToolsDockEntryCategory as Q, DevToolsChildProcessExecuteOptions as R, EntriesToObject as S, DevToolsDocksUserSettings as T, DevToolsTerminalSessionStreamChunkEvent as U, DevToolsTerminalSession as V, DevToolsTerminalStatus as W, DevToolsDockEntry as X, DevToolsDockEntriesGrouped as Y, DevToolsDockEntryBase as Z, DevToolsRpcClientOptions as _, DevToolsLogHandle as _t, DockEntryState as a, DevToolsViewIframe as at, RpcDefinitionsToFunctions as b, DevToolsLogsHost as bt, DocksContext as c, DevToolsViewLauncherStatus as ct, RpcClientEvents as d, JsonRenderer as dt, DevToolsDockHost as et, DevToolsRpcClient as f, DevToolsLogElementPosition as ft, DevToolsRpcClientMode as g, DevToolsLogFilePosition as gt, DevToolsRpcClientCallOptional as h, DevToolsLogEntryInput as ht, DockClientType as i, DevToolsViewCustomRender as it, RpcSharedStateHost as j, RpcFunctionsHost as k, DocksEntriesContext as l, JsonRenderElement as lt, DevToolsRpcClientCallEvent as m, DevToolsLogEntryFrom as mt, DevToolsClientContext as n, DevToolsViewAction as nt, DockEntryStateEvents as o, DevToolsViewJsonRender as ot, DevToolsRpcClientCall as p, DevToolsLogEntry as pt, DevToolsRpcSharedStates as q, DevToolsClientRpcHost as r, DevToolsViewBuiltin as rt, DockPanelStorage as s, DevToolsViewLauncher as st, DockClientScriptContext as t, DevToolsDockUserEntry as tt, DocksPanelContext as u, JsonRenderSpec as ut, getDevToolsRpcClient as v, DevToolsLogLevel as vt, Thenable as w, PluginWithDevTools as x, RpcDefinitionsFilter as y, DevToolsLogsClient as yt, DevToolsChildProcessTerminalSession as z };
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 DevToolsDockEntryIcon, A as RpcSharedStateGetOptions, B as DevToolsTerminalHost, C as PartialWithoutId, D as DevToolsNodeRpcSessionMeta, E as DevToolsNodeRpcSession, F as DevToolsNodeUtils, G as DevToolsRpcClientFunctions, H as DevToolsTerminalSessionBase, I as DevToolsPluginOptions, J as ClientScriptEntry, K as DevToolsRpcServerFunctions, L as DevToolsViewHost, M as ConnectionMeta, N as DevToolsCapabilities, O as RpcBroadcastOptions, P as DevToolsNodeContext, Q as DevToolsDockEntryCategory, R as DevToolsChildProcessExecuteOptions, S as EntriesToObject, T as DevToolsDocksUserSettings, U as DevToolsTerminalSessionStreamChunkEvent, V as DevToolsTerminalSession, W as DevToolsTerminalStatus, X as DevToolsDockEntry, Y as DevToolsDockEntriesGrouped, Z as DevToolsDockEntryBase, _t as DevToolsLogHandle, at as DevToolsViewIframe, b as RpcDefinitionsToFunctions, bt as DevToolsLogsHost, ct as DevToolsViewLauncherStatus, dt as JsonRenderer, et as DevToolsDockHost, ft as DevToolsLogElementPosition, gt as DevToolsLogFilePosition, ht as DevToolsLogEntryInput, it as DevToolsViewCustomRender, j as RpcSharedStateHost, k as RpcFunctionsHost, lt as JsonRenderElement, mt as DevToolsLogEntryFrom, nt as DevToolsViewAction, ot as DevToolsViewJsonRender, pt as DevToolsLogEntry, q as DevToolsRpcSharedStates, rt as DevToolsViewBuiltin, st as DevToolsViewLauncher, tt as DevToolsDockUserEntry, ut as JsonRenderSpec, vt as DevToolsLogLevel, w as Thenable, x as PluginWithDevTools, y as RpcDefinitionsFilter, yt as DevToolsLogsClient, z as DevToolsChildProcessTerminalSession } from "./index-WxzZW3L-.js";
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.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.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.rpc.sharedState.get('my-plugin:state')
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
+ ```