preface

Esbuild is a new tool that has become very popular in recent days. It is called the third generation build solution. The most distinctive feature of esbuild is its unique use of GO language. Thanks to this, it packs surprisingly fast.

Threejs takes 0.37 seconds to build ten replicas of threejs, which became famous as the underlying build solution for vite’s packaging tool.

Unfortunately, esbuild itself does not support the Babel plugin, so in this article, we will write a plugin named babel-plugin-import that will give esbuild a very common plugin feature in Babel — dynamic import.

why

The idea of dynamic import is actually quite simple, which is to force the code to only import a single file by changing the AST:

import { Button, Select } from 'antd'
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
import Button from 'antd/lib/button'
import Select from 'antd/lib/select'
Copy the code

Why do you do that?

We know that importing modules like import {Button} from ‘antd’ will import all modules.

When we look at a library, we first read the pacakge.json file of the library:

The main and module fields indicate which files the user imports when using the import statement. The main field represents the commonJS import default file, and the module field is the ES import default file. This is a holdovers from the CommonJS era, so when you use a library without a module field like import {Button} from ‘antd’ it always brings in files marked by main, missing the TREE-shaking feature of ESM.

In this case we need to look at the file pointed to in main, which is lib/index.js.

This is a CommonJS file, and you’ll notice that all of the components are introduced through require, and we know that CommonJS require is dynamically dependent, This means that as soon as you introduce import {Button} from ‘antd’ (even if you only use the Button component), the full component will be introduced, resulting in larger packaging results.

Try to import only antD’s Button component, delete the module field in PACKage. json of ANTD and package it:

Then use dynamic introduction:

OMG!!! 80% less!

We bypass the lib/index.js file by importing the file that specifies the path, so we don’t package all the components. This behavior is commonly referred to in the community as “dynamic import” or “import on Demand.”

how

In the most popular Webpacks we can solve this problem with the babel-plugin-import plug-in. Developed by the ANTD team, the plugin has been downloaded more than 300,000 times a week, making it a super Internet celebrity in the front-end world. It is designed to solve this situation:

// .babelrc
"plugins": [["import", { "libraryName": "antd"}]]Copy the code

With this setup, Babel only imports on demand for antD libraries, and the default import form is /lib/

. This is not necessarily the case with other library paths, such as LoDash. Lodash’s function path is lodash/

, so it allows you to configure libraryDirectory to change the import package path:

// .babelrc
"plugins": [["import", { "libraryName": "lodash".libraryDirectory: ' '}]]Copy the code

Babel-plugin-import seems to have been prepared. Scrolling through the documentation, we see the following configuration items that cover all incoming cases:

Configuration items meaning
libraryName The package name
libraryDirectory Module import path
styleLibraryDirectory Style entry path
camel2DashComponentName Whether to convert camelCase to kebabCase. Default is true
style Whether styles are introduced by default and functional form is supported
customName A function that changes the reference path of a module
customStyleName A function that changes the style of the introduced path

So, let’s start the development of the Esbuild plug-in. First, let’s look at the mechanism of the esbuild plug-in.

Esbuild plugin mechanism

Compared with WebPack, the mechanism of esBuild plug-in is extremely simple. It has only onStart and onEnd links for programs, and only onResolve and onLoad links for files.

The esBuild specification requires a plugin to be a name and setup object. Setup is a function that receives the build process. You can register periodic hooks on your build, which are mounted at the start of the build process and fired at the corresponding stage.

const plugin = {
  name: "myPlugin".setup: (build) = > {
    build.onLoad({}, (args) = > {
      const contents = fs.readFileSync(args.path, "utf-8");
      console.log("content: ", contents);
      return {
        contents: contents + "\n"}; }); }};Copy the code

See the esBuild official Using plugins column for the specific accepted and returned values of periodic hooks.

To start developing

We can leverage the capabilities of GogoCode, an AST analysis framework that has become very popular recently, to quickly turn our ideas into reality.

What we’re going to do is actually two things:

Case 1: The ImportDefaultSpecifier type

import Antd from "antd";

const Button = Antd.Button;
const { Select } = Antd;
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
import Button from "antd/lib/button";
import Select from "antd/lib/select";

ReactDOM.render(
  <>
    <Button />
    <Select />
    <Antd.Table />
  </>.document.querySelector("#root"));Copy the code

Case 2: ImportSepecifier type

import { Button, Select } from 'antd'
// ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
import Button from 'antd/lib/button'
import Select from 'antd/lib/select'

ReactDOM.render(
  <>
    <Button />
    <Select />
  </>.document.querySelector('#root'))Copy the code

The second case is relatively simple to deal with. We analyze all the ImportSepecifier of its ImportDeclaration, and push several DefaultImportSepecifier below the original AST as long as it is analyzed. And delete the original ImportDeclaration.

… The: ellipsis is used to omit AST declarations that are not related to this requirement

Note that there is a special case for this statement: Import {Button as MyButton} from ‘antd’, We need to record its name of “introducing” (importSepecifier. Imported) and “developers want to use the new name” (importSepecifier. Local), when replacing the assignment.

For case one, we can divide it into three cases:

  • Direct use:<Antd.Select />
  • Indirect reference:const Button = Antd.Button
  • Indirect list references:const { Button: MyButton, Select } = Antd

For the latter two, we have to mentally convert a VariableDeclaration to an ImportDeclaration.

Because GogoCode uses a direct matching pattern, you can optionally skip extranets, which is very convenient.

The code!

Take a look back at what getUsedComponents did:

That’s it meow!

The test case

For the test case, babel-plugin-import has been written for us and we can use it directly.

The loader used for esBuild files is different from WebPack in that it is determined directly by the extension name, so we will change all files using JS to JSX. Such as:

Then, let’s write the test logic:

Run down and try:

Wuhu! It went better than expected, and that’s all!

The library has been released to NPM as esbuild-dynamic-import-plugin and has been added to the Awesome Esbuild family bucket. There are plenty of plug-ins in Awesome EsBuild that are not covered, so you are welcome to use GogoCode to develop!

Write in the back

In our article on webPack, Babel, Vite, rollup and how gogoCode works, we did a little breakdown of webPack’s plugin system, which is more manageable, but too heavy: Webpack has an amazing 28 lifecycle hooks, while EsBuild only has 4; Webpack’s Tapable specification can be confusing for developers, while EsBuild is relatively clean.

Second, GogoCode’s matching and replacing ast is very efficient and saves a lot of code. For example, if we were to match ImportDeclaration with Babel visitor pattern, we would write at least ten times more code. Gogocode is a simple one-line string. Getting developers to focus on ideas for transcoding saves time and hair. Really achieved with “ape” (¿) For Ben, give me a thumbs up!

Why can become such ~! Write a plug-in for the esbuild framework for the first time; For the first time to usegogocodeTo make such a complex plug-in. The two pleasant events were intertwined. It’s supposed to be double fun, but why is it so ~! (meaning become four times happier)

GoGoCode related links

Github repository for GoGoCode (new project begged star ^_^) github.com/thx/gogocod…

GoGoCode’s official website gogocode. IO /

Play. Gogocode. IO /

Ali mom out of the new tool, to batch modify the project code to alleviate the pain of “GoGoCode actual battle” learn 30 AST code replacement tips 0 cost start AST, with GoGoCode to solve Vue2 Vue3 problem GoGoCode to help clean up the code in the “garbage”

Author: Ice cubes