14 · Build Tools & Bundlers
How source modules become optimized browser assets: Webpack, Vite, Rspack, esbuild, Rollup/Rolldown, Turbopack, SWC/Oxc — plus tree-shaking, code-splitting, and module resolution. Current as of Vite 8 (Rolldown, March 2026), Turbopack default in Next 16, and the Rust-toolchain era.
Positioning
The bundler is the compiler of the frontend: it resolves your module graph, transforms syntax, eliminates dead code, splits output for caching and lazy loading, and produces assets a browser can load fast. For a senior engineer, the bundler is a performance and DX lever — it governs build times, bundle size (and therefore Core Web Vitals, 15), and how cleanly you can do code-splitting and federation (09). In 2026 the story is stratification, not a single winner: the ecosystem settled into clear roles, and most fast tools are now Rust-based.
Foundations: what a bundler does
- Module resolution — turn
importspecifiers into files (Node resolution,exports/importsmaps, path aliases, extensions). - Dependency graph construction — walk imports from entry points to build the full graph.
- Transformation — TS→JS, JSX→JS, modern→legacy syntax (Babel/SWC/Oxc), CSS handling.
- Tree-shaking — eliminate statically-unreachable exports (relies on ESM’s static structure +
sideEffectshints). - Bundling / chunking — combine modules; code-split into chunks for lazy loading and long-term caching.
- Optimization — minification, scope hoisting, asset hashing, compression.
- Output — hashed assets + a manifest; ESM and/or legacy formats.
Two foundational concepts:
- ESM vs CJS (
02): ESM’s staticimport/exportis what makes tree-shaking and bundler analysis possible; CJS’s dynamicrequireresists it. - The dev/prod split: dev wants instant startup + HMR; prod wants the smallest, fastest output. Tools historically used different engines for each (Vite: esbuild dev + Rollup prod) — a source of subtle inconsistency that the Rust-bundler wave is now closing.
Deep dive: the 2026 tool landscape
Webpack — the legacy workhorse
Mature, infinitely configurable, the largest plugin/loader ecosystem, and the origin of Module Federation (09). Still powering a huge install base (~25M weekly downloads) but no major new features since 2021 and slow builds. State-of-JS sentiment is telling: most developers use it, few like it. Keep it where Webpack-specific plugins or deeply-embedded enterprise CI justify staying; otherwise modernize.
Vite — the default for new projects
Native-ESM dev server (serve modules unbundled, transform on demand → near-instant startup and fast HMR), with a batteries-included DX. The historical wart was its split engine (esbuild for dev, Rollup for prod). Vite 8 (stable, March 12 2026) replaces both with Rolldown — a single Rust bundler — closing the dev/prod gap and delivering large production-build speedups (commonly cited 10–30× vs the old Rollup path; one team reported ~7× faster CI). Vite 8 also moves to Oxc-based tooling. Migration path: rolldown-vite (Vite 7 + Rolldown) as an intermediate step, then Vite 8. The ecosystem default in 2026 (~40M weekly downloads); powers most modern frameworks.
Rspack — the Webpack-compatible Rust rewrite
ByteDance’s Rust reimplementation of Webpack with ~98% plugin-API compatibility and 5–10× faster builds. The lowest-risk migration target for large Webpack apps (often hours, not weeks) and a first-class home for Module Federation 2.0. Choose it when you depend on the Webpack ecosystem but need speed. (Rsbuild is the higher-level, batteries-included wrapper over Rspack.)
esbuild — the fast primitive
Go-based bundler/minifier, extremely fast, simple API. Best as a standalone tool for scripts, CLIs, libraries, and as a transform step inside other tools. Less suited as a full app bundler (lighter on advanced splitting/plugin needs). Vite used it for dev pre-bundling pre-8.
Rollup → Rolldown — the library/app bundler lineage
Rollup: the gold standard for library bundling (clean ESM output, best-in-class tree-shaking, the plugin API everyone targets). Rolldown: a Rust, Rollup-API-compatible successor that now powers Vite 8 — Rollup’s ergonomics at Rust speed. The convergence point of the Vite ecosystem.
Turbopack — Next.js’s bundler
Rust-based, now default and stable in Next 16 for dev and build (08). Roadmap stays Next-focused for the foreseeable future, so it’s not a general-purpose bundler choice. Note: custom Webpack config doesn’t carry over — Turbopack is its own engine, and BundleAnalyzer/Define/custom-plugin-dependent builds need Turbopack equivalents or an opt-out.
SWC vs Oxc — the transform/toolchain layer
- SWC (Rust) — the Babel replacement used by Next and many tools for TS/JSX transform and minification; broad framework/plugin support.
- Oxc (Rust, the “Oxidation Compiler”) — a newer, even-faster toolchain (parser, linter, resolver, transformer) powering parts of Vite 8/Rolldown and
oxlint. Choose Oxc for Rolldown/Vite/lint work; SWC where frameworks/plugins depend on it. - These are transformers, not bundlers — they sit inside the bundler pipeline.
Bun — the integrated runtime/toolkit
Bundler + runtime + package manager + test runner in one (Zig). Compelling for all-in-one speed; in app contexts often paired with Vite rather than replacing it. Worth tracking; not yet the default for production web app bundling.
Core techniques every senior should command
Tree-shaking
Dead-code elimination over the ESM graph. Requirements: ESM (static imports), accurate "sideEffects": false in package.json (or a precise list), and avoiding patterns that defeat it (re-export barrels that pull everything, dynamic property access on namespace imports, CJS deps). Import specifically (import { x } from 'lib'), prefer libraries that ship ESM and mark side-effect-free.
Code-splitting & dynamic import
- Route-based splitting — each route is its own chunk (
React.lazy+Suspense, framework routing). The biggest, easiest win. - Component-level splitting — lazy-load heavy/below-the-fold components (
dynamic()in Next). import()— the dynamic-import expression that creates a split point; returns a promise.- Vendor/shared chunks — split stable dependencies for long-term caching; tune
splitChunks/manualChunks. - Watch the waterfall: too-granular splitting causes request waterfalls (mitigated by HTTP/2/3 multiplexing,
18, and preloading/modulepreload).
Module resolution & aliases
Understand Node resolution, the exports/imports fields (conditional exports: import/require/browser/worker), tsconfig path aliases (and keeping them in sync with the bundler), and how monorepos resolve workspace packages.
Asset & output optimization
Content-hashed filenames for immutable caching, CSS code-splitting, image/font handling, brotli/gzip (18), source maps (and uploading them to error monitoring, not shipping to users), and bundle analysis (rollup-plugin-visualizer / webpack-bundle-analyzer) to enforce budgets (15).
Worked example: route-split + analyze (Vite/React)
// Route-based code splitting — each route becomes its own chunk
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./routes/Dashboard')); // split point via import()
<Suspense fallback={<Spinner />}>
<Dashboard />
</Suspense>
// vite.config.ts — manual vendor chunking + bundle visualization
import { visualizer } from 'rollup-plugin-visualizer';
export default {
plugins: [visualizer({ gzipSize: true })],
build: {
rollupOptions: { output: { manualChunks: { react: ['react', 'react-dom'] } } },
},
};
Run the build, open the visualizer, set a budget, and fail CI if a chunk blows past it (15).
Pitfalls & gotchas
- Barrel files (
index.tsre-exporting everything) defeating tree-shaking and ballooning chunks — import from source paths. - CJS dependencies preventing shaking/ESM optimization — prefer ESM libs; check with the analyzer.
- Over-splitting → request waterfalls and worse latency than one slightly larger chunk.
- Assuming Turbopack/Vite read your old Webpack config — they don’t; federation/plugins need migration.
- Mismatched tsconfig paths and bundler aliases — resolves in the editor, fails at build.
- Shipping source maps publicly or shipping dev builds — check
NODE_ENV/mode. - Chasing benchmarks over fit — in 2026, pick by project shape (migration risk, ecosystem, framework), not raw numbers.
- Module Federation singleton misconfig (
09) — duplicate React across remotes.
Interview questions
- What does a bundler do, step by step (resolution → output)?
- Why does tree-shaking need ESM, and what defeats it?
- Why was Vite’s dev/prod engine split a problem, and how does Rolldown (Vite 8) fix it?
- Webpack vs Vite vs Rspack — when would you pick each in 2026?
- What is Rspack and why is it the low-risk Webpack migration?
- SWC vs Oxc vs Babel — where do they sit in the pipeline?
- How does code-splitting work, and what’s the downside of over-splitting?
- What is the
sideEffectsfield for? - How do conditional
exports(import/require/browser) work? - Why isn’t Turbopack a general-purpose bundler choice?
Recommendations
- New app: Vite (8 + Rolldown). Big Webpack app: evaluate Rspack first (near-drop-in, 5–10×). Next.js: Turbopack (default). Library: Rollup/Rolldown. Standalone scripts/transforms: esbuild/SWC/Oxc.
- Make route-based code-splitting the baseline; lazy-load heavy components; preload critical chunks.
- Enforce bundle budgets in CI with an analyzer (
15). - Ship ESM, side-effect-free libraries; avoid barrels; import from source.
- For MFEs, use Module Federation 2.0 on Rspack/Webpack/Vite with correct singletons (
09). - Pick by project fit and migration risk, not benchmark leaderboards.
Books & references
- Official docs — the canonical sources: vite.dev (esp. the v7→v8/Rolldown migration guide), webpack.js.org, rspack.dev, esbuild.github.io, rollupjs.org, rolldown.rs, swc.rs, oxc.rs, turbo.build.
- module-federation.io — Module Federation 2.0 across bundlers.
- “Survive JS — Webpack” (survivejs.com) — still the clearest deep explanation of bundler concepts (chunking, tree-shaking), even if you use Vite.
- State of JS (stateofjs.com) — yearly adoption/sentiment signal for build tools.
- web.dev — code-splitting,
modulepreload, and bundling-for-performance guides (15). - Bundler comparison guides 2026 (PkgPulse, openreplay) — current, source-backed role breakdowns.
Connections
02-javascript-deep-dive.md— ESM vs CJS is the foundation of tree-shaking and resolution.15-performance-and-core-web-vitals.md— bundle size and splitting directly drive LCP/INP and budgets.09-micro-frontends.md— Module Federation, Rspack/Vite federation, singleton sharing.18-networking-and-protocols.md— HTTP/2/3 multiplexing, compression, and caching interact with chunking.08-nextjs-and-meta-frameworks.md— Turbopack as Next’s default bundler.03-typescript.md— tsconfig paths, declaration emit, and bundler alias alignment.