DEV Community

Dan L
Dan L

Posted on

πŸš€ Fastest way to bundle JavaScript in 2023

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 or Rust. 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 and Terser, two popular JavaScript tools for transpiling and minifying code, respectively. They are often used together, and many popular toolkits such as CRA (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, 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 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 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 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

Results

Time in sec

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.

A couple of words about Parcel, when I tried to build 10 copies of three.js my results were completely identical to Esbuild benchmark, 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 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)

Top comments (5)

Collapse
 
rizqyhi profile image
Rizqy H.

Any reason why not including Vite here? Is it because it built with Rollup + esbuild?

Collapse
 
s1owjke profile image
Dan L • Edited

Partly yes, to make Vite do something it was not originally designed to do you have to spend a lot of time, this is true for most libraries that are configured out of the box.

In my case it was much faster to set up Rollup in 3 different configurations, than to configure Vite.

Since it's often asked I finally added support for Vite and will do performance measurements soon.

Collapse
 
stefanseeger profile image
stefanseeger

You are missing rspack.dev/ which is a rust based webpack implementation.

Collapse
 
s1owjke profile image
Dan L • Edited

You are right, when I started writing the article and doing the measurements it was not yet publicly available, it was publicly released in March 2023

So, I just added it to benchmark and made early measurements, you can check it here, I'm a little confused about the bundle sizes and need to double-check that.
github.com/s1owjke/js-bundler-benc...

Collapse
 
gertvaliakhmetov profile image
GertValiakhmetov

Good job! πŸ‘