Skip to content
Merged
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
27 changes: 27 additions & 0 deletions docs/guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,33 @@ pnpm dev

Then open your app in the browser and open the DevTools panel.

#### Building with the App

You can also generate a static DevTools build alongside your app's build output by enabling the `build.withApp` option:

```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
},
}),
],
build: {
rolldownOptions: {
devtools: {},
},
}
})
```

When `build.withApp` is enabled, running `pnpm build` will automatically generate the static DevTools output into the build output directory. This captures real build data from the same build context, so DevTools can display accurate build analysis without a separate build step.

## What's Next?

Now that you have Vite DevTools set up, you can:
Expand Down
2 changes: 1 addition & 1 deletion docs/kit/rpc.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ const plugin: Plugin = {

### Dump Feature for Build Mode

When using `vite devtools build` to create a static DevTools build, the server cannot execute functions at runtime. The **dump feature** solves this by pre-computing RPC results at build time.
When creating a static DevTools build (via `vite devtools build` CLI or the [`build.withApp`](/guide/#building-with-the-app) plugin option), the server cannot execute functions at runtime. The **dump feature** solves this by pre-computing RPC results at build time.

#### How It Works

Expand Down
78 changes: 78 additions & 0 deletions packages/core/src/node/build-static.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/* eslint-disable no-console */

import type { DevToolsNodeContext } from '@vitejs/devtools-kit'
import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import {
DEVTOOLS_CONNECTION_META_FILENAME,
DEVTOOLS_DIRNAME,
DEVTOOLS_DOCK_IMPORTS_FILENAME,
DEVTOOLS_MOUNT_PATH,
DEVTOOLS_RPC_DUMP_DIRNAME,
DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME,
} from '@vitejs/devtools-kit/constants'
import c from 'ansis'
import { dirname, join, relative, resolve } from 'pathe'
import { dirClientStandalone } from '../dirs'
import { MARK_NODE } from './constants'

export interface BuildStaticOptions {
context: DevToolsNodeContext
outDir: string
}

export async function buildStaticDevTools(options: BuildStaticOptions): Promise<void> {
const { context, outDir } = options

if (existsSync(outDir))
await fs.rm(outDir, { recursive: true })

const devToolsRoot = join(outDir, DEVTOOLS_DIRNAME)
await fs.mkdir(devToolsRoot, { recursive: true })
await fs.cp(dirClientStandalone, devToolsRoot, { recursive: true })

for (const { baseUrl, distDir } of context.views.buildStaticDirs) {
console.log(c.cyan`${MARK_NODE} Copying static files from ${distDir} to ${join(outDir, baseUrl)}`)
await fs.mkdir(join(outDir, baseUrl), { recursive: true })
await fs.cp(distDir, join(outDir, baseUrl), { recursive: true })
}

const { renderDockImportsMap } = await import('./plugins/server')

await fs.mkdir(resolve(devToolsRoot, DEVTOOLS_RPC_DUMP_DIRNAME), { recursive: true })
await fs.writeFile(resolve(devToolsRoot, DEVTOOLS_CONNECTION_META_FILENAME), JSON.stringify({ backend: 'static' }, null, 2), 'utf-8')
await fs.writeFile(resolve(devToolsRoot, DEVTOOLS_DOCK_IMPORTS_FILENAME), renderDockImportsMap(context.docks.values()), 'utf-8')

console.log(c.cyan`${MARK_NODE} Writing RPC dump to ${resolve(devToolsRoot, DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME)}`)
const { collectStaticRpcDump } = await import('./static-dump')
const dump = await collectStaticRpcDump(
context.rpc.definitions.values(),
context,
)
for (const [filepath, data] of Object.entries(dump.files)) {
const fullpath = resolve(devToolsRoot, filepath)
await fs.mkdir(dirname(fullpath), { recursive: true })
await fs.writeFile(fullpath, JSON.stringify(data, null, 2), 'utf-8')
}
await fs.writeFile(resolve(devToolsRoot, DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME), JSON.stringify(dump.manifest, null, 2), 'utf-8')
await fs.writeFile(
resolve(outDir, 'index.html'),
[
'<!doctype html>',
'<html lang="en">',
'<head>',
' <meta charset="UTF-8">',
' <meta name="viewport" content="width=device-width, initial-scale=1.0">',
' <title>Vite DevTools</title>',
` <meta http-equiv="refresh" content="0; url=${DEVTOOLS_MOUNT_PATH}">`,
'</head>',
'<body>',
` <script>location.replace(${JSON.stringify(DEVTOOLS_MOUNT_PATH)})</script>`,
'</body>',
'</html>',
].join('\n'),
'utf-8',
)

console.log(c.green`${MARK_NODE} Built DevTools to ${relative(context.cwd, outDir)}`)
}
65 changes: 6 additions & 59 deletions packages/core/src/node/cli-commands.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
/* eslint-disable no-console */

import { existsSync } from 'node:fs'
import fs from 'node:fs/promises'
import {
DEVTOOLS_CONNECTION_META_FILENAME,
DEVTOOLS_DIRNAME,
DEVTOOLS_DOCK_IMPORTS_FILENAME,
DEVTOOLS_MOUNT_PATH,
DEVTOOLS_RPC_DUMP_DIRNAME,
DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME,
} from '@vitejs/devtools-kit/constants'
import c from 'ansis'
import { dirname, join, relative, resolve } from 'pathe'
import { dirClientStandalone } from '../dirs'
import { resolve } from 'pathe'
import { MARK_NODE } from './constants'
import { normalizeHttpServerUrl } from './utils'

Expand Down Expand Up @@ -93,57 +85,12 @@ export async function build(options: BuildOptions) {

const outDir = resolve(devtools.config.root, options.outDir)

if (existsSync(outDir))
await fs.rm(outDir, { recursive: true })

const devToolsRoot = join(outDir, DEVTOOLS_DIRNAME)
await fs.mkdir(devToolsRoot, { recursive: true })
await fs.cp(dirClientStandalone, devToolsRoot, { recursive: true })

for (const { baseUrl, distDir } of devtools.context.views.buildStaticDirs) {
console.log(c.cyan`${MARK_NODE} Copying static files from ${distDir} to ${join(outDir, baseUrl)}`)
await fs.mkdir(join(outDir, baseUrl), { recursive: true })
await fs.cp(distDir, join(outDir, baseUrl), { recursive: true })
}
const { buildStaticDevTools } = await import('./build-static')
await buildStaticDevTools({
context: devtools.context,
outDir,
})

const { renderDockImportsMap } = await import('./plugins/server')

await fs.mkdir(resolve(devToolsRoot, DEVTOOLS_RPC_DUMP_DIRNAME), { recursive: true })
await fs.writeFile(resolve(devToolsRoot, DEVTOOLS_CONNECTION_META_FILENAME), JSON.stringify({ backend: 'static' }, null, 2), 'utf-8')
await fs.writeFile(resolve(devToolsRoot, DEVTOOLS_DOCK_IMPORTS_FILENAME), renderDockImportsMap(devtools.context.docks.values()), 'utf-8')

console.log(c.cyan`${MARK_NODE} Writing RPC dump to ${resolve(devToolsRoot, DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME)}`)
const { collectStaticRpcDump } = await import('./static-dump')
const dump = await collectStaticRpcDump(
devtools.context.rpc.definitions.values(),
devtools.context,
)
for (const [filepath, data] of Object.entries(dump.files)) {
const fullpath = resolve(devToolsRoot, filepath)
await fs.mkdir(dirname(fullpath), { recursive: true })
await fs.writeFile(fullpath, JSON.stringify(data, null, 2), 'utf-8')
}
await fs.writeFile(resolve(devToolsRoot, DEVTOOLS_RPC_DUMP_MANIFEST_FILENAME), JSON.stringify(dump.manifest, null, 2), 'utf-8')
await fs.writeFile(
resolve(outDir, 'index.html'),
[
'<!doctype html>',
'<html lang="en">',
'<head>',
' <meta charset="UTF-8">',
' <meta name="viewport" content="width=device-width, initial-scale=1.0">',
' <title>Vite DevTools</title>',
` <meta http-equiv="refresh" content="0; url=${DEVTOOLS_MOUNT_PATH}">`,
'</head>',
'<body>',
` <script>location.replace(${JSON.stringify(DEVTOOLS_MOUNT_PATH)})</script>`,
'</body>',
'</html>',
].join('\n'),
'utf-8',
)

console.log(c.green`${MARK_NODE} Built to ${relative(devtools.config.root, outDir)}`)
console.warn(c.yellow`${MARK_NODE} Static build is still experimental and not yet complete.`)
console.warn(c.yellow`${MARK_NODE} Generated output may be missing features and can change without notice.`)
}
41 changes: 41 additions & 0 deletions packages/core/src/node/plugins/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable no-console */

import type { DevToolsNodeContext } from '@vitejs/devtools-kit'
import type { Plugin, ResolvedConfig } from 'vite'
import c from 'ansis'
import { resolve } from 'pathe'
import { MARK_NODE } from '../constants'

export interface DevToolsBuildOptions {
outDir?: string
}

export function DevToolsBuild(options: DevToolsBuildOptions = {}): Plugin {
let context: DevToolsNodeContext
let resolvedConfig: ResolvedConfig

return {
name: 'vite:devtools:build',
apply: 'build',

configResolved(config) {
resolvedConfig = config
},

async buildStart() {
const { createDevToolsContext } = await import('../context')
context = await createDevToolsContext(resolvedConfig)
},

async closeBundle() {
console.log(c.cyan`${MARK_NODE} Building static Vite DevTools...`)

const outDir = options.outDir
? resolve(resolvedConfig.root, options.outDir)
: resolve(resolvedConfig.root, resolvedConfig.build.outDir)

const { buildStaticDevTools } = await import('../build-static')
await buildStaticDevTools({ context, outDir })
},
}
}
24 changes: 24 additions & 0 deletions packages/core/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Plugin } from 'vite'
import { DevToolsBuild } from './build'
import { DevToolsInjection } from './injection'
import { DevToolsServer } from './server'

Expand All @@ -9,18 +10,40 @@ export interface DevToolsOptions {
* @default true
*/
builtinDevTools?: boolean

/**
* Options for building static DevTools output alongside `vite build`.
*/
build?: {
/**
* Automatically build DevTools when running `vite build`.
*
* @default false
*/
withApp?: boolean
/**
* Output directory for the DevTools build (relative to root).
* Defaults to Vite's `build.outDir`.
*/
outDir?: string
}
}

export async function DevTools(options: DevToolsOptions = {}): Promise<Plugin[]> {
const {
builtinDevTools = true,
build,
} = options

const plugins = [
DevToolsInjection(),
DevToolsServer(),
]

if (build?.withApp) {
plugins.push(DevToolsBuild({ outDir: build.outDir }))
}

if (builtinDevTools) {
// eslint-disable-next-line ts/ban-ts-comment
// @ts-ignore ignore the type error
Expand All @@ -31,6 +54,7 @@ export async function DevTools(options: DevToolsOptions = {}): Promise<Plugin[]>
}

export {
DevToolsBuild,
DevToolsInjection,
DevToolsServer,
}
Loading