Esbuild为什么那么快

716 阅读4分钟

1. 语言方法

大多前端打包工具都是基于js实现的 而Esbuild则选择使用Go语言编写 两种语言虽然都有其各自擅长的场景 但是在资源打包这种CPU密集场景下,Go根据性能优势

比如计算50次裴波那切数列:

  • js版 大约 332.58s
function test(num){
    if (num < 2) {
        return 1
    }
    
    return test(num - 1) + test(num - 2)
}

(()=>{
    let a = 0
    while(a < 50) {
        test(a++)
    }
})()

image.png

  • Go版 大约147.08s
package main
func test(num int){
    if num < 2 {
        return 1
    }
    
    return test(num - 1) + test(num - 2)
}

func main(){
    for i :=0; i < 50; i++ {
        test(i)
    }
}

image.png

Go 差不多是 js 的 1.25

这个并不能就证明 两种语言的 性能 差别, 但 感官上还是能感知到 Go 语言在CPU密集场景下会有更好的性能表现。

  • 原因: js 归根到底, 本质上依然是一门 解释性语言
js解释性语言:程序每次执行都需要先由解释器一边将源码翻译成机器语言,一边调度执行

Go编译性语言:在编译阶段就已经将源码编译为机器码,启动时只需直接执行这些机器码即可

特别是在打包的场景下: js运行时还在解释代码的时候,Esbuild已经在解析用户代码了。 js运行时解释完代码,并刚准备启动的时候,Esbuild可能已经打包完毕,退出进程了。

在编译运行层面,Go 前置了源码编译过程, 相对 js 边解释 边运行的 方式 有更高的 执行性能

image.gif

2. 多线程优势

  • Go 天生具有多线程能力,而 js 本质上就是一门单线程语言,直到引入了 WebWorker 规范之后,才有可能在浏览器、Node中实现多线程 image.gif

image.gif

  • 其次是: Go 语言的多个线程之间, 还能共享相同的 内存空间,而 js 的每个线程都有自己独有的内存栈,
  • 也就意味着: Go 可以处理多个单元的结果。比如: 解析资源 A 的线程, 可以直接读取资源 B 的线程的运行结果
  • 而 js 中相同的操作,需要调用通讯接口 worker.postMessage 在线程间复制数据

image.png

3. 节制

Esbuild 并不是一个 webpack, 没那么多杂七杂八的东西, 他就是一个现代web应用最小功能集合 Esbuild的主要功能特性:

  • 支持 js ts jsx css json 文本 图片 等资源
  • 增量更新
  • Sourcemap
  • 开发服务器支持
  • 代码压缩
  • Code Split
  • Tree Shaking
  • 插件支持

所以,Esbuild的工程化特性非常的少, 甚至不足以支撑一个大型项目的开发需求,比如:

  • 不支持 vue angular 等代码格式文件
  • 没有 Ts 类型检查
  • 不支持 AST 相关操作的 API
  • 不支持 HMR (Hot Module Replace)
  • 不支持 Module Federation

总体来讲:

Esbuild 的架构复杂度相对较小,相对的编码复杂度也会小很多
相对于 `Webpack` `Rollup` 这些大一统的工具, 
Esbuild 也自然更容易的把性能做的极致

节制的功能设计, 还带来一个好处: 
完全为性能定制的各种附加工具

4. 定制

传统的WebpackRollup中, 经常要配备各种第三方插件, Eg:

  • 使用 babel 实现 ES 版本转义
  • 使用 Eslint 实现代码检查
  • 使用 TSC 实现 ts 代码转义 与 代码检查
  • 使用 less stylus sass 等 实现 css 预处理

Esbuild 完全颠覆了这些东西,Eg:

  • 重写 ts 转移工具, 完全抛弃了 ts 类型检查, 只做代码转换
  • 原来的打包工具为: 词法分析 语法分析 符号声明 等步骤,各个模块职责分明,可读性,可维护性较高
  • Esbuild 是性能第一原则,将多个处理算法 混合在一起 , 来降低编译过程中 数据流转 所带来的性能损耗。
  • 一致性的数据结构, 衍生出了 高效的 缓存策略

4. 结构一致性

在传统的 Webpack中, 使用 babel-loader来处理 js 代码是, 可能需要经过多次的数据转换:

image.png

源码需要经历 string => AST => AST => string => AST => string ,在字符串与 AST 之间反复横跳

Esbuild 重写了大多数的编译工具, 能够在多个编译阶段共用相似的 AST, 尽可能减少 字符串AST的转化,提升效率

5. 愿景

Esbuild的这种大的变革,也是有代价的, 除了语言层面的天然优势外, 它在功能层面上直接放弃了对 less stylus sass vue angular 等资源的支持, 放弃了 MF HMR TS等这些牛逼的功能, 所以, 他是当下与未来都不可能替代 webpack 的, 也不适合直接用于生产环境, 而更适合作为一种偏底层的模块打包工具, 我们需要在他的基础上进行二次封装, 扩展出一套 既兼顾性能, 又有完备工程化能力的工具链才行, 比如 Vite Snowpack SevlteKit Remix Run

所以总体来说, Esbuild 提供了一种新的设计思路, 值得学习, 但对大多数业务场景还不适合 直接投入生产使用

感谢原文链接 : mp.weixin.qq.com/s?__biz=Mzg…