The next generation of front-end building, Esbuild used to play like this!

Hello everyone, today I bring you Esbuild related introduction and practice, hope to inspire you.

What is a Esbuild?

Esbuild is a golang-based packaging tool developed by Figma’s CTO “Evan Wallace” that offers performance advantages over traditional packaging tools and can be built 10 to 100 times faster.

Architecture advantages

1. Golang development

Compared with single-threaded + JIT interpreted languages, Go has the following advantages:

  • On the one hand, it can make full use of multi-thread packaging and share content between threads, while JS also needs the overhead of thread communication (postMessage) if it uses multi-thread.
  • On the other hand, directly compiled into machine code, rather than like Node to first parse JS code into bytecode, and then converted into machine code, greatly saving the program running time.

2. Multi-core parallelism

Internal packaging algorithm takes full advantage of multi-core CPU. The Esbuild internal algorithm design is carefully designed to take advantage of all CPU cores as much as possible. All steps are as parallel as possible, thanks to the multi-threaded shared memory in Go, whereas in JS all steps are only serial. Data to Drag

3. Build a wheel from scratch

Build the wheel from scratch, without any third party library black box logic, to ensure the ultimate code performance.

4. Use memory efficiently

In general, AST data is frequently parsed and passed in traditional JS packaging tools such as String -> TS -> JS -> String, which involves complex compilation tool chains such as webpack -> Babel -> terser, Every time a new toolchain is touched, the AST has to be reparsed, resulting in a large footprint. Esbuild uses as much AST node data as possible from start to finish, which greatly improves memory utilization and compilation performance.

Compared with SWC

speed

To convert 800+ TSX files as Transformer, use pure Esbuild and SWC to compile the code. Do not write any JS glue code (such as esbuild-Register, esbuild-loader, SWC-loader itself in order to adapt to the corresponding host tools, will write a bunch of JS glue code, affect the judgment).

Esbuild SWC TSC
For the first time, 138 ms 217 ms 8640 ms
The second time 154 ms 206 ms 8400 ms
The third time 142 ms 258 ms 8480 ms
On average, 144.7 ms 227 ms 8507 ms
Time consuming rate x1 X 1.58 X 58.8

As can be seen from this example, Esbuild and SWC are on the same order of magnitude in performance. Here, Esbuild is slightly faster in the repository example, but there are other scenarios where SWC is slightly faster than Esbuild.

compatibility

Limitations of Esbuild itself include the following:

  • There is no TS type check
  • Cannot operate AST
  • Decorator syntax is not supported
  • Product targets cannot be downgraded to ES5 or below

This means that scenarios that require ES5 artifacts are not up to Esbuild alone.

In contrast, SWC has better compatibility:

  • The product supports ES5 format
  • Support for decorator syntax
  • You can manipulate the AST by writing JS plug-ins

Application scenarios

With Esbuild and SWC, a lot of times we compare the performance of the two and ignore the application scenario. There are several vertical functions for front-end build tools:

  • Bundler
  • Transformer
  • Minimizer

As you can see from the speed and compatibility comparisons above, Esbuild and SWC have similar performance as a transformer, but Esbuild compatibility is nowhere near as good as SWC. Therefore, SWC is superior as Transformer.

However, as Bundler and Minimizer, SWC is in a tight spot. First of all, the official SWCPack is basically unusable at present, and Minimizer is very immature, so it is easy to encounter compatibility problems.

Esbuild as Bundler has been used by Vite as a dependent pre-packaging tool in the development stage, and is also widely used as online ESM CDN services, such as ESM.sh, etc. As Minimizer, Esbuild is mature enough to be used by Vite as a compression tool for JS and CSS code in production environments.

Taken together, the relationship between SWC and Esbuild is similar to that of Babel and Webpack, which are more suitable for compatible and customized transformers (such as mobile business scenarios) and Bundler and Minimizer. And Transformer with low compatibility and customization requirements.

Plug-in mechanism

The esBuild plugin is an object with name and setup properties. Name is the name of the plugin, and setup is a function whose input is a Build object, which has hooks that allow you to customize your build logic. Here is a simple example of the esbuild plug-in:

let envPlugin = {
  name: 'env'.setup(build) {
     // Triggered during file parsing
    // Limit the scope of the plug-in to the env file and identify it with the namespace "env-ns"
    build.onResolve({ filter: /^env$/ }, args= > ({
      path: args.path,
      namespace: 'env-ns',}))// Triggered when the file is loaded
    // Only files with namespace "env-ns" will be processed
    // Deserialize the process.env object into a string and hand it over to jSON-loader
    build.onLoad({ filter: /. * /, namespace: 'env-ns' }, () = > ({
      contents: JSON.stringify(process.env),
      loader: 'json',}}}))require('esbuild').build({
  entryPoints: ['app.js'].bundle: true.outfile: 'out.js'.// Application plug-in
  plugins: [envPlugin],
}).catch(() = > process.exit(1))
Copy the code

Use as follows:

*// When env is applied, it will be replaced by the process.env object * at build time

import { PATH } from 'env'

console.log(`PATH is ${PATH}`)
Copy the code

There are a few things to be aware of when writing plug-ins:

  1. The Esbuild plugin mechanism only works with the Build API, not the transformAPI, This means that esbuild-loader in Webpack, which only uses esBuild Transform functionality, cannot take advantage of the esBuild plugin mechanism.

  2. The filter regex in the plug-in is implemented using the go native regex to filter files, and the rules should be as strict as possible in order not to degrade performance too much. At the same time, it itself and JS re are different, such as the forward-looking (? <=), after (? =) and backreferences (\1) are not supported.

  3. The actual plug-in should take into account custom caching (reducing the repetitive overhead of load), sourcemAP merging (proper mapping of source code), and error handling. You can refer to the Svelte Plugin.

Virtual Module support

Compared with a Rollup

As a packer, you generally need two types of modules, one that exists in the real disk file system and the other that exists not on disk but in memory, that is, virtual modules. Rollup naturally supports virtual modules, and Vite makes heavy use of virtual modules based on its plugin mechanism. Take wASM file processing as an example:

const wasmHelperId = '/__vite-wasm-helper'
// Helper function implementation
const wasmHelper = async (opts = {}, url: string) => {
  // Omit the implementation
}
export const wasmPlugin = (config: ResolvedConfig): Plugin= > {
  return {
    name: 'vite:wasm'.resolveId(id) {
      if (id === wasmHelperId) {
        return id
      }
    },
    async load(id) {
      if (id === wasmHelperId) {
        return `export default ${wasmHelperCode}`
      }
      if(! id.endsWith('.wasm')) {
        return
      }
      const url = await fileToUrl(id, config, this)
      // Virtual module
      return `
import initWasm from "${wasmHelperId}"
export default opts => initWasm(opts, The ${JSON.stringify(url)})
`}}}Copy the code

However, Rollup’s virtual module has some limitations. To distinguish it from the real module, the default convention is to spell a ‘\0’ before the path. This will cause some invasion of the path, and it will cause problems to import directly from the browser (Vite also replaces \0 with __xx, so as not to import directly from the path with \0):

Esbuild’s support for virtual modules is a little friendlier, with namespaces separating real modules from virtual modules without hacks like \0.

Ability to compile the

Using the Esbuild virtual module, you can do a lot of things. In addition to calculating env in memory as the content of the module, you can also compile the module name as a function, and you can even recurse the function at compile time. For example, the Esbuild plugin:

{
  name: 'fibo'.setup(build) {
    build.onResolve({ filter: /^fib\(\d+\)/ }, args= > {
      return { path: args.path, namespace: 'fib' }
    })
    build.onLoad({ filter: /^fib\(\d+\)/, namespace: 'fib' }, args= > {
      const match = /^fib\((\d+)\)/.exec(args.path);
      n = Number(match[1]);
      
      console.log(n);
      let contents = n < 2 ? `export default ${n+1}` : `
          import n1 from 'fib(${n - 1})'
          import n2 from 'fib(${n - 2})'
          export default n1 + n2`
      return { contents }
    })
  }
}
Copy the code

With this plug-in, you can parse import statements like this:

import fib5 from 'fib(5)'

console.log(fib5)

/ / 13
Copy the code

All modules are virtual modules that do not exist in the real file system

In addition, URL Import can be done with the help of virtual modules, which support the following Import code:

import React from 'https://esm.sh/react@17'
Copy the code

This can also be done in plug-ins, as shown in the examples.

The ground scene

1. Code compression tools

Esbuild’s code compression features are excellent and can close the performance gap of traditional compression tools by an order of magnitude or more. Vite 2.6 also officially uses Esbuild directly in production to compress JS and CSS code.

2. Instead of ts – node

The community already has a solution esno: github.com/antfu/esno

ts-node index.ts
/ / replace
esno hello.ts
Copy the code

3. Replace the ts – jest

Using ESbuild-JEST instead of TS-JEST and trying to use Esbuild-Jest as transformer in a large project, the overall test efficiency is about 3 times higher than ts-JEST. The time difference is as follows:

Github address: github.com/aelbore/esb…

4. Third-party library Bundler

Vite uses Esbuild to pre-package dependencies during development, turning all used third-party dependencies into ESM Bundle artifacts, and intending to use them in production in the future.

At the same time, there are some platforms to do online CJS -> ESM CDN services based on pure Esbuild, such as ESM. sh and Skypack:

5. Pack the Node library

Why bundle Node libraries:

  • Reduce node_modules code to prevent services from installing a lot of node_modules code and reduce installation volume
  • Improved startup speed, all code typed into a file, reducing the number of file IO operations
  • More secure. All code packaging is also a way to lock dependent versions, which can avoid the problem of large CI failure caused by coA packages. Please refer to this article of Yunqian.

In this respect, Esbuild works in much the same way as the current NCC from the Vercel team, but with some restrictions on how the code can be written. It cannot analyze dynamic require or import statements that contain variables:

6. Applets compilation

For small program scenarios, Esbuild can also be used instead of Webpack to greatly improve the compilation speed. For AST conversion, Esbuild plug-in is embedded in SWC to achieve fast compilation. See 132 shared Esbuild production for details.

7. Web construction

Web scenarios are more complex, requiring high compatibility and a surrounding tool ecosystem, such as low browser syntax degradation, CSS precompilers, HMR, etc., and a lot of additional capabilities to do with pure Esbuild.

Previously, Sanyuan implemented a set of Web development scaffolding EWAS based on Esbuild, which has been open source on Github and successfully implemented into my previous volume project. Compared with create-React-app, the startup speed was more than 100 times (30s -> 0.3s). Warehouse address: github.com/sanyuan0704…

Now Remix 1.0 has been released and built with Esbuild, which brings the ultimate performance experience and makes it a strong competitor to next.js.

Overall, however, Esbuild currently has a lot of capabilities that it doesn’t support for real Web scenarios. There are some weaknesses, including syntax that doesn’t support downgrading to ES5, inflexibles in unpacking, and no HMR support, and it still has a long way to go before it can truly be used as a WebPack-like build tool.

The resources

[1] Svelte plugin: * esbuild. Making. IO # / plugins/sv…

[2] the plugin example: * esbuild lot. IO/plugins / # ht…

[3] github.com/vitejs/vite…

[4] sm.sh : *esm.sh/

[5] kypack: *www.skypack.dev/

[6] esbuild on production: * zhuanlan.zhihu.com/p/408379997

[7] ewas: github.com/sanyuan0704…

[8] Volume Project: github.com/sanyuan0704…

[9] Remix 1.0: want to run /