Skip to content

Commit b3a12fb

Browse files
authored
feat(kit)!: move json-render to @vitejs/devtools (#604)
* feat(kit)!: move json-render to @vitejs/devtools `@devframes/json-render` pulled `@json-render/core` and `zod` (~7.4 MB) into every `@vitejs/devtools-kit` install, more than all of kit's other dependencies combined. Vite DevTools is the only host that creates the kit context, so it now owns the dependency and attaches `ctx.createJsonRenderer()` itself, typing it onto `KitNodeContext` via module augmentation. Plugins running under `@vitejs/devtools` keep working unchanged. Authors who import `JsonRenderSpec` / `JsonRenderer` / `defineJsonRenderSpec` now take them from `@vitejs/devtools` instead of the kit. BREAKING CHANGE: `@vitejs/devtools-kit` no longer exports `defineJsonRenderSpec`, `JsonRenderSpec`, `JsonRenderElement`, `JsonRenderer`, `JsonRenderView`, `JsonRenderViewRef` or `DevToolsViewJsonRender`; import them from `@vitejs/devtools`. * feat(core): expose json-render helpers under @vitejs/devtools/json-render Keeps the root entry focused on the plugin and hub API. The root still loads the `KitNodeContext.createJsonRenderer` augmentation so plugin authors who only import `DevTools()` keep the typed method.
1 parent bc7ffe2 commit b3a12fb

25 files changed

Lines changed: 201 additions & 292 deletions

File tree

‎alias.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const alias = {
1818
'@vitejs/devtools-vite': r('vite/src/index.ts'),
1919
'@vitejs/devtools-vitest': r('vitest/src/index.ts'),
2020
'@vitejs/devtools/internal': r('core/src/internal.ts'),
21+
'@vitejs/devtools/json-render': r('core/src/json-render.ts'),
2122
'@vitejs/devtools/client/inject': r('core/src/client/inject/index.ts'),
2223
'@vitejs/devtools/client/inject-passive': r('core/src/client/inject-passive/index.ts'),
2324
'@vitejs/devtools/client/inject-hidden': r('core/src/client/inject-hidden/index.ts'),

‎docs/kit/json-render.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ outline: deep
66

77
JSON render panels build DevTools UIs from server-side TypeScript alone. You describe the UI as a JSON spec; the DevTools client renders it with the built-in component library. It builds on [Devframe's JSON-Render](https://devfra.me/guide/json-render).
88

9+
Vite DevTools provides `ctx.createJsonRenderer()`; the `JsonRenderSpec` / `JsonRenderer` types and `defineJsonRenderSpec` live in `@vitejs/devtools/json-render`, so `@vitejs/devtools-kit` stays light. Add `@vitejs/devtools` as a dev dependency to author specs with full type inference.
10+
911
## Getting started
1012

1113
Create a renderer handle with `ctx.createJsonRenderer()` and pass it as `ui` when registering a `json-render` dock entry:
@@ -616,7 +618,8 @@ Expandable tree view for inspecting nested objects.
616618
A complete panel combining layout, data display, inputs, and actions:
617619

618620
```ts
619-
import type { JsonRenderSpec, PluginWithDevTools } from '@vitejs/devtools-kit'
621+
import type { PluginWithDevTools } from '@vitejs/devtools-kit'
622+
import type { JsonRenderSpec } from '@vitejs/devtools/json-render'
620623
import { defineRpcFunction } from '@vitejs/devtools-kit'
621624

622625
function buildSpec(data: { modules: number, time: string, size: string }): JsonRenderSpec {

‎examples/plugin-git-ui/src/node/plugin.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { JsonRenderer, PluginWithDevTools } from '@vitejs/devtools-kit'
1+
import type { PluginWithDevTools } from '@vitejs/devtools-kit'
2+
import type { JsonRenderer } from '@vitejs/devtools/json-render'
23
import { defineRpcFunction } from '@vitejs/devtools-kit'
34
import { exec } from 'tinyexec'
45
import { getGitState, git } from './git'

‎examples/plugin-git-ui/src/node/spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { JsonRenderElement, JsonRenderSpec } from '@vitejs/devtools-kit'
1+
import type { JsonRenderElement, JsonRenderSpec } from '@vitejs/devtools/json-render'
22
import type { GitState } from './git'
33

44
const statusMeta: Record<string, { label: string, title: string, variant: string }> = {

‎packages/core/package.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"./dirs": "./dist/dirs.js",
2828
"./integration": "./dist/integration.js",
2929
"./internal": "./dist/internal.js",
30+
"./json-render": "./dist/json-render.js",
3031
"./package.json": "./package.json"
3132
},
3233
"types": "./dist/index.d.ts",
@@ -69,6 +70,7 @@
6970
"@devframes/agentic": "catalog:deps",
7071
"@devframes/hub": "catalog:deps",
7172
"@devframes/hub-ui": "catalog:deps",
73+
"@devframes/json-render": "catalog:deps",
7274
"@devframes/json-render-ui": "catalog:deps",
7375
"@devframes/plugin-inspect": "catalog:deps",
7476
"@devframes/plugin-messages": "catalog:deps",

‎packages/core/src/index.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Loads the `KitNodeContext.createJsonRenderer` augmentation for anyone who
2+
// only imports `DevTools()`; the types themselves live under `./json-render`.
3+
import './json-render'
4+
15
export { createDevToolsContext } from './node/context'
26
export { DevTools } from './node/plugins'
37
export type { BuiltinServerFunctions } from './node/rpc'

‎packages/core/src/json-render.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export { defineJsonRenderSpec } from './node/json-render'
2+
export type {
3+
DevToolsViewJsonRender,
4+
JsonRenderElement,
5+
JsonRenderer,
6+
JsonRenderSpec,
7+
JsonRenderView,
8+
JsonRenderViewRef,
9+
} from './node/json-render'

‎packages/core/src/node/context.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { DEVTOOLS_ASSETS_BASE, dirAssets } from '../dirs'
88
import { getAuthHandler, isClientAuthDisabled } from './auth-handler'
99
import { DEVTOOLS_CLIENT_MODULE_RESOLUTION } from './constants'
1010
import { diagnostics } from './diagnostics'
11+
import { attachJsonRenderer } from './json-render'
1112
import {
1213
defaultResolvedDevToolsConfig,
1314
setResolvedDevToolsConfig,
@@ -53,6 +54,8 @@ export async function createDevToolsContext(
5354
viteServer,
5455
})) as ViteDevToolsNodeContext
5556

57+
attachJsonRenderer(context)
58+
5659
setResolvedDevToolsConfig(
5760
context,
5861
devtoolsConfig ?? defaultResolvedDevToolsConfig,
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type {
2+
DevframeJsonRenderSpec,
3+
JsonRenderView,
4+
JsonRenderViewRef,
5+
UIElement,
6+
} from '@devframes/json-render'
7+
import type { CreateJsonRenderViewOptions } from '@devframes/json-render/node'
8+
import type { KitNodeContext } from '@vitejs/devtools-kit'
9+
import { createJsonRenderView } from '@devframes/json-render/node'
10+
import { nanoid } from '@vitejs/devtools-kit/utils/nanoid'
11+
12+
// `json-render` is the opt-in `@devframes/json-render` integration, which
13+
// contributes the `'json-render'` variant to the hub's open dock union. This
14+
// type-only re-export also pulls in that module augmentation so
15+
// `docks.register({ type: 'json-render' })` resolves for plugin authors.
16+
export type { DevframeJsonRenderDockEntry as DevToolsViewJsonRender } from '@devframes/json-render/hub'
17+
18+
/** A json-render spec — the declarative UI description a plugin authors. */
19+
export type JsonRenderSpec<Element extends UIElement = UIElement> = DevframeJsonRenderSpec<Element>
20+
21+
/** A single element within a spec's `elements` map. */
22+
export type JsonRenderElement = UIElement
23+
24+
export type { JsonRenderView, JsonRenderViewRef }
25+
26+
/**
27+
* The handle returned by `ctx.createJsonRenderer()`. It wraps a devframe
28+
* {@link JsonRenderView} and exposes the kit's method names.
29+
*
30+
* Register a `json-render` dock entry with the handle as `ui`
31+
* (`docks.register({ type: 'json-render', ui, … })`); the client subscribes
32+
* through its `stateKey`. Drive the panel by calling `updateSpec` /
33+
* `updateState` on the handle.
34+
*/
35+
export interface JsonRenderer<SpecType extends JsonRenderSpec = JsonRenderSpec> {
36+
/** Replace the entire spec. */
37+
updateSpec: (spec: SpecType) => void
38+
/** Shallow-merge values into the view's `state`. */
39+
updateState: (state: Record<string, unknown>) => void
40+
/** Unregister the underlying view's shared state and listeners. */
41+
dispose: () => void
42+
/** Shared-state key the client subscribes to for the live spec + state. */
43+
readonly _stateKey: string
44+
/** The serializable reference to the underlying view. */
45+
readonly view: JsonRenderViewRef<SpecType>
46+
}
47+
48+
type CreateJsonRenderer = <SpecType extends JsonRenderSpec>(
49+
spec: SpecType,
50+
options?: Pick<CreateJsonRenderViewOptions<SpecType>, 'schema'>,
51+
) => JsonRenderer<SpecType>
52+
53+
declare module '@vitejs/devtools-kit' {
54+
interface KitNodeContext {
55+
/**
56+
* Create a json-render handle for building declarative, server-driven
57+
* panels. Provided by Vite DevTools (`@vitejs/devtools`), which owns the
58+
* `@devframes/json-render` dependency so the kit stays light.
59+
*/
60+
createJsonRenderer: CreateJsonRenderer
61+
}
62+
}
63+
64+
/** Identity helper that types a json-render spec literal with inference. */
65+
export function defineJsonRenderSpec<SpecType extends JsonRenderSpec>(spec: SpecType): SpecType {
66+
return spec
67+
}
68+
69+
export function attachJsonRenderer(context: KitNodeContext): void {
70+
const createJsonRenderer: CreateJsonRenderer = (spec, options = {}) => {
71+
const view = createJsonRenderView(context, { id: `kit-${nanoid()}`, spec, ...options })
72+
73+
// Methods are non-enumerable so the handle stays serializable when carried
74+
// on a dock entry's `ui` field — the docks shared-state projection walks
75+
// only enumerable own keys, so the closures never reach the wire.
76+
const handle = {
77+
_stateKey: view.ref.stateKey,
78+
view: view.ref,
79+
} as JsonRenderer<typeof spec> // methods are attached right below
80+
Object.defineProperties(handle, {
81+
updateSpec: { value: (next: typeof spec) => view.update(next) },
82+
updateState: {
83+
value: (state: Record<string, unknown>) => {
84+
view.patchState(
85+
Object.entries(state).map(([key, value]) => ({ op: 'add' as const, path: `/${key}`, value })),
86+
)
87+
},
88+
},
89+
dispose: { value: () => view.dispose() },
90+
})
91+
return handle
92+
}
93+
94+
Object.defineProperty(context, 'createJsonRenderer', { value: createJsonRenderer, enumerable: true })
95+
}

‎packages/core/tsdown.config.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default defineConfig({
3434
'index': 'src/index.ts',
3535
'integration': 'src/integration.ts',
3636
'internal': 'src/internal.ts',
37+
'json-render': 'src/json-render.ts',
3738
'dirs': 'src/dirs.ts',
3839
'cli': 'src/node/cli.ts',
3940
'cli-commands': 'src/node/cli-commands.ts',

0 commit comments

Comments
 (0)