Vite 是怎样利用 Esbuild 来提升性能的
什么是 Esbuild
Esbuild 是一款基于 Go 语言开发的 javascript 打包工具,最大的一个特征就是快。
通过官网提供的一张图,可以清晰的看到 Esbuild 的表现是多么优秀:
同样规模的项目,使用 Esbuild 可以将打包速度提升 10 - 100 倍,这对广大一直饱受 Webpack 缓慢打包速度折磨的开发人员来说,简直就是福音。
而 Esbuild 之所以能这么快,主要原因有两个:
- Go 语言开发,可以多线程打包,代码直接编译成机器码;
Webpack 一直被人诟病构建速度慢,主要原因是在打包构建过程中,存在大量的 resolve、load、transform、parse 操作,而这些操作通常是通过 javascript 代码来执行的。要知道,javascript 并不是什么高效的语言,在执行过程中要先编译后执行,还是单线程并且不能利用多核 cpu 优势,和 Go 语言相比,效率很低。
- 可充分利用多核 cpu 优势;
关键 API - transfrom & build
Esbuild 并不复杂。它对外提供了两个 API - transform 和 build,使用起来非常简单。
transfrom,转换的意思。通过这个 api,可以将 ts、jsx、tsx 等格式的内容转化为 js。 transfrom 只负责文件内容转换,并不会生成一个新的文件。
build,构建的意思,根据指定的单个或者多个入口,分析依赖,并使用 loader 将不同格式的内容转化为 js 内容,生成一个或多个 bundle 文件。
这两个 API 的使用方式:
const res = await esbuild.transform(code, options); // 将 code 转换为指定格式的内容
esbuild.build(options); // 打包构建
plugin
和 Webpack、Rollup 等构建工具一样,Esbuild 也提供了供外部使用的 plugin,使得可以介入构建打包过程。
在这里要说明一点,只有 build 这个 API 的入参中可以配置 plugin,transform 不可以。
一个标准的 plugin 的标准格式如下:
let customerPlugin = {
name: 'xxx',
setup: (build) => {
build.onResolve({ filter: '', namespace: '' }, args => { ...});
build.onLoad({ filter: '', namespace: ''}, args => { ... });
build.onStart(() => { ... });
build.onEnd((result) => { ... });
}
}
其中,setup 可以帮助在 build 的各个过程中注册 hook。
Esbuild 对外提供的 hook 比较简单,总共 4 个:
onResolve, 解析 url 时触发,可自定义 url 如何解析。如果 callback 有返回 path,后面的同类型 callback 将不会执行。所有的 onResolve callback 将按照对应的 plugin 注册的顺序执行。
onLoad, 加载模块时触发,可自定义模块如何加载。 如果 callback 有返回 contents,后面的同类型 callback 将不会执行。所有的 onl oad callback 将按照对应的 plugin 注册的顺序执行。
onStart, 每次 build 开始时都会触发,没有入参,因此不具有改变 build 的能力。多个 plugin 的 onStart 并行执行。
onEnd, 每次 build 结束时会触发,入参为 build 的结果,可对 result 做修改。所有的的 onEnd 将按照对应的 plugin 注册的顺序执行。
正是有了 onResolve、onLoad、onStart、onEnd,可以在 build 过程中的解析 url、加载模块内容、构建开始、构建结束阶段介入,做自定义操作。
Esbuild 在 Vite 中的巧妙使用
预构建
先来回顾一下为什么要做预构建。
原因有两点:
将非 ESM 规范的代码转换为符合 ESM 规范的代码;
将第三方依赖内部的多个文件合并为一个,减少 http 请求数量;
要完成预构建,最关键的两点是找到项目中所有的第三份依赖和对第三方依赖做合并、转换。借助 Esbuild,Vite 很轻松的实现了这两个诉求。
寻找第三方依赖
寻找第三方依赖的过程非常简单,分为两步:
定义一个带 onResolve hook 和 onl oad hook 的 esbuild plugin;
执行 esbuild 的 build 方法做打包构建;
和 Webpack、Rollup、Parcel 等构建工具一样,Esbuild 在做打包构建时也要构建模块依赖图 - module graph。
在构建 module graph 时,第一步就是解析模块的绝对路径,这个时候就会触发 onResolve hook。在 onResolve hook 触发时,会传入模块的路径。根据模块的路径,就可以判断出这个模块是第三方依赖还是业务代码。
举个