esbuild vs SWC in 2026: Bundler vs Transformer
TL;DR
esbuild (~30M weekly downloads) is both a bundler AND a transformer. SWC (~25M weekly downloads) is a transformer only — it needs to live inside a bundler like Rspack or Vite. esbuild is written in Go; SWC is written in Rust. Both are 50–100x faster than Babel for TypeScript/JSX transpilation. The real question is not speed — it is what you are trying to do: if you need bundling, reach for esbuild (or Vite, which uses esbuild under the hood). If you need TypeScript compilation inside an existing pipeline (webpack, Rspack), reach for SWC.
Key Takeaways
- esbuild: ~30M weekly downloads — SWC: ~25M (npm, April 2026)
- esbuild is a complete bundler — SWC is a transpiler only (important distinction)
- esbuild written in Go — SWC written in Rust — both are compiled native code
- Both are 50–100x faster than Babel — the reason they replaced it
- SWC's high downloads are Next.js-driven — every
next buildpulls SWC - Neither performs type checking — run
tsc --noEmitseparately
The Fundamental Distinction
This is the most important thing to understand before choosing:
esbuild pipeline:
TypeScript source → [parse] → [transform] → [bundle imports] → [minify] → output JS
SWC pipeline:
TypeScript source → [parse] → [transform] → output JS
(bundling is handled by webpack, Rspack, or another tool that wraps SWC)
esbuild can replace webpack. SWC cannot — it is a drop-in replacement for Babel specifically.
When you use Vite, you get both: esbuild handles dependency pre-bundling and TypeScript transpilation in development. When you use Next.js, SWC handles TypeScript and JSX transformation (replacing Babel since Next.js 12). When you use Rspack (the Rust-based webpack replacement), builtin:swc-loader provides SWC transforms inside a bundling context.
Why SWC Has So Many Downloads
SWC has ~25M weekly downloads, which seems surprising for a "transpiler only" tool. The reason is simple: Next.js ships SWC as a dependency. Every project running next build or next dev downloads SWC. Millions of Next.js developers are downloading SWC every week without ever configuring it directly.
esbuild's ~30M downloads come from a mix of direct users (build scripts, library authors) and tools that embed it (Vite, tsup, Bun's bundler, Turbopack's esbuild layer).
esbuild: The Complete Build Pipeline
esbuild can handle your entire frontend build in a single binary — no webpack config, no Babel config.
// esbuild build script (build.mjs)
import * as esbuild from 'esbuild';
// Simple bundle
await esbuild.build({
entryPoints: ['src/index.tsx'],
bundle: true, // Follow and bundle all imports
minify: true, // Production minification
splitting: true, // Code splitting (requires esm format)
format: 'esm',
target: ['es2022', 'chrome110', 'firefox110'],
platform: 'browser',
outdir: 'dist',
define: {
'process.env.NODE_ENV': '"production"',
},
external: ['react', 'react-dom'], // Exclude from bundle (CDN/peer dep)
metafile: true, // Generate bundle analysis JSON
});
// Library build (no bundling of dependencies)
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
format: 'esm',
platform: 'node',
packages: 'external', // Exclude all node_modules
outfile: 'dist/index.js',
});
# esbuild CLI — direct use
esbuild src/index.tsx --bundle --outfile=dist/bundle.js --minify
esbuild src/index.ts --bundle --platform=node --outfile=dist/index.js
# With watch mode for development
esbuild src/index.tsx --bundle --outfile=dist/bundle.js --watch
# Transform only (no bundling) — single file
esbuild src/component.tsx --loader=tsx
esbuild processes files in parallel across all CPU cores by default. The Go binary starts up in milliseconds. A project that takes 30 seconds to bundle with webpack typically takes under 1 second with esbuild.
esbuild Limitations
esbuild is not a complete replacement for every use case:
- No TypeScript type checking — it strips types without verifying them (
tsc --noEmitfills this role) - Limited tree-shaking for some patterns — CommonJS modules do not tree-shake as well as ESM
- No decorators proposal (stage 3) — requires a plugin or SWC for decorator support
- Plugin ecosystem is smaller — the Go plugin API exists but community plugins are fewer than webpack/Rollup
- No HMR built-in — use Vite if you need hot module replacement
SWC: The Babel Replacement
SWC is what you use when you have a bundler already (webpack, Rspack) and want to replace Babel with something dramatically faster.
// .swcrc — drop-in Babel config replacement
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": true,
"dynamicImport": true
},
"transform": {
"react": {
"runtime": "automatic",
"refresh": true
},
"legacyDecorator": true,
"decoratorMetadata": true
},
"target": "es2020",
"loose": false,
"externalHelpers": true,
"keepClassNames": true
},
"module": {
"type": "es6"
},
"minify": false
}
# SWC CLI — install and use directly
npm install -D @swc/cli @swc/core
# Transpile a single file
swc src/index.ts -o dist/index.js
# Transpile a directory
swc src -d dist
# Watch mode
swc src -d dist --watch
SWC Inside webpack
// webpack.config.js — replace babel-loader with swc-loader
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: {
// .swcrc config or inline options
jsc: {
parser: { syntax: 'typescript', tsx: true },
transform: { react: { runtime: 'automatic' } },
},
},
},
},
],
},
};
SWC Plugins
The @swc/plugin-* ecosystem provides transforms beyond Babel equivalents:
npm install @swc/plugin-emotion # CSS-in-JS (Emotion)
npm install @swc/plugin-styled-components
npm install @swc/plugin-relay # GraphQL (Relay)
npm install @swc/plugin-transform-imports # Named import optimization
The SWC plugin ecosystem is more limited than Babel's — many legacy Babel plugins have no SWC equivalent. If you rely on obscure Babel transforms, check compatibility before migrating.
Performance Comparison
Transpiling a 1,000-file TypeScript project (median across machines):
| Tool | Time | Notes |
|---|---|---|
| SWC | ~280ms | Rust, file-by-file transforms |
| esbuild | ~350ms | Go, includes dep resolution overhead |
| Babel | ~14,000ms | JavaScript, single-threaded |
| tsc (emit) | ~7,500ms | TypeScript compiler, type-checks |
For bundling specifically (resolving modules, combining files), esbuild has no SWC equivalent — you cannot compare them directly on this axis.
Where Each Tool Lives in the 2026 Ecosystem
| Tool | Bundles with esbuild | Transpiles with SWC |
|---|---|---|
| Vite | Yes (dev transforms + pre-bundle) | Optional (via @vitejs/plugin-react-swc) |
| Next.js 15+ | — | Yes (default, replaces Babel) |
| Rspack | — | Yes (builtin:swc-loader) |
| Turbopack | — | Yes |
| tsup | Yes | — |
| Parcel 2 | — | Yes |
| Bun | Yes (own bundler, esbuild-inspired) | Yes (own SWC-based transpiler) |
TypeScript Type Checking: Neither Tool Does It
This trips up many developers. Both esbuild and SWC strip TypeScript types without verifying them. This is a feature, not a bug — it is why they are so fast. You need a separate type-checking step:
# Add to your CI and pre-commit hooks
tsc --noEmit # Type-check without emitting any files
# Watch mode for development
tsc --noEmit --watch
# Typical package.json scripts
{
"scripts": {
"build": "tsc --noEmit && esbuild src/index.ts --bundle --outfile=dist/index.js",
"typecheck": "tsc --noEmit",
"dev": "esbuild src/index.ts --bundle --outfile=dist/bundle.js --watch"
}
}
Package Health
| Package | Weekly Downloads | Language | Type |
|---|---|---|---|
esbuild | ~30M | Go | Bundler + Transformer |
@swc/core | ~25M | Rust | Transformer only |
@swc/cli | ~5M | — | CLI wrapper for @swc/core |
Both packages are actively maintained. esbuild is maintained primarily by Evan Wallace (creator). SWC is an open-source project under the Vercel umbrella, heavily invested in by the Next.js team.
When to Choose Each
Use esbuild when:
- Building a simple app or library without needing webpack's full plugin ecosystem
- Writing a build script (Node.js scripts that transform or bundle files)
- Using Vite — esbuild is already powering it, you likely do not need to configure it
- Building a CLI tool or server-side bundle
- You want zero-config bundling for a small to medium project
Use SWC when:
- You are using Next.js — it is already configured, leave it alone
- You are migrating an existing webpack project from
babel-loaderto something faster - You are using Rspack (SWC is the default loader)
- You need specific SWC plugins (emotion, styled-components transforms)
- You want Babel-compatible transforms but faster
Use both (via Vite):
- Most new React/Vue/Svelte frontend projects — Vite uses esbuild internally, and you can optionally switch JSX transform to SWC
Practical Decision Guide
Do you need to bundle multiple files into fewer output files?
YES → Use esbuild (or Vite, which uses esbuild)
NO → SWC is sufficient for transpilation only
Are you inside an existing framework?
Next.js → SWC is already configured — do not change it
Vite → esbuild is already configured — use @vitejs/plugin-react-swc only if needed
webpack → Replace babel-loader with swc-loader for faster builds
Building a TypeScript library for npm?
→ Use tsup (wraps esbuild with great DX for lib builds)
→ Run tsc --noEmit for type checking, tsup for building
Need decorator support (NestJS, TypeORM, etc.)?
→ Use SWC with @swc/plugin-* (or ts-node with SWC integration)
→ esbuild decorators support is limited
Internal Links
See the live comparison
View esbuild vs. swc on PkgPulse →