Choosing a Bundler
Bundlers and compilers solve different jobs: transform source for browsers/Node, ship optimized production assets, and keep local feedback loops fast. Vite, Webpack, Rollup, esbuild, SWC, Turbopack, Parcel, Rspack, and tsup overlap, but each optimizes for a different constraint set. Pick from requirements — not from blog-post benchmarks alone.
This guide maps tools to product shapes (SPA, SSR app, published library, monorepo), then to hard requirements (HMR latency, Module Federation, dual ESM/CJS packages, CI cold starts). Use it as a decision matrix, then deep-dive the dedicated pages for the shortlist.
info
Group tools by primary role before comparing brand names.
| Role | Tools | Best when |
|---|---|---|
| App bundler + Dev server | Vite, Webpack, Parcel, Rspack, Turbopack | You ship a browser or SSR app |
| Library bundler | Rollup, tsup, esbuild, Vite lib mode | You publish packages to npm |
| Compiler / transformer | SWC, Babel, esbuild, tsc | You need transpile/minify inside another bundler |
| Monorepo orchestrator | Turborepo, Nx (not bundlers) | You cache and schedule many package tasks |
note
Rough relative strengths. Scores are directional — measure on your repo size and plugin set.
| Tool | Dev HMR | Prod maturity | Plugin ecosystem | Typical use |
|---|---|---|---|---|
| Vite | Excellent (native ESM) | Excellent (Rollup) | Large | SPA / Vite SSR / libs |
| Webpack | Good (heavier) | Excellent | Largest | Complex apps, federation |
| Rspack | Good–Excellent | Strong (Webpack-compat) | Webpack-compatible | Webpack migrations |
| Rollup | Via plugins / not primary | Excellent for libs | Strong for ESM | Libraries, Vite prod |
| esbuild | Serve API / limited | Fast; less flexible | Plugins limited | TS transpile, tsup, Vite dep optimize |
| SWC | N/A (compiler) | Excellent as transform | Growing | Next.js, Webpack loader |
| Turbopack | Excellent in Next.js | Maturing | Next.js-scoped | Next.js apps |
| Parcel | Excellent | Good | Built-in transforms | Zero-config apps |
| tsup | Watch only | Excellent for libs | esbuild + dts | TS libraries |
Start with what you ship. The wrong category choice costs more than picking Vite vs Parcel inside the right category.
| Shape | Default pick | Strong alternatives | Avoid unless required |
|---|---|---|---|
| SPA / multi-page app | Vite | Parcel, Rspack | Raw esbuild alone for large apps |
| Next.js app | Next defaults (SWC + Webpack/Turbopack) | Turbopack for dev | Replacing Next's bundler with Vite |
| SSR / custom Node server | Vite SSR / Webpack | Rspack | tsup as the app bundler |
| Published TS library | tsup or Rollup | Vite library mode, unbuild | Full Webpack for a small util package |
| Micro-frontends / federation | Webpack or Rspack | Module Federation 2.x plugins | Parcel / tsup as federation host |
| Monorepo many packages | pnpm + Turborepo/Nx + Vite/tsup per pkg | Nx generators / boundaries | One giant Webpack for all packages |
Translate stakeholder asks into tool constraints. If two tools satisfy the hard requirements, prefer the one your team already knows.
| Requirement | Why it matters | Leads toward |
|---|---|---|
| Sub-100ms HMR on large graphs | Developer productivity on big SPAs | Vite, Turbopack (Next), Parcel |
| SSR / streaming frameworks | Server + client dual graphs | Framework bundler (Next/Remix) or Vite SSR |
| Module Federation | Independent deployable remotes | Webpack 5, Rspack |
| Dual ESM + CJS package | Consumers on Node CJS and ESM | tsup, Rollup, unbuild |
| CI cold starts / clean runners | Minutes on every PR without cache | esbuild/SWC transforms, remote cache (turbo/nx) |
| Legacy Webpack loaders | Migration cost dominates | Rspack first, then Vite if rewriting |
| Zero config for juniors | HTML entry, auto transforms | Parcel, Vite create templates |
| DTS generation for libs | Types must ship with package | tsup, rollup-plugin-dts, api-extractor |
warning
Apps optimize for code-splitting, asset pipeline, and HMR. Libraries optimize for clean ESM, dual packages, small surface area, and accurate .d.ts files.
| 1 | { |
| 2 | "name": "@acme/utils", |
| 3 | "type": "module", |
| 4 | "main": "./dist/index.cjs", |
| 5 | "module": "./dist/index.js", |
| 6 | "types": "./dist/index.d.ts", |
| 7 | "exports": { |
| 8 | ".": { |
| 9 | "types": "./dist/index.d.ts", |
| 10 | "import": "./dist/index.js", |
| 11 | "require": "./dist/index.cjs" |
| 12 | } |
| 13 | }, |
| 14 | "scripts": { |
| 15 | "build": "tsup src/index.ts --format esm,cjs --dts --clean" |
| 16 | } |
| 17 | } |
For apps, prefer Vite or a framework-integrated bundler. For libraries, prefer tsup/Rollup and keep peer dependencies external so React/Vue are not bundled twice into consumer apps.
best practice
In monorepos, choose per-package bundlers and a repo-level orchestrator. Mixing Vite apps + tsup packages + Next apps is normal.
| 1 | { |
| 2 | "$schema": "https://turbo.build/schema.json", |
| 3 | "tasks": { |
| 4 | "build": { |
| 5 | "dependsOn": ["^build"], |
| 6 | "outputs": ["dist/**", ".next/**"] |
| 7 | }, |
| 8 | "dev": { |
| 9 | "cache": false, |
| 10 | "persistent": true |
| 11 | }, |
| 12 | "lint": { |
| 13 | "dependsOn": ["^build"] |
| 14 | } |
| 15 | } |
| 16 | } |
| Need | Orchestrator | Bundler pairing |
|---|---|---|
| Simple pipelines + remote cache | Turborepo | Vite / tsup / Next per package |
| Generators, boundaries, affected | Nx | Same — Nx wraps executors |
| Few packages, no remote cache | pnpm workspaces alone | Scripts + pnpm -r |
HMR architecture differs: Vite invalidates ESM modules over the wire; Webpack/Rspack patch modules inside a graph; Turbopack uses incremental function caches inside Next.js.
- SPA with frequent UI edits — Vite or Parcel usually wins on feel.
- Next.js App Router — use Turbopack for next dev; do not invent a parallel Vite setup.
- Custom SSR — Vite SSR or a Webpack isomorphic setup; ensure CSS and client hydration share chunk IDs correctly.
- Library watch mode — tsup --watch or Vite library mode; HMR is for consumers, not the package itself.
pro tip
Independent team deploys with shared runtime dependencies point strongly at Webpack 5 or Rspack. Vite has community federation plugins, but Webpack/Rspack remain the path of least resistance for mature MF setups.
| 1 | import { ModuleFederationPlugin } from "@module-federation/enhanced/rspack"; |
| 2 | |
| 3 | export default { |
| 4 | plugins: [ |
| 5 | new ModuleFederationPlugin({ |
| 6 | name: "host", |
| 7 | remotes: { |
| 8 | catalog: "catalog@https://cdn.example.com/catalog/mf-manifest.json", |
| 9 | }, |
| 10 | shared: { |
| 11 | react: { singleton: true, requiredVersion: "^19.0.0" }, |
| 12 | "react-dom": { singleton: true, requiredVersion: "^19.0.0" }, |
| 13 | }, |
| 14 | }), |
| 15 | ], |
| 16 | }; |
CI machines often have empty disk caches. Tool choice and cache strategy matter more than microbenchmarks on a warm laptop.
| Lever | How / why | When |
|---|---|---|
| Faster transforms | SWC/esbuild vs Babel | CPU-bound transpile steps |
| Remote task cache | Turborepo / Nx remote cache | Monorepos with many packages |
| Dependency install cache | pnpm store + lockfile hash | Every CI job |
| Persist bundler caches | Webpack/Rspack FS cache, Vite cache dir | Repeated builds on same branch |
| Affected-only builds | Nx affected / turbo filter | Large monorepos on PRs |
note
Walk this checklist top-down. Stop at the first hard yes.
| 1 | 1. Framework-owned bundler? |
| 2 | Yes (Next.js, Nuxt, SvelteKit, Remix) → use framework defaults; |
| 3 | enable Turbopack/Vite where the framework documents it. |
| 4 | |
| 5 | 2. Publishing an npm library? |
| 6 | Yes → tsup or Rollup (+ dts). Dual package if CJS consumers remain. |
| 7 | |
| 8 | 3. Need Module Federation / mature Webpack loaders? |
| 9 | Yes → Webpack 5 or Rspack (prefer Rspack when migrating for speed). |
| 10 | |
| 11 | 4. Greenfield SPA / multi-page web app? |
| 12 | Prefer Vite. Parcel if you want zero-config HTML entry UX. |
| 13 | |
| 14 | 5. Only need fast TS → JS for Node scripts? |
| 15 | esbuild or SWC CLI — not a full app bundler. |
| 16 | |
| 17 | 6. Monorepo orchestration pain? |
| 18 | Add Turborepo or Nx on top — do not replace bundlers. |
- Replacing Babel with SWC and Webpack with Vite in one PR — split migrations.
- Using Vite for a library but forgetting to externalize peers — bloated dual React copies.
- Choosing Turbopack for a non-Next project — it is not a general-purpose Vite replacement today.
- Benchmarking empty Hello World apps — measure your real plugin set and node_modules size.
- Skipping source maps in production "for speed" without an error-reporting strategy.
danger
| Scenario | Pick |
|---|---|
| New React/Vue/Svelte SPA | Vite |
| Next.js app DX | Turbopack for dev |
| Webpack app too slow | Rspack migration |
| TS util / UI kit package | tsup |
| HTML-first prototype | Parcel |
| Monorepo task graph | Turborepo or Nx |
| Bun-native toolchain | bun build (evaluate carefully) |
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.