Skip to main content

esbuild vs SWC in 2026: Bundler vs Transformer

·PkgPulse Team
0

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 build pulls SWC
  • Neither performs type checking — run tsc --noEmit separately

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 --noEmit fills 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):

ToolTimeNotes
SWC~280msRust, file-by-file transforms
esbuild~350msGo, includes dep resolution overhead
Babel~14,000msJavaScript, single-threaded
tsc (emit)~7,500msTypeScript 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

ToolBundles with esbuildTranspiles with SWC
ViteYes (dev transforms + pre-bundle)Optional (via @vitejs/plugin-react-swc)
Next.js 15+Yes (default, replaces Babel)
RspackYes (builtin:swc-loader)
TurbopackYes
tsupYes
Parcel 2Yes
BunYes (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

PackageWeekly DownloadsLanguageType
esbuild~30MGoBundler + Transformer
@swc/core~25MRustTransformer only
@swc/cli~5MCLI 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-loader to 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

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.