Esbuild is one of the most popular compilation tools in the world, replacing WebPack or Babel in some areas. Let’s take a look at this tool in detail.

First, speed superiority comparison

Here is a pressure gauge, showing the ESbuild performance cluster

The official explanation for why it performs so well is as follows:

1. It is written in the Go language and compiled into executable code

JavaScript must be executed within the interpreter’s Node environment, so by the time a tool like WebPack has interpreted its code, esBuild may have finished compiling and WebPack will start compiling.

Furthermore, the core design of Go is parallel, whereas JavaScript is not.

Go has shared memory between threads, whereas JavaScript must serialize data between threads.

Go and JavaScript both have parallel garbage collectors, but the Go heap is shared between all threads, whereas JavaScript has a separate heap for each thread. JavaScript worker thread parallelism is cut in half because one half of the CPU core is busy collecting garbage for the other half.

2. Parallelism is widely used

The algorithms inside ESbuild are carefully designed to fully saturate all available CPU cores as much as possible.

There are roughly three phases: parsing, wiring, and code generation. Parsing and code generation are the bulk of the work and are completely parallel.

Because all threads share memory, work can be easily shared when compiling and importing different entry points from the same JavaScript library. Most modern computers have many cores, so parallelization is a big performance boost.

3. Everything in EsBuild is written from scratch

There are many performance benefits to writing your own library instead of using a third-party library. Performance can be considered from the start to ensure that everything uses a consistent data structure, avoids expensive transformations, and allows for extensive architectural changes if necessary. The downside, of course, is that it’s a lot of work.

4. Memory is used efficiently

Compilers are ideally mostly O(n) complexity of input length. So if you’re dealing with a lot of data, memory access speed can seriously affect performance. The less you have to do with the data, the faster the compiler will be.

Ii. Loader

The function of esBuild loader is similar to that of Loader in WebPack. It compiles certain types of files. The specific functions are as follows:

1.js-loader

This loader defaults to.js,.cjs, and.mjs files. The.cjs extension is used by Node for CommonJS modules and the.mjs extension is used by Node for ECMAScript modules, although esBuild does not distinguish between the two.

Esbuild supports all modern JavaScript syntax. However, newer syntax may not be supported by older browsers, so you may want to configure the target option to tell ESBuild to convert newer syntax to the appropriate older syntax.

Note, however, that ES5 is not very well supported and there is currently no support for converting ES6+ syntax to ES5.

2. Ts – loader or benchmark – loader

This loader is enabled by default for.ts and.tsx files, which means esBuild has built-in support for parsing TypeScript syntax and discarding type comments. However, esbuild does not do any type checking, so you still need to run tSC-noemit in parallel in ESbuild to check the type. Note that esBuild does not do type checking at compile time, which should be done using the IDE before compiling

3. jsx-loader

XML code will be converted to JS code

4. json-loader

This loader is enabled by default for. Json files. It parses the JSON file into a JavaScript object at build time and exports that object as the default.

5. css-loader

This loader is enabled by default for.css files. It loads files as CSS syntax. CSS is a Category 1 content type in ESBuild, which means esBuild can compile CSS files directly without importing your CSS from JavaScript code.

You can import other CSS files @, reference images and font files with urls (), and esBuild compiles everything together. Note that you have to configure a loader for the image and font files because esbuild doesn’t have any pre-configuration. Usually this is a data URL loader or an external file loader.

Note that ESBuild does not yet support CSS Modules, so the set of exported names from CSS files is currently always empty. Future plans support CSS Modules.

6. text-loader

For.txt files, this loader is enabled by default. It loads the file as a string at build time and exports the string as the default export. Use it to look something like this.

7. binary-loader

This loader will load the file as a binary buffer at build time and embed it in the package using Base64 encoding. The raw bytes of the file are decoded from Base64 at run time and exported as Uint8Array using the default export method.

8. Base64-loader

This loader will load the file as a binary buffer at build time and embed it into the string in the compilation using Base64 encoding. This string will be exported using the default export method.

9. dataurl-loader

This loader will load the file as a binary buffer at build time and embed it in the compilation as a Base64-encoded data URL. This string is exported using the default export method.

10. file-loader

The loader copies the file to the output directory and inserts the file name as a string into the compilation. This string is exported using the default export method.

3. The API calls

In order to make it easier to use, ESbuild provides the WAY of API call. When calling API, pass option to set the corresponding function. In the ESBuild API, there are two main API calls: Transform and Build. The difference between the two is whether the final file is generated.

1.Transform API

The Transform API calls operate on a single string without accessing the file system. Great for use in environments without file systems or as part of another tool chain. Here’s a simple example:

require('esbuild').transformSync('let x: number = 1', {
  loader: 'ts'. = > {})code: 'let x = 1; \n'.map: ' '.warnings: []}Copy the code
2.Build API

Build API calls operate on one or more files in the file system. This allows files to refer to each other and be compiled together. Here’s a simple example:

require('fs').writeFileSync('in.ts'.'let x: number = 1')
require('esbuild').buildSync({
  entryPoints: ['in.ts'].outfile: 'out.js',})Copy the code

4. Plug-in API

1. Introduction

The plug-in API, which is part of the API calls mentioned above, allows you to inject code into various parts of the build process. Unlike the rest of the API, it is not available from the command line. You must write JavaScript or Go code to use the plug-in API.

The plugin API can only be used with the Build API, not the Transform API

If you are looking for an existing Esbuild plug-in, you should look at the list of existing Esbuild plug-ins. The plugins in this list were deliberately added by the authors to be used by others in the EsBuild community.

2. Write a plug-in

An esBuild plug-in is an object containing the name and setup functions. They are passed as arrays to build API calls. The setup function is run once each time the BUILD API is called. Let’s try to customize a plug-in

import fs from 'fs'

export default {
  name: "env".setup(build) {
    build.onLoad({ filter: /\.tsx$/ }, async (args) => {
      const source = await fs.promises.readFile(args.path, "utf8");
      const contents = source.toString();
      console.log('File contents :',contents)
      return {
        contents: contents,
        loader: "tsx"}; }); }};Copy the code
2.1 the name

Name generally stands for the name of the plug-in

2.2 the setup function
2.2.1. Namespace

Each module has an associated namespace. By default, esBuild operates in the file namespace, which corresponds to files in the file system. But EsBuild can also handle “virtual” modules that have no corresponding location on the file system. Virtual modules typically use namespaces other than files to distinguish them from file system modules.

2.2.2. Filters

Each callback must provide a regular expression as a filter. Esbuild skips callbacks when paths do not match filters to improve performance. Moving from esbuild’s highly parallel internal calls to single-threaded JavaScript code is expensive and should be avoided as much as possible for maximum speed.

You should try to use filter regular expressions rather than JavaScript code for filtering. This is faster, because regular expressions are evaluated within ESBuild and JavaScript is not called at all.

2.2.3. Resolve callbacks

A callback added using onResolve will run on every import path of every module that esBuild builds. This callback customizes how esBuild does path resolution.

2.2.4. Load callbacks

A callback added using onLoad can process and return the contents of the file.

3. How to solve the execution sequence between plug-ins and Loaders

The loader in esBuild processes and returns files in a certain format directly, and the plug-in API has access to file contents. The timing of execution of both is not mentioned in the documentation.

import fs from "fs";

export default {
  name: "env".setup(build) {
    build.onLoad({ filter: /\.tsx$/ }, async (args) => {
      const source = await fs.promises.readFile(args.path, "utf8");
      const contents = source.toString();
      //astHandle can only handle JS content, does not know TS or JSX, compiler error
      const result = astHandle(contents)
      return {
        contents: result,
        loader: "tsx"}; }); }};Copy the code

As you can see from the code example above, the plug-in API does not directly process the TSX content after receiving the file content, because we may not have the TSX capability to do so. In this case, it does not show that the plug-in is executed after TSX is converted to JS. The only way to handle this is with esBuild’s Transform API capabilities.

import fs from "fs";
import esbuild from "esbuild";

export default {
  name: "env".setup(build) {
    build.onLoad({ filter: /\.tsx$/ }, async (args) => {
      const source = await fs.promises.readFile(args.path, "utf8");
      const contents = source.toString();
      const result = astHandle(esbuild.transformSync(contents, {
        loader: 'tsx',}))return {
        contents: result.code,
        loader: "tsx"}; }); }};Copy the code
4. Babel migration

Because Babel has a large number of community plug-ins, it is difficult for the project that originally uses Babel to migrate to esbuild. You can use the esbuild-plugin-babel one-click migration provided by the community. For example, antD components need to be used together with antD-plugin-import plug-in. Details are as follows:

import babel from "esbuild-plugin-babel";
import esbuild from "esbuild";
import path, { dirname } from "path";
import { fileURLToPath } from "url";

const babelJSON = babel({
  filter: /\.tsx? /,
  config: {
    presets: ["@babel/preset-react"."@babel/preset-typescript"].plugins: [["import", { libraryName: "antd".style: "css"}]],}});const __dirname = dirname(fileURLToPath(import.meta.url));

esbuild
  .build({
    entryPoints: [path.join(__dirname + "/app.tsx")].outdir: "out".plugins: [babelJSON],
  })
  .catch(() = > process.exit(1));

Copy the code
5. Restrictions on the use of plug-ins

The plug-in API is not intended to cover all use cases. It is impossible to correlate every part of the compilation process. For example, it is currently not possible to modify the AST directly. This limitation exists to maintain the excellent performance characteristics of esBuild, but also to avoid exposing too much API surface, which would be a maintenance burden and prevent improvements that involve changing the AST.

One way to think of EsBuild is as a “linker” for the web. Just like a linker for native code, esBuild’s job is to take a set of files, parse and bind references between them, and generate a single file containing all the code links. The job of a plug-in is to generate a single file that will eventually be linked.

Plug-ins in EsBuild work best at a relative scale and customize only a small aspect of the build. For example, a plug-in with a special configuration file in a custom format (such as YAML) is very appropriate. The more plug-ins you use, the slower you will build, especially if your plug-in is written in JavaScript. If a plug-in applies to every file in your build, your build is likely to be very slow. If caching is appropriate, it must be done by the plug-in itself.