Deep Dive into Vite's Dependency Pre‑Bundling Process
This article explains why Vite is faster than bundlers like Webpack, introduces the concept of dependency pre‑bundling, walks through its implementation using esbuild plugins, and shows how to build a simplified custom Vite server that scans, resolves, and pre‑bundles third‑party modules.
Most developers associate Vite with keywords like "fast" and "no‑bundle" and wonder why it outperforms bundlers such as Webpack or Rollup. The article clarifies that Vite’s speed comes from a development‑time dependency pre‑bundling step that transforms third‑party modules into optimized ESM files.
The core concept of dependency pre‑bundling is that before the dev server starts, Vite scans the project’s entry HTML, extracts the <script type="module" src=...> entry, and uses esbuild to bundle all external dependencies into a .vite/deps directory. A _metadata.json file records the mapping between original package entry points and the generated bundle files.
Pre‑bundling is necessary for two reasons: (1) to convert non‑ESM third‑party packages (e.g., React) into ESM so the browser can import them directly, and (2) to reduce the number of HTTP requests by consolidating many small module files (e.g., lodash‑es ) into a single bundle.
The article then walks through a step‑by‑step implementation of a minimal custom Vite server. It starts by setting up a simple project structure, creating a CLI entry ( bin/vite ) that imports a createServer function, and building a dev server with connect and serve-static . Configuration is simulated in src/config.js , which now includes a cacheDir for pre‑bundled assets.
To parse the HTML entry, an esbuild plugin ( esbuildScanPlugin ) is written. The plugin’s onResolve hook resolves HTML files, while onLoad reads the HTML, extracts the script source, and returns a virtual JS module that imports the original script. This allows esbuild to treat the HTML as a JS entry point.
Recursive parsing of JS/TS files is added by extending the plugin with a second onResolve hook that distinguishes between source files and third‑party modules. Third‑party imports are marked as external: true and recorded in a depImports map; source files are resolved normally for further scanning.
After collecting all external dependencies, the optimizer creates a cache directory ( .custom-vite/deps ), writes a _metadata.json file with relative paths, and runs a second esbuild build that bundles each dependency into its own .js file inside the cache directory.
The article maps this custom implementation to Vite’s actual source code, showing how Vite’s CLI invokes createServer , how the plugin container resolves IDs, and how the real discoverProjectDependencies and runOptimizeDeps functions perform the same scanning and bundling steps using internal plugins.
Finally, the author notes that the presented code is a simplified version and that Vite’s real implementation handles many edge cases (symbolic links, Yarn PnP, virtual modules, etc.). The tutorial concludes with a reminder to follow the author’s series for deeper dives into Vite’s file transformation, HMR, and production build pipelines.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.