On this page · sections
Rollup vs esbuild vs SWC
Think layers, not rivals. Hard build-tool questions test whether you compose tools or treat them as interchangeable “fast bundlers.”
| Tool | Role | Strength | Limitation |
|---|---|---|---|
| Rollup | Bundler + tree-shake | Library ESM/CJS output | Slower than esbuild transform |
| esbuild | Transform + minify + simple bundle | Go speed | Narrow plugins, no Federation |
| SWC | Rust compiler (Babel-class) | Fast TS/JSX lowering | Not a bundler alone |
Pipeline Roles
Definition
Source TS/JSX → transform (esbuild or SWC) → bundle + shake (Rollup or webpack) → minify → dist/Rollup builds the module graph, tree-shakes, code-splits, emits ESM/CJS/IIFE.
esbuild parses/transforms/minifies at extreme speed — Vite uses it for dep pre-bundling and optional minify.
SWC is a Rust compiler — Babel-compatible transform for TS/JSX/React refresh; paired with webpack, Next.js, or Rspack.
// Typical Vite prod path
// 1. Rollup resolves graph from src/main.jsx
// 2. esbuild or plugin transforms TS
// 3. esbuild minifies output
// 4. Hashed files to dist/
// Typical Next.js path
// 1. SWC compiles TSX per page
// 2. webpack or Turbopack bundles graphBabel still wins for niche plugins, experimental proposals, and codemods without SWC equivalents. Terser when esbuild minify produces larger gzip.
Interview: Say “bundler vs transpiler vs minifier” in the first sentence — separates staff answers from buzzwords.
Rollup for Libraries
Definition
Rollup was built for ESM-first libraries — preserves module structure, produces lean ESM with strong tree-shaking, and supports multiple outputs from one graph.
// rollup.config.mjs
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
export default {
input: 'src/index.js',
external: ['react', 'react-dom'],
output: [
{ file: 'dist/index.mjs', format: 'esm', sourcemap: true },
{ file: 'dist/index.cjs', format: 'cjs', exports: 'named', sourcemap: true },
],
plugins: [resolve(), commonjs()],
};src/index.js → Rollup graph → dist/index.mjs + dist/index.cjs
react — external → not in bundleNever bundle React into a component library — breaks hooks. Use external for peers and node builtins.
preserveModules emits one file per module — better for tree-shake-friendly deep imports; trade-off is more published files. rollup-plugin-dts bundles type declarations.
tsup wraps esbuild for fast library builds — good for Node libraries; verify dual ESM/CJS needs.
Interview: Peer deps external — never bundle React into a library; duplicate React breaks hooks.
esbuild Speed & Limits
Definition
esbuild is Go-native, parallelized, and avoids per-file process spawn — orders of magnitude faster than Babel-on-Node for TS/JSX downlevel and minification.
import * as esbuild from 'esbuild';
await esbuild.build({
entryPoints: ['src/cli.ts'],
bundle: true,
platform: 'node',
outfile: 'dist/cli.js',
minify: true,
sourcemap: true,
});Limitations:
- Narrower plugin ecosystem than Rollup/webpack
- No full Module Federation story
- Bundling code splitting less flexible than Rollup for complex apps
- Strips types — run
tsc --noEmitseparately
esbuild-loader in webpack offloads transform step — hybrid speed without leaving webpack graph.
Use esbuild for speed layers; Rollup/webpack for graph complexity.
Interview: esbuild does not typecheck —
tsc --noEmitstays in CI regardless of transform tool.
SWC as Babel Replacement
Definition
SWC implements syntax lowering — TypeScript, JSX, React Fast Refresh, decorators (progressive) — compatible with much of the Babel workflow but faster.
{
"jsc": {
"parser": { "syntax": "typescript", "tsx": true },
"transform": { "react": { "runtime": "automatic", "refresh": true } },
"target": "es2020"
},
"module": { "type": "es6" }
}Next.js defaulted to SWC for compile. Rspack uses SWC under the hood.
Choose SWC when transforms are standard and speed matters. Stay on Babel for custom plugins without SWC equivalents.
SWC = transform · Rollup/webpack = bundle · esbuild can do both with different trade-offs.
Interview: SWC is not a bundler — it sits where
@babel/preset-envsat in the pipeline.
Monorepo Pipeline Design
Definition
Staff composition for a large React monorepo:
- Dev: Vite with esbuild transform +
optimizeDeps - Prod app: Rollup via Vite + esbuild minify;
tsc --noEmitfor types - Shared UI library: Rollup dual ESM/CJS +
rollup-plugin-dts, peers external - Legacy webpack app:
swc-loaderoresbuild-loader, webpack for Federation - CI: cache
.vite, webpack filesystem cache; parallelize with Turborepo
// turbo.json (conceptual)
// {
// "tasks": {
// "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
// "typecheck": { "dependsOn": ["^build"] }
// }
// }Principle: fast transform at scale, mature bundler for graph features, one browserslist source of truth.
Transform ≠ bundle ≠ typecheck ≠ lint — never skip tsc because SWC or esbuild runs.
Benchmark pipeline changes: median CI build duration, main chunk gzip, source-map upload size over a 7-day window.
Interview: Platform team owns shared rollup/vite config factories — tooling ADRs per app.
Senior Interview Summary
Rollup
Bundle + tree-shake + code-split. Default for library ESM/CJS publish with external peers.
esbuild
Fast Go transform + minify. Vite dep pre-bundle. Run
tscseparately.
SWC
Rust Babel-class compile. Next.js default. Needs webpack/Rspack for bundling.
Pipeline layers
Transform → esbuild or SWC
Bundle → Rollup or webpack
Minify → esbuild or Terser
Typecheck → tsc --noEmit (always separate)Escape hatches
Babel for unmigrated macro packages. webpack+SWC when Federation required. Rspack for webpack API at Rust speed.
Best mental model
One tool does not do everything — pipelines compose specialists
Library publish → Rollup rules (external peers, dual format)
App build → Vite/Rollup or webpack graphPractical rules
Never bundle react into libraries → external + peerDependencies
Never skip tsc because SWC runs → types are compile-time only
Benchmark minifier → esbuild vs Terser on your output gzipWhat to say in interviews
Place each tool in the correct pipeline layer. Explain why Rollup is default for libraries, what esbuild does in Vite dev vs prod, and why tsc --noEmit stays in CI with esbuild/SWC.