The use case I have in mind is packaging a large codebase for production. This includes minifying the code, which reduces network transfer time, and producing source maps, which are important for debugging errors in production. Ideally the build tool should also build quickly without having to warm up a cache first.
|Bundler||Time||Relative slowdown||Absolute speed||Output size|
|rollup + terser||40.48s||75x||13.5 kloc/s||5.80mb|
Each time reported is the best of three runs. I'm running esbuild with
--bundle --minify --sourcemap. I used the
rollup-plugin-terser plugin because rollup itself doesn't support minification. Webpack uses
--mode=production --devtool=sourcemap. Parcel uses the default options. FuseBox is configured with
useSingleBundle: true. Absolute speed is based on the total line count including comments and blank lines, which is currently 547,441. The tests were done on a 6-core 2019 MacBook Pro with 16gb of RAM.
Why is it fast?
- It's written in Go, a language that compiles to native code
- Parsing, printing, and source map generation are all fully parallelized
- Everything is done in very few passes without expensive data transformations
- Code is written with speed in mind, and tries to avoid unnecessary allocations
- CommonJS modules
- ES6 modules
- Bundling with static binding of ES6 modules using
- Full minification with
--minify(whitespace, identifiers, and mangling)
- Full source map support when
- Compile-time identifier substitutions via
- Path substitution using the
- Automatic detection of
This is a hobby project that I wrote over the 2019-2020 winter break. I believe that it's relatively complete and functional. However, it's brand new code and probably has a lot of bugs. It also hasn't yet been used in production by anyone. Use at your own risk.
Also keep in mind that this doesn't have complete support for lowering modern language syntax to earlier language versions. Right now only class fields and the nullish coalescing operator are lowered.
I don't personally want to run a large open source project, so I'm not looking for contributions at this time.
A prebuilt binary can be installed using npm:
Local install (recommended)
This installs the
esbuildcommand locally in your project's
npm install --save-dev esbuild
Invoke it using
npx esbuild [arguments]. Note that this uses the
npxpackage runner command, not the
npmpackage manager command.
This is the recommended project-based workflow because it allows you to have a different version of
esbuildfor each project and it ensures that everyone working on a given project has the same version of
This adds a global command called
esbuildto your path:
npm install -g esbuild
Invoke it using
A global install can be handy if you want to run
esbuildoutside of a project context for one-off file manipulation tasks.
esbuild package should work on 64-bit macOS, Linux, and Windows systems. It contains an install script that downloads the appropriate package for the current platform. If the install script isn't working or you need to run esbuild on an unsupported platform, there is a fallback WebAssembly package called esbuild-wasm that should work on all platforms.
For development, the executable can be built by running
make (assuming you have the Go language toolchain installed).
The command-line interface takes a list of entry points and produces one bundle file per entry point. Here are the available options:
Usage: esbuild [options] [entry points] Options: --name=... The name of the module --bundle Bundle all dependencies into the output files --outfile=... The output file (for one entry point) --outdir=... The output directory (for multiple entry points) --sourcemap Emit a source map --error-limit=... Maximum error count or 0 to disable (default 10) --target=... Language target (default esnext) --loader:X=L Use loader L to load file extension X, where L is one of: js, jsx, json, text, base64 --minify Sets all --minify-* flags --minify-whitespace Remove whitespace --minify-identifiers Shorten identifiers --minify-syntax Use equivalent but shorter syntax --define:K=V Substitute K with V while parsing --jsx-factory=... What to use instead of React.createElement --jsx-fragment=... What to use instead of React.Fragment --trace=... Write a CPU trace to this file --cpuprofile=... Write a CPU profile to this file Examples: # Produces dist/entry_point.js and dist/entry_point.js.map esbuild --bundle entry_point.js --outdir=dist --minify --sourcemap # Allow JSX syntax in .js files esbuild --bundle entry_point.js --outfile=out.js --loader:.js=jsx # Substitute the identifier RELEASE for the literal true esbuild example.js --outfile=out.js --define:RELEASE=true
Using with React
To use esbuild with React:
Either put all JSX syntax in
.jsxfiles instead of
.jsfiles, or use
--loader:.js=jsxto use the JSX loader for
If you're using TypeScript, run
tscfirst to convert
.tsxfiles into either
If you're using esbuild to bundle React yourself instead of including it with a
<script>tag in your HTML, you'll need to pass
'--define:process.env.NODE_ENV="production"'to esbuild on the command line.
If you're using Preact instead of React, you'll also need to pass
--jsx-factory=preact.h --jsx-fragment=preact.Fragmentto esbuild on the command line.
For example, if you have a file called
example.jsx with the following contents:
import * as React from 'react' import * as ReactDOM from 'react-dom' ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('root') );
Use this for a development build:
esbuild example.jsx --bundle '--define:process.env.NODE_ENV="development"' --outfile=out.js
Use this for a production build:
esbuild example.jsx --bundle '--define:process.env.NODE_ENV="production"' --minify --outfile=out.js