Esbuild is a relatively popular compilation tool recently. It has begun to replace webpack or babel in some fields. Let's take a look at the details of this tool.
1. Speed superiority comparison
Here is a pressure test data, from the figure you can see the outstanding performance of esbuild
Regarding why its performance is so superior, there are the following points in the official explanation:
1. It is written in Go language and compiled into executable code
JavaScript must be executed based on the node environment of the interpreter, so when webpack and other tools have interpreted its own code, esbuild may have completed the compilation work, and then webpack will start to execute the compilation.
In addition, Go's core design is parallel, while JavaScript is not.
Go has shared memory between threads, while JavaScript must serialize data between threads.
Both Go and JavaScript have parallel garbage collectors, but Go's heap is shared among all threads, and each thread of JavaScript has an independent heap. The amount of JavaScript worker thread parallelism has been cut in half, because half of the CPU cores are busy collecting garbage for the other half.
2. Parallel is heavily used
The algorithm inside esbuild is carefully designed to fully saturate all available CPU cores as much as possible.
There are roughly three stages: parsing, connection, and code generation. Parsing and code generation are most of the work and are completely parallel.
Since all threads share memory, work can be easily shared when compiling and importing different entry points of the same JavaScript library. Most modern computers have many cores, so parallelization is a big performance improvement.
3. Everything in esbuild is written from scratch
There are many performance benefits of writing your own rather than using third-party libraries. Performance issues can be considered from the beginning to ensure that everything uses a consistent data structure, avoid costly conversions, and make extensive architectural changes when necessary. Of course, the disadvantage is that this is a lot of work.
4. Memory is effectively used
Compilers are mostly O(n) complexity of input length under ideal circumstances. Therefore, if you are dealing with a large amount of data, the memory access speed may severely affect performance. The less you need to process the data, the faster the compiler will be.
Two. Loader (loader)
The role of the esbuild loader is similar to that of the loader in webpack. It compiles certain types of files. The specific functions are as follows:
1.js-loader
This loader is used by default for .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, the newer syntax may not be supported by older browsers, so you may want to configure target options to tell esbuild to convert the newer syntax to the appropriate old syntax.
But please note that ES5 support is not very good, and the conversion of ES6+ syntax to ES5 is currently not supported.
2. ts-loader or tsx-loader
This loader is enabled by default for .ts and .tsx files, which means that esbuild has built-in support for parsing TypeScript syntax and discarding type annotations. However, esbuild does not do any type checking, so you still need to run tsc -noEmit in esbuild in parallel to check the type.
It should be noted that esbuild will not perform type checking when compiling. This should be checked with ide before compiling.
3. jsx-loader
Will convert xml code into js code
4. json-loader
For .json files, this loader is enabled by default. It parses the JSON file into a JavaScript object when it is built, and exports the object as the default.
5. css-loader
For .css files, this loader is enabled by default. It loads files in the form of CSS syntax. CSS is a first-class content type in esbuild, which means that esbuild can directly compile CSS files without importing your CSS from JavaScript code.
You can @import other CSS files, use url() to reference images and font files, and esbuild will compile everything together. Note that you must configure a loader for image and font files, because esbuild does not have any pre-configuration. Usually this is a data URL loader or external file loader.
Please note that esbuild does not yet support CSS Module, so the set of exported names from CSS files is currently always empty. It is planned to support CSS Module in the future.
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 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 runtime and exported as Uint8Array using the default export method.
8. Base64-loader
This loader will load the file in the form of a binary buffer at build time and embed it into the compiled string 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
This loader will copy the file to the output directory and embed the file name as a string in the compilation. This string is exported using the default export method.
Three. api call
In order to be more convenient to use, esbuild provides a way to call api, and when calling api, pass in option to set the corresponding function. In the API of esbuild, there are two main API calling methods: transform and build . The difference between the two is whether the file is finally generated.
1.Transform API
The Transform API call operates on a single string without accessing the file system. Very suitable for use in an environment without a file system or as part of another tool chain. Here is a simple example:
require('esbuild').transformSync('let x: number = 1', {
loader: 'ts',
})
=>
{
code: 'let x = 1;\n',
map: '',
warnings: []
}
2.Build API
The Build API calls operate on one or more files in the file system. This allows files to reference each other and be compiled together. Here is a simple example:
require('fs').writeFileSync('in.ts', 'let x: number = 1')
require('esbuild').buildSync({
entryPoints: ['in.ts'],
outfile: 'out.js',
})
4. Plug-in API
1 Introduction
The plug-in API is part of the API calls mentioned above. The plug-in API allows you to inject code into various parts of the build process. Unlike other parts of the API, it cannot be obtained from the command line. You must write JavaScript or Go code to use the plug-in API.
Plug-in API can only be used for Build API, not for Transform API
If you are looking for an existing esbuild plugin, you should look at the list of . The plugins in this list are specially added by the author for the purpose of being used by others in the esbuild community.
2. Write plug-ins
An esbuild plugin is an object containing name and setup functions. They are passed to the build API call in the form of an array. The setup function will run once every 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('文件内容:',contents)
return {
contents: contents,
loader: "tsx",
};
});
},
};
2.1 name
name generic represents the name of this plugin
2.2 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 do not have a corresponding location on the file system. Virtual modules usually 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. When the path does not match the filter, esbuild will skip calling the callback. This is done to improve performance. From the highly parallel internal calls of esbuild to single-threaded JavaScript code is very expensive, in order to obtain the maximum speed, should be avoided as much as possible.
You should try to use filter regular expressions instead of using JavaScript code for filtering. This is faster because the regular expressions are evaluated inside esbuild and no JavaScript is required at all.
2.2.3.Resolve callbacks
A callback added using onResolve will run on every import path of every module built by esbuild. This callback can customize how esbuild performs path resolution.
2.2.4. Load callbacks
A callback added using onLoad can process the file content and return.
3. How to solve the problem of the execution order of plug-ins and loader
The loader in esbuild directly processes and returns files in a certain format, and the plug-in api also has the opportunity to contact the content of the file. The execution timing of the two is not mentioned in the document.
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只能处理js内容,对ts或jsx不认识,编译报错
const result = astHandle(contents)
return {
contents: result,
loader: "tsx",
};
});
},
};
It can be seen from the above code example that the plugin api cannot directly process the content of tsx after receiving the file content, because we may not have the ability to process tsx. At this time, it is not possible to show that the defined plugin is executed after tsx is converted to js. To deal with this situation, you can only use the transform api capability of esbuild.
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",
};
});
},
};
4.babel migration
Due to the large number of community plugins in Babel, this creates obstacles for migrating projects that originally used Babel to esbuild. You can use the community-provided esbuild-plugin-babel one-click migration. For example, you need to use the antd-plugin-import plugin when using antd components ,details 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));
5. Restrictions on the use of plug-ins
The plugin API is not intended to cover all use cases. It is impossible to associate every part of the compilation process. For example, it is currently impossible to modify the AST directly. This limitation exists to maintain the excellent performance characteristics of esbuild, but also to avoid exposing too many API surfaces, which will be a maintenance burden and will prevent improvements involving changes to the AST.
One way to think about esbuild is as a "linker" for the network. Just like a linker for native code, esbuild's job is to receive a set of files, parse and bind the references between them, and generate a single file that contains all the code links. The job of a plug-in is to generate a single file that is ultimately linked.
The plug-ins in esbuild are best to work within a relative scope and only customize 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 suitable. The more plug-ins you use, the slower your build speed, especially when your plug-ins are written in JavaScript. If a plugin applies to every file in your build, then your build is likely to be very slow. If caching is applicable, it must be done by the plugin itself.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。