18 — Module Bundlers — Two Jobs, One Dependency Graph
The tools between npm run dev and a working page were a magic black box until I named their two jobs. The unsticking: a bundler does two jobs — transform and bundle [1]. Once I could name those two jobs separately, the whole zoo of tools (Webpack, Rollup, Parcel, esbuild, SWC, Vite, Rolldown) collapsed into "which of the two jobs, and how fast."
The dependency graph, first
Browsers historically couldn't load a project's worth of JavaScript files the way I wrote them — scattered across folders, importing each other with import/export, mixing .tsx and .css and images. So a bundler's starting point is the same in every tool: starting from an entry file, follow every import/require and build a dependency graph of the whole project [1]. Then it walks that graph doing its two jobs.
Job one — transform
The first job is translating files into something the browser can run. TypeScript gets compiled to JavaScript; JSX becomes plain function calls; newer syntax gets transpiled (rewritten into an older version browsers already understand) [3][4]. Anything that isn't vanilla browser-runnable JS — CSS imports, image imports, .vue/.svelte files — gets turned into JS in this pass too.
This is where the older tool, Babel, used to live. Babel is correct and mature but written in JavaScript, so on a big codebase the transform pass alone could take tens of seconds. The whole recent speed story in bundling is "rewrite the transform in a faster language." SWC does exactly that — a Rust-based compiler for JS/TypeScript/JSX that's orders of magnitude faster than Babel and now powers Next.js's compilation underneath [3]. esbuild is the parallel move with a different engine: a Go-based bundler/minifier that does the transform and bundling at near-instant speed (a minifier shrinks a file by stripping whitespace and shortening names), deliberately trading a few advanced plugin features for raw throughput [4]. SWC and esbuild aren't competitors so much as two answers to the same question — "can the transform stop being the slow part?" — built in Rust and Go respectively.
Job two — bundle
The second job is walking that graph and turning many files into the few assets the browser actually loads [1][2]. That means three sub-tasks I used to blur together:
- Resolve the import graph — figure out which modules depend on which, and in what order.
- Code-split — break the output into chunks so the browser loads a route's code lazily instead of one giant file up front.
- Tree-shake — drop exported code that no importer ever uses, so dead code never ships [5].
This is the job Rollup made its name on. Rollup is built around ES modules (the modern import/export system) and its tree-shaking is the reference implementation — it produces unusually small, clean output, which is why it became the default for libraries [5]. Webpack, the legacy incumbent, does both jobs and does them with a configuration surface so large it became a meme; it earned its dominance by handling every asset type and edge case, at the cost of being slow and config-heavy [2]. Parcel sits at the opposite end of that dial: a zero-config bundler that detects your file types and transforms them automatically, favoring simplicity and rapid prototyping over control [7].
The Vite shift — dev and prod are not the same job
The single idea that reorganized all of this for me: the dev server and the production build don't have to be the same tool, and they shouldn't be. During development I want instant feedback; for production I want the smallest, most optimized bundle. Vite's whole design is to stop forcing one tool to do both badly.
Vite splits the work. In dev it doesn't bundle at all — it serves source files as native ES modules (the import/export system served straight to the browser, which resolves the imports itself), and it uses esbuild to transform files on the fly. The result is a dev server that starts in milliseconds regardless of project size, and Hot Module Replacement (HMR) — swapping in a changed file without reloading the whole page — that feels instantaneous [6]. For production, where native ESM would be too slow to ship, Vite hands off to a real bundler — historically Rollup, increasingly Rolldown — for code-splitting, tree-shaking, and minification [6].
Rolldown — Rust-ifying the production bundler
Rolldown is the piece that clicked last. Rollup's tree-shaking is great but, being JavaScript, it's the slow part of Vite's production build on large apps. Rolldown is a from-scratch Rust rewrite of Rollup, designed as a drop-in replacement — same module-resolution semantics and plugin compatibility, but with the bundling pass running at native speed [8]. Its whole reason for existing is to remove that last JS-shaped bottleneck so Vite's dev and prod paths can both be fast without trading away Rollup-grade output quality.
That's the broader arc worth seeing: the transform job moved to Rust/Go (SWC, esbuild), the dev server moved to native ESM (Vite), and now the bundling job itself is moving to Rust (Rolldown). Webpack, Rollup-in-JS, and Babel aren't wrong — they're the mature generation the faster tools are standing on the shoulders of.
How I map them
When a tool name comes up now, I just ask which of the two jobs it's doing:
- esbuild / SWC — the transform job, fast (Go / Rust).
- Webpack — both jobs, legacy, maximal config.
- Rollup — the bundle job, library-grade tree-shaking, written in JS.
- Rolldown — the bundle job, Rollup's replacement in Rust.
- Parcel — both jobs, zero config.
- Vite — the orchestrator: native ESM + esbuild in dev, Rollup/Rolldown in prod.
Two jobs, one graph, and a clear answer to "which job, and how fast." That's the whole category. The mystery was that I'd been treating six tools as six unrelated things; they're really six points on the same two-axis grid.
References
[1] webpack, "Concepts — Modules and the dependency graph," webpack Documentation. [Online]. Available: https://webpack.js.org/concepts/dependency-graph/
[2] webpack, "What is webpack?," webpack Documentation. [Online]. Available: https://webpack.js.org/concepts/
[3] SWC Project, "SWC (Speedy Web Compiler) — Overview," 2024. [Online]. Available: https://swc.rs/docs/
[4] E. Wallace, "esbuild — An extremely fast bundler for the web," 2024. [Online]. Available: https://esbuild.github.io/
[5] Rollup, "Introduction — Tree-shaking," Rollup Documentation. [Online]. Available: https://rollupjs.org/introduction/
[6] Vite, "Why Vite — Problems & Solutions," Vite Documentation. [Online]. Available: https://vite.dev/guide/why.html
[7] Parcel, "Getting Started," Parcel Documentation. [Online]. Available: https://parceljs.org/docs/
[8] Rolldown, "Guide — Introduction," Rolldown Documentation. [Online]. Available: https://rolldown.rs/guide/
Knowledge check · Question 1 of 5
A bundler's two core jobs are…
Comments
Leave a Comment
You must be signed in to comment
0 Comments
No comments yet. Be the first to comment!