Architecture
The dev/build split: native ESM dev server + esbuild dependency pre-bundling, Rollup production build, and the plugin API that spans both.
Third-party documentation. This is independently authored analysis of the public Vite codebase — not the official docs, and not reviewed or endorsed by the Vite team.
Architecture
Vite runs two entirely different engines: a native-ESM dev server backed by esbuild, and a Rollup production build. Both are coordinated by a single config and a plugin API that can hook into either phase. Understanding the split explains almost every Vite behavior.
The dev server
- The browser requests the entry
/index.html; Vite rewrites bare imports likeimport React from 'react'into URLs it can serve (/node_modules/.vite/deps/react.js). - Dependencies (code in
node_modules) are pre-bundled once with esbuild into a cached set undernode_modules/.vite, converted to ESM, and served as a single optimized file. This makes them load fast and avoids per-file transform overhead. The cache is invalidated when a dependency's version or the lockfile changes. - Application source files are served as-is (native ESM), transformed lazily per request: TS/JSX goes through esbuild, Vue/Svelte files through their compiler plugins, and CSS is inlined or served as a module.
- HMR works over a WebSocket: when a module changes, Vite invalidates just that module and its importers, and framework plugins push a hot update to the browser — component state is preserved where the framework supports Fast Refresh.
The production build
- Vite hands the module graph to Rollup, which performs a full static analysis pass: tree-shaking unused exports, resolving dynamic imports into separate chunks, minifying, and applying CSS/asset post-processing.
- Output lands in
dist/(configurable viabuild.outDir), with hashed filenames for long-term caching and anindex.htmlthat references the entry bundles. - Because dev and build share the plugin pipeline (
resolveId,load,transform, and Rollup's output hooks), most plugins work identically in both modes — Vite adds dev-only hooks likeconfigureServerandhandleHotUpdateon top.
Why the split exists
Pre-bundling dependencies once with esbuild makes cold starts fast and independent of app size, but esbuild's output isn't designed for optimal production caching — that's Rollup's job. Using the right tool for each phase means developers get an instant dev loop without sacrificing the quality of the shipped bundle.
System Diagram
Interface
DEV PROD┌────────────────────────────────────┐ ┌────────────────────────────────────┐│ Browser (native ESM imports) │ │ Rollup (full bundle) ││ ▲ │ │ │ ▲ ││ │ import │ on-demand │ │ │ bundle + tree-shake + minify ││ │ │ transform │ │ │ ││ ┌─────┴─────┐ ┌─────▼─────┐ │ │ ┌────────────┐ ┌────────────┐ ││ │ Vite dev │ │ esbuild │ │ │ │ Rollup │ │ plugins │ ││ │ server │ │ dep opt │ │ │ │ (bundler) │ │ (Rollup- │ ││ │ (HMR via │ │ + TS/JSX │ │ │ │ │ │ compatible│ ││ │ WebSocket)│ │ transform │ │ │ └────────────┘ └────────────┘ ││ └───────────┘ └───────────┘ │ └────────────────────────────────────┘│ node_modules/.vite (dep cache) │ dist/ (optimized static output)└────────────────────────────────────┘
