乐趣区

关于javascript:esbuild完整学习

esbuild 是最近比拟火的编译工具,在有些畛域曾经开始代替 webpack 或 babel,上面一起来看看这个工具的具体内容。

一. 速度优越性比拟

这里是一份压测数据,从图中能够看出 esbuild 性能拔群

对于它的性能为何如此优越,在官网解释中有以下几点:

1. 它是用 Go 语言编写的,编译成可执行代码

JavaScript 必须基于解释器的 node 环境能力执行,所以当 webpack 等工具解释完自身的代码后,可能 esbuild 曾经实现编译工作了,而这时候 webpack 才开始执行编译。

此外,Go 的外围设计是并行的,而 JavaScript 不是。

Go 有线程之间的共享内存,而 JavaScript 则必须在线程之间进行数据序列化。

Go 和 JavaScript 都有并行的垃圾收集器,但 Go 的堆是在所有线程之间共享的,而 JavaScript 的每个线程都有一个独立的堆。JavaScript 工作线程并行量减少了一半,因为还有一半 CPU 外围正忙于为另一半收集垃圾。

2. 并行被大量应用

esbuild 外部的算法是通过精心设计的,以尽可能使所有可用的 CPU 外围齐全饱和。

大抵有三个阶段:解析、连贯和代码生成。解析和代码生成是大部分的工作,并且是齐全可并行的。

因为所有线程都共享内存,在编译导入雷同的 JavaScript 库的不同入口点时,工作能够很容易地被分享。大多数古代计算机都有很多内核,所以并行化是一个很大的性能晋升。

3.esbuild 中的所有内容都是从头开始编写的

本人编写而不是应用第三方库有很多性能上的益处。能够从一开始就思考到性能问题,以确保所有的货色都应用统一的数据结构,防止低廉的转换,而且在必要时可进行宽泛的架构变更。当然,毛病是这是一个很大的工作量。

4. 内存失去无效利用

编译器在现实状况下大多是输出长度的 O(n)复杂性。因而,如果你正在解决大量的数据,内存访问速度可能会重大影响性能。你须要对数据处理的越少,编译器速度就越快。

二. 加载器(loader)

esbuild 加载器的作用与 webpack 中 loader 作用相似,都是对于某种类型的文件进行编译,具体性能介绍如下:

1.js-loader

这个加载器默认用于.js、.cjs 和.mjs 文件。.cjs 扩展名被 node 用于 CommonJS 模块,而.mjs 扩展名被 node 用于 ECMAScript 模块,只管 esbuild 并没有对这两者进行辨别。

esbuild 反对所有古代 JavaScript 语法。然而,较新的语法可能不被旧的浏览器所反对,所以你可能想配置指标选项,通知 esbuild 将较新的语法转换为适当的旧语法。

但请留神,ES5 反对的不是很好,目前还不反对将 ES6+ 语法转换为 ES5。

2. ts-loader 或 tsx-loader

这个加载器对于.ts 和.tsx 文件是默认启用的,这意味着 esbuild 内置了对 TypeScript 语法的解析和抛弃类型正文的反对。然而,esbuild 不做任何类型查看,所以你依然须要在 esbuild 中并行运行 tsc -noEmit 来查看类型。
须要留神的是,esbuild 在编译时不会进行类型查看,这应该在编译之前应用 ide 去查看

3. jsx-loader

将会把 xml 代码转换成 js 代码

4. json-loader

对于.json 文件,这个加载器是默认启用的。它在构建时将 JSON 文件解析成一个 JavaScript 对象,并将该对象作为默认导出。

5. css-loader

对于.css 文件,这个加载器是默认启用的。它以 CSS 语法的模式加载文件。CSS 在 esbuild 中是一种第一类内容类型,这意味着 esbuild 能够间接编译 CSS 文件,而不须要从 JavaScript 代码中导入你的 CSS。

你能够 @导入其余 CSS 文件,用 url()援用图片和字体文件,esbuild 会把所有货色编译在一起。留神,你必须为图像和字体文件配置一个加载器,因为 esbuild 没有任何预配置。通常这是一个数据 URL 加载器或内部文件加载器。

请留神,esbuild 还不反对 CSS Module,所以来自 CSS 文件的导出名称集目前总是空的。将来打算反对 CSS Module。

6. text-loader

对于.txt 文件,这个加载器是默认启用的。它在构建时将文件加载为字符串,并将字符串导出为默认导出。应用它看起来像这样。

7. binary-loader

这个加载器将在构建时以二进制缓冲区的模式加载文件,并应用 Base64 编码将其嵌入到包中。文件的原始字节在运行时被从 Base64 解码,并应用默认的导出形式导出为 Uint8Array。

8. Base64-loader

这个加载器将在构建时以二进制缓冲区的模式加载文件,并应用 Base64 编码将其嵌入到编译中的字符串。这个字符串将应用默认的导出形式导出。

9. dataurl-loader

这个加载器将在构建时作为二进制缓冲区加载文件,并将其作为 Base64 编码的数据 URL 嵌入到编译中。这个字符串是用默认的导出形式导出的。

10. file-loader

这个加载器会将文件复制到输入目录,并将文件名作为一个字符串嵌入到编译中。这个字符串是应用默认的导出形式导出的。

三.api 调用

为了更加不便的应用,esbuild 提供了 api 调用的形式,在调用 api 时传入 option 进行相应性能的设置。在 esbuild 的 API 中,有两个次要的 API 调用形式:transformbuild。两者的区别在于是否最终生成文件。

1.Transform API

Transform API 调用对单个字符串进行操作,不须要拜访文件系统。非常适合在没有文件系统的环境中应用或作为另一个工具链的一部分。上面是个简略例子:

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

Build API 调用对文件系统中的一个或多个文件进行操作。这使得文件能够互相援用,并被编译在一起。上面是个简略例子:

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

四. 插件 API

1. 简介

插件 API 属于下面提到的 API 调用的一部分,插件 API 容许你将代码注入到构建过程的各个局部。与 API 的其余局部不同,它不能从命令行中取得。你必须编写 JavaScriptGo代码来应用插件 API。

插件 API 只能用于 Build API,不能用于 Transform API

如果你正在寻找一个现有的 esbuild 插件,你应该看看现有的 esbuild 插件的列表。这个列表中的插件都是作者特意增加的,目标是为了让 esbuild 社区中的其他人应用。

2. 写插件

一个 esbuild 插件是一个蕴含 name 和 setup 函数的对象。它们以数组的模式传递给构建 API 调用。setup 函数在每次 BUILD API 调用时都会运行一次。
上面咱们来尝试自定义一个插件

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 通用代表这个插件的名称

2.2 setup 函数
2.2.1. Namespace

每个模块都有一个相干的命名空间。默认状况下,esbuild 在文件命名空间中操作,它对应于文件系统中的文件。然而 esbuild 也能够解决那些在文件系统上没有对应地位的 “ 虚构 “ 模块。虚构模块通常应用文件以外的命名空间来辨别它们和文件系统模块。

2.2.2.Filters

每个回调都必须提供一个正则表达式作为过滤器。当门路与过滤器不匹配时,esbuild 会跳过调用回调,这样做是为了进步性能。从 esbuild 的高度并行的外部调用到单线程的 JavaScript 代码是很低廉的,为了取得最大的速度,应该尽可能地防止。

你应该尽量应用过滤器正则表达式,而不是应用 JavaScript 代码进行过滤。这样做更快,因为正则表达式是在 esbuild 外部评估的,基本不须要调用 JavaScript。

2.2.3.Resolve callbacks

一个应用 onResolve 增加的回调将在 esbuild 构建的每个模块的每个导入门路上运行。这个回调能够定制 esbuild 如何进行门路解析。

2.2.4. Load callbacks

一个应用 onLoad 增加的回调能够对文件内容进行解决并返回。

3. 如何解决插件与 loader 的执行程序问题

esbuild 中的 loader 是间接把某个格局的文件间接解决并返回,而插件 api 也具备接触文件内容的机会,这两者的执行机会在文档中并没有提到。

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",
      };
    });
  },
};

从下面代码例子中看出,插件 api 在承受到文件内容后,并不能间接解决 tsx 的内容,因为咱们可能不具备解决 tsx 的能力,这时候并不能显示定义插件在 tsx 转换成 js 之后执行。要想解决这种状况只能借助 esbuild 的 transform api 能力。

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 迁徙

因为 babel 的社区插件较多,这给本来应用 babel 的我的项目迁徙到 esbuild 设置了阻碍,能够应用社区提供的 esbuild-plugin-babel 一键迁徙,例如应用 antd 组件时须要配合应用 antd-plugin-import 插件,具体如下:

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. 插件的应用限度

插件 API 并不打算涵盖所有的用例。它不可能关联编译过程的每个局部。例如,目前不可能间接批改 AST。这个限度的存在是为了放弃 esbuild 杰出的性能特色,同时也是为了防止暴露出太多的 API 外表,这将是一个保护的累赘,并且会阻止波及扭转 AST 的改良。

一种思考 esbuild 的形式是作为网络的 “ 链接器 ”。就像本地代码的链接器一样,esbuild 的工作是接管一组文件,解析并绑定它们之间的援用,并生成一个蕴含所有代码链接的繁多文件。一个插件的工作是生成最终被链接的单个文件。

esbuild 中的插件最好是在绝对范畴内工作,并且只定制构建的一个小方面。例如,一个自定义格局(如 YAML)的非凡配置文件的插件是十分适合的。你应用的插件越多,你的构建速度就越慢,尤其是当你的插件是用 JavaScript 编写的时候。如果一个插件实用于你构建中的每一个文件,那么你的构建很可能会十分慢。如果缓存实用,必须由插件自身来实现。

退出移动版