Topics
v6.0.0
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.”

ToolRoleStrengthLimitation
RollupBundler + tree-shakeLibrary ESM/CJS outputSlower than esbuild transform
esbuildTransform + minify + simple bundleGo speedNarrow plugins, no Federation
SWCRust compiler (Babel-class)Fast TS/JSX loweringNot 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 graph

Babel 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 bundle

Never 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 --noEmit separately

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 --noEmit stays 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-env sat 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 --noEmit for types
  • Shared UI library: Rollup dual ESM/CJS + rollup-plugin-dts, peers external
  • Legacy webpack app: swc-loader or esbuild-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 tsc separately.

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 graph

Practical 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 gzip

What 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.

Last updated on