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
4 changes: 0 additions & 4 deletions docs/errors/DTK0013.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,10 @@ Authorize the browser. When an untrusted client connects, the dev-server termina
For automated setups (CI, shared machines), configure static trusted tokens instead — a client presenting one via the `devframe_auth_token` connection parameter is trusted without the interactive step:

```ts
import { DevTools } from '@vitejs/devtools'
// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [
DevTools(),
],
devtools: {
enabled: true,
clientAuthTokens: ['your-trusted-token'],
Expand Down
41 changes: 13 additions & 28 deletions docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,43 +70,33 @@ export default defineConfig({

### Customize the embedded UI

Vite adds the embedded dock automatically during `vite dev`. To customize it, add the `DevTools()` plugin manually. The examples keep the automatic integration enabled only for build to avoid mounting the dock twice.
Vite adds the embedded dock automatically during `vite dev`. Configure its UI through the core `devtools` option.

`embeddedVisibility` controls when the dock appears. The default `'normal'` shows it immediately. `'passive'` hides it until <kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>D</kbd> (<kbd>⇧</kbd> <kbd>⌥</kbd> <kbd>D</kbd> on macOS) and remembers when it has been revealed. `'hidden'` uses the same shortcut without remembering the choice.

```ts [vite.config.ts] twoslash
import { DevTools } from '@vitejs/devtools'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [
DevTools({
embeddedVisibility: 'passive',
}),
],
devtools: {
apply: 'build',
apply: 'serve',
embeddedVisibility: 'passive',
},
})
Comment thread
webfansplz marked this conversation as resolved.
```

Use `dockPreferences` to set the initial dock layout. Users can still change these settings in DevTools.

```ts [vite.config.ts] twoslash
import { DevTools } from '@vitejs/devtools'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [
DevTools({
dockPreferences: {
defaultMode: 'edge',
defaultPosition: 'bottom',
},
}),
],
devtools: {
apply: 'build',
apply: 'serve',
dockPreferences: {
defaultMode: 'edge',
defaultPosition: 'bottom',
},
},
})
```
Expand Down Expand Up @@ -137,21 +127,16 @@ See [Client Script & Context](/kit/client-context#client-script-not-injected) fo
Set `build.withApp` to write the static DevTools files alongside the app build:

```ts [vite.config.ts] twoslash
import { DevTools } from '@vitejs/devtools'
import { defineConfig } from 'vite'

export default defineConfig({
plugins: [
DevTools({
build: {
withApp: true, // generate DevTools output during `vite build`
// outDir: 'custom-dir', // optional, defaults to Vite's build.outDir
},
}),
],
devtools: {
apply: 'build',
}
build: {
withApp: true, // generate DevTools output during `vite build`
// outDir: 'custom-dir', // optional, defaults to Vite's build.outDir
},
},
})
```

Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/integration.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import type { DevToolsIntegrationConfig } from './node/plugins/integration'
import {
DevToolsIntegration as _DevToolsIntegration,
runDevTools as _runDevTools,
} from './node/plugins/integration'

export interface DevToolsIntegrationOptions {
config: unknown
devtools: DevToolsIntegrationConfig
}

export function DevToolsIntegration(options: DevToolsIntegrationOptions): Promise<{ name: string }[]> {
return _DevToolsIntegration(options as Parameters<typeof _DevToolsIntegration>[0])
}

export function runDevTools(builder: unknown): Promise<void> {
return _runDevTools(builder)
export function runDevTools(
builder: unknown,
devtools: DevToolsIntegrationConfig,
): Promise<void> {
return _runDevTools(builder, devtools)
}

export type { DevToolsIntegrationConfig }
17 changes: 12 additions & 5 deletions packages/core/src/node/__tests__/auth-handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import type { ResolvedConfig } from 'vite'
import type { DevToolsConfig } from '../config'
import process from 'node:process'
import { describe, expect, it, vi } from 'vitest'
import { getAuthHandler } from '../auth-handler'
import { normalizeDevToolsConfig } from '../config'
import { createDevToolsContext } from '../context'
import '@vitejs/devtools-kit'

function createConfig(config?: Partial<DevToolsConfig>): ResolvedConfig {
function createConfig(): ResolvedConfig {
return {
root: process.cwd(),
command: 'serve',
plugins: [],
server: { port: 5173 },
devtools: config === undefined ? undefined : { config },
} as unknown as ResolvedConfig
}

describe('getAuthHandler banner', () => {
it('forwards a configured banner to the interactive auth handler', async () => {
const banner = vi.fn()
const ctx = await createDevToolsContext(createConfig({ banner }))
const ctx = await createDevToolsContext(
createConfig(),
undefined,
normalizeDevToolsConfig({ banner }, 'localhost'),
)

getAuthHandler(ctx).printBanner()

Expand All @@ -31,7 +34,11 @@ describe('getAuthHandler banner', () => {

it('falls back to the default stdout banner when unset', async () => {
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
const ctx = await createDevToolsContext(createConfig())
const ctx = await createDevToolsContext(
createConfig(),
undefined,
normalizeDevToolsConfig(true, 'localhost'),
)

try {
getAuthHandler(ctx).printBanner()
Expand Down
35 changes: 28 additions & 7 deletions packages/core/src/node/__tests__/context-auth.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ResolvedConfig } from 'vite'
import process from 'node:process'
import { afterEach, describe, expect, it } from 'vitest'
import { normalizeDevToolsConfig } from '../config'
import { createDevToolsContext } from '../context'
import '@vitejs/devtools-kit'

Expand All @@ -12,25 +13,37 @@ function createConfig(options: {
root: process.cwd(),
command: options.command ?? 'serve',
plugins: [],
devtools: options.clientAuth === undefined
? undefined
: { config: { clientAuth: options.clientAuth } },
} as unknown as ResolvedConfig
}

function createDevToolsConfig(clientAuth?: boolean) {
return normalizeDevToolsConfig(
clientAuth === undefined ? true : { clientAuth },
'localhost',
)
}

describe('createDevToolsContext auth registration', () => {
afterEach(() => {
delete process.env.VITE_DEVTOOLS_DISABLE_CLIENT_AUTH
})

it('registers the interactive-auth handshake when client auth is enabled', async () => {
const ctx = await createDevToolsContext(createConfig())
const ctx = await createDevToolsContext(
createConfig(),
undefined,
createDevToolsConfig(),
)

expect(ctx.rpc.definitions.has('anonymous:devframe:auth')).toBe(true)
})

it('skips the interactive-auth handshake in build mode (regression #539)', async () => {
const ctx = await createDevToolsContext(createConfig({ command: 'build' }))
const ctx = await createDevToolsContext(
createConfig({ command: 'build' }),
undefined,
createDevToolsConfig(),
)

// Left unregistered so devframe's `auth: false` auto-trust shim (armed
// by `createDevToolsHub`) can install its own noop handler and mark the
Expand All @@ -39,15 +52,23 @@ describe('createDevToolsContext auth registration', () => {
})

it('skips the interactive-auth handshake when `devtools.clientAuth` is false (regression #539)', async () => {
const ctx = await createDevToolsContext(createConfig({ clientAuth: false }))
const ctx = await createDevToolsContext(
createConfig({ clientAuth: false }),
undefined,
createDevToolsConfig(false),
)

expect(ctx.rpc.definitions.has('anonymous:devframe:auth')).toBe(false)
})

it('skips the interactive-auth handshake when VITE_DEVTOOLS_DISABLE_CLIENT_AUTH=true (regression #539)', async () => {
process.env.VITE_DEVTOOLS_DISABLE_CLIENT_AUTH = 'true'

const ctx = await createDevToolsContext(createConfig())
const ctx = await createDevToolsContext(
createConfig(),
undefined,
createDevToolsConfig(),
)

expect(ctx.rpc.definitions.has('anonymous:devframe:auth')).toBe(false)
})
Expand Down
109 changes: 92 additions & 17 deletions packages/core/src/node/__tests__/integration.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
import type { Plugin, ResolvedConfig } from 'vite'
import { describe, expect, it } from 'vitest'
import { DevToolsIntegration } from '../plugins/integration'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { DevToolsIntegration, runDevTools } from '../plugins/integration'
import { startDevTools } from '../start'

function createConfig(command: 'serve' | 'build', apply: 'serve' | 'build' | 'all' = command): ResolvedConfig {
vi.mock('../start', () => ({
startDevTools: vi.fn(),
}))

function createConfig(
command: 'serve' | 'build',
environments: ResolvedConfig['environments'] = {},
): ResolvedConfig {
return {
command,
root: '/vite-devtools-test-project',
devtools: {
apply,
config: {},
enabled: true,
},
environments,
plugins: [],
} as unknown as ResolvedConfig
}

function createDevToolsConfig(apply: 'serve' | 'build' | 'all') {
return {
host: 'localhost',
options: { apply },
} as const
}

describe('devToolsIntegration', () => {
beforeEach(() => {
vi.mocked(startDevTools).mockClear()
})

it('returns the existing DevTools plugins for serve', async () => {
const plugins = await DevToolsIntegration({ config: createConfig('serve') })
const plugins = await DevToolsIntegration({
config: createConfig('serve'),
devtools: createDevToolsConfig('serve'),
})

expect((plugins as Plugin[]).map(plugin => plugin.name)).toEqual([
'vite:devtools:builtin',
Expand All @@ -26,19 +45,38 @@ describe('devToolsIntegration', () => {
})

it('returns the build integration plugin for build', async () => {
const [plugin] = await DevToolsIntegration({ config: createConfig('build') })
const [plugin] = await DevToolsIntegration({
config: createConfig('build'),
devtools: createDevToolsConfig('build'),
})

expect(plugin).toMatchObject({
name: 'vite:devtools:integration',
apply: 'build',
})
})

it('creates the static build plugin from the core config', async () => {
const plugins = await DevToolsIntegration({
config: createConfig('build'),
devtools: {
host: 'localhost',
options: { build: { withApp: true } },
},
})

expect(plugins.map(plugin => plugin.name)).toContain('vite:devtools:build')
expect(plugins.map(plugin => plugin.name)).not.toContain('vite:devtools')
})

it.each([
{ command: 'serve', expected: 'post' },
{ command: 'build', expected: undefined },
] as const)('uses the current $command integration when apply is all', async ({ command, expected }) => {
const plugins = await DevToolsIntegration({ config: createConfig(command, 'all') })
const plugins = await DevToolsIntegration({
config: createConfig(command),
devtools: createDevToolsConfig('all'),
})
const plugin = command === 'serve'
? plugins.find(plugin => plugin.name === 'vite:devtools:server')
: plugins[0]
Expand All @@ -47,20 +85,57 @@ describe('devToolsIntegration', () => {
})

it('returns no plugins when apply excludes the current command', async () => {
const plugins = await DevToolsIntegration({ config: createConfig('serve', 'build') })
const plugins = await DevToolsIntegration({
config: createConfig('serve'),
devtools: createDevToolsConfig('build'),
})

expect(plugins).toEqual([])
})

it('passes the resolved config to standalone DevTools', async () => {
const config = createConfig('build', { client: {} as never })

await runDevTools({ config }, {
host: 'dev.example.com',
options: {
allowedOrigins: ['https://dev.example.com'],
builtinDevTools: false,
clientAuthTokens: ['trusted-token'],
},
})

const resolvedConfig = {
apply: 'all',
config: expect.objectContaining({
allowedOrigins: ['https://dev.example.com'],
builtinDevTools: false,
clientAuth: true,
clientAuthTokens: ['trusted-token'],
host: 'dev.example.com',
}),
enabled: true,
}
expect(startDevTools).toHaveBeenCalledWith(
expect.objectContaining({
host: 'dev.example.com',
root: '/vite-devtools-test-project',
}),
resolvedConfig,
)
})

it('enables Rolldown DevTools for selected build environments', async () => {
const [plugin] = await DevToolsIntegration({ config: createConfig('build') })
const [plugin] = await DevToolsIntegration({
config: createConfig('build'),
devtools: {
host: 'localhost',
options: { environments: ['client'] },
},
})
const client: { build: { rolldownOptions: { devtools?: object } } } = { build: { rolldownOptions: {} } }
const ssr: { build: { rolldownOptions: { devtools?: object } } } = { build: { rolldownOptions: {} } }
const config = {
devtools: {
config: { environments: ['client'] },
enabled: true,
},
environments: { client, ssr },
} as unknown as ResolvedConfig

Expand Down
Loading
Loading