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

Plugin Ecosystem and Extensibility Limits

esbuild's plugin system operates at the file resolution and transform level, allowing plugins to intercept imports, transform file contents, and inject virtual modules. The Go-based plugin API is accessible from JavaScript via the plugin option in the build config. However, the plugin ecosystem is substantially smaller than webpack's because esbuild's speed depends on its ability to process files in parallel with minimal inter-operation overhead — complex plugins that require cross-file analysis break this model. Common use cases like CSS modules, SVG-as-component, and markdown imports have community plugins, but niche transforms and custom code generation that would be trivial Babel plugins may not have esbuild equivalents. SWC's plugin system uses Wasm-compiled Rust plugins, which are fast but add significant complexity to the authoring process — writing an SWC plugin requires Rust knowledge, whereas Babel plugins are plain JavaScript. The practical implication: for standard TypeScript and JSX pipelines, both tools cover the common cases well; for highly customized transform pipelines with unusual requirements, evaluate specific plugin availability before committing.

Source Maps and Debugging in Production

Both esbuild and SWC generate source maps, but the source map quality differs in ways that matter for production debugging. esbuild generates compact source maps by default and supports the sourcemap option with values "linked", "inline", "external", and "both". The linked mode writes a separate .map file and appends a sourceMappingURL comment — the correct approach for production builds where source maps are served separately and not included in the main bundle payload. SWC generates source maps per-file during transpilation; when used inside webpack or Rspack, source map merging happens at the bundler layer. For production error tracking with Sentry or Datadog, source maps from esbuild are straightforward to upload using esbuild's metafile: true output to identify all generated files. SWC-based pipelines typically rely on the outer bundler's source map configuration, meaning source map behavior is controlled at the webpack or Rspack level rather than in SWC's config. Both approaches work, but esbuild's single-tool model makes source map configuration more predictable.

The tooling built on top of esbuild and SWC is where most developers actually encounter these compilers. Vite uses esbuild for dependency pre-bundling and TypeScript stripping; Next.js uses SWC for its Babel replacement and React Fast Refresh. Developers who have switched from Create React App or Vite to Next.js have effectively changed their compiler from esbuild to SWC without making a direct tool choice — it happened automatically when the framework changed.

Understanding this layer distinction helps clarify why esbuild vs SWC comparisons can feel confusing: in most projects, you are not choosing between them directly. You are choosing a framework or build tool that has made this choice internally. The direct esbuild and SWC choice surfaces primarily for library authors building TypeScript packages, framework authors building their own build pipelines, or platform teams optimizing shared build infrastructure. In those contexts, the decision framework above applies directly. The clearest signal for each: reach for esbuild when bundling is the requirement and TypeScript decorator support is not needed; reach for SWC when you need a Babel drop-in replacement with transformer plugin support that esbuild cannot provide.

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.