Vite 01 ⚡ Why Vite — The unbundled dev server philosophy
Vite was created on a single observation: traditional bundlers process your entire application before the browser can display anything, and the larger the app, the longer you wait . Vite rejected that trade-off by splitting the work into two parts. Dependencies—the libraries that rarely change—are pre-bundled once using fast native tooling. Source code—the application code you edit constantly—is served on-demand over native ES modules, and the browser loads only what it needs for the current page . The dev server startup became nearly instant regardless of application size, and editing a file triggered a Hot Module Replacement update over native ESM without a full page reload or a rebuild wait .
Key point: Vite’s unbundled dev server does not bundle your source code during development. The browser requests modules individually, and Vite transforms each file as it is requested. Bundling is still used for production builds, where the network round-trip cost of unbundled ESM makes shipping the raw modules inefficient .
Why Vite exists
The cold-start problem.
A traditional bundler walks the entire dependency graph, merges every module into a bundle, and only then serves the result. A large application means a long wait before the first page appears, and every restart repeats the cost . Vite moves the graph traversal from the build tool to the browser’s runtime. The dev server starts a static server, and when the browser requests main.ts, Vite transforms that file and returns it. When the file imports another module, the browser requests that module, and Vite transforms it on demand . Startup time becomes largely independent of project size, because there is no full bundle to produce.
The pre-bundling problem.
Native ESM alone would flood the browser with requests for the hundreds of internal modules inside libraries like lodash-es. Vite solves this with dependency pre-bundling: esbuild converts CommonJS and UMD dependencies into ESM and merges the internal modules of a library into a single file . A bare import like import { createApp } from 'vue' is rewritten to a path under /node_modules/.vite/deps/, and the browser fetches one pre-built file instead of the library’s entire module tree . The pre-bundle is cached with a hash in the URL, so a changed dependency version invalidates the cache and triggers a re-bundle .
The HMR problem.
Hot Module Replacement in Vite does not rebuild the bundle. It maintains a module dependency chain, and when a file changes, Vite sends an update over native ESM that replaces only that module in the browser . The application state is preserved, and the update latency is measured in milliseconds rather than the seconds that a full rebuild would take . The browser’s native module system is the transport, and Vite’s only job is to transform the changed file and notify the client.
The production bundling problem.
Unbundled ESM is efficient during development because the browser caches the many small requests. In production, the nested imports create additional network round trips, and shipping the raw modules is inefficient . Vite therefore uses a bundler for vite build, producing optimized static assets. In the current architecture, that bundler is Rolldown, a Rust-based bundler that replaced the earlier esbuild-and-Rollup combination in Vite 8 . The production path is a bundle; the development path is not.
a. The unbundled dev server in practice
The dev server does not pre-process the source tree. It transforms files as the browser requests them.
# Start the Vite dev server
npx vite
# The server starts in milliseconds, regardless of the project size
When the browser requests the entry module, Vite returns the transformed source. The imports inside that module become browser requests for the imported files.
// src/main.ts
import { createApp } from 'vue' // Bare import: rewritten to the pre-bundled path
import App from './App.vue' // Relative import: served as a separate module
createApp(App).mount('#app')
The vue import is a bare specifier that the browser cannot resolve, so Vite rewrites it to the pre-bundled dependency path. The ./App.vue import is a relative path that the browser requests directly, and Vite transforms the .vue file into JavaScript on demand .
b. The pre-bundling cache
The pre-bundled dependencies are stored under node_modules/.vite/deps with a content hash. The hash changes when the lockfile, the dependency versions, the optimizeDeps configuration, or the Vite version changes, and the browser fetches the new files because the URL changed .
// vite.config.ts
export default defineConfig({
optimizeDeps: {
include: ['lodash-es'], // Pre-bundle this dependency explicitly
exclude: ['@my/local-package'], // Skip the pre-bundling for a local ESM package
},
})
The include option is necessary when a dependency is discovered only at runtime, such as a dynamic import that the initial scan did not detect. The exclude option is for a dependency that is already ESM and has few internal modules, where the pre-bundling would add an unnecessary layer .
c. The production bundle
The production build uses the bundler to produce optimized assets. The unbundled dev server is not the deployment artifact.
# Build for production
npx vite build
# The output is a set of bundled assets in dist/
The build produces a main chunk and any code-split chunks created by dynamic imports. The static imports are merged into the main chunk to reduce the HTTP requests, and the dynamic imports become separate chunks that load on demand . The result is the optimized static output that the CDN or the web server serves.
Complete Example Session
# ============================================
# PART 1: SCAFFOLD THE PROJECT
# ============================================
npm create vite@latest my-app -- --template react
# ============================================
# PART 2: INSTALL THE DEPENDENCIES
# ============================================
cd my-app
npm install
# ============================================
# PART 3: START THE DEV SERVER
# ============================================
npm run dev
# The server starts in milliseconds
# ============================================
# PART 4: OBSERVE THE PRE-BUNDLED DEPENDENCIES
# ============================================
ls node_modules/.vite/deps/
# The pre-bundled react and react-dom files
# ============================================
# PART 5: EDIT A COMPONENT
# ============================================
# Edit src/App.jsx and save
# The browser updates the module without a full reload
# ============================================
# PART 6: BUILD FOR PRODUCTION
# ============================================
npm run build
# The bundled assets are written to dist/
# ============================================
# PART 7: PREVIEW THE PRODUCTION BUILD
# ============================================
npm run preview
The seven parts demonstrate the unbundled dev server lifecycle: the scaffold, the install, the instant startup, the pre-bundled dependencies, the HMR update, the production build, and the production preview.
Quick Reference
Dev vs Production
| Aspect | Dev Server | Production Build |
|---|---|---|
| Source code | Served on-demand over ESM | Bundled |
| Dependencies | Pre-bundled with esbuild | Bundled |
| Startup | Instant, independent of app size | Build time depends on app size |
| HMR | Native ESM update | Not applicable |
| Output | No bundle | Optimized static assets |
Pre-Bundling
| Option | Purpose |
|---|---|
optimizeDeps.include | Pre-bundle a runtime-discovered dependency |
optimizeDeps.exclude | Skip the pre-bundling for a local ESM package |
| Cache location | node_modules/.vite/deps |
| Cache invalidation | Lockfile, dependency version, config, Vite version |
The Two Bundlers (Vite 8+)
| Stage | Bundler |
|---|---|
| Dependency pre-bundling | esbuild |
| Production build | Rolldown |
Best Practices
✅ Do This:
# Start the dev server with the default config
npm run dev # ✅
# Use the include option for a runtime-discovered dependency
optimizeDeps: { include: ['lodash-es'] } # ✅
# Use the exclude option for a local ESM package
optimizeDeps: { exclude: ['@my/utils'] } # ✅
# Build for production with the bundler
npm run build # ✅
# Preview the production build
npm run preview # ✅
❌ Don’t Do This:
# Expect the dev server to bundle the source code
# It serves the modules on demand # ❌
# Ship the unbundled dev output to production
# Use the build command # ❌
# Ignore the pre-bundling cache
# The changed dependency needs a re-bundle # ❌
# Pre-bundle a local ESM package that has few modules
# Use the exclude option # ❌
# Assume the production build uses the same mechanism as dev
# The production uses the bundler # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| The dependency error at runtime | The dependency was not pre-bundled | Add it to optimizeDeps.include |
| The local package change is not reflected | The package is pre-bundled and cached | Add it to optimizeDeps.exclude |
| The browser floods the server with requests | A large dependency is not pre-bundled | Pre-bundle it |
| The production build is slow | The large app is bundled | Expected; the bundler processes the full graph |
| The HMR update is slow | The module chain is large | Expected; the update is still faster than a rebuild |
Real-World Examples
1. Start the dev server
npm run dev
2. Pre-bundle a dependency
optimizeDeps: { include: ['echarts/core'] }
3. Exclude a local package
optimizeDeps: { exclude: ['@my/utils'] }
4. The cache directory
ls node_modules/.vite/deps/
5. Build for production
npm run build
6. Preview the build
npm run preview
7. The source import
import { createApp } from 'vue'
8. The pre-bundled path
// Vite rewrites the import to /node_modules/.vite/deps/vue.js?v=...
9. The HMR update
// Edit a component and the browser updates the module
10. The production output
ls dist/assets/
# The main chunk and the code-split chunks
Visual
The Dev Server vs the Bundler
┌──────────────────────────────────────────────────────────────┐
│ DEV: ON-DEMAND; PRODUCTION: BUNDLED │
│ │
│ Dev server: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Browser requests a module │ │
│ │ → Vite transforms it and returns it │ │
│ │ → The browser requests the imports │ │
│ │ → The cycle repeats │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ Production build: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ The bundler walks the full graph │ │
│ │ → merges the static imports │ │
│ │ → splits the dynamic imports │ │
│ │ → minifies and writes the assets │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
The Pre-Bundling
┌──────────────────────────────────────────────────────────────┐
│ PRE-BUNDLING SOLVES THE REQUEST FLOOD │
│ │
│ Without pre-bundling: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ import { x } from 'lodash-es' │ │
│ │ → the browser requests hundreds of internal modules │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ With pre-bundling: │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ import { x } from '/node_modules/.vite/deps/lodash-es.js│ │
│ │ → the browser requests one file │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
The Production Build
┌──────────────────────────────────────────────────────────────┐
│ THE PRODUCTION BUILD MERGES THE STATIC IMPORTS │
│ │
│ Static import (import ... from ...): │
│ └── merged into the main chunk │
│ │
│ Dynamic import (import()): │
│ └── a separate chunk, loaded on demand │
│ │
│ The result: fewer HTTP requests and a smaller initial load │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Dev server | Unbundled, on-demand over native ESM |
| Dependencies | Pre-bundled with esbuild |
| Source code | Served as separate modules |
| HMR | Native ESM update |
| Production build | Bundled (Rolldown in Vite 8+) |
| Pre-bundle cache | node_modules/.vite/deps |
| Pre-bundle invalidation | Lockfile, version, config, Vite version |
| Production bundling reason | Network round-trip cost of unbundled ESM |
Key takeaways:
- Vite’s dev server does not bundle the source code. It serves the modules on demand over native ESM, and the browser requests each module it needs. The startup time is nearly independent of the application size .
- Dependencies are pre-bundled to prevent the request flood.
esbuildconverts the CommonJS dependencies to ESM and merges the internal modules into a single file, so the browser fetches one pre-built file per dependency . - HMR uses the native ESM transport. When a file changes, Vite sends an update for that module only, and the application state is preserved. The latency is in milliseconds rather than seconds .
- Production still uses a bundler. The unbundled ESM creates additional network round trips, so the production build merges the static imports and splits the dynamic imports for the optimized output .
- The pre-bundling cache is invalidated by the config changes. The lockfile, the dependency versions, the
optimizeDepsconfiguration, and the Vite version all change the cache hash and trigger a re-bundle . - Vite 8 uses Rolldown for the production build. The esbuild-and-Rollup combination was replaced by a single Rust-based bundler that is 10–30× faster than Rollup .
Remember: Vite is fast in development because it does not bundle the source code. The browser does the graph traversal, and Vite transforms the files as they are requested. The dependencies are pre-bundled once, and the HMR updates a single module over native ESM. The production build is still bundled, because the unbundled ESM is inefficient over the network. The result is the instant dev server and the optimized production build, which is the split that made Vite the default build tool for the modern front-end.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!