What is esbuild?

esbuild is a JavaScript bundler and minifier written in Go. It was created by Evan Wallace, co-founder of Figma, with one goal in mind: bring web tooling into a different order of magnitude when it comes to speed. Where traditional bundlers count their work in seconds, esbuild counts it in milliseconds.

You can use it directly from the command line, drive it from a script through its JavaScript or Go APIs, or - more commonly today - rely on it through another tool that already has it embedded. It is one of those projects whose influence is bigger than its name recognition: even if you do not run esbuild yourself, there is a good chance it is running somewhere in your build pipeline.

An extremely fast bundler for the web.- esbuild.github.io

Why it is so fast

esbuild's speed is not the result of clever caching or a single shortcut. It is a series of deliberate engineering choices, each one stacked on top of the next - and the official benchmark is the clearest way to see what they add up to.

JS BUNDLE BENCHMARK · 10× THREE.JS esbuild 0.39 s baseline parcel 2 14.91 s 38× slower rollup 4 + terser 34.10 s 87× webpack 5 41.21 s 106× 0 s 10 s 20 s 30 s 40 s
10× three.js, --bundle --minify --sourcemap, no caches, best of three on a 6-core 2019 MacBook Pro · esbuild.github.io/faq

In absolute terms, esbuild sustains around 1.4 million lines of code per second on this benchmark. The reasons fall into three categories.

Native code, real threads. The compiler is written in Go, which compiles to native machine code and gives the bundler real, OS-level threads. JavaScript-based bundlers have a structural ceiling here: every time you run one, Node has to parse and warm up the JIT for the bundler's own source before it touches yours. By the time that warm-up is done, esbuild has often already finished bundling and exited.

Three passes over the AST, not a dozen. Most bundlers re-walk your code many times - one tool parses TypeScript into JavaScript, then a separate tool minifies, then another bundles, and each step round-trips through strings on the way in and out. esbuild touches the AST exactly three times, interleaving multiple operations into each pass:

  1. Lex, parse, set up scopes, declare symbols.
  2. Bind symbols, minify syntax, transform JSX and TypeScript, downlevel ESNext to ES2015.
  3. Minify identifiers, minify whitespace, generate output code, generate source maps.

Three passes over an AST that stays hot in CPU cache is dramatically less work than a half-dozen passes through serialized strings.

A narrow scope, on purpose. Features that would slow the hot path down, or that pull in heavy general-purpose abstractions, are kept out of the core. There is no general-purpose AST library sitting in the middle of it all. Memory layouts are tuned for the kind of work a bundler actually does, and the tool deliberately leaves out anything it cannot do at native speed.

How esbuild works

At its heart, esbuild does what every bundler does: it walks your import graph from one or more entry files, resolves each module, transforms what needs transforming, and emits a small number of output files.

The difference is in how those phases are wired together. A build runs in three phases:

  • Parsing - turn every source file into an AST. Fully parallel across all cores.
  • Linking - resolve cross-module references, collapse the dependency graph, decide what goes into which output file. Inherently sequential in places, but parallelizable when there are multiple output files.
  • Code generation - serialize the final ASTs back to JavaScript and CSS, run minification, write source maps. Fully parallel again.

The two most expensive phases - parsing and code generation - saturate every core you have. The result is a build that scales with the size of your project, on hardware that is much closer to fully utilised.

For local development esbuild also offers a watch mode and a small built-in HTTP server. Both are designed to keep the feedback loop tight without trying to compete with framework-aware dev servers on features like Hot Module Replacement.

What it supports out of the box

esbuild ships with a focused set of features that cover the vast majority of frontend bundling needs. There is no plugin to install for any of this.

  • TypeScript and JSX are stripped and transformed directly - no separate compiler step required.
  • CSS bundling with imports, asset references, and minification.
  • Tree shaking on both ECMAScript modules and CommonJS where statically possible.
  • Minification of JavaScript and CSS that is competitive with the best minifiers available today.
  • Source maps, code splitting for ESM output, and configurable target browsers via --target.

One thing worth flagging: esbuild does not type-check your TypeScript. It transpiles it as if the types were correct. Type checking is left to tsc or your editor, which is the right split of responsibilities for a tool whose first promise is speed.

The plugin API

esbuild exposes a small, intentional plugin API. Plugins can hook into module resolution and loading, which is enough to support a wide range of custom behaviours: virtual modules, alternative file formats, on-the-fly compilation, or integration with frameworks that have their own template syntax.

The API is narrower than Rollup's on purpose. Anything that would force esbuild to step out of its parallel pipeline and back into a single-threaded callback gets paid for in build time, so the surface area is kept small. For most projects this is a feature, not a limitation - the things you actually need are there.

esbuild inside Vite

If you use Vite, you are already using esbuild. Vite leans on it in two places.

  • Dependency pre-bundling. Before the dev server starts, Vite uses esbuild to convert your node_modules dependencies into a small number of ES module files. This is why a Vite cold start feels nearly instantaneous even on large projects.
  • Source transforms. esbuild handles TypeScript and JSX inside Vite as files are requested by the browser, one module at a time.

For production builds, Vite has historically used Rollup (and is moving toward Rolldown), not esbuild. The two tools play different roles: esbuild for raw speed during development and dependency processing, a tree-shaking-focused bundler for the final shippable artefact.

Trade-offs to know

esbuild is sharp, and being sharp comes with edges. A few worth knowing before you reach for it as your primary bundler:

  • No type checking. TypeScript types are stripped, not validated. Run tsc --noEmit in CI or your editor.
  • Smaller plugin ecosystem than Rollup. Many advanced bundling tricks (legacy bundle output, complex code splitting strategies) live elsewhere.
  • No first-class Hot Module Replacement. esbuild handles watch and rebuild, but framework-aware HMR is the job of a dev server like Vite that sits on top.
  • Output is intentionally simple. esbuild is great at fast, sensible bundles. If you need exotic output formats or per-route splitting, you may end up combining it with another tool.

When to reach for esbuild directly

For a typical frontend application, you do not need to install esbuild yourself - your framework's tooling already does. But there are places where reaching for it directly is the right call.

  • Bundling a small library or CLI tool where you want one binary-fast command and no framework overhead.
  • Building serverless functions or edge workers where cold start size and build time both matter.
  • Writing a custom build script that needs to transform TypeScript or JSX on the fly without spinning up a full toolchain.
  • Replacing a slow legacy build step inside a monorepo, one package at a time.

esbuild is rarely the whole story of a modern frontend project, but it is almost always part of it. Knowing what it is good at - and what it deliberately leaves to other tools - is the difference between fighting your build and forgetting it is there.