esbuild vs Rollup vs webpack: Comparing JS Bundlers
esbuild, Rollup, and webpack all bundle JavaScript, but trade off speed, configurability, and output shape differently. Which fits which project.
esbuild, Rollup, and webpack are all JavaScript bundlers — tools that take a graph of modules and combine them into optimized output files for the browser — but they differ sharply in build speed, configuration philosophy, and what kind of project they’re built for. Most developers today don’t reach for any of these three directly; they use a build tool like Vite that bundles one of them under the hood. Knowing what each does well still matters, because it explains why your dev server is instant but your production build takes longer, and why switching build tools sometimes changes output size or behavior in surprising ways.
What a bundler actually does
At its core, a bundler resolves import/require statements across your codebase, builds a dependency graph, and emits one or more output files containing everything the browser needs — with dead code removed (tree-shaking), modules concatenated, and often the whole thing minified. Beyond that shared job, the three tools take very different approaches.
esbuild: speed as the whole point
esbuild is written in Go and compiled to native code, rather than running on the JavaScript runtime it’s bundling for. That’s the source of its headline feature: it’s routinely an order of magnitude faster than JavaScript-based bundlers on comparable projects, because it avoids the overhead of running on a JIT-compiled, garbage-collected runtime for a workload that’s fundamentally CPU-bound parsing and transformation.
esbuild trades some flexibility for that speed. Its plugin API is much smaller than webpack’s, and it doesn’t natively support some of the more elaborate code-splitting and output-shaping features the other two offer. In practice, most projects don’t need that extra configurability — which is why esbuild has become popular as the fast transform-and-bundle layer inside other tools, including Vite’s development server, rather than being configured directly by most end users.
Rollup: built for libraries
Rollup predates both of the others in its current form and was designed around a specific goal: producing clean, minimal output for publishing JavaScript libraries. Its tree-shaking was, for a long time, more aggressive and reliable than webpack’s, because Rollup’s whole model centers on ES modules’ static import/export structure, which is easier to analyze than the more dynamic module patterns webpack has historically had to support.
Rollup shines when the output needs to be a clean library bundle — say, publishing a package to npm in multiple formats (ESM, CommonJS, UMD). It has historically been weaker than webpack for the messier requirements of bundling a large application: code-splitting across many routes, handling non-JS assets, hot module replacement during development. Vite uses Rollup for its production builds specifically because Rollup’s output for library-shaped code is so clean, while using esbuild for the fast development-time transforms where output shape matters less.
webpack: the configurable generalist
webpack has the longest history of the three and, correspondingly, the most extensive feature set and plugin ecosystem. Its defining trait is a loader system: every file type, not just JavaScript, gets processed through a chain of loaders, which is how webpack became the tool of choice for applications that need to bundle CSS, images, fonts, and other non-JS assets alongside code, all through one unified pipeline.
That flexibility comes with real configuration overhead — webpack configs for a nontrivial application can run to hundreds of lines, and getting code-splitting, caching, and asset handling right historically required understanding a fairly deep API surface. Its build speed, being pure JavaScript, is also generally the slowest of the three for comparable projects. webpack remains dominant in large, established codebases where its plugin ecosystem and fine-grained configurability outweigh the speed cost, and where migrating away carries its own risk.
Comparison
| esbuild | Rollup | webpack | |
|---|---|---|---|
| Implementation | Go (native) | JavaScript | JavaScript |
| Build speed | Fastest | Fast for ESM-shaped code | Slowest of the three |
| Plugin ecosystem | Small, growing | Moderate | Largest |
| Best suited for | Fast dev-time transforms | Publishing libraries | Large, asset-heavy applications |
| Non-JS asset handling | Limited | Limited | Extensive (loaders) |
| Typical usage today | Embedded inside other tools | App bundling, library builds | Direct config, legacy apps |
Which one you’re actually using
If your project uses Vite, Next.js, or most other modern frontend tooling, you’re likely using esbuild and Rollup already without configuring either directly — Vite uses esbuild for fast development-time transforms and Rollup to produce the production bundle. webpack is still what you’re configuring directly if you’re on Create React App’s underlying tooling, an older Angular CLI project, or a codebase that predates the current generation of Vite-based tooling and hasn’t migrated.
For a brand-new project, reaching for a build tool that already picks a sensible bundler under the hood — rather than hand-configuring esbuild, Rollup, or webpack yourself — is almost always the right call. Direct bundler configuration is mostly relevant now if you’re publishing a library (where Rollup’s clean output still matters), maintaining legacy webpack infrastructure, or building custom tooling where you need control the higher-level build tools don’t expose.
The takeaway
esbuild wins on raw speed by compiling to native code instead of running on the JS runtime it bundles for; Rollup produces the cleanest output for publishing libraries by leaning on ES modules’ static structure; webpack trades speed for the deepest configurability and the widest support for non-JS assets. Most projects today get all three secondhand, through a build tool like Vite that picks the right one for each job — direct configuration is mostly a concern for library authors and legacy application maintainers.
Keep reading
Takina · · 4 min read Web Streams API Explained: Readable, Writable, Transform
The Streams API lets JavaScript process data as chunks arrive instead of buffering it all in memory. How ReadableStream, WritableStream, and pipes work.
Takina · · 4 min read npm and pnpm Workspaces: Managing a Monorepo
Workspaces let npm and pnpm manage multiple packages in one repository, sharing a single dependency tree and letting packages reference each other locally.
Takina · · 5 min read toSorted, toReversed, with(): JS's New Array Methods
toSorted(), toReversed(), toSpliced(), and with() copy an array instead of mutating it, fixing a long-standing footgun in JavaScript's Array API.