Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/core/playground/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ export default defineConfig({
action: ctx.utils.createSimpleClientScript(() => {}),
})

ctx.docks.register({
id: 'test',
type: 'action',
icon: 'material-symbols:bug-report',
title: 'debug',
// TODO: HMR
action: ctx.utils.createSimpleClientScript(async (ctx) => {
console.log(await ctx.rpc.call('vite:internal:rpc:server:list'))
}),
})

ctx.docks.register({
id: 'shared-state',
type: 'iframe',
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { builtinRpcSchemas } from '../../core/src/node/rpc'
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import path is incorrect. It should be './node/rpc' instead of '../../core/src/node/rpc'. The current path goes up two directories to packages/, then back down to core/src/node/rpc, which is unnecessarily complex and could cause issues if the directory structure changes.

Suggested change
export { builtinRpcSchemas } from '../../core/src/node/rpc'
export { builtinRpcSchemas } from './node/rpc'

Copilot uses AI. Check for mistakes.
export { createDevToolsContext } from './node/context'
export { DevTools } from './node/plugins'
export { createDevToolsMiddleware } from './node/server'
2 changes: 1 addition & 1 deletion packages/core/src/node/cli-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export async function build(options: BuildOptions) {

console.log(c.cyan`${MARK_NODE} Writing RPC dump to ${resolve(devToolsRoot, '.vdt-rpc-dump.json')}`)
const dump: Record<string, any> = {}
for (const [key, value] of Object.entries(devtools.context.rpc.functions)) {
for (const [key, value] of Object.entries(devtools.context.rpc.definitions)) {
if (value.type === 'static')
dump[key] = await value.handler?.()
}
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/node/rpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ export const builtinRpcDeclarations = [

export type BuiltinServerFunctions = RpcDefinitionsToFunctions<typeof builtinRpcDeclarations>

export const builtinRpcSchemas = new Map(
builtinRpcDeclarations.map(d => [
d.name,
{ args: d.argsSchema, returns: d.returnSchema },
]),
)

export type BuiltinServerFunctionsStatic = RpcDefinitionsToFunctions<
RpcDefinitionsFilter<typeof builtinRpcDeclarations, 'static'>
>
Expand All @@ -51,7 +58,7 @@ export type BuiltinServerFunctionsDump = {
}

declare module '@vitejs/devtools-kit' {
export interface DevToolsRpcServerFunctions extends BuiltinServerFunctions {}
export interface DevToolsRpcServerFunctions extends BuiltinServerFunctions { }
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an unnecessary space before the closing brace. While this doesn't affect functionality, it's inconsistent with TypeScript formatting conventions. Should be extends BuiltinServerFunctions {}

Suggested change
export interface DevToolsRpcServerFunctions extends BuiltinServerFunctions { }
export interface DevToolsRpcServerFunctions extends BuiltinServerFunctions {}

Copilot uses AI. Check for mistakes.

// @keep-sorted
export interface DevToolsRpcClientFunctions {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/node/rpc/internal/docks-on-launch.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { defineRpcFunction } from '@vitejs/devtools-kit'
import * as v from 'valibot'

export const docksOnLaunch = defineRpcFunction({
name: 'vite:internal:docks:on-launch',
type: 'action',
args: [v.string()],
return: v.void(),
setup: (context) => {
const launchMap = new Map<string, Promise<void>>()
return {
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"@vitejs/devtools-rpc": "workspace:*",
"birpc": "catalog:deps",
"birpc-x": "catalog:deps",
"immer": "catalog:deps"
"immer": "catalog:deps",
"valibot": "catalog:deps"
},
"devDependencies": {
"my-ua-parser": "catalog:frontend",
Expand Down
5 changes: 4 additions & 1 deletion packages/kit/src/client/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { UAParser } from 'my-ua-parser'
import { createEventEmitter } from '../utils/events'
import { nanoid } from '../utils/nanoid'
import { promiseWithResolver } from '../utils/promise'
import { validateRpcArgs, validateRpcReturn } from '../utils/rpc-validation'
import { createRpcSharedStateClientHost } from './rpc-shared-state'

const CONNECTION_META_KEY = '__VITE_DEVTOOLS_CONNECTION_META__'
Expand Down Expand Up @@ -241,10 +242,12 @@ export async function getDevToolsRpcClient(
ensureTrusted,
requestTrust,
call: (...args: any): any => {
return serverRpc.$call(
validateRpcArgs(args[0], args.slice(1))
const ret = serverRpc.$call(
// @ts-expect-error casting
...args,
)
validateRpcReturn(args[0], ret)
},
Comment on lines 244 to 251
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call method has three issues: (1) Missing return statement - the function should return 'ret', (2) The validation of return value should happen after the promise resolves since $call returns a Promise, requiring async/await or .then(), (3) The function signature should be async if validating async return values. Consider: call: async (...args: any): Promise<any> => { validateRpcArgs(args[0], args.slice(1)); const ret = await serverRpc.$call(...args); validateRpcReturn(args[0], ret); return ret; }

Copilot uses AI. Check for mistakes.
callEvent: (...args: any): any => {
return serverRpc.$callEvent(
Expand Down
37 changes: 36 additions & 1 deletion packages/kit/src/utils/define.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,39 @@
import type { RpcFunctionDefinition, RpcFunctionType } from 'birpc-x'
import type { GenericSchema } from 'valibot'
import type { DevToolsNodeContext } from '../types'
import { createDefineWrapperWithContext } from 'birpc-x'

export const defineRpcFunction = createDefineWrapperWithContext<DevToolsNodeContext>()
export interface RpcOptions<
NAME extends string,
TYPE extends RpcFunctionType,
A extends any[],
R,
AS extends GenericSchema[] | undefined = undefined,
RS extends GenericSchema | undefined = undefined,
>
extends RpcFunctionDefinition<NAME, TYPE, A, R, DevToolsNodeContext> {
args?: AS
return?: RS
}

export function defineRpcFunction<
NAME extends string,
TYPE extends RpcFunctionType,
A extends any[],
R,
AS extends GenericSchema[] | undefined = undefined,
RS extends GenericSchema | undefined = undefined,
>(
options: RpcOptions<NAME, TYPE, A, R, AS, RS>,
) {
const { args, return: ret, ...rest } = options
const birpc = createDefineWrapperWithContext<DevToolsNodeContext>()

const fn = birpc(rest)

const augmentedFn = fn as typeof fn & { argsSchema?: AS, returnSchema?: RS }
augmentedFn.argsSchema = args
augmentedFn.returnSchema = ret

return augmentedFn
}
57 changes: 57 additions & 0 deletions packages/kit/src/utils/rpc-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { GenericSchema } from 'valibot'
import { builtinRpcSchemas } from '@vitejs/devtools'
import { serverRpcSchemas } from '@vitejs/devtools-vite'
import { parse } from 'valibot'

const rpcSchemas = new Map([
...builtinRpcSchemas,
...serverRpcSchemas,
])

export function validateSchema(schema: GenericSchema, data: any, prefix: string) {
try {
parse(schema, data)
}
catch (e) {
throw new Error(`${prefix}: ${(e as Error).message}`)
}
}

function getSchema(method: string) {
const schema = rpcSchemas.get(method as any)
if (!schema)
throw new Error(`RPC method "${method}" is not defined.`)
return schema
}

export function validateRpcArgs(method: string, args: any[]) {
const schema = getSchema(method)
if (!schema)
throw new Error(`RPC method "${method}" is not defined.`)
Comment on lines +28 to +30
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema existence check on lines 29-30 is redundant because getSchema() already throws an error if the schema is not found (lines 22-23). This duplicate check can be removed.

Copilot uses AI. Check for mistakes.

const { args: argsSchema } = schema
if (!argsSchema)
return

if (argsSchema.length !== args.length)
throw new Error(`Invalid number of arguments for RPC method "${method}". Expected ${argsSchema.length}, got ${args.length}.`)

for (let i = 0; i < argsSchema.length; i++) {
const s = argsSchema[i]
if (!s)
continue
validateSchema(s, args[i], `Invalid argument #${i + 1}`)
}
}

export function validateRpcReturn(method: string, data: any) {
const schema = getSchema(method)
if (!schema)
throw new Error(`RPC method "${method}" is not defined.`)
Comment on lines +47 to +50
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema existence check on lines 49-50 is redundant because getSchema() already throws an error if the schema is not found (lines 22-23). This duplicate check can be removed.

Copilot uses AI. Check for mistakes.

const { returns: returnSchema } = schema
if (!returnSchema)
return

validateSchema(returnSchema, data, `Invalid return value for RPC method "${method}"`)
}
1 change: 1 addition & 0 deletions packages/vite/src/node/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './plugin'
export { serverRpcSchemas } from './rpc/index'
7 changes: 7 additions & 0 deletions packages/vite/src/node/rpc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export const rpcFunctions = [

export type ServerFunctions = RpcDefinitionsToFunctions<typeof rpcFunctions>

export const serverRpcSchemas = new Map(
rpcFunctions.map(d => [
d.name,
{ args: d.argsSchema, returns: d.returnSchema },
]),
)

export type ServerFunctionsStatic = RpcDefinitionsToFunctions<
RpcDefinitionsFilter<typeof rpcFunctions, 'static'>
>
Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export default defineNuxtConfig({
build: {
rolldownOptions: {
devtools: {},
// https://github.com/parcel-bundler/lightningcss/issues/701
external: ['lightningcss'],
},
minify: NUXT_DEBUG_BUILD ? false : undefined,
cssMinify: false,
Expand Down
8 changes: 8 additions & 0 deletions packages/vite/tsdown.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ export default defineConfig({
index: 'src/index.ts',
dirs: 'src/dirs.ts',
},
/*
* Since `lightningcss` is a dependency of vite, and devtools/vite -> devtools/kit -> devtools/core, lightningcss
* would be bundled into the final build output, which would cause issues when used in environments where
* lightningcss is expected to be an external dependency. https://github.com/parcel-bundler/lightningcss/issues/701
*/
external: [
'lightningcss',
],
tsconfig: '../../tsconfig.base.json',
target: 'esnext',
exports: true,
Expand Down
18 changes: 18 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ catalogs:
tinyglobby: ^0.2.15
unconfig: ^7.4.2
unstorage: ^1.17.3
valibot: ^1.2.0
webext-bridge: ^6.0.1
ws: ^8.19.0
devtools:
Expand Down
1 change: 1 addition & 0 deletions test/exports/@vitejs/devtools-vite.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.:
DevToolsViteUI: function
serverRpcSchemas: object
./dirs:
clientPublicDir: string
1 change: 1 addition & 0 deletions test/exports/@vitejs/devtools.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.:
builtinRpcSchemas: object
createDevToolsContext: function
createDevToolsMiddleware: function
DevTools: function
Expand Down
Loading