tools 5 min read

esbuild Won and Nobody Threw a Party

Webpack conferences had thousands of attendees. esbuild just shipped 0.25 and almost nobody noticed. That is what winning looks like.

I rebuilt a project last week. Switched from webpack to esbuild. Build went from 34 seconds to 0.8 seconds.

# Before (webpack 5.97.1)
$ npm run build
webpack 5.97.1 compiled successfully in 34,218 ms

# After (esbuild 0.25.2)
$ npm run build
  index.js   142.3kb
  index.css   18.7kb

Done in 812ms

I sat there staring at the terminal. I thought it failed. Nothing that fast can be correct. I ran it again. 791ms. Then a third time. 803ms. It was real.

The Architecture Gap

Webpack is written in JavaScript. It runs in a single-threaded Node.js process, parsing JavaScript with JavaScript. That’s like writing a spell checker in the language it’s checking.

esbuild is written in Go. It compiles to a native binary that runs across multiple CPU cores. The performance gap isn’t because someone wrote better code. It’s because the tool is built from a different material entirely.

Evan Wallace (esbuild’s creator) wrote about this in the docs. Go’s goroutines let esbuild parse, link, and generate code in parallel. JavaScript’s event loop can’t do that. Not without worker threads, and even then you’re paying for serialization overhead every time data crosses a boundary.

This isn’t something you can fix with caching or incremental builds. Webpack could get 10x faster and still be 4x slower than esbuild. The ceiling is different.

The Switch

Here’s what my package.json looked like before and after:

// Before
{
  "devDependencies": {
    "webpack": "^5.97.1",
    "webpack-cli": "^6.0.1",
    "babel-loader": "^9.2.1",
    "css-loader": "^7.1.2",
    "mini-css-extract-plugin": "^2.9.2",
    "html-webpack-plugin": "^5.6.3",
    "@babel/core": "^7.26.0",
    "@babel/preset-env": "^7.26.0",
    "@babel/preset-react": "^7.26.3"
  }
}
// After
{
  "devDependencies": {
    "esbuild": "^0.25.2"
  }
}

Eight dependencies replaced by one. The webpack.config.js file (87 lines) got replaced by a build script (12 lines). I deleted the babel config. I deleted the postcss config. I deleted the loader chain.

// build.js
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['src/index.tsx'],
  bundle: true,
  outdir: 'dist',
  minify: true,
  sourcemap: true,
  target: ['es2020'],
  loader: { '.css': 'css' },
  format: 'esm',
  splitting: true,
})

That’s it. No loaders. No plugins. No configuration inheritance. You tell it what to build and where to put it.

Webpack Isn’t Dead

I want to be clear about this. Webpack isn’t dead. It’s the thing you use when you have to. When you need Module Federation. When your company has 400 webpack plugins and nobody wants to rewrite them. When your build depends on some obscure loader that only exists in the webpack ecosystem.

That’s a lot of projects. Enterprise codebases don’t switch build tools because a new one is faster. They switch when the old one stops getting security patches.

But for new projects? I don’t know why you’d start with webpack in 2026.

The Vite Question

Most people reading this probably use Vite, not esbuild directly. And that’s fine. Vite uses esbuild under the hood for dependency pre-bundling and TypeScript transpilation. When you run vite dev and it feels instant, that’s esbuild doing the work.

Turbopack exists too. Written in Rust. Similar performance story to esbuild (native binary, parallel execution). It’s the default in Next.js 15+. I’ve used it. It’s fast. But outside the Next.js world, most people just use Vite.

The point isn’t which wrapper you pick. The point is that the engine underneath changed from JavaScript to native code, and that change was permanent.

What Winning Looks Like

esbuild 0.25 shipped a few weeks ago. I follow the GitHub releases. The changelog was short. Bug fixes. A couple of new features. No blog post. No launch event. No Twitter thread with 47 replies.

Webpack had conferences. WebpackConf. Multiple years. Thousands of attendees. Sponsors. T-shirts. Talks about tree-shaking that lasted 45 minutes.

esbuild’s documentation is one page. The API has maybe 30 options. The project has one primary maintainer and a handful of contributors.

The best tools are the ones you forget about. I don’t think about my bundler anymore. I don’t debug configuration. I don’t wait for builds. esbuild is invisible. That is the highest compliment you can give a build tool.

Nobody threw esbuild a party. That’s because the people using it are too busy shipping.