构建系列之新一代利器Esbuild(上)
What is Esbuild?
Esbuild 是由 Figma 的 CTO 「Evan Wallace」基于 Golang 开发的一款打包工具,相比传统的打包工具,主打性能优势,在构建速度上可以快10~100 倍。
为什么会这么快?
go实现,编译为本地代码
大多数打包器都是用 JavaScript 编写的,esbuild采用 Go 语言开发,相比于 单线程 + JIT 性质的解释型语言 ,使用 Go 的优势在于 :
-
一方面可以充分利用多线程打包,并且线程之间共享内容,而 JS 如果使用多线程还需要有线程通信(postMessage)和序列化数据的开销;
-
另一方面直接编译成机器码,而不用像 Node 一样先将 JS 代码解析为字节码,然后转换为机器码,大大节省了程序运行时间。
-
垃圾回收,go的堆线程之间共享,javascrit是每个js线程独享一个堆。
多核并行
内部打包算法充分利用多核 CPU 优势。Esbuild 内部算法设计是经过精心设计的,尽可能充分利用所有的 CPU 内核。所有的步骤尽可能并行,这也是得益于 Go 当中多线程共享内存的优势,而在 JS 中所有的步骤只能是串行的。具体来说:解析
和代码生成
是大部分工作,并且能够齐全并行化
(链接在大多数状况下是固有的串行工作)。
极致的代码
esbuild通过自行实现所有逻辑来避免第三方库带来的性能问题, 统一的数据结构可以减少数据转换开销, 并且可以根据需要改变架构, 当然最大的缺点就是工作量倍增.
内存高效利用
ESBuild在实现时尽量减少数据的传递以及数据的转换, ESBuild尽量减少了对整体AST的传递, 并且尽可能复用AST数据, 其他的Bundler可能会在编译的不同阶段往复转换数据格式(string -> TS -> JS -> older JS -> string...,这其中会涉及复杂的编译工具链,比如 webpack -> babel -> terser,每次接触到新的工具链,都得重新解析 AST). 在内存存储效率方面Go也比JavaScript更高效.
劣势
其实也不能说是劣势,更准确地说Esbuild 本身的限制,每个构建工具都有自己的初衷,而在更完美地实现这个目标的同时也会在一些方面做取舍:
-
没有 TS 类型检查
-
不能操作 AST,这个确实会带来一些不便之处,很多大型项目都会有操作AST的需要,所以就需要引入babel插件解决
-
不支持装饰器语法
-
产物 target 无法降级到 ES5 及以下,意味着需要 ES5 产物的场景只用 Esbuild 无法胜任
插件机制
esbuild 插件就是一个对象,里面有name和setup两个属性,name是插件的名称,setup是一个函数,其中入参是一个 build 对象,这个对象上挂载了一些钩子可供我们自定义一些构建逻辑。以下是一个简单的esbuild插件示例:
let envPlugin = {
name: 'env',
setup(build) {
// 文件解析时触发 // 将插件作用域限定于env文件,并为其标识命名空间"env-ns" build.onResolve({ filter: /^env$/ },
args => ({
path: args.path,
namespace: 'env-ns',
}))
// 加载文件时触发 // 只有命名空间为"env-ns"的文件才会被处理
// 将process.env对象反序列化为字符串并交由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',
// 应用插件
plugins: [envPlugin], })
.catch(() => process.exit(1))
使用如下:
*// 应用了env插件后,构建时将会被替换成process.env对象*
import { PATH } from 'env'
console.log(`This is ${PATH}`)
插件实现的时候需注意:Esbuild 插件机制只可作用于 build API,而不适用于 transformAPI,这意味着 webpack 当中的 esbuild-loader 这种只使用 Esbuild transform 功能的地方无法利用 Esbuild 的插件机制。
场景
对于前端的构建工具来说主要有这样几个垂直的功能:
-
Bundler
-
Transformer
-
Minimizer
Esbuild作为 transformer 性能是可以的,但 Esbuild 兼容性不足,可以不优先考虑。
Esbuild 作为 Bundler 已经被 Vite 作为开发阶段的依赖预打包工具,同时也被大量用作线上 esm CDN 服务,比如esm.sh等等;作为 Minimizer ,Esbuild 也已足够成熟,目前已经被 Vite 作为 JS 和 CSS 代码的压缩工具用上了生产环境。
总结
本篇文章主要围绕着esbuild的强大性能做介绍,快是它最大的利器,所谓天下武功唯快不破的道理大家懂得都懂,我们也是从底到上的分析了esbuild快的原因,下半部分主要围绕着esbuild的使用场景为切入点,欢迎期待。微信首发,欢迎关注微信公众号:江湖修行,感谢各位老铁。