Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

Vite Administrator & Developer Guide

Generated May 2026 from vitejs/vite at 66f3194

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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's dist/ 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:

  1. vite.config.ts (or .js, .mjs, .mts, .cjs, .cts)
  2. Inline config passed via --config CLI flag
  3. 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 the VITE_ 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-legacy does 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:

  1. Scans entry points for bare import specifiers
  2. Bundles each dependency into a single ESM file using Rolldown
  3. Caches results in node_modules/.vite/deps/
  4. 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/.vite and restarting the dev server often resolves stale cache issues. The --force flag 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

  1. 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.

  2. optimizeDeps.holdUntilCrawlEnd (default true) delays serving modules until the initial dependency scan completes, preventing mid-session re-optimization that would trigger a full page reload.

  3. 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.fs settings 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 envPrefix to an empty string ('') exposes ALL environment variables to client-side code, including DATABASE_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 .gitignore for node_modules)
  • Regenerated in CI environments (--force or 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 enforce property on user plugins determines their position in this pipeline. 'pre' inserts before core transforms, 'post' inserts after analysis. Omitting enforce places 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 timestamps
  • full-reload — trigger full page reload (when HMR boundary is not found)
  • prune — remove stale modules
  • error — display error overlay
  • custom — custom events for plugin communication

Client → Server messages:

  • connected — client connected
  • custom — 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_BUILD env 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:

  1. Enable build.manifest: true in config
  2. In development, include <script type="module" src="http://localhost:5173/@vite/client"> and your entry point
  3. In production, read .vite/manifest.json to 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.emitFile work in build mode only
  • Plugins expecting Rollup-specific internal APIs may need adaptation
  • The enforce and apply properties are Vite-specific extensions
  • Object hook format with filter is 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