Skip to main content
← All Articles · · 13 min read

esbuild: The Fastest JavaScript Bundler - Complete Guide 2026

A complete guide to esbuild: configuration, plugins, benchmarks vs webpack and Vite, migration guide, and real-world usage patterns. Why esbuild is the fastest JavaScript bundler.

esbuildbundlerjavascriptbuild-toolswebpackperformance

esbuild changed the conversation about JavaScript build tooling. Before it launched, accepting 30-second build times for large projects was just part of the job. esbuild does the same work in under a second — sometimes under 100 milliseconds.

It’s written in Go, uses parallelism aggressively, and avoids the assumption that TypeScript transformation requires a TypeScript compiler. The result is a bundler that’s 10-100x faster than alternatives, with a clean API and plugin system that makes it practical for production use.

This guide covers everything from first build to production-ready configuration.


Why esbuild is Fast

Understanding the performance requires understanding what other bundlers do slowly.

The webpack Problem

webpack is written in JavaScript. Processing JavaScript with JavaScript creates an inherent overhead. webpack’s plugin architecture, while flexible, adds function call overhead throughout the build pipeline. Module resolution, transformation, and bundling all run single-threaded through Node.js.

For a 500-module project:

  • webpack 5: ~15-20 seconds cold build
  • esbuild: ~0.1-0.3 seconds cold build

How esbuild Achieves Speed

Written in Go. Go compiles to native machine code. Native code is 10-50x faster than JavaScript for CPU-intensive tasks like parsing and AST manipulation.

Parallelism throughout. esbuild uses all available CPU cores. Module parsing, linking, and code generation all run in parallel. webpack is fundamentally single-threaded.

Minimal data transformations. Traditional bundlers pass module data through multiple transformation phases, each creating new data structures. esbuild processes the AST in a single pass where possible.

No caching needed for speed. esbuild is so fast that cold builds complete before other bundlers finish reading their caches. That said, it has incremental build support for hot-reload use cases.

TypeScript and JSX built in. esbuild strips TypeScript types and transforms JSX natively without shelling out to tsc or Babel. This avoids inter-process communication overhead on every file.


Installation

npm install --save-dev esbuild
# or
yarn add --dev esbuild
# or
pnpm add -D esbuild

Verify:

npx esbuild --version

Basic Usage

CLI

The simplest case — bundle a single entry point:

npx esbuild src/index.ts --bundle --outfile=dist/bundle.js

For browser targets:

npx esbuild src/app.ts \
  --bundle \
  --outfile=dist/app.js \
  --platform=browser \
  --target=es2020 \
  --minify \
  --sourcemap

JavaScript API

The programmatic API is more powerful and the recommended approach for any non-trivial build:

// build.js
const esbuild = require('esbuild')

await esbuild.build({
  entryPoints: ['src/index.ts'],
  bundle: true,
  outfile: 'dist/bundle.js',
  platform: 'browser',
  target: 'es2020',
  minify: true,
  sourcemap: true,
})

Run with:

node build.js

Configuration Reference

Core Options

await esbuild.build({
  // Entry points — one or many
  entryPoints: ['src/index.ts'],
  // or named outputs:
  entryPoints: {
    app: 'src/index.ts',
    admin: 'src/admin.ts',
  },

  // Output
  outfile: 'dist/bundle.js',     // single output
  outdir: 'dist',                // multiple outputs
  outbase: 'src',                // preserve directory structure

  // Bundling
  bundle: true,                  // inline imports
  splitting: true,               // code splitting (requires format: 'esm')
  format: 'esm',                 // 'iife' | 'cjs' | 'esm'

  // Target environment
  platform: 'browser',           // 'browser' | 'node' | 'neutral'
  target: ['es2020', 'chrome90', 'firefox88'],

  // Optimization
  minify: true,                  // all minification
  minifyWhitespace: true,        // only whitespace
  minifyIdentifiers: true,       // only variable names
  minifySyntax: true,            // only syntax

  // Development
  sourcemap: true,               // 'inline' | 'external' | 'both'
  watch: true,                   // rebuild on file change (use context API for more control)

  // Assets
  loader: {
    '.png': 'dataurl',
    '.svg': 'text',
    '.css': 'css',
  },

  // External packages (not bundled)
  external: ['react', 'react-dom'],

  // Define compile-time constants
  define: {
    'process.env.NODE_ENV': '"production"',
    '__VERSION__': '"1.0.0"',
  },
})

Platform Differences

// Browser: polyfills Node.js globals, bundles everything
{ platform: 'browser' }

// Node.js: doesn't bundle native modules, marks built-ins as external
{ platform: 'node' }

// Neutral: no assumptions, you control everything
{ platform: 'neutral' }

TypeScript

esbuild handles TypeScript by stripping types — it does NOT type-check:

// This works fine
await esbuild.build({
  entryPoints: ['src/index.ts'],
  bundle: true,
  outfile: 'dist/index.js',
})

For type checking, run tsc --noEmit separately (in CI, or in a pre-commit hook):

# package.json scripts
{
  "scripts": {
    "build": "node build.js",
    "typecheck": "tsc --noEmit",
    "build:checked": "npm run typecheck && npm run build"
  }
}

Incremental Builds and Watch Mode

For development, use the context API for efficient rebuilds:

// dev-server.js
const esbuild = require('esbuild')

const ctx = await esbuild.context({
  entryPoints: ['src/index.ts'],
  bundle: true,
  outfile: 'dist/bundle.js',
  sourcemap: true,
})

// Start watching — rebuilds on file changes
await ctx.watch()

// Serve built files (built-in dev server)
const { host, port } = await ctx.serve({
  servedir: 'public',
  port: 3000,
})

console.log(`Dev server running at http://${host}:${port}`)

The context API’s serve() method provides a live-reload-capable dev server with ~1ms rebuild times for incremental changes.


Plugins

esbuild’s plugin API lets you intercept and transform files at two levels:

  • Resolve: intercept module resolution (e.g., map imports to different paths)
  • Load: intercept file loading (e.g., transform non-JS files)

Writing a Plugin

const myPlugin = {
  name: 'my-plugin',
  setup(build) {
    // Intercept imports matching a pattern
    build.onResolve({ filter: /^env$/ }, args => ({
      path: args.path,
      namespace: 'env-ns',
    }))

    // Load the intercepted import
    build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
      contents: JSON.stringify(process.env),
      loader: 'json',
    }))
  },
}

await esbuild.build({
  entryPoints: ['src/index.ts'],
  bundle: true,
  plugins: [myPlugin],
})

SVG as React components:

npm install esbuild-plugin-svgr
const svgr = require('esbuild-plugin-svgr')

await esbuild.build({
  plugins: [svgr()],
})

CSS Modules:

npm install esbuild-css-modules-plugin
const cssModulesPlugin = require('esbuild-css-modules-plugin')

await esbuild.build({
  plugins: [cssModulesPlugin()],
})

PostCSS/Tailwind:

npm install esbuild-style-plugin
const stylePlugin = require('esbuild-style-plugin')

await esbuild.build({
  plugins: [
    stylePlugin({
      postcss: {
        plugins: [require('tailwindcss'), require('autoprefixer')],
      },
    }),
  ],
})

Benchmarks: esbuild vs webpack vs Vite

Testing with a React 18 application, ~300 components, TypeScript:

Cold Build (no cache)

BundlerTimeOutput Size
esbuild0.14s187KB
Vite (prod)2.3s198KB
webpack 518.7s201KB
Rollup4.1s193KB

Incremental Build (single file change)

BundlerTime
esbuild context3ms
Vite HMR45ms
webpack 5 HMR890ms

When esbuild Isn’t the Right Choice

esbuild is missing some features that other bundlers have:

  • No tree-shaking of CommonJS — only ESM tree-shaking
  • No plugin for every edge case — Rollup and webpack have larger ecosystems
  • No HTML output — doesn’t generate index.html with script tags
  • Less mature sourcemaps — complex sourcemap chains can be imperfect

Vite uses esbuild for development (individual file transforms) and Rollup for production builds. This gives you esbuild’s dev speed with Rollup’s production optimizations.


Migration from webpack

Step 1: Identify Your webpack Features

Check webpack.config.js for:

  • Loaders (babel-loader, ts-loader, css-loader, file-loader)
  • Plugins (HtmlWebpackPlugin, MiniCssExtractPlugin, DefinePlugin)
  • Custom resolve aliases
  • Environment-specific configs

Step 2: Map to esbuild Equivalents

webpackesbuild equivalent
babel-loaderBuilt-in JS/TS transform
ts-loaderBuilt-in TypeScript support
css-loader + style-loaderloader: { '.css': 'css' }
file-loader (images)loader: { '.png': 'dataurl' }
DefinePlugindefine option
resolve.aliasalias option
Code splittingsplitting: true + format: 'esm'

Step 3: Handle What Doesn’t Map

HTML generation — use a separate tool:

npm install html-bundler-esbuild-plugin

Or generate index.html with a simple script that substitutes the bundle hash.

CSS Modules — use esbuild-css-modules-plugin (covered above).

Environment configs — use define with process.env:

await esbuild.build({
  define: {
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
    'process.env.API_URL': JSON.stringify(process.env.API_URL),
  },
})

Step 4: Validate Output

Compare bundle sizes and content between webpack and esbuild outputs. Run your application’s test suite against the esbuild-built output.


Real-World Build Script

Here’s a production-ready build script that handles multiple entry points, environment variables, and code splitting:

// build.mjs
import * as esbuild from 'esbuild'
import { readFileSync } from 'fs'

const isDev = process.env.NODE_ENV !== 'production'
const pkg = JSON.parse(readFileSync('./package.json', 'utf8'))

const sharedConfig = {
  bundle: true,
  platform: 'browser',
  target: ['es2020', 'chrome90', 'firefox88', 'safari14'],
  external: Object.keys(pkg.peerDependencies || {}),
  define: {
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
    '__APP_VERSION__': JSON.stringify(pkg.version),
  },
  sourcemap: isDev ? 'inline' : 'external',
  minify: !isDev,
}

// Main app bundle
await esbuild.build({
  ...sharedConfig,
  entryPoints: { app: 'src/index.tsx' },
  outdir: 'dist',
  splitting: true,
  format: 'esm',
  chunkNames: 'chunks/[name]-[hash]',
  assetNames: 'assets/[name]-[hash]',
  loader: {
    '.png': 'file',
    '.svg': 'text',
    '.woff2': 'file',
  },
})

console.log(`Build complete (${isDev ? 'dev' : 'production'})`)

esbuild in the Ecosystem

Even if you don’t use esbuild directly, you’re likely using it indirectly:

  • Vite uses esbuild for dependency pre-bundling and TypeScript/JSX transforms
  • tsup (TypeScript library bundler) uses esbuild under the hood
  • Bun has a built-in bundler inspired by esbuild
  • Remix uses esbuild for development builds
  • Jest via ts-jest can use esbuild for faster transforms

Understanding esbuild directly helps you configure these tools more effectively.


Key Takeaways

  • esbuild is 10-100x faster than webpack thanks to Go, parallelism, and minimal passes
  • TypeScript support is built-in but doesn’t type-check — run tsc --noEmit separately
  • Use the context API for watch mode and dev server
  • The plugin API covers most edge cases (CSS Modules, SVG, PostCSS)
  • Migration from webpack requires mapping loaders/plugins, but most have equivalents
  • Consider Vite if you want esbuild’s dev speed with Rollup’s production optimizations

For new projects, esbuild is a strong choice for libraries and server-side code. For full browser applications, Vite (which uses esbuild internally) often provides a better out-of-the-box experience.

Free Download

50 CLI Commands That Save Hours Every Week

The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.

  • Git mastery (10 cmds)
  • File & search wizardry
  • Docker & DevOps tricks
  • Process & network tools
  • Productivity boosters
  • macOS & Linux install guide

No spam. 1 email/week max. Unsubscribe anytime.

Found this guide useful? Check out our free developer tools.