Why Vite Pre-Bundles Dependencies with esbuild and How to Manage the Trade-offs
Vite speeds up dev server startup by pre‑bundling node_modules with esbuild, caching the result, and letting you control the process via optimizeDeps. Learn how it works, its limits, and practical checks.
10 Feb 2026, 21:13 UTC

The problem: bare imports explode in the browser
When Vite serves your source files as native ES modules, every bare import like import _ from 'lodash-es' would cause the browser to request each individual file inside the package. A single dependency can consist of hundreds of tiny modules, and many of them are still written in CommonJS, which browsers cannot execute directly. The result is a slow-loading page and runtime errors.
Vite's decision: pre‑bundle with esbuild
On dev server start Vite scans the dependency graph, extracts all bare imports from node_modules, and runs esbuild - a fast Go-based bundler to:
- Convert CommonJS to ESM so the browser can understand the code.
- Collapse each package's many internal files into a single (or a few) virtual module(s).
- Produce the output and write it to a disk cache under
node_modules/.vite.
Because esbuild is written in Go, this step is usually much faster than a JavaScript-based bundler, and the result is reused across restarts as long as the cache remains valid.
Cache behaviour and forcing a re‑optimization
The cache is invalidated when:
- The package lockfile (
package-lock.json,yarn.lockorpnpm-lock.yaml) changes. - Vite configuration that influences dependency optimization (
optimizeDeps) is modified. - A patch file applied to a dependency is added or removed.
When any of these events occur, the next dev start triggers a fresh esbuild run. Developers can also force a re‑optimization manually:
# Run from the project root vite --force # or, if using npm scripts npm run dev -- --force
This deletes the existing node_modules/.vite content and starts the optimization again.
Worked example: avoiding a late‑discovery reload
Suppose you have a component that loads lodash-es only when a button is clicked:
import { ref } from 'vue'
const show = ref(false)
function loadLodash() {
import('lodash-es').then(_ => { /* use _ */ })
}
On first load Vite sees only the static import of Vue, so lodash-es is not included in the initial pre‑bundle. When the button is clicked, the dynamic import triggers a re-optimization of lodash-es and causes a full page reload, which can be disruptive during development.
To prevent this interruption you can tell Vite to pre‑bundle lodash-es up front by adding it to optimizeDeps.include:
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
optimizeDeps: {
include: ['lodash-es']
}
})
After saving the config and restarting the dev server, Vite will include lodash-es in the initial esbuild step. The dynamic import now resolves to the already‑bundled module, and the page does not reload when the button is clicked.
Limitations and practical checks
While pre‑bundling speeds up every subsequent request, it introduces a few trade‑offs:
- Cold-start cost. The very first dev start (or after a cache miss) can take several seconds as esbuild works through all dependencies.
- Cache staleness. If you switch branches or edit a lockfile without realizing it, the server may appear to hang while it rebuilds the cache.
- ESBuild vs Node interop differences. Some packages rely on Node‑specific behaviour (e.g.,
require.resolve) and may emit errors like 'does not provide an export named'. - Monorepo linked packages. By default Vite excludes linked local packages from pre‑bundling, so their transitive CommonJS dependencies need to be added manually to
optimizeDeps.includeif they cause problems.
You can verify that the cache is being used:
- Start the dev server and note the time until the UI appears.
- While the server is running, open
node_modules/.viteand confirm that a folder for each pre‑bundled dependency exists. - Stop the server, delete the lockfile (or run
git checkoutto another branch), then start the server again. You should see a fresh optimization step and the UI re‑appear after a comparable delay. - Restart the server without changing anything; the second start should be noticeably faster because the cache is reused.
If you suspect an interop issue, try adding the problematic package to optimizeDeps.include or, as a last resort, to optimizeDeps.exclude and let Vite serve it as raw ES modules (which will fall back to slower individual requests).
Takeaway
Vite's decision to pre‑bundle dependencies with esbuild is a deliberate, cacheable optimization that trades a one‑time CPU cost for near‑instant module loading during development. Understanding how the cache works, when it is invalidated, and how to influence the include list lets you keep the dev server snappy while avoiding unexpected reloads or errors.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.