Vite handles CommonJS and UMD modules through dependency pre-bundling with esbuild during development, converting them to ESM format, while production builds rely on @rollup/plugin-commonjs for the same purpose
In a native ES Modules (ESM) development environment, browsers cannot directly understand CommonJS (CJS) or UMD modules. When you run import { useState } from 'react', the browser would normally throw an error because it doesn't recognize bare module specifiers or CJS format . Vite solves this through a process called dependency pre-bundling, which automatically converts all CJS and UMD dependencies to ESM format before serving them to the browser. This ensures that named imports from legacy modules work exactly as developers expect .
When you run vite for the first time, Vite scans your source code to discover all bare module imports (like 'react', 'lodash-es') .
It then uses esbuild to pre-bundle these dependencies into ESM format, storing them in node_modules/.vite with cache-busting query parameters .
For CommonJS modules, esbuild performs smart import analysis so that named imports work even when exports are dynamically assigned (e.g., import React, { useState } from 'react' works correctly) .
This pre-bundling serves two purposes: CJS/UMD compatibility and performance optimization (reducing HTTP requests for packages with many internal modules like lodash-es which has 600+ modules) .
Vite's smart import analysis ensures that named imports from CommonJS modules work as expected. When esbuild converts a CJS module to ESM, it analyzes the exports and generates a proper ESM wrapper. For example, even though React uses module.exports with dynamic exports, Vite's pre-bundling ensures that import { useState } from 'react' correctly maps to React's exports. This is handled automatically during the pre-bundling step and doesn't require any configuration from the developer .
While development uses esbuild for speed, production builds take a different approach. Vite switches to @rollup/plugin-commonjs for handling CommonJS modules during vite build . This is because Rollup provides more sophisticated tree-shaking and output optimization for production bundles. The plugin works alongside Rollup's bundling process to convert any remaining CJS dependencies to ESM format while ensuring optimal chunk splitting and dead code elimination.
File system cache: Vite stores pre-bundled dependencies in node_modules/.vite and only rebuilds when lockfiles, config, or NODE_ENV change .
Browser cache: Resolved dependency requests are served with Cache-Control: max-age=31536000,immutable headers, ensuring they never hit the dev server again after first load .
Cache invalidation: When dependency versions change (reflected in lockfiles), Vite automatically invalidates the cache via appended version queries .
Force rebuild: Use vite --force to manually re-bundle dependencies when debugging or making local edits .
In monorepo setups with linked packages, Vite automatically detects dependencies not resolved from node_modules and treats them as source code. However, this requires the linked dependency to be exported as ESM. If not, you must add it to both optimizeDeps.include and build.commonjsOptions.include in your config. After making changes to the linked dep, restart the dev server with --force to rebuild the cache .