Fast JavaScript bundling with esbuild

Modular programming is a software design technique whereby a program’s various functions are subdivided into code modules that are developed separately. Modern programming relies heavily on modularity, which is why you need a module bundler to merge all separate files into a single one.

There are a few bundlers available in the JavaScript community, such as WebPack, Rollup, and Parcel. However, these are not fast enough because they are built with JavaScript, which, as well all know, leaves much to be desired in terms of performance. Fortunately, there is a new bundler built with Go that works faster than other bundlers.

In this guide, we’ll explore esbuild, a JavaScript bundler and minifier that packages JS code for distribution on the web. We’ll examine how it’s able to work so fast and discuss why you should keep an eye on this tool in 2020 and beyond.

What is esbuild, and why?

This is a JavaScript bundler and minifier. It packages up JavaScript code for distribution on the web.

Why build another JavaScript build tool? The current build tools for the web are at least an order of magnitude slower than they should be. I’m hoping that this project serves as an “existence proof” that our JavaScript tooling can be much, much faster.

Comparing esbuild to other bundlers (Benchmark)

esbuild fully parallelizes parsing, printing, and source map generation. All these features combine to make esbuild extremely fast. That said, to help you choose the best bundler for your next JavaScript project, let’s compare esbuild to other tools on the market.

My main benchmark approximates a large codebase by duplicating the three.js library 10 times and building a single bundle from scratch, without any caches. For this benchmark, esbuild is 10-100x faster than the other JavaScript bundlers I tested (Webpack, Rollup, Parcel, and FuseBox). The benchmark can be run with make bench-three.

Benchmark Comparison of JavaScript Bundlers

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.

The result of the benchmarking is mind-blowing: esbuild is 10 to 100 times faster than other bundlers.

By the way, you can run this benchmark on your machine and see it for yourself. Install the Go language toolchain and run the following command:

make bench-three

Why is it fast?

Several reasons:

  • 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

Status

Currently supported:

  • CommonJS modules
  • ES6 modules
  • Bundling with static binding of ES6 modules using --bundle
  • Full minification with --minify (whitespace, identifiers, and mangling)
  • Full source map support when --sourcemap is enabled
  • JSX-to-JavaScript conversion for .jsx files
  • Compile-time identifier substitutions via --define
  • Path substitution using the browser field in package.json
  • Automatic detection of baseUrl in tsconfig.json

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.

Is esbuild production-ready?

There’s no disputing this bundler’s speed. But is it production-ready?

For now, esbuild is a small open-source project; it’s developed and maintained by one man. This is largely by design. Per the author: “I don’t personally want to run a large open-source project, so I’m not looking for contributions at this time.”

Although this will inevitably slow the development of the tool, it’s already a great bundler with robust support for common JS modules, JSX-to-JavaScript conversion, etc. However, it has yet to be used in production; doing so right now would be risky and would likely unearth some bugs.

That said, esbuild has tremendous potential to streamline the traditionally sluggish task of bundling modules in JavaScript, and it’s worth trying out in your next project.

Install

The executable can be built using make, assuming you have the Go language toolchain installed. Prebuilt binaries are currently available on npm under separate packages:

npm install -g esbuild-linux-64   # for Linux
npm install -g esbuild-darwin-64  # for macOS
npm install -g esbuild-windows-64 # for Windows
npm install -g esbuild-wasm       # for all other platforms

This adds a command called esbuild.

Usage

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)

  --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

Example:
  # Produces dist/entry_point.js and dist/entry_point.js.map
  esbuild --bundle entry_point.js --outdir=dist --minify --sourcemap

Using with React

To use esbuild with React:

  • Make sure all JSX syntax is put in .jsx files instead of .js files because esbuild uses the file extension to determine what syntax to parse.

  • If you’re using TypeScript, run tsc first to convert .tsx files into either .jsx or .js files.

  • 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="development"' or '--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.Fragment to 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

Conclusion

If nothing else, esbuild is proof that our current JavaScript build tools are not fast enough. Given that the gap between esbuild and other bundlers is so wide in terms of performance, I hope this tool will help improve build tools in general across the JS ecosystem.

#javascript #web-development

Fast JavaScript bundling with esbuild
195.05 GEEK