Vite v8.0.8 is a native-ESM-powered web development build tool created by Evan You and maintained under the MIT License. It provides an instant dev server with hot module replacement (HMR) and a highly optimized production build pipeline powered by Rolldown. This guide covers the complete architecture, configuration surface, deployment considerations, security posture, and internal mechanics that administrators, platform engineers, and contributing developers need to operate and extend Vite at scale.
1. Architecture Overview
1.1 Monorepo Structure
Vite ships as a pnpm v10.33.0 workspace monorepo containing three publishable packages:
| Package | Version | Purpose |
|---|---|---|
packages/vite |
8.0.8 | Core dev server + production bundler |
packages/create-vite |
9.0.4 | Project scaffolding CLI (npm create vite) |
packages/plugin-legacy |
8.0.1 | Legacy browser support via Babel + SystemJS |
packages/
├── vite/ # Core package
│ ├── bin/vite.js # CLI entry point
│ ├── src/
│ │ ├── node/ # Node.js server + build logic
│ │ │ ├── cli.ts # CLI command definitions (cac-based)
│ │ │ ├── index.ts # Public API exports
│ │ │ ├── server/ # Dev server (connect-based)
│ │ │ │ ├── index.ts
│ │ │ │ ├── environments/
│ │ │ │ └── middlewares/
│ │ │ ├── plugins/ # 29 built-in plugins (~81K chars)
│ │ │ ├── optimizer/ # Dependency pre-bundling
│ │ │ ├── ssr/ # Server-side rendering
│ │ │ │ └── runtime/ # Module runner
│ │ │ └── __tests__/
│ │ ├── client/ # Browser-side HMR client + env injection
│ │ ├── module-runner/ # Standalone module runner (SSR/Workers)
│ │ ├── shared/ # Shared utilities (node + client)
│ │ └── types/ # Internal TypeScript type definitions
│ ├── types/ # Public type definitions
│ │ └── internal/ # Internal-only types
│ ├── client.d.ts # Client-side ambient type declarations
│ ├── rolldown.config.ts # Self-bundle configuration
│ └── rolldown.dts.config.ts # Type generation config
├── create-vite/ # Scaffolding tool
│ ├── src/index.ts # CLI logic
│ └── template-*/ # 20+ project templates
└── plugin-legacy/ # Legacy browser plugin
└── src/index.ts # Babel + SystemJS transforms
1.2 Technology Stack
| Component | Technology | Version |
|---|---|---|
| Production bundler | Rolldown | 1.0.0-rc.16 |
| Self-build bundler | Rolldown | 1.0.0-rc.16 |
| JS transform | oxc / esbuild | esbuild ^0.28.0 |
| CSS processing | PostCSS + LightningCSS | postcss ^8.5.9, lightningcss ^1.32.0 |
| Dev server | connect | ^3.7.0 |
| File watching | chokidar | ^3.6.0 |
| Module analysis | es-module-lexer | ^1.7.0 |
| Glob matching | picomatch + tinyglobby | picomatch ^4.0.4 |
| Unit testing | Vitest | ^4.1.4 |
| E2E testing | Playwright (Chromium) | ^1.59.1 |
| TypeScript | TypeScript | ~6.0.2 |
| Linting | ESLint | ^9.39.4 |
| Formatting | oxfmt | ^0.44.0 |
1.3 Runtime Dependencies
The packages/vite package has an intentionally minimal runtime dependency footprint:
Runtime (installed by consumers): lightningcss, picomatch, postcss, rolldown, tinyglobby
Optional: fsevents (macOS file system events — automatically skipped on non-Darwin platforms)
Bundled devDependencies (compiled into dist): cac (CLI framework), connect, chokidar, dotenv/dotenv-expand, es-module-lexer, esbuild, escape-html, magic-string, postcss-import, postcss-modules, ws (WebSocket), open, strip-literal, @rollup/plugin-alias, @rollup/pluginutils, cors, etag, http-proxy, launch-editor-middleware, mrmime, parse5, picocolors, sirv, artichokie, @vitest/utils, @vitejs/devtools
Important
The distinction between runtime and bundled dependencies matters for deployment. Runtime dependencies must be installed in the consuming project's
node_modules. Bundled dependencies are compiled into Vite'sdist/during the Vite build process itself and do not need separate installation.
1.4 Package Exports Map
The packages/vite package exposes a carefully controlled public API surface through its exports field:
| Export Path | Target | Purpose |
|---|---|---|
. |
./dist/node/index.js |
Main programmatic API |
./client |
client.d.ts (types only) |
Client-side type declarations |
./module-runner |
./dist/node/module-runner.js |
Standalone module runner |
./internal |
./dist/node/internal.js |
Internal APIs (unstable) |
./dist/client/* |
Client files | Browser-side HMR client |
./types/* |
Type definitions | Public TypeScript types |
1.5 Dual-Mode Architecture
Vite operates in two fundamentally different modes, each with distinct code paths:
Development (vite / vite dev / vite serve):
- connect-based HTTP server serves files on demand
- Native ESM — no bundling of application code
- Dependency pre-bundling via the optimizer (Rolldown) for CommonJS/UMD compatibility and request waterfall elimination
- WebSocket-based HMR for instant updates
- Module graph tracks dependencies for surgical invalidation
- Environment API enables multi-target dev (browser client, SSR, workers)
Production (vite build):
- Full application bundle via Rolldown
- Code splitting, tree shaking, minification
- CSS extraction and code splitting
- Asset hashing for long-term caching
- HTML processing with script/style injection
- Optional legacy browser output via plugin-legacy
2. System Requirements
2.1 Node.js
Node.js ^20.19.0 || >=22.12.0
Warning
Node.js 21.x and versions below 20.19.0 are explicitly unsupported. The version constraint is enforced at the engine level in
package.json. Odd-numbered Node.js releases (21, 23) are never targeted because they are non-LTS.
2.2 Package Manager
The monorepo requires pnpm v10.33.0 for workspace management. The version is pinned via packageManager in the root package.json and enforced by Corepack.
corepack enable
corepack prepare pnpm@10.33.0 --activate
2.3 Operating System
Vite runs on Linux, macOS, and Windows. Platform-specific considerations:
| Platform | Notes |
|---|---|
| macOS | fsevents optional dependency provides superior file-watching performance |
| Linux | inotify watches may need tuning for large projects (fs.inotify.max_user_watches) |
| Windows | Forward slashes in config paths are normalized; WSL2 recommended for best performance |
2.4 Build Requirements (Contributing to Vite Itself)
To build the Vite source from the monorepo:
# Clone and install
git clone https://github.com/vitejs/vite.git
cd vite
pnpm install
# Build all packages
pnpm build
# Dev mode (watch) for all packages
pnpm dev
Additional tools for development:
| Tool | Purpose | Installation |
|---|---|---|
| Playwright | E2E browser tests | pnpm exec playwright install chromium |
| Git hooks | Pre-commit lint | Automatic via simple-git-hooks |
2.5 Browser Support
Default modern build targets: Browsers supporting native ESM, native ESM dynamic import, and import.meta.
With plugin-legacy: Configurable via browserslist. Default modern polyfill targets: edge>=105, firefox>=106, chrome>=105, safari>=16.4.
3. Configuration Reference
3.1 Configuration File Resolution
Vite resolves configuration from these locations, in order:
vite.config.ts(or.js,.mjs,.mts,.cjs,.cts)- Inline config passed via
--configCLI flag - Programmatic API (
createServer(),build())
The config file is transpiled on the fly using esbuild, so TypeScript is supported natively without pre-compilation.
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
// configuration options
})
Conditional configuration via function form:
export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => {
if (command === 'serve') {
return { /* dev-specific config */ }
} else {
return { /* build-specific config */ }
}
})
Async configuration:
export default defineConfig(async ({ command, mode }) => {
const data = await fetchSomeConfig()
return { /* config using data */ }
})
3.2 Core Configuration Options
Shared Options
| Option | Type | Default | Description |
|---|---|---|---|
root |
string |
process.cwd() |
Project root directory |
base |
string |
'/' |
Public base path |
mode |
string |
'development' (serve) / 'production' (build) |
Mode for .env loading |
define |
Record<string, any> |
— | Compile-time global constant replacements |
plugins |
(Plugin | Plugin[])[] |
[] |
Plugin array |
publicDir |
string | false |
'public' |
Static asset directory (copied as-is) |
cacheDir |
string |
'node_modules/.vite' |
Cache directory for pre-bundled deps |
resolve.alias |
Record<string, string> | Array |
— | Path aliases |
resolve.extensions |
string[] |
['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json'] |
Extension resolution order |
resolve.conditions |
string[] |
— | Additional export conditions |
css.modules |
CSSModulesOptions |
— | CSS Modules configuration |
css.postcss |
string | PostCSSConfig |
— | PostCSS config (inline or path) |
css.preprocessorOptions |
Record<string, object> |
— | Pre-processor options (sass, less, stylus) |
css.lightningcss |
LightningCSSOptions |
— | LightningCSS transformer config |
json.stringify |
boolean |
false |
JSON imports as strings for performance |
esbuild |
ESBuildOptions | false |
— | esbuild transform options |
assetsInclude |
string | RegExp | (string | RegExp)[] |
— | Additional file types to treat as assets |
logLevel |
'info' | 'warn' | 'error' | 'silent' |
'info' |
Console output level |
envDir |
string |
root |
Directory to load .env files from |
envPrefix |
string | string[] |
'VITE_' |
Env variables exposed to client code |
Server Options (server.*)
| Option | Type | Default | Description |
|---|---|---|---|
server.host |
string | boolean |
'localhost' |
Bind address (true = 0.0.0.0) |
server.port |
number |
5173 |
Dev server port |
server.strictPort |
boolean |
false |
Exit if port is taken |
server.https |
ServerOptions |
— | TLS configuration |
server.open |
boolean | string |
false |
Open browser on start |
server.proxy |
Record<string, ProxyOptions> |
— | Proxy rules (uses http-proxy) |
server.cors |
boolean | CorsOptions |
— | CORS configuration |
server.headers |
Record<string, string> |
— | Custom response headers |
server.hmr |
boolean | HmrOptions |
— | HMR configuration |
server.watch |
WatchOptions |
— | chokidar file watcher options |
server.middlewareMode |
boolean |
false |
Create server without HTTP listener |
server.fs.strict |
boolean |
true |
Restrict file access to workspace |
server.fs.allow |
string[] |
— | Additional allowed directories |
server.fs.deny |
string[] |
['.env', '.env.*', '*.{crt,pem}', '**/.git/**'] |
Denied file patterns |
server.warmup.clientFiles |
string[] |
— | Files to pre-transform on startup |
server.warmup.ssrFiles |
string[] |
— | SSR files to pre-transform |
Build Options (build.*)
| Option | Type | Default | Description |
|---|---|---|---|
build.target |
string | string[] |
'modules' |
Browser compatibility target |
build.outDir |
string |
'dist' |
Output directory |
build.assetsDir |
string |
'assets' |
Assets subdirectory within outDir |
build.assetsInlineLimit |
number | ((filePath, content) => boolean) |
4096 (4KB) |
Inline assets below this size as base64 |
build.cssCodeSplit |
boolean |
true |
Enable CSS code splitting |
build.cssTarget |
string | string[] |
same as build.target |
CSS browser target |
build.cssMinify |
boolean | 'lightningcss' |
same as build.minify |
CSS minification |
build.sourcemap |
boolean | 'inline' | 'hidden' |
false |
Source map generation |
build.rolldownOptions |
RolldownOptions |
— | Pass-through to Rolldown |
build.lib |
LibraryOptions |
— | Library mode configuration |
build.manifest |
boolean | string |
false |
Generate manifest.json for backend integration |
build.ssrManifest |
boolean | string |
false |
SSR manifest for preload directives |
build.ssr |
boolean | string |
false |
SSR build |
build.minify |
boolean | 'oxc' |
'oxc' (non-lib) / false (lib+es) |
Minification engine |
build.emptyOutDir |
boolean |
true (if outDir is inside root) |
Empty output directory before build |
build.copyPublicDir |
boolean |
true |
Copy public directory to outDir |
build.chunkSizeWarningLimit |
number |
500 (KB) |
Chunk size warning threshold |
SSR Options (ssr.*)
| Option | Type | Default | Description |
|---|---|---|---|
ssr.external |
string[] | true |
— | Force-externalize dependencies |
ssr.noExternal |
string | RegExp | (string | RegExp)[] | true |
— | Force-bundle dependencies |
ssr.target |
'node' | 'webworker' |
'node' |
SSR target environment |
ssr.resolve.conditions |
string[] |
— | SSR-specific resolve conditions |
ssr.resolve.externalConditions |
string[] |
— | Conditions for externalized SSR deps |
Optimization Options (optimizeDeps.*)
| Option | Type | Default | Description |
|---|---|---|---|
optimizeDeps.entries |
string | string[] |
— | Custom entries for dep scanning |
optimizeDeps.exclude |
string[] |
— | Dependencies to exclude from pre-bundling |
optimizeDeps.include |
string[] |
— | Force include dependencies |
optimizeDeps.esbuildOptions |
EsbuildBuildOptions |
— | esbuild options for dep optimization |
optimizeDeps.force |
boolean |
false |
Force re-bundle on server start |
optimizeDeps.holdUntilCrawlEnd |
boolean |
true |
Hold module loading until initial crawl completes |
optimizeDeps.needsInterop |
string[] |
— | Force CJS interop for specific deps |
Preview Options (preview.*)
| Option | Type | Default | Description |
|---|---|---|---|
preview.host |
string | boolean |
server.host |
Bind address |
preview.port |
number |
4173 |
Preview server port |
preview.strictPort |
boolean |
server.strictPort |
Exit if port taken |
preview.https |
ServerOptions |
server.https |
TLS configuration |
preview.open |
boolean | string |
server.open |
Open browser on start |
preview.proxy |
Record<string, ProxyOptions> |
server.proxy |
Proxy rules |
preview.cors |
boolean | CorsOptions |
server.cors |
CORS configuration |
preview.headers |
Record<string, string> |
server.headers |
Response headers |
3.3 Environment Variables
.env File Loading
Vite loads .env files from the envDir directory (defaults to project root). Files are loaded in this order, with later files overriding earlier ones:
.env # Always loaded
.env.local # Always loaded, gitignored
.env.[mode] # Loaded for specific mode
.env.[mode].local # Loaded for specific mode, gitignored
Only variables prefixed with VITE_ (configurable via envPrefix) are exposed to client-side code through import.meta.env.
Built-in Environment Variables
| Variable | Type | Description |
|---|---|---|
import.meta.env.MODE |
string |
Current mode (development, production, etc.) |
import.meta.env.BASE_URL |
string |
Base URL from base config |
import.meta.env.PROD |
boolean |
true in production mode |
import.meta.env.DEV |
boolean |
true in development mode |
import.meta.env.SSR |
boolean |
true during SSR |
Caution
Never store secrets in
VITE_-prefixed environment variables. They are statically replaced in client bundles and visible in shipped JavaScript. Use server-side environment variables (without theVITE_prefix) for API keys, database credentials, and other sensitive values.
3.4 CLI Commands
Vite's CLI is built on cac and provides these commands:
# Development server
vite [root] # Start dev server
vite dev [root] # Alias for above
vite serve [root] # Alias for above
# Production build
vite build [root] # Build for production
# Preview production build
vite preview [root] # Locally preview production build
# Dependency optimization
vite optimize [root] # Pre-bundle dependencies
Common CLI flags:
| Flag | Applies To | Description |
|---|---|---|
--config <file> |
All | Config file path |
--base <path> |
All | Public base path |
--mode <mode> |
All | Set mode |
--logLevel <level> |
All | info | warn | error | silent |
--clearScreen |
All | Allow/disable clear screen |
--host [host] |
dev, preview | Bind address |
--port <port> |
dev, preview | Port number |
--strictPort |
dev, preview | Exit if port taken |
--https |
dev, preview | Enable TLS |
--open [path] |
dev, preview | Open browser |
--cors |
dev, preview | Enable CORS |
--force |
dev | Force dependency re-optimization |
--outDir <dir> |
build | Output directory |
--sourcemap |
build | Generate source maps |
--minify [minifier] |
build | Enable/disable minification |
--emptyOutDir |
build | Empty output before build |
--watch |
build | Watch mode |
3.5 CSS Configuration Deep Dive
The CSS plugin is the largest built-in plugin (~3,500 lines) and warrants detailed configuration coverage.
CSS Modules
export default defineConfig({
css: {
modules: {
localsConvention: 'camelCaseOnly',
scopeBehaviour: 'local',
generateScopedName: '[name]__[local]___[hash:base64:5]',
hashPrefix: '',
globalModulePaths: [/global\.module\.css/],
}
}
})
Files matching *.module.css, *.module.scss, *.module.sass, *.module.less, *.module.styl, or *.module.stylus are automatically treated as CSS Modules.
Pre-processor Options
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler', // Use modern Sass compiler API
additionalData: `@use "vars" as *;`,
includePaths: ['src/styles'],
},
less: {
math: 'always',
globalVars: { primary: '#1890ff' },
},
stylus: {
define: { primary: '#1890ff' },
},
}
}
})
LightningCSS as CSS Transformer
import { Features } from 'lightningcss'
export default defineConfig({
css: {
transformer: 'lightningcss',
lightningcss: {
targets: { chrome: 105 },
drafts: { customMedia: true },
include: Features.Nesting,
}
}
})
Note
When using LightningCSS as the transformer, PostCSS plugins are bypassed. This means tools like Tailwind CSS (which is a PostCSS plugin) require the default PostCSS transformer.
4. Deployment & Scaling
4.1 Production Build Output
A standard vite build produces:
dist/
├── index.html # Processed HTML with injected scripts/styles
├── assets/
│ ├── index-[hash].js # Main application bundle
│ ├── vendor-[hash].js # Vendor chunk (if code-split)
│ ├── index-[hash].css # Extracted CSS
│ └── logo-[hash].svg # Hashed static assets
└── [public files] # Copied from public/ directory
All asset filenames include content hashes for long-term cache busting. Configure your CDN/server with aggressive caching for assets/:
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
4.2 Build Manifest
For backend integration (Rails, Laravel, Django, etc.), enable the build manifest:
export default defineConfig({
build: {
manifest: true, // generates .vite/manifest.json
}
})
The manifest maps original file names to their hashed output paths, enabling server-side rendering of correct <script> and <link> tags.
4.3 Multi-Page Application (MPA) Build
import { resolve } from 'node:path'
export default defineConfig({
build: {
rolldownOptions: {
input: {
main: resolve(__dirname, 'index.html'),
about: resolve(__dirname, 'about/index.html'),
admin: resolve(__dirname, 'admin/index.html'),
}
}
}
})
4.4 Library Mode
For building reusable libraries:
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'MyLib',
fileName: 'my-lib',
formats: ['es', 'cjs'],
},
rolldownOptions: {
external: ['react', 'react-dom'],
}
}
})
Important
@vitejs/plugin-legacydoes not support library mode. Library consumers are responsible for their own transpilation targets.
4.5 SSR Build
Server-side rendering builds produce a Node.js-compatible bundle:
export default defineConfig({
build: {
ssr: 'src/entry-server.ts',
}
})
Or via CLI: vite build --ssr src/entry-server.ts
SSR builds externalize node_modules by default, producing a bundle that import()s runtime dependencies rather than bundling them. Control this with ssr.external and ssr.noExternal.
4.6 Legacy Browser Support
For projects requiring IE11/older browser support, use @vitejs/plugin-legacy:
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
legacy({
targets: ['defaults', 'not IE 11'],
// Automatic polyfill injection via core-js + regenerator-runtime
})
]
})
This produces dual bundles:
- Modern: Native ESM for capable browsers
- Legacy: SystemJS-wrapped + transpiled via Babel for older browsers
The HTML output includes <script type="module"> (modern) and <script nomodule> (legacy) tags for automatic browser-appropriate loading.
Modern polyfill targets: edge>=105, firefox>=106, chrome>=105, safari>=16.4.
4.7 Containerized Deployment
Example Dockerfile for building a Vite project:
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Corresponding nginx.conf for SPA routing:
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
try_files $uri $uri/ /index.html;
}
}
5. Operations
5.1 Development Server Management
Starting the Dev Server Programmatically
import { createServer } from 'vite'
const server = await createServer({
root: '/path/to/project',
server: {
port: 3000,
},
})
await server.listen()
server.printUrls()
server.bindCLIShortcuts({ print: true })
Middleware Mode
For embedding Vite in an existing Express/Koa/Fastify server:
import express from 'express'
import { createServer } from 'vite'
const app = express()
const vite = await createServer({
server: { middlewareMode: true },
appType: 'custom',
})
// Use Vite's connect middleware
app.use(vite.middlewares)
app.listen(3000)
Server Warmup
Pre-transform frequently used files to reduce initial page load latency:
export default defineConfig({
server: {
warmup: {
clientFiles: [
'./src/main.ts',
'./src/components/App.vue',
],
ssrFiles: [
'./src/entry-server.ts',
],
}
}
})
5.2 Dependency Pre-Bundling (The Optimizer)
The optimizer is a critical subsystem that pre-bundles dependencies on first server start:
- Scans entry points for bare import specifiers
- Bundles each dependency into a single ESM file using Rolldown
- Caches results in
node_modules/.vite/deps/ - Invalidates cache when
package.json, lockfile, or config changes
Force re-optimization:
vite --force
# or
vite optimize
Programmatic invalidation:
server.restart({ forceOptimize: true })
Tip
If you encounter mysterious module errors during development, deleting
node_modules/.viteand restarting the dev server often resolves stale cache issues. The--forceflag accomplishes this automatically.
Controlling Pre-Bundling
export default defineConfig({
optimizeDeps: {
// Force include deps that Vite can't auto-discover
include: ['lodash-es', 'axios'],
// Exclude deps that must remain un-bundled
exclude: ['@vueuse/core'],
// Hold module loading until initial crawl completes
holdUntilCrawlEnd: true,
// Force CJS interop for specific packages
needsInterop: ['some-cjs-package'],
}
})
5.3 Proxy Configuration
Forward API requests to a backend server during development:
export default defineConfig({
server: {
proxy: {
// String shorthand
'/foo': 'http://localhost:4567',
// With options
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
// WebSocket proxy
'/socket.io': {
target: 'ws://localhost:5174',
ws: true,
},
// Regex match
'^/fallback/.*': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/fallback/, ''),
},
}
}
})
5.4 Build Watch Mode
For development workflows that need continuous builds (e.g., backend-rendered apps):
vite build --watch
The watcher uses Rolldown's built-in watch mode and rebuilds on file changes.
5.5 Environment API (Multi-Environment)
Vite supports configuring multiple environments (browser, SSR, custom workers) with independent settings:
export default defineConfig({
environments: {
client: {
// Default browser environment
build: {
outDir: 'dist/client',
},
},
ssr: {
// SSR environment
build: {
outDir: 'dist/server',
ssr: true,
},
},
rsc: {
// Custom environment (e.g., React Server Components)
resolve: {
conditions: ['react-server'],
},
build: {
outDir: 'dist/rsc',
ssr: true,
},
},
},
})
Each environment maintains its own module graph, plugin pipeline, and optimization settings during development.
5.6 Debugging and Diagnostics
Debug Logging
Vite uses the debug package internally. Enable verbose logging with:
# All Vite debug logs
DEBUG="vite:*" vite
# Specific subsystems
DEBUG="vite:resolve" vite # Module resolution
DEBUG="vite:hmr" vite # HMR events
DEBUG="vite:deps" vite # Dependency optimization
DEBUG="vite:transform" vite # Module transforms
DEBUG="vite:legacy" vite # Legacy plugin (plugin-legacy)
Development/Testing Environment Variables
| Variable | Purpose |
|---|---|
DEBUG |
Enable debug logging for specific Vite subsystems |
DEBUG_DISABLE_SOURCE_MAP |
Disable source maps during Vite's own build |
VITE_TEST_BUILD |
Enable build-mode E2E testing |
VITE_DEBUG_SERVE |
Debug serve mode |
VITE_PRESERVE_BUILD_ARTIFACTS |
Keep build artifacts after tests for inspection |
6. Performance
6.1 Dev Server Performance
Startup Optimization
-
Dependency pre-bundling converts many small ESM files into a single bundled file per dependency, eliminating the request waterfall that would occur if, say,
lodash-es(600+ modules) were served unbundled. -
optimizeDeps.holdUntilCrawlEnd(defaulttrue) delays serving modules until the initial dependency scan completes, preventing mid-session re-optimization that would trigger a full page reload. -
Server warmup pre-transforms critical files so they are ready when the browser requests them.
HMR Performance
HMR updates are surgical — only the changed module and its direct importers are re-processed. The module graph enables this by tracking precise dependency relationships.
Key factors affecting HMR speed:
- Module graph depth (deeply nested imports = more traversal)
- Transform plugin count and complexity
- CSS changes are independently hot-replaceable (no JS re-execution needed)
6.2 Build Performance
Rolldown as Bundler
Vite v8 uses Rolldown (a Rust-based Rollup-compatible bundler) for production builds, providing substantial performance improvements over the esbuild+Rollup combination in earlier Vite versions.
Code Splitting Strategy
Automatic code splitting occurs at:
- Dynamic
import()boundaries - CSS associated with async chunks
- Shared modules referenced by multiple entry points
Control chunk splitting:
export default defineConfig({
build: {
rolldownOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
lodash: ['lodash-es'],
},
// Or function form
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
},
}
}
}
})
Asset Inlining
Assets smaller than build.assetsInlineLimit (default 4KB) are inlined as base64 data URIs to reduce HTTP requests. Custom MIME types are registered for:
| Extension | MIME Type |
|---|---|
.ico |
image/x-icon |
.cur |
image/x-icon |
.flac |
audio/flac |
.eot |
application/vnd.ms-fontobject |
Chunk Size Monitoring
export default defineConfig({
build: {
chunkSizeWarningLimit: 500, // KB — warns when chunks exceed this
}
})
6.3 CSS Performance
CSS Code Splitting
When build.cssCodeSplit is true (default), CSS is extracted per async chunk and loaded on demand. This prevents loading CSS for routes the user hasn't visited.
LightningCSS for Speed
LightningCSS (written in Rust) is significantly faster than PostCSS for CSS transformation and minification:
export default defineConfig({
css: {
transformer: 'lightningcss',
},
build: {
cssMinify: 'lightningcss',
}
})
6.4 Module Runner Performance
The module runner (vite/module-runner) is a lightweight, framework-agnostic module execution runtime. It is designed for SSR and worker environments with a small footprint — the build target enforces a 55KB bundle size limit.
7. Security
7.1 File System Access Control
Warning
The dev server can expose sensitive files if not properly configured. Always verify
server.fssettings in shared or CI environments.
Strict Mode (Default: Enabled)
When server.fs.strict is true, Vite restricts file serving to the project root and its dependencies. Files outside this boundary receive a 403 response.
export default defineConfig({
server: {
fs: {
strict: true,
// Explicitly allow additional directories
allow: [
'/path/to/shared-components',
'../sibling-project/src',
],
// Deny patterns (applied even within allowed directories)
deny: [
'.env',
'.env.*',
'*.{crt,pem}',
'**/.git/**',
],
}
}
})
The default deny list blocks .env files, TLS certificates/keys, and .git directories.
7.2 Content Security Policy (CSP)
Vite supports CSP via nonce injection in HTML:
export default defineConfig({
html: {
cspNonce: 'PLACEHOLDER', // Replaced at runtime by your server
}
})
The plugin-legacy package generates CSP hashes for inline scripts when cspInlineScripts is enabled, allowing strict CSP policies without nonces:
legacy({
renderModernChunks: true,
// CSP hashes are automatically generated for legacy inline scripts
})
7.3 Source Map Security
Production source maps expose original source code. Control exposure:
export default defineConfig({
build: {
sourcemap: 'hidden', // Generates maps but doesn't reference them in output
// Options: true | false | 'inline' | 'hidden'
}
})
Use 'hidden' source maps for error monitoring services (Sentry, Datadog) that upload maps separately — the browser never sees them.
7.4 Environment Variable Security
The envPrefix configuration controls which environment variables leak to client bundles:
export default defineConfig({
envPrefix: ['VITE_', 'PUBLIC_'], // Only these prefixes are exposed
})
Caution
Setting
envPrefixto an empty string ('') exposes ALL environment variables to client-side code, includingDATABASE_URL, API keys, and other secrets. Never do this.
7.5 Dependency Security
Pre-bundled dependencies are stored in node_modules/.vite/deps/. This cache should be:
- Excluded from version control (it is, via standard
.gitignorefornode_modules) - Regenerated in CI environments (
--forceor clean installs) - Audited via standard
npm audit/pnpm audit
7.6 Dev Server Network Exposure
By default, the dev server binds to localhost only. Binding to 0.0.0.0 (--host) exposes it to the local network:
# Binds to localhost only (safe)
vite
# Binds to all interfaces (use only on trusted networks)
vite --host
The HMR WebSocket connection follows the same host binding. When using --host, the HMR overlay displays the server URL, which could leak internal network information.
8. Developer Internals
8.1 Self-Build Pipeline
Vite builds itself using Rolldown. The build configuration in rolldown.config.ts produces four distinct outputs:
| Config | Entry | Output | Target |
|---|---|---|---|
envConfig |
src/client/env.ts |
dist/client/env.mjs |
Browser (es2020) |
clientConfig |
src/client/client.ts |
dist/client/client.mjs |
Browser (es2020) |
nodeConfig |
src/node/{index,cli,internal}.ts |
dist/node/*.js |
Node.js (ESM) |
moduleRunnerConfig |
src/module-runner/index.ts |
dist/node/module-runner.js |
Node.js (minified) |
Build plugins used in self-build:
| Plugin | Purpose |
|---|---|
shimDepsPlugin |
Patches postcss-load-config and postcss-import for ESM compatibility |
buildTimeImportMetaUrlPlugin |
Resolves import.meta.url references at build time |
licensePlugin |
Collects and emits third-party license file |
writeTypesPlugin |
Generates TypeScript declaration files |
enableSourceMapsInWatchModePlugin |
Enables source maps only during watch builds |
externalizeDepsInWatchPlugin |
Externalizes dependencies in watch mode for faster rebuilds |
bundleSizeLimit(55) |
Enforces 55KB limit on the module-runner bundle |
8.2 Internal Plugin Pipeline
The 29+ built-in plugins are loaded in a specific order that is critical to correct operation. Understanding this order is essential for plugin authors.
Phase 1: Pre-alias
1. optimizedDepsPlugin (dev only — serves pre-bundled deps)
2. watchPackageDataPlugin (watches package.json changes)
3. preAliasPlugin (dev only — resolves optimized deps before alias)
4. aliasPlugin (path alias resolution)
Phase 2: User pre-plugins
5. [user plugins with enforce: 'pre']
Phase 3: Core transforms
6. modulePreloadPolyfillPlugin (polyfill for modulepreload)
7. oxcResolvePlugin (module resolution via oxc)
8. htmlInlineProxyPlugin (proxy for inline HTML scripts)
9. cssPlugin (CSS processing — largest plugin)
10. esbuildBannerFooterCompatPlugin
11. oxcRuntimePlugin (dev only — oxc transform runtime)
12. oxcPlugin (oxc/esbuild transforms for TS/JSX)
13. jsonPlugin (JSON imports, native)
14. wasmHelperPlugin (WASM support)
15. webWorkerPlugin (Web Worker bundling)
16. assetPlugin (static asset handling)
17. forwardConsolePlugin (console forwarding)
Phase 4: User normal plugins
18. [user plugins without enforce]
Phase 5: Post-processing
19. wasmFallbackPlugin (native WASM fallback)
20. definePlugin (compile-time constants)
21. cssPostPlugin (CSS post-processing — extraction, minification)
22. buildHtmlPlugin (build only — HTML bundling)
23. workerImportMetaUrlPlugin (import.meta.url worker support)
24. assetImportMetaUrlPlugin (import.meta.url asset support)
Phase 6: Build pre-plugins
25. [build pre plugins]
Phase 7: Analysis
26. dynamicImportVarsPlugin (dynamic import variable support)
27. importGlobPlugin (import.meta.glob)
Phase 8: User post-plugins
28. [user plugins with enforce: 'post']
Phase 9: Build post + dev-only
29. [build post plugins]
30. clientInjectionsPlugin (dev only — injects client scripts)
31. cssAnalysisPlugin (dev only — CSS dependency analysis)
32. importAnalysisPlugin (dev only — import rewriting)
Note
The
enforceproperty on user plugins determines their position in this pipeline.'pre'inserts before core transforms,'post'inserts after analysis. Omittingenforceplaces the plugin between core transforms and post-processing.
8.3 Plugin API
Vite's plugin API extends Rollup's plugin interface with additional hooks:
Vite-Specific Hooks
| Hook | When | Purpose |
|---|---|---|
config(config, env) |
Config resolution | Modify or extend config |
configResolved(config) |
After config resolution | Read final config |
configureServer(server) |
Server creation | Add custom middleware |
configurePreviewServer(server) |
Preview server creation | Add preview middleware |
transformIndexHtml(html, ctx) |
HTML processing | Transform HTML |
handleHotUpdate(ctx) |
HMR event | Custom HMR handling |
hotUpdate(ctx) |
HMR event (new API) | Per-environment HMR |
Plugin Ordering
const myPlugin = {
name: 'my-plugin',
enforce: 'pre', // 'pre' | 'post' | undefined
apply: 'serve', // 'serve' | 'build' | undefined | function
}
Object Hook Format (Performance Optimization)
const myPlugin = {
name: 'my-plugin',
transform: {
filter: {
id: /\.(ts|tsx)$/, // Only process matching files
},
handler(code, id) {
// Transform logic
},
order: 'pre', // Hook execution order
},
}
The filter property enables Vite to skip calling the hook for non-matching modules, providing significant performance improvement in large projects.
8.4 Module Graph
The dev server maintains a module graph (server.moduleGraph) that tracks:
- Module URL → file path mapping
- Import/export relationships
- Transform results (cached)
- HMR boundaries and acceptors
- Self-accepting modules (handle their own updates)
Each environment has its own module graph instance, enabling independent module tracking for client, SSR, and custom environments.
Key module graph operations:
// Get module by URL
const mod = server.moduleGraph.getModuleByUrl('/src/main.ts')
// Invalidate module (triggers re-transform)
server.moduleGraph.invalidateModule(mod)
// Iterate all modules
for (const mod of server.moduleGraph.idToModuleMap.values()) {
console.log(mod.url, mod.importedModules.size)
}
8.5 HMR Protocol
The HMR system communicates between server and client via WebSocket:
Server → Client messages:
update— module update with list of changed modules and timestampsfull-reload— trigger full page reload (when HMR boundary is not found)prune— remove stale moduleserror— display error overlaycustom— custom events for plugin communication
Client → Server messages:
connected— client connectedcustom— custom events
The client-side HMR runtime (dist/client/client.mjs) handles:
- WebSocket connection management with auto-reconnect
- Module update application via
import.meta.hot.accept() - CSS hot replacement (swap
<link>tags without JS re-execution) - Error overlay rendering and dismissal
- Custom event dispatch
8.6 Type System
Vite ships extensive type declarations:
client.d.ts — Ambient declarations for module types in application code:
| Pattern | Type | Description |
|---|---|---|
*.module.css/scss/sass/less/styl/stylus/pcss/sss |
CSS Modules | Record<string, string> |
*.css |
Plain CSS | string |
*.png/jpg/jpeg/gif/svg/ico/webp/avif/jxl/... |
Images | string (URL) |
*.mp4/webm/ogg/mp3/wav/flac/aac/opus/... |
Media | string (URL) |
*.woff/woff2/ttf/otf |
Fonts | string (URL) |
*.wasm?init |
WASM | Init function |
*?worker |
Web Worker | Worker constructor |
*?sharedworker |
Shared Worker | SharedWorker constructor |
*?raw |
Raw | string (file contents) |
*?url |
URL | string (resolved URL) |
*?inline |
Inline | string (inlined content) |
importMeta.d.ts — Augments ImportMeta with env, hot, and glob types.
8.7 Code Conventions
The Vite codebase enforces these conventions:
| Convention | Enforcement |
|---|---|
ESM-only ("type": "module") |
Package config |
| Consistent type imports | @typescript-eslint/consistent-type-imports |
No require() in packages |
ESLint rule |
No __dirname/__filename |
ESLint rule — use import.meta.dirname |
| Node protocol required | node:fs not fs |
| Semicolons off, single quotes | oxfmt |
| Pre-commit lint | pnpm exec lint-staged --concurrent false via simple-git-hooks |
8.8 Testing Infrastructure
Test Categories
# Unit tests (fast, no browser)
pnpm test-unit
# E2E tests — dev server mode
pnpm test-serve
# E2E tests — build mode
VITE_TEST_BUILD=1 pnpm test-build
# All tests
pnpm test
E2E Test Architecture
E2E tests use a playground pattern: each directory under playground/ is a self-contained Vite project that serves as a test fixture.
playground/
├── css/ # CSS feature tests
├── html/ # HTML processing tests
├── worker/ # Web Worker tests
├── ssr-* # SSR feature tests
└── ... # ~50+ test apps
Tests use Playwright (Chromium) to interact with the running dev server or built output, asserting on DOM state, network requests, and console output.
Test Configuration
- Unit tests: Default Vitest config
- E2E tests:
vitest.config.e2e.ts - Serve vs build mode: Controlled by
VITE_TEST_BUILDenv var - Artifact preservation:
VITE_PRESERVE_BUILD_ARTIFACTS=1
Monorepo Scripts
| Script | Purpose |
|---|---|
pnpm build |
Build all packages |
pnpm dev |
Dev mode (watch) for all packages |
pnpm test |
Run all tests (unit + serve + build) |
pnpm test-unit |
Unit tests only |
pnpm test-serve |
E2E serve tests |
pnpm test-build |
E2E build tests |
pnpm lint |
ESLint |
pnpm format |
oxfmt formatting |
pnpm typecheck |
TypeScript type checking |
pnpm docs |
VitePress dev server for docs |
pnpm docs-build |
Build documentation site |
pnpm docs-serve |
Preview built docs |
pnpm release |
Release workflow |
pnpm ci-publish |
CI publish workflow |
9. Integration Points
9.1 Framework Integrations
Vite is designed as a framework-agnostic build tool with official and community plugins for major frameworks:
| Framework | Plugin | Capabilities |
|---|---|---|
| Vue | @vitejs/plugin-vue |
SFC compilation, HMR, SSR |
| React | @vitejs/plugin-react |
JSX transform (Babel), Fast Refresh |
| React (SWC) | @vitejs/plugin-react-swc |
JSX transform (SWC), Fast Refresh |
| Svelte | @sveltejs/vite-plugin-svelte |
Svelte compilation, HMR |
| Solid | vite-plugin-solid |
Solid JSX, HMR |
| Preact | @preact/preset-vite |
Preact JSX, prefresh HMR |
9.2 Backend Integration
For non-SPA architectures (Rails, Laravel, Django, etc.), Vite provides a manifest-based integration:
- Enable
build.manifest: truein config - In development, include
<script type="module" src="http://localhost:5173/@vite/client">and your entry point - In production, read
.vite/manifest.jsonto resolve hashed file names
The manifest JSON structure:
{
"src/main.ts": {
"file": "assets/main-abc123.js",
"src": "src/main.ts",
"isEntry": true,
"css": ["assets/main-def456.css"],
"imports": ["_vendor-ghi789.js"]
}
}
9.3 Rolldown/Rollup Plugin Compatibility
Vite's plugin interface is Rollup-compatible with extensions. Most Rollup plugins work in Vite with these caveats:
- Rollup plugins that use
this.emitFilework in build mode only - Plugins expecting Rollup-specific internal APIs may need adaptation
- The
enforceandapplyproperties are Vite-specific extensions - Object hook format with
filteris a Vite/Rolldown extension
Tip
When a Rollup plugin doesn't work in dev mode, wrap it with
apply: 'build'to limit it to production builds, and implement a Vite-native equivalent for dev.
9.4 create-vite Scaffolding
The create-vite package provides 20+ project templates:
# Interactive mode
npm create vite@latest
# Direct template selection
npm create vite@latest my-app -- --template react-ts
Available template families: vanilla, vue, react, preact, lit, svelte, solid, qwik — each with TypeScript variants.
Templates are stored in packages/create-vite/template-*/ and copied to the target directory with package name and dependency version substitution.
9.5 Programmatic API
The full programmatic API exported from vite:
import {
// Server
createServer,
preview,
// Build
build,
// Config
defineConfig,
loadConfigFromFile,
resolveConfig,
// Environment
createNodeDevEnvironment,
// Plugins
createFilter,
// Module runner
ModuleRunner, // from 'vite/module-runner'
// Utilities
normalizePath,
searchForWorkspaceRoot,
send,
transformWithEsbuild,
preprocessCSS,
// Types (re-exported)
type Plugin,
type UserConfig,
type ResolvedConfig,
type ViteDevServer,
type HmrContext,
// ... extensive type exports
} from 'vite'
9.6 Known Community Issues
Current open issues that administrators should be aware of:
| Issue | Description | Impact |
|---|---|---|
| #22239 | Same module executed twice with different URL queries | Potential state duplication |
| #22022 | Runtime exception importing conditional CJS module | CJS interop edge case |
| #22225 | plugin-legacy fails with esbuild 0.27.7+ | Legacy browser builds may fail |
| #21395 | Hidden errors from plugins | Silent failures in plugin pipeline |
| #21911 | CSS minifier blocks progressive modern CSS | Modern CSS features may be stripped |
| #22176 | CJS interop factory shadowed re-bundling | Rolldown CJS handling regression |
| #22234 | Oxc minifier removes if guard | Minification correctness bug |
| #22251 | HMR stops with TS circular dependency | Dev experience degradation |
| #21903 | CSS cascade order differs dev vs production | Visual inconsistencies between modes |
Warning
Issue #21903 (CSS cascade order differences between dev and production) is particularly impactful. Always verify CSS-heavy pages in both modes. The root cause is that dev mode serves CSS as individual
<style>tags in import order, while production mode extracts and concatenates CSS, which can change cascade specificity.
9.7 Troubleshooting Reference
Dependency Pre-Bundling Failures
| Error | Cause | Solution | Prevention |
|---|---|---|---|
Failed to resolve dependency |
Package not found or misconfigured exports | Check node_modules installation and package exports field |
Use lockfile, pnpm install --frozen-lockfile |
Outdated pre-bundle deps |
Cache mismatch after dependency update | Delete node_modules/.vite or use --force |
Always restart dev server after pnpm install |
CJS interop error |
CommonJS module with complex export pattern | Add to optimizeDeps.needsInterop |
Document known CJS interop requirements |
Build Failures
| Error | Cause | Solution | Prevention |
|---|---|---|---|
Chunk exceeds size limit |
Large vendor bundles | Configure manualChunks to split vendors |
Set chunkSizeWarningLimit appropriately |
Missing export |
Tree-shaking removed needed export | Mark as side-effect in package.json sideEffects |
Verify side-effect declarations |
CSS order mismatch |
Import order differs between dev and build | Ensure deterministic CSS import ordering | Test production builds regularly |
Dev Server Issues
| Error | Cause | Solution | Prevention |
|---|---|---|---|
EADDRINUSE |
Port already in use | Use --port flag or kill existing process |
Set server.strictPort: false (default) |
403 Restricted |
File outside server.fs.allow |
Add directory to server.fs.allow |
Configure allow list in shared config |
HMR connection lost |
WebSocket disconnected | Check proxy/firewall WebSocket pass-through | Configure server.hmr.host/port explicitly |
Full page reload (unexpected) |
No HMR boundary found for module | Add import.meta.hot.accept() in boundary module |
Ensure framework plugin provides HMR boundaries |