Sitelet https://github.com/vitejs/devtools/commit/e2928c65
Skip to content

Commit e2928c6

Browse files
authored
feat(core): support configurable dock renderers (#542)
1 parent 85e6f7d commit e2928c6

15 files changed

Lines changed: 171 additions & 35 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
2+
import { mkdtemp, readFile, writeFile } from 'node:fs/promises'
3+
import { tmpdir } from 'node:os'
4+
import { join } from 'node:path'
5+
import { beforeEach, describe, expect, it, vi } from 'vitest'
6+
import { buildStaticDevTools } from '../build-static'
7+
8+
const mutateRendererManifest = vi.hoisted(() => vi.fn())
9+
10+
vi.mock('@devframes/json-render-ui/hub', () => ({
11+
jsonRenderUiRenderer: () => ({ type: 'json-render', file: '/unused-builtin.mjs' }),
12+
}))
13+
14+
vi.mock('devframe/rpc/dump', () => ({
15+
collectStaticRpcDump: () => ({ files: {}, manifest: {} }),
16+
}))
17+
18+
vi.mock('../ui', () => ({
19+
createViteDevToolsUi: () => ({}),
20+
}))
21+
22+
function fakeContext(): ViteDevToolsNodeContext {
23+
return {
24+
cwd: process.cwd(),
25+
host: { getStorageDir: () => tmpdir() },
26+
views: { buildStaticDirs: [] },
27+
docks: { values: () => [] },
28+
services: { ready: vi.fn() },
29+
rpc: {
30+
definitions: { values: () => [] },
31+
sharedState: {
32+
get: vi.fn(() => ({ mutate: mutateRendererManifest })),
33+
},
34+
},
35+
} as unknown as ViteDevToolsNodeContext
36+
}
37+
38+
describe('buildStaticDevTools renderers', () => {
39+
beforeEach(() => {
40+
vi.clearAllMocks()
41+
})
42+
43+
it('copies the resolved configured renderers and publishes one manifest entry per type', async () => {
44+
expect.assertions(4)
45+
const temporaryDirectory = await mkdtemp(join(tmpdir(), 'vite-devtools-renderers-'))
46+
const replacementFile = join(temporaryDirectory, 'replacement.mjs')
47+
const customFile = join(temporaryDirectory, 'custom.mjs')
48+
const outputDirectory = join(temporaryDirectory, 'output')
49+
await writeFile(replacementFile, 'export const replacement = true')
50+
await writeFile(customFile, 'export const custom = true')
51+
52+
await buildStaticDevTools({
53+
context: fakeContext(),
54+
outDir: outputDirectory,
55+
renderers: [
56+
{ type: 'json-render', file: replacementFile },
57+
{ type: 'custom-render', file: customFile },
58+
],
59+
})
60+
61+
expect(await readFile(join(outputDirectory, '__devtools', '__renderers', 'json-render.mjs'), 'utf8')).toBe('export const replacement = true')
62+
expect(await readFile(join(outputDirectory, '__devtools', '__renderers', 'custom-render.mjs'), 'utf8')).toBe('export const custom = true')
63+
expect(mutateRendererManifest).toHaveBeenCalledOnce()
64+
expect(mutateRendererManifest.mock.calls[0]![0]({})).toEqual({
65+
'json-render': { importFrom: '/__devtools/__renderers/json-render.mjs' },
66+
'custom-render': { importFrom: '/__devtools/__renderers/custom-render.mjs' },
67+
})
68+
})
69+
})

‎packages/core/src/node/__tests__/server-client-module-resolution.test.ts‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ vi.mock('@devframes/hub/initiate', () => ({
1010
}))
1111

1212
vi.mock('@devframes/json-render-ui/hub', () => ({
13-
jsonRenderUiRenderer: () => ({}),
13+
jsonRenderUiRenderer: () => ({ type: 'json-render', file: '/builtin-json-render.mjs' }),
1414
}))
1515

1616
vi.mock('../ui', () => ({
@@ -57,4 +57,29 @@ describe('createDevToolsHub client module resolution', () => {
5757
expect(initHub).toHaveBeenCalledOnce()
5858
expect(initHub.mock.calls[0]![0]).not.toHaveProperty('clientModuleResolution')
5959
})
60+
61+
it('uses the built-in renderer list by default', async () => {
62+
expect.assertions(1)
63+
await createDevToolsHub({ context: fakeContext() })
64+
65+
expect(initHub.mock.calls[0]![0].renderers).toEqual([
66+
{ type: 'json-render', file: '/builtin-json-render.mjs' },
67+
])
68+
})
69+
70+
it('replaces matching built-ins and appends new configured renderers', async () => {
71+
expect.assertions(1)
72+
await createDevToolsHub({
73+
context: fakeContext(),
74+
renderers: [
75+
{ type: 'json-render', file: '/replacement.mjs' },
76+
{ type: 'custom-render', file: '/custom.mjs' },
77+
],
78+
})
79+
80+
expect(initHub.mock.calls[0]![0].renderers).toEqual([
81+
{ type: 'json-render', file: '/replacement.mjs' },
82+
{ type: 'custom-render', file: '/custom.mjs' },
83+
])
84+
})
6085
})

‎packages/core/src/node/build-static.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
/* eslint-disable no-console */
22

3-
import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
3+
import type { DockRendererRegistration, ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
44
import type { ViteDevToolsUiOptions } from './ui'
55
import { existsSync } from 'node:fs'
66
import fs from 'node:fs/promises'
77
import { DOCK_RENDERERS_STATE_KEY } from '@devframes/hub/constants'
8-
import { jsonRenderUiRenderer } from '@devframes/json-render-ui/hub'
98
import {
109
DEVTOOLS_CONNECTION_META_FILENAME,
1110
DEVTOOLS_DIRNAME,
@@ -18,11 +17,14 @@ import { colors as c } from 'devframe/utils/colors'
1817
import { resolveStaticAssetsSource } from 'devframe/utils/remote-assets'
1918
import { dirname, join, relative, resolve } from 'pathe'
2019
import { MARK_NODE } from './constants'
20+
import { resolveDockRendererRegistrations } from './renderers'
2121
import { createViteDevToolsUi } from './ui'
2222

2323
export interface BuildStaticOptions {
2424
context: ViteDevToolsNodeContext
2525
outDir: string
26+
/** Dock renderer modules copied into the static output, replacing built-ins by matching type. */
27+
renderers?: readonly DockRendererRegistration[]
2628
withApp?: boolean
2729
/** Reference-UI options forwarded to `createUi`. */
2830
ui?: ViteDevToolsUiOptions
@@ -77,7 +79,7 @@ export async function buildStaticDevTools(options: BuildStaticOptions): Promise<
7779
const rendererManifest: Record<string, { importFrom: string, importName?: string }> = {}
7880
const renderersRoot = resolve(devToolsRoot, '__renderers')
7981
await fs.mkdir(renderersRoot, { recursive: true })
80-
for (const registration of [jsonRenderUiRenderer()]) {
82+
for (const registration of resolveDockRendererRegistrations(options.renderers)) {
8183
await fs.cp(registration.file, resolve(renderersRoot, `${registration.type}.mjs`))
8284
rendererManifest[registration.type] = {
8385
importFrom: `${DEVTOOLS_MOUNT_PATH}__renderers/${registration.type}.mjs`,

‎packages/core/src/node/plugins/build.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-console */
22

3-
import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
3+
import type { DockRendererRegistration, ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
44
import type { Plugin, ResolvedConfig } from 'vite'
55
import type { ViteDevToolsUiOptions } from '../ui'
66
import { colors as c } from 'devframe/utils/colors'
@@ -9,6 +9,7 @@ import { MARK_NODE } from '../constants'
99

1010
export interface DevToolsBuildOptions {
1111
outDir?: string
12+
renderers?: readonly DockRendererRegistration[]
1213
/** Reference-UI options forwarded to the static snapshot's `createUi`. */
1314
ui?: ViteDevToolsUiOptions
1415
}
@@ -38,7 +39,7 @@ export function DevToolsBuild(options: DevToolsBuildOptions = {}): Plugin {
3839
: resolve(resolvedConfig.root, resolvedConfig.build.outDir)
3940

4041
const { buildStaticDevTools } = await import('../build-static')
41-
await buildStaticDevTools({ context, outDir, withApp: true, ui: options.ui })
42+
await buildStaticDevTools({ context, outDir, withApp: true, ui: options.ui, renderers: options.renderers })
4243
},
4344
}
4445
}

‎packages/core/src/node/plugins/index.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { DockRendererRegistration } from '@vitejs/devtools-kit'
12
import type { Plugin } from 'vite'
23
import type { ViteDevToolsUiOptions } from '../ui'
34
import { DevToolsBuild } from './build'
@@ -15,6 +16,9 @@ export interface DevToolsOptions {
1516
*/
1617
builtinDevTools?: boolean
1718

19+
/** Dock renderer modules, replacing built-ins with the same type and appending new types. */
20+
renderers?: readonly DockRendererRegistration[]
21+
1822
/**
1923
* Override the branding handed to the DevTools client (`@devframes/hub-ui`)
2024
* — product name, logo, wordmark, primary color, tagline, favicon, and
@@ -85,11 +89,11 @@ export async function DevTools(options: DevToolsOptions = {}): Promise<Plugin[]>
8589

8690
const plugins = [
8791
DevToolsInjection(),
88-
DevToolsServer(ui),
92+
DevToolsServer(ui, options.renderers),
8993
]
9094

9195
if (build?.withApp) {
92-
plugins.push(DevToolsBuild({ outDir: build.outDir, ui }))
96+
plugins.push(DevToolsBuild({ outDir: build.outDir, ui, renderers: options.renderers }))
9397
}
9498

9599
plugins.unshift(

‎packages/core/src/node/plugins/server.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ClientScriptEntry, DevToolsDockEntry, ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
1+
import type { ClientScriptEntry, DevToolsDockEntry, DockRendererRegistration, ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
22
import type { Server as NodeHttpServer } from 'node:http'
33
import type { Plugin } from 'vite'
44
import type { ViteDevToolsUiOptions } from '../ui'
@@ -37,7 +37,10 @@ export function renderDockImportsMap(docks: Iterable<DevToolsDockEntry>): string
3737
].join('\n')
3838
}
3939

40-
export function DevToolsServer(options: ViteDevToolsUiOptions = {}): Plugin {
40+
export function DevToolsServer(
41+
options: ViteDevToolsUiOptions = {},
42+
renderers?: readonly DockRendererRegistration[],
43+
): Plugin {
4144
let context: ViteDevToolsNodeContext
4245
let close: (() => Promise<void>) | undefined
4346
return {
@@ -54,6 +57,7 @@ export function DevToolsServer(options: ViteDevToolsUiOptions = {}): Plugin {
5457
const devtools = await createDevToolsHub({
5558
context,
5659
ui: options,
60+
renderers,
5761
// Share Vite's HTTP server for a route-bound WS upgrade; fall back to a
5862
// side-car when Vite runs in middleware mode without its own server.
5963
// Vite types `httpServer` as a broader union (incl. http2); at dev
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { DockRendererRegistration } from '@vitejs/devtools-kit'
2+
import { jsonRenderUiRenderer } from '@devframes/json-render-ui/hub'
3+
4+
/** Merge configured renderers over the built-ins while keeping one entry per type. */
5+
export function resolveDockRendererRegistrations(
6+
configuredRenderers: readonly DockRendererRegistration[] = [],
7+
): DockRendererRegistration[] {
8+
const renderersByType = new Map<string, DockRendererRegistration>()
9+
const registrations = [jsonRenderUiRenderer(), ...configuredRenderers]
10+
for (const registration of registrations)
11+
renderersByType.set(registration.type, registration)
12+
return [...renderersByType.values()]
13+
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import type { HubInstance } from '@devframes/hub/initiate'
2-
import type { ConnectionMeta, ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
2+
import type { ConnectionMeta, DockRendererRegistration, ViteDevToolsNodeContext } from '@vitejs/devtools-kit'
33
import type { ViteDevToolsHost } from '@vitejs/devtools-kit/node'
44
import type { Server as NodeHttpServer } from 'node:http'
55
import type { DevToolsConfig } from './config'
66
import type { ViteDevToolsUiOptions } from './ui'
77
import { initHub } from '@devframes/hub/initiate'
8-
import { jsonRenderUiRenderer } from '@devframes/json-render-ui/hub'
98
import { DEVTOOLS_MOUNT_PATH } from '@vitejs/devtools-kit/constants'
109
import { getAuthHandler, isClientAuthDisabled } from './auth-handler'
10+
import { resolveDockRendererRegistrations } from './renderers'
1111
import { createViteDevToolsUi } from './ui'
1212

1313
export interface CreateDevToolsHubOptions {
1414
context: ViteDevToolsNodeContext
15+
/** Dock renderer modules served by the hub, replacing built-ins by matching type. */
16+
renderers?: readonly DockRendererRegistration[]
1517
/**
1618
* Reference-UI options forwarded to `createUi` — the embedded dock's
1719
* reveal policy and the dock-bar rendering preferences.
@@ -67,7 +69,7 @@ export async function createDevToolsHub(options: CreateDevToolsHubOptions): Prom
6769
// Serve + advertise the reference json-render frontend so `json-render`
6870
// docks (kit's `createJsonRenderer`, the git/data-inspector devframes)
6971
// render instead of hub-ui's missing-renderer fallback.
70-
renderers: [jsonRenderUiRenderer()],
72+
renderers: resolveDockRendererRegistrations(options.renderers),
7173
// With a live Vite dev server, route bare-specifier dock client scripts
7274
// (`ClientScriptEntry.importFrom` naming an npm module, e.g.
7375
// vue-tracer's `vite-plugin-vue-tracer/client/vite-devtools`) through

‎packages/kit/src/define.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export { defineCommand, defineDockEntry } from '@devframes/hub'
99
* package, whose spec is a plain `@json-render/core` `Spec`), so the kit keeps
1010
* the convenience helper for authoring specs with inference.
1111
*/
12-
export function defineJsonRenderSpec(spec: JsonRenderSpec): JsonRenderSpec {
12+
export function defineJsonRenderSpec<SpecType extends JsonRenderSpec>(spec: SpecType): SpecType {
1313
return spec
1414
}
1515

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

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { CreateHubContextOptions, DevframeHubContext } from '@devframes/hub/node'
2+
import type { CreateJsonRenderViewOptions } from '@devframes/json-render/node'
23
import type { ResolvedConfig, ViteDevServer } from 'vite'
34
import type { JsonRenderer, JsonRenderSpec } from '../types/json-render'
45
import { createHubContext } from '@devframes/hub/node'
@@ -22,7 +23,10 @@ export interface KitNodeContext extends DevframeHubContext {
2223
* (`docks.register({ type: 'json-render', view: renderer.view, … })`) and
2324
* call `updateSpec` / `updateState` on the handle to drive it reactively.
2425
*/
25-
createJsonRenderer: (spec: JsonRenderSpec) => JsonRenderer
26+
createJsonRenderer: <SpecType extends JsonRenderSpec>(
27+
spec: SpecType,
28+
options?: Pick<CreateJsonRenderViewOptions<SpecType>, 'schema'>,
29+
) => JsonRenderer<SpecType>
2630
}
2731

2832
export interface CreateKitContextOptions extends CreateHubContextOptions {
@@ -48,7 +52,10 @@ export async function createKitContext(options: CreateKitContextOptions): Promis
4852
Object.defineProperty(context, 'viteServer', { value: options.viteServer, enumerable: true })
4953

5054
Object.defineProperty(context, 'createJsonRenderer', {
51-
value: (spec: JsonRenderSpec) => createJsonRenderer(context, spec),
55+
value: <SpecType extends JsonRenderSpec>(
56+
spec: SpecType,
57+
rendererOptions?: Pick<CreateJsonRenderViewOptions<SpecType>, 'schema'>,
58+
) => createJsonRenderer(context, spec, rendererOptions),
5259
enumerable: true,
5360
})
5461

@@ -62,17 +69,21 @@ export async function createKitContext(options: CreateKitContextOptions): Promis
6269
* shared-state projection walks only enumerable own keys, so the live
6370
* closures never reach the wire while `_stateKey` does.
6471
*/
65-
function createJsonRenderer(context: KitNodeContext, spec: JsonRenderSpec): JsonRenderer {
66-
const view = createJsonRenderView(context, { id: `kit-${nanoid()}`, spec })
72+
function createJsonRenderer<SpecType extends JsonRenderSpec>(
73+
context: KitNodeContext,
74+
spec: SpecType,
75+
options: Pick<CreateJsonRenderViewOptions<SpecType>, 'schema'> = {},
76+
): JsonRenderer<SpecType> {
77+
const view = createJsonRenderView(context, { id: `kit-${nanoid()}`, spec, ...options })
6778

6879
const handle = {
6980
_stateKey: view.ref.stateKey,
7081
view: view.ref,
71-
} as JsonRenderer
82+
} as JsonRenderer<SpecType>
7283

7384
Object.defineProperties(handle, {
7485
updateSpec: {
75-
value: (next: JsonRenderSpec) => view.update(next),
86+
value: (next: SpecType) => view.update(next),
7687
enumerable: false,
7788
},
7889
updateState: {

0 commit comments

Comments
 (0)