| name | vite-impl-optimization |
| description | Use when configuring dependency pre-bundling, troubleshooting slow dev server starts, handling monorepo dependencies, or forcing cache invalidation. Prevents stale cache issues and missing optimizeDeps.include for dynamically imported dependencies. Covers pre-bundling (CJS-to-ESM conversion), optimizeDeps.include/exclude/force, automatic discovery, monorepo linked deps, caching (node_modules/.vite), and Rolldown vs esbuild. Keywords: optimizeDeps, pre-bundling, node_modules/.vite, cache invalidation, monorepo, esbuild, Rolldown, dev server slow, slow startup, dependencies not pre-bundled, force rebundle.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Vite 6.x, 7.x, or 8.x. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
vite-impl-optimization
Quick Reference
Why Pre-Bundling Exists
Vite pre-bundles dependencies on first dev server start for two reasons:
| Reason | Problem | Solution |
|---|
| CJS/UMD to ESM conversion | Dev server serves native ESM only; most npm packages ship CJS/UMD | Pre-bundler converts to ESM with smart import analysis |
| HTTP request reduction | ESM deps with many internal modules cause hundreds of requests (e.g., lodash-es = 600+ modules) | Pre-bundler collapses into a single module |
CRITICAL: Pre-bundling applies ONLY in development mode. Production builds use Rolldown (v8) or Rollup (v5-v7) natively.
Pre-Bundling Tool by Version
| Vite Version | Pre-Bundling Tool | Bundler Options Key |
|---|
| v5, v6, v7 | esbuild | optimizeDeps.esbuildOptions |
| v8+ | Rolldown | optimizeDeps.rolldownOptions |
Critical Warnings
NEVER exclude CJS dependencies from pre-bundling -- they MUST be converted to ESM. Excluding a CJS dep causes runtime errors in the browser.
NEVER set optimizeDeps.noDiscovery: true without explicitly listing ALL dependencies in include -- the dev server will fail to resolve unlisted bare imports.
ALWAYS add linked monorepo packages to optimizeDeps.include if they do NOT export ESM -- Vite skips pre-bundling for linked deps by default.
ALWAYS use --force flag after modifying files inside node_modules -- the cache will serve stale pre-bundled code otherwise.
How Automatic Discovery Works
- Vite crawls source code starting from HTML entry points (or
optimizeDeps.entries)
- Every bare import (e.g.,
import X from 'package-name') is identified
- Discovered dependencies are pre-bundled into
node_modules/.vite
- If a NEW dependency import appears after initial crawl, Vite re-runs pre-bundling and reloads the page
When Discovery Fails
Automatic discovery misses dependencies when:
- Imports come from plugin-transformed code (not in original source)
- Dependencies are dynamically imported with variable paths
- Imports exist only in files not reachable from entry points
FIX: Add missed dependencies to optimizeDeps.include explicitly.
Decision Tree: optimizeDeps.include vs exclude
Is the dependency from node_modules?
├── YES: Is it CommonJS or UMD?
│ ├── YES → NEVER exclude. Let pre-bundling convert it.
│ │ Add to include if auto-discovery misses it.
│ └── NO (pure ESM): Does it have many internal modules?
│ ├── YES (e.g., lodash-es) → Add to include for performance
│ └── NO (few modules) → Can exclude safely
└── NO (linked/monorepo package):
├── Exports ESM? → Vite treats as source code (no pre-bundling needed)
└── Does NOT export ESM? → MUST add to optimizeDeps.include
Decision Tree: Monorepo Linked Dependencies
Is the package linked (symlinked from workspace)?
├── YES: Vite auto-detects linked packages
│ ├── Does the package export valid ESM?
│ │ ├── YES → No action needed. Vite analyzes its dependency list instead.
│ │ └── NO → Add to optimizeDeps.include
│ ├── Did you change the linked dep's code?
│ │ └── Restart dev server with --force flag
│ └── Does the linked dep have deep imports?
│ └── Use glob pattern: include: ['my-lib/components/**/*.vue']
└── NO → Standard node_modules dependency (auto-handled)
Cache Invalidation Triggers
Vite automatically re-bundles dependencies when ANY of these change:
| Trigger | What Vite Checks |
|---|
| Package manager lockfile | package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb |
| Patches folder | Modification time of patches/ directory |
vite.config.js | Relevant config fields (optimizeDeps, resolve, plugins) |
NODE_ENV value | Environment variable change between runs |
Browser Cache Behavior
Pre-bundled dependencies are served with aggressive cache headers:
Cache-Control: max-age=31536000,immutable
Vite appends a version query string (e.g., ?v=abc123) to invalidate browser cache when dependencies change. The browser NEVER re-requests cached deps unless the version query changes.
Force Cache Reset
| Method | Command |
|---|
| CLI flag | vite --force or vite dev --force |
| Config option | optimizeDeps.force: true |
| Manual delete | Remove node_modules/.vite directory |
Debugging Dependency Issues
Symptom: Dev Server Slow to Start
- Check if many dependencies lack pre-bundling → add large ESM deps to
include
- Check if
optimizeDeps.holdUntilCrawlEnd is false → set to true (default) to batch discovery
- Check if repeated re-bundling occurs → look for dynamic imports triggering new discoveries
Symptom: Module Not Found or CJS Errors in Browser
- Verify the dependency is NOT in
optimizeDeps.exclude
- If CJS dep → MUST be pre-bundled, remove from exclude
- If linked dep → add to
optimizeDeps.include
- Run
vite --force to clear stale cache
Symptom: Stale Code After Editing node_modules
- Stop dev server
- Delete
node_modules/.vite
- Disable browser cache in DevTools Network tab
- Restart with
vite --force
- Hard-reload the page
Symptom: Interop Issues with CJS Default Exports
Use optimizeDeps.needsInterop (experimental) to force ESM interop for specific packages:
export default defineConfig({
optimizeDeps: {
needsInterop: ['cjs-package-with-broken-default'],
},
})
Common Patterns
Standard SPA with Heavy Dependencies
export default defineConfig({
optimizeDeps: {
include: [
'lodash-es',
'rxjs',
'chart.js',
],
},
})
Monorepo with Linked Packages
export default defineConfig({
optimizeDeps: {
include: [
'shared-utils',
'ui-lib/components/**/*.vue',
],
},
})
Disable Auto-Discovery (Full Manual Control)
export default defineConfig({
optimizeDeps: {
noDiscovery: true,
include: [
'react',
'react-dom',
'my-specific-dep',
],
},
})
WARNING: With noDiscovery: true, you MUST list every dependency. Missing deps cause runtime failures.
Custom Entry Points for Discovery
export default defineConfig({
optimizeDeps: {
entries: [
'src/main.ts',
'src/workers/*.ts',
],
},
})
Reference Links
Official Sources