Skip to content

Commit 68aeb8a

Browse files
feat(devtools): enable dev server integration (#23333)
Co-authored-by: sapphi-red <49056869+sapphi-red@users.noreply.github.com>
1 parent f794133 commit 68aeb8a

13 files changed

Lines changed: 552 additions & 168 deletions

File tree

docs/config/shared-options.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,22 @@ Learn more in Vite's [SSR guide](/guide/ssr#vite-cli). Related: [`server.middlew
624624
- **Type:** `boolean` | `DevToolsConfig`
625625
- **Default:** `false`
626626

627-
Enable devtools integration for visualizing the internal state and build analysis.
628-
Ensure that `@vitejs/devtools` is installed as a dependency. This feature is currently supported only in build mode.
627+
Enable devtools integration for inspecting the dev server and analyzing builds.
628+
Ensure that `@vitejs/devtools` is installed as a dependency. Install `@vitejs/devtools-vite` to inspect the Vite dev server and `@vitejs/devtools-rolldown` to enable build analysis. DevTools runs for both `serve` and `build` by default; use `apply` to limit it to either command.
629+
630+
Plugin `config` hooks cannot change the `devtools` option. Set it in the user config instead.
631+
632+
When installed, `@vitejs/devtools` provides the type definitions for this option:
633+
634+
```ts
635+
import { defineConfig } from 'vite'
636+
637+
export default defineConfig({
638+
devtools: {
639+
apply: 'serve',
640+
},
641+
})
642+
```
629643

630644
See [Vite DevTools](https://github.com/vitejs/devtools) for more details.
631645

packages/vite/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"@types/escape-html": "^1.0.4",
8787
"@types/pnpapi": "^0.0.5",
8888
"@vercel/detect-agent": "^1.2.5",
89-
"@vitejs/devtools": "^0.5.1",
89+
"@vitejs/devtools": "^0.7.1",
9090
"@vitest/utils": "4.1.11",
9191
"@voidzero-dev/vite-task-client": "^0.2.0",
9292
"artichokie": "^0.4.4",
@@ -135,7 +135,7 @@
135135
},
136136
"peerDependencies": {
137137
"@types/node": "^20.19.0 || >=22.12.0",
138-
"@vitejs/devtools": "^0.4.0 || ^0.5.0",
138+
"@vitejs/devtools": "^0.7.1",
139139
"esbuild": "^0.27.0 || ^0.28.0",
140140
"jiti": ">=1.21.0",
141141
"less": "^4.0.0",

packages/vite/rolldown.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ const moduleRunnerConfig = defineConfig({
151151
'fsevents',
152152
'lightningcss',
153153
/^rolldown\//,
154-
'@vitejs/devtools/cli-commands',
155154
...Object.keys(pkg.dependencies),
156155
],
157156
plugins: [bundleSizeLimit(55), enableSourceMapsInWatchModePlugin()],

packages/vite/src/node/__tests__/config.spec.ts

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import os from 'node:os'
44
import path from 'node:path'
55
import { stripVTControlCharacters } from 'node:util'
66
import { afterEach, assert, describe, expect, test, vi } from 'vitest'
7-
import type { InlineConfig, PluginOption } from '..'
7+
import type { InlineConfig, Plugin, PluginOption } from '..'
88
import { isWindows } from '../../shared/utils'
99
import type { UserConfig, UserConfigExport } from '../config'
1010
import {
@@ -23,6 +23,200 @@ import {
2323
normalizePath,
2424
} from '../utils'
2525

26+
const devToolsIntegration = vi.hoisted(() => vi.fn())
27+
28+
vi.mock('@vitejs/devtools/integration', () => ({
29+
DevToolsIntegration: devToolsIntegration,
30+
}))
31+
32+
describe('DevTools plugin resolution', () => {
33+
afterEach(() => {
34+
devToolsIntegration.mockReset()
35+
})
36+
37+
test('uses the standard plugin lifecycle and exposes the resolved config', async () => {
38+
const configHooks: string[] = []
39+
const plugin = (
40+
name: string,
41+
enforce?: 'pre' | 'post',
42+
apply?: 'serve' | 'build',
43+
config?: InlineConfig,
44+
): Plugin => ({
45+
name,
46+
enforce,
47+
apply,
48+
config() {
49+
configHooks.push(name)
50+
return config
51+
},
52+
})
53+
54+
devToolsIntegration.mockResolvedValueOnce([
55+
plugin('devtools-pre', 'pre'),
56+
plugin('devtools-normal'),
57+
plugin('devtools-post', 'post'),
58+
plugin('devtools-build-only', undefined, 'build'),
59+
])
60+
61+
const config = await resolveConfig(
62+
{
63+
configFile: false,
64+
devtools: true,
65+
plugins: [
66+
plugin('user-pre', 'pre'),
67+
plugin('user-normal'),
68+
plugin('user-post', 'post'),
69+
],
70+
},
71+
'serve',
72+
)
73+
74+
expect(devToolsIntegration).toHaveBeenCalledWith({
75+
command: 'serve',
76+
devtools: {
77+
options: true,
78+
},
79+
})
80+
expect(configHooks).toEqual([
81+
'user-pre',
82+
'devtools-pre',
83+
'user-normal',
84+
'devtools-normal',
85+
'user-post',
86+
'devtools-post',
87+
])
88+
expect(config.devtools).toMatchObject({
89+
apply: 'all',
90+
enabled: true,
91+
})
92+
93+
const pluginNames = config.plugins.map((plugin) => plugin.name)
94+
expect(pluginNames).not.toContain('devtools-build-only')
95+
expect(pluginNames.indexOf('user-pre')).toBeLessThan(
96+
pluginNames.indexOf('devtools-pre'),
97+
)
98+
expect(pluginNames.indexOf('user-normal')).toBeLessThan(
99+
pluginNames.indexOf('devtools-normal'),
100+
)
101+
expect(pluginNames.indexOf('user-post')).toBeLessThan(
102+
pluginNames.indexOf('devtools-post'),
103+
)
104+
})
105+
106+
test('does not allow a plugin config hook to enable DevTools', async () => {
107+
const logger = createLogger('silent')
108+
const warn = vi.spyOn(logger, 'warn')
109+
const config = await resolveConfig(
110+
{
111+
configFile: false,
112+
customLogger: logger,
113+
plugins: [
114+
{
115+
name: 'enable-devtools',
116+
config: () => ({ devtools: true }),
117+
},
118+
],
119+
},
120+
'serve',
121+
)
122+
123+
expect(config.devtools).toBe(false)
124+
expect(devToolsIntegration).not.toHaveBeenCalled()
125+
expect(warn).toHaveBeenCalledWith(
126+
expect.stringContaining(
127+
"The `devtools` option cannot be changed from a plugin's `config` hook.",
128+
),
129+
)
130+
})
131+
132+
test('does not allow a plugin config hook to disable DevTools', async () => {
133+
const logger = createLogger('silent')
134+
const warn = vi.spyOn(logger, 'warn')
135+
devToolsIntegration.mockResolvedValueOnce([])
136+
const config = await resolveConfig(
137+
{
138+
configFile: false,
139+
customLogger: logger,
140+
devtools: true,
141+
plugins: [
142+
{
143+
name: 'disable-devtools',
144+
config: () => ({ devtools: false }),
145+
},
146+
],
147+
},
148+
'serve',
149+
)
150+
151+
expect(config.devtools).toMatchObject({ enabled: true })
152+
expect(devToolsIntegration).toHaveBeenCalledOnce()
153+
expect(warn).toHaveBeenCalledWith(
154+
expect.stringContaining(
155+
"The `devtools` option cannot be changed from a plugin's `config` hook.",
156+
),
157+
)
158+
})
159+
160+
test('does not allow a plugin config hook to mutate DevTools options in place', async () => {
161+
const logger = createLogger('silent')
162+
const warn = vi.spyOn(logger, 'warn')
163+
devToolsIntegration.mockResolvedValueOnce([])
164+
const devtools = { enabled: true }
165+
const config = await resolveConfig(
166+
{
167+
configFile: false,
168+
customLogger: logger,
169+
devtools,
170+
plugins: [
171+
{
172+
name: 'mutate-devtools',
173+
config() {
174+
devtools.enabled = false
175+
},
176+
},
177+
],
178+
},
179+
'serve',
180+
)
181+
182+
expect(config.devtools).toMatchObject({ enabled: true })
183+
expect(devToolsIntegration).toHaveBeenCalledOnce()
184+
expect(warn).toHaveBeenCalledWith(
185+
expect.stringContaining(
186+
"The `devtools` option cannot be changed from a plugin's `config` hook.",
187+
),
188+
)
189+
})
190+
191+
test('does not allow a plugin config hook to change non-enablement DevTools options', async () => {
192+
const logger = createLogger('silent')
193+
const warn = vi.spyOn(logger, 'warn')
194+
devToolsIntegration.mockResolvedValueOnce([])
195+
const config = await resolveConfig(
196+
{
197+
configFile: false,
198+
customLogger: logger,
199+
devtools: { apply: 'serve' },
200+
plugins: [
201+
{
202+
name: 'change-devtools-options',
203+
config: () => ({ devtools: { apply: 'build' } }),
204+
},
205+
],
206+
},
207+
'serve',
208+
)
209+
210+
expect(config.devtools).toMatchObject({ apply: 'serve' })
211+
expect(devToolsIntegration).toHaveBeenCalledOnce()
212+
expect(warn).toHaveBeenCalledWith(
213+
expect.stringContaining(
214+
"The `devtools` option cannot be changed from a plugin's `config` hook.",
215+
),
216+
)
217+
})
218+
})
219+
26220
describe('mergeConfig', () => {
27221
test('handles configs with different alias schemas', () => {
28222
const baseConfig = defineConfig({

packages/vite/src/node/build.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1901,7 +1901,7 @@ export async function createBuilder(
19011901
return output
19021902
},
19031903
async runDevTools() {
1904-
if (config.devtools.enabled) {
1904+
if (config.devtools) {
19051905
try {
19061906
const { runDevTools } = await import('@vitejs/devtools/integration')
19071907
await runDevTools(builder)

0 commit comments

Comments
 (0)