Pre-bundling in Vite uses esbuild to convert dependencies from various formats to ESM, consolidate multiple internal modules, and optimize for browser performance, existing to solve ESM compatibility and network efficiency problems
Pre-bundling is a critical optimization step in Vite that runs when you first start the development server. It uses esbuild to process all your project's dependencies (node_modules) before serving them to the browser. This exists for two fundamental reasons: first, to convert CommonJS and UMD dependencies into proper ESM format that browsers can natively understand, and second, to improve browser performance by consolidating multiple internal modules from a dependency into a single file, reducing HTTP requests. The combination of esbuild's blazing speed and this optimization strategy creates the smooth development experience Vite is known for .
Scans your source code to discover all bare module imports (like 'react', 'lodash-es') automatically .
Converts CommonJS (CJS) and UMD dependencies to proper ESM format using esbuild's intelligent export analysis .
Bundles together packages with many internal modules (e.g., lodash-es has 600+ separate files) into single ESM modules .
Stores the pre-bundled results in node_modules/.vite/deps with cache-busting query parameters .
Rewrites bare module specifiers in your code to point to these pre-bundled files with proper URLs .
Pre-bundling exists to solve two fundamental problems that arise when serving JavaScript modules natively in the browser. First, ESM compatibility: many npm packages are still distributed as CommonJS, which browsers cannot parse . Vite must convert them to ESM before they can be used. Second, network efficiency: some packages like lodash-es or antd export hundreds or thousands of separate modules. If each import statement resulted in a separate HTTP request, development performance would be terrible . Pre-bundling consolidates these modules, dramatically reducing the number of requests while preserving the developer experience of fine-grained imports .
HTTP request count: Reduces hundreds of requests to just one per dependency package (e.g., lodash-es from 600+ to 1) .
Module format compatibility: Transforms CJS to ESM so browsers can execute dependencies directly .
Build cache efficiency: Stores results in .vite directory with content-based hashing for lightning-fast rebuilds .
Named imports from CJS: esbuild's smart export analysis ensures import { useState } from 'react' works correctly even though React uses module.exports .
Browser cache optimization: Serves pre-bundled dependencies with immutable cache headers so they're never requested again during development .
Vite chose esbuild for pre-bundling because it's exceptionally fast. Written in Go and compiled to native code, esbuild can parse, transform, and bundle entire dependency trees in milliseconds to seconds, depending on size. The official esbuild documentation explains that its parallelism and compilation to native code allow it to saturate all CPU cores during parsing and code generation . For a typical project, dependency pre-bundling completes in 1-2 seconds on first run, and subsequent runs use the cache, making restarts virtually instantaneous .
Pre-bundling runs automatically when you start the dev server, but Vite provides configuration options for edge cases. Use optimizeDeps.include when you have imports that aren't automatically discovered (e.g., generated by plugins) or when you need to force pre-bundling of specific packages. Use optimizeDeps.exclude when a dependency should not be pre-bundled (rare, but needed for some packages with side effects) . The system also intelligently invalidates the cache when your lockfile changes, ensuring you always have the correct versions .
Missing imports: If you import from a package not automatically discovered (e.g., via plugin), add to optimizeDeps.include .
Linked dependencies: For monorepo packages, add them to both optimizeDeps.include and build.commonjsOptions.include .
Dynamic imports: Packages that use require() dynamically may need optimizeDeps.include to ensure proper handling .
Legacy browser support: Pre-bundling targets modern browsers by default; use build.target to adjust output .