🚀 Fastest way to bundle JavaScript in 2023
Tags: javascript, performance, tooling, react
Published: 2023-03-31 18:20:53

JavaScript bundlers are essential tools for modern web development. They handle all the routine tasks for building an application, which allows developers to focus on writing code. 

However, as applications grow in size and complexity, bundling performance can become a bottleneck, slowing down the deployment process and negatively impacting the overall developer experience.

Luckily, we are now entering a new era of build tools written in high-performance languages like [Go](https://go.dev/) or [Rust](https://www.rust-lang.org/). In this article, we will compare the speed and bundle size of the most popular bundlers across various configurations.

## Available options

Despite the variety of available bundlers, most of them use the same packages for transpilation and minification.

The first option is [Babel](https://babeljs.io) and [Terser](https://terser.org), two popular JavaScript tools for transpiling and minifying code, respectively. They are often used together, and many popular toolkits such as [CRA](https://create-react-app.dev/) (Create React App) are based on them. While both tools have large communities and are widely used, which makes it easy to find answers to most questions, their main drawback is their speed.

The second option is [Esbuild](https://esbuild.github.io), a lightweight and extremely fast bundler and minifier developed by Evan Wallace. It also supports bundling of other web assets like CSS and images, and can be extended using a variety of plugins. Esbuild is written in Go, which enables it to be significantly faster than any JavaScript bundlers. It’s easy to configure and supports JSX syntax and TypeScript out of the box.

And last but not least, [SWC](https://swc.rs) is a popular compiler designed to be blazingly fast and efficient. It is written in Rust and maintained by the developer community. It can be used to transpile and minimize JavaScript or TypeScript code. Also worth noting is that the [swcpack](https://swc.rs/docs/usage/bundling) bundler is currently under active development and it has all chances to become an extremely fast alternative to Webpack.

## Benchmark

The story of this performance benchmark began with an attempt to understand why it takes more than half a minute to build a relatively small application with [Material UI](https://mui.com/) components and how to improve that.

For tests I created several React projects:
- Empty as the baseline
- Project containing five different libraries with a large codebase commonly used in web development
- All components from Material UI
- Synthetic test with 5000 small components as a way to measure the overhead of the bundler itself

Initially, the project with Material UI components also included an icons package, but after the first measurements I found a performance degradation in Rollup, so as not to distort the final results I removed it.

To keep results accurate there are no other files that can require additional loaders or plugins, as the main goal is to measure time spent on JavaScript bundling not on CSS or other assets.

The benchmark includes four bundlers in various configurations, all of them are configured as closely as possible (production build, no cache):
- Esbuild as bundler without any extra plugins
- Parcel in its default configuration with Babel and Terser
- Rollup and Webpack in three different presets: Babel and Terser, Esbuild and SWC

For the testing environment I used Mac OS `11.5.2` and Node `16.20.0`, with the latest package versions at the time of writing this article.

Tests were done on a 6-core 2019 MacBook Pro with 16gb of RAM. To ensure the accuracy of my measurements each test was run three times consecutively, the final results represent the average value. 

In addition, I have measured the size of the resulting bundle and its size after gzip compression with standard settings.

All code is available in the [GitHub repository](https://github.com/s1owjke/js-bundler-benchmark)

## Results

![Time in sec](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jstateha4xgfxxrbzvni.png)

Time in sec (average time for 3 runs)

|                             | **Empty** | **Libraries** | **Mui** | **Synthetic** |
| --------------------------- | --------- | ------------- | ------- | ------------- |
| **Esbuild**                 | 0.046     | 0.142         | 0.192   | 0.685         |
| **Parcel: babel + terser**  | 3.737     | 11.529        | 8.892   | 57.232        |
| **Rollup: babel + terser**  | 3.121     | 13.056        | 9.495   | 37.689        |
| **Rollup: esbuild**         | 1.874     | 5.553         | 5.746   | 14.612        |
| **Rollup: swc**             | 1.788     | 5.966         | 5.802   | 14.644        |
| **Webpack: babel + terser** | 2.471     | 11.529        | 6.406   | 23.889        |
| **Webpack: esbuild**        | 0.808     | 3.145         | 2.665   | 8.798         |
| **Webpack: swc**            | 0.849     | 4.033         | 2.927   | 9.134         |

Bundle size in KiB

|                             | **Empty** | **Libraries** | **Mui** | **Synthetic** |
| --------------------------- | --------- | ------------- | ------- | ------------- |
| **Esbuild**                 | 164.60    | 1239.04       | 607.99  | 1008.50       |
| **Parcel: babel + terser**  | 348.28    | 1546.24       | 950.09  | 1351.68       |
| **Rollup: babel + terser**  | 157.85    | 1454.08       | 592.64  | 786.78        |
| **Rollup: esbuild**         | 163.61    | 1239.04       | 621.77  | 816.58        |
| **Rollup: swc**             | 166.95    | 1351.68       | 712.35  | 1064.96       |
| **Webpack: babel + terser** | 158.33    | 1464.32       | 593.95  | 868.48        |
| **Webpack: esbuild**        | 166.17    | 1617.92       | 613.37  | 891.19        |
| **Webpack: swc**            | 163.00    | 1454.08       | 600.74  | 878.15        |

Bundle size after gzip in KiB

|                             | **Empty** | **Libraries** | **Mui** | **Synthetic** |
| --------------------------- | --------- | ------------- | ------- | ------------- |
| **Esbuild**                 | 54.75     | 367.50        | 183.51  | 132.16        |
| **Parcel: babel + terser**  | 106.91    | 424.13        | 264.39  | 139.05        |
| **Rollup: babel + terser**  | 52.81     | 353.12        | 173.57  | 67.34         |
| **Rollup: esbuild**         | 54.68     | 368.10        | 185.70  | 84.23         |
| **Rollup: swc**             | 54.24     | 368.08        | 190.88  | 93.57         |
| **Webpack: babel + terser** | 52.90     | 407.58        | 175.36  | 84.42         |
| **Webpack: esbuild**        | 56.04     | 445.32        | 185.35  | 86.71         |
| **Webpack: swc**            | 54.49     | 409.35        | 178.29  | 93.34         |

## Conclusion

Esbuild as a bundler currently is significantly faster than others, it will be fascinating to see how it compares to swcpack in the future, but at the moment it's the ultimate winner.

When we consider Esbuild and SWC as plugins with other bundlers, there is really not much difference between them, it completely depends on the specific configuration, detailed comparison between them you can [find here](https://swc.rs/docs/benchmarks).

A couple of words about Parcel, when I tried to build 10 copies of [three.js](https://threejs.org) my results were completely identical to [Esbuild benchmark](https://esbuild.github.io/faq/#benchmark-details), it really was the fastest, but for React application the metrics are different.

The unpleasant discovery for me was the build time of Rollup, on a large number of files/imports it shows worse results even than Webpack.

So, what to choose for React app:
- To achieve maximum performance and if you don't care about [HMR](https://webpack.js.org/concepts/hot-module-replacement/) support consider using Esbuild as bundler
- If you are already using Webpack, you can simply improve the build speed by using Esbuild or SWC as a loader and minimizer, in this case you can keep using CRA configuration
- If you really need HMR you can use combination of the previous options, Esbuild for production, Webpack + Esbuild loader for development (don’t use different tools for dev and production builds, there is a chance that the generated code may be different)