The full text of 2500 words, about 20 minutes to read, welcome thumb up follow forward.

What is Esbuild

Esbuild is a very new module packaging tool that offers similar resource packaging capabilities to Webpack, Rollup, Parcel, etc., but with a ridiculously high performance advantage:

This performance advantage has made Esbuild a huge hit among many Node-based build tools, especially since Vite 2.0 announced the use of Esbuild pre-build dependencies, and the front-end community has started to talk about it.

So the question is, how does this work? After reviewing a lot of data, I summarized some key factors:

So let’s go into more detail.

Why do fast

Language advantage

Most front-end packaging tools are implemented in JavaScript, while Esbuild chooses to write in Go. Both languages have their own scenarios, but in a CPU-intensive scenario like resource packaging, Go has the performance advantage. How big is the difference? For example, calculate the Fibonacci sequence 50 times. JS version:

function fibonacci(num) {
    if (num < 2) {
        return 1
    }
    return fibonacci(num - 1) + fibonacci(num - 2)
}

(() => {
    let cursor = 0;
    while (cursor < 50) {
        fibonacci(cursor++)
    }
})()

Go version:

package main func fibonacci(num int) int{ if num<2{ return 1 } return fibonacci(num-1) + fibonacci(num-2) } func main(){  for i := 0; i<50; i++{ fibonacci(i) } }

The JavaScript version took about 332.58s to execute, and the Go version took about 147.08s to execute, which is about a factor of 1.25. This simple experiment does not quantify the exact performance difference between the two languages. But there is a clear sense that the Go language performs better in CPU-intensive scenarios.

In the end, although modern JS engines are vastly improved from 10 years ago, JavaScript is still an interpreted language at heart. Every execution of a JavaScript program requires an interpreter to schedule execution while translating the source code into machine language. Go, on the other hand, is a compiled language, which translates the source code into machine code at the compile stage, and only needs to execute the machine code directly at startup. This means that programs written in Go have one less process of dynamic interpretation than those written in JavaScript.

This linguistic difference is particularly acute in the packaging scenario, where, to exaggerate, Esbuild is parsing user code while JavaScript is still interpreting it; By the time the JavaScript runtime interprets the code and is ready to start, Esbuild may have already packaged and exited the process!

Therefore, at the level of compilation and operation, Go presets the source code compilation process, which has higher execution performance compared with the way that JavaScript interprets and runs.

Multithreading Advantages

Go is inherently multi-threaded, while JavaScript is essentially a single-threaded language. It was not until the introduction of the WebWorker specification that it was possible to implement multi-threaded operations in browsers and nodes.

I’ve looked at the code for Rollup and Webpack, and neither, to the best of my knowledge, uses the multithreading capabilities provided by WebWorker. In contrast, the core selling point of Esbuild is its performance. Its implementation algorithm is carefully designed to use each CPU core as saturated as possible. In particular, the parsing and code generation phases of the packaging process have been fully parallel.

In addition to parallel lines at the CPU instruction level, multiple threads in the Go language can share the same memory space with each other, while each thread in JavaScript has its own unique memory heap. This means that multiple processing units in Go, such as the thread parsing resource A, can read the results of resource B thread directly, whereas in JavaScript the same operation requires calling the communication interface woker.postMessage to copy data between threads.

So at the runtime level, Go has natural multithreading capabilities, which means more efficient memory usage, which means higher performance.

moderation

Yes, that’s right, abstinence!

Esbuild is not another Webpack, it only provides a minimal set of features needed to build a modern Web application, and it will not be a massive addition of the build features we are already familiar with. The main features of the latest version of Esbuild are:

  • Support JS, TS, JSX, CSS, JSON, text, images and other resources
  • Incremental updating
  • Sourcemap
  • Development server support
  • Code compression
  • Code split
  • Tree shaking
  • Plug-in support

As you can see, this list supports very few resource types and engineering features, not even enough to support the development needs of a large project. In addition, the website clearly states that there are no plans to support the following features in the future:

  • Elm, Svelte, Vue, Angular, and other code file formats
  • TS type check
  • AST related operation API
  • Hot Module Replace
  • Module Federation

Also, the plugin system designed by Esbuild is not intended to cover these scenarios, which means that third party developers can’t do this in a non-intrusive way. Emmm, we can expect a lot of magic changes in the future.

Because Esbuild only solves part of the problem, it has relatively little architectural complexity, relatively little coding complexity, and it’s easier to get the best performance out of it than a single tool like Webpack or Rollup. There is another benefit to restrained functional design: a variety of additional tools that are completely customized for performance.

custom

Recall that in tools like Webpack and Rollup, we had to use a lot of additional third-party plug-ins to address various engineering requirements, such as:

  • ES version translation is implemented using Babel
  • Implement code checks using ESLint
  • Use TSC to implement TS code translation and code review
  • Use less, stylus, sass and other CSS preprocessing tools

We’re so used to it that we think it’s the way things should be, and most of us probably don’t even realize that there’s another way things can be done. Esbuild starts, select all! Completely rewrite all the tools needed for the entire compilation process! This means that it needs to rewrite the loading, parsing, linking, code generation logic of JS, TS, JSX, JSON, and other resource files.

The development costs are high and there is a risk of being passively closed, but the benefits are huge, as it follows the principle of customizing all phases of compilation with performance as the highest priority, such as:

  • Rewrite the TS translation tool to do away with TS type checking entirely and do only transcoding
  • Most packaging tools disassemble lexical analysis, grammar analysis, symbol declaration and other steps into multiple processing units with high cohesion and low coupling. Each module has distinct responsibilities and has high readability and maintainability. Esbuild, on the other hand, adheres to the performance-first principle and adopts a counterintuitive design pattern that mixes multiple processing algorithms to reduce the performance loss associated with data flow during compilation
  • Consistent data structures, and derived efficient caching strategies, are covered in more detail in the next section

On the one hand, this kind of deep customization reduces the design cost and can maintain the architectural consistency of the compilation chain. On the one hand, it can implement the principle of performance first to ensure the optimal performance of each link and the interaction between the links. With the sacrifice of functionality, readability, and maintainability, compile performance is almost as good as it gets.

Structural consistency

In the previous section, we talked about Esbuild’s choice of overwriting translation tools for languages such as JS, TS, JSX, CSS, etc., so it is better to ensure the structural consistency of source code across compilation steps. For example, when using Babel-Loader for JavaScript code in Webpack, Multiple data conversions may be required:

  • Webpack reads in the source code, which is now a string
  • Babel parses the source code and converts it to AST form
  • Babel converts the source AST to a lower version of the AST
  • Babel generates low version AST as low version source, in the form of a string
  • Webpack parses the low-level source code
  • Webpack packages multiple modules into the final product

String => AST => AST => string => AST => string

By rewriting most translation tools, ESbuild is able to share similar AST structures across multiple compilation phases, minimizing string-to-AST structural conversions and improving memory efficiency.

conclusion

In terms of compilation performance alone, Esbuild beats all other packaging frameworks in the world by a factor of a hundred:

Time consuming Performance difference speed Product size
esbuild 0.11 s 1x 1198.5 kloc/s 0.97 MB
esbuild (1 thread) 0.40 s 4x 329.6 kloc/s 0.97 MB
webpack 4 19.14 s 174x 6.9 kloc/s 1.26 MB
parcel 1 22.41 s 204x 5.9 kloc/s 1.56 MB
webpack 5 25.61 s 233x 5.1 kloc/s 1.26 MB
parcel 2 31.39 s 285x 4.2 kloc/s 0.97 MB

But it comes at a cost. In addition to its natural advantages at the language level, at the functional level, it directly gives up support for less, stylus, sass, vue, Angular and other resources, and gives up MF, HMR, TS type checking and other functions, as the author said:

This will involve saying “no” to requests for adding major features to esbuild itself. I don’t think esbuild should become an all-in-one solution for all frontend needs\!

In my opinion, ESbuild is not a substitute for Webpack at present and in the future. It is not suitable for direct use in production environment, but is more suitable as a low level module packaging tool. It needs to be re-packaged on its basis to expand a set of tool chains that both take into consideration performance and complete engineering capabilities. Such as Snowpack, Vite, Sveltekit, Remix Run and so on.

Overall, Esbuild provides a new design approach that is worth learning about, but not yet ready for direct production use in most business scenarios.