乐趣区

关于前端:Esbuild-为什么那么快

全文 2500 字,浏览耗时约 20 分钟,欢送点赞关注转发。

Esbuild 是什么

Esbuild 是一个十分新的模块打包工具,它提供了与 Webpack、Rollup、Parcel 等工具 类似 的资源打包能力,却有着高的离谱的性能劣势:

从上到下,耗时逐渐回升达到数百倍的差别,这个微小的性能劣势使得 Esbuild 在一众基于 Node 的构建工具中迅速蹿红,特地是 Vite 2.0 发表应用 Esbuild 预构建依赖后,前端社区对于它的探讨热度逐步回升。

那么问题来了,这是怎么做到的?我翻阅了很多材料后,总结了一些关键因素:

上面开展细讲。

为什么快

语言劣势

大多数前端打包工具都是基于 JavaScript 实现的,而 Esbuild 则抉择应用 Go 语言编写,两种语言各自有其善于的场景,然而在资源打包这种 CPU 密集场景下,Go 更具性能劣势,差距有多大呢?比方计算 50 次斐波那契数列,JS 版本:

function fibonacci(num) {if (num < 2) {return 1}
    return fibonacci(num - 1) + fibonacci(num - 2)
}

(() => {
    let cursor = 0;
    while (cursor < 50) {fibonacci(cursor++)
    }
})()

Go 版本:

package main

func fibonacci(num int) int{
    if num<2{return 1}
    
    return fibonacci(num-1) + fibonacci(num-2)
}

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

JavaScript 版本执行耗时大概为 332.58s,Go 版本执行耗时大概为 147.08s,两者相差约 1.25 倍,这个简略试验并不能准确定量两种语言的性能差异,但感官上还是能显著感知 Go 语言在 CPU 密集场景下会有更好的性能体现。

归根到底,尽管古代 JS 引擎与 10 年前相比有微小的晋升,但 JavaScript 实质上仍然是一门解释型语言,JavaScript 程序每次执行都须要先由解释器一边将源码翻译成机器语言,一边调度执行;而 Go 是一种编译型语言,在编译阶段就曾经将源码转译为机器码,启动时只须要间接执行这些机器码即可。也就意味着,Go 语言编写的程序比 JavaScript 少了一个动静解释的过程。

这种语言层面的差别在打包场景下特地突出,说的夸大一点,JavaScript 运行时还在解释代码的时候,Esbuild 曾经在解析用户代码;JavaScript 运行时解释完代码刚筹备启动的时候,Esbuild 可能曾经打包结束,退出过程了!

所以在编译运行层面,Go 前置了源码编译过程,绝对 JavaScript 边解释边运行的形式有更高的执行性能。

多线程劣势

Go 天生具备多线程运行能力,而 JavaScript 实质上是一门单线程语言,直到引入 WebWorker 标准之后才有可能在浏览器、Node 中实现多线程操作。

我已经研读过 Rollup、Webpack 的代码,就我熟知的范畴内两者均未应用 WebWorker 提供的多线程能力。反观 Esbuild,它最外围的卖点就是性能,它的实现算法通过十分精心的设计,尽可能饱和地应用各个 CPU 核,特地是打包过程的解析、代码生成阶段曾经实现齐全并行处理。

除了 CPU 指令运行层面的并行外,Go 语言多个线程之间还能共享雷同的内存空间,而 JavaScript 的每个线程都有本人独有的内存堆。这意味着 Go 中多个处理单元,例如解析资源 A 的线程,能够间接读取资源 B 线程的运行后果,而在 JavaScript 中雷同的操作须要调用通信接口 woker.postMessage 在线程间复制数据。

所以在运行时层面,Go 领有人造的多线程能力,更高效的内存使用率,也就意味着更高的运行性能。

节制

对,没错,节制!

Esbuild 并不是另一个 Webpack,它仅仅提供了构建一个古代 Web 利用所需的最小性能汇合,将来也不会大规模退出咱们业已相熟的各类构建个性。最新版本 Esbuild 的次要性能个性有:

  • 反对 js、ts、jsx、css、json、文本、图片等资源
  • 增量更新
  • Sourcemap
  • 开发服务器反对
  • 代码压缩
  • Code split
  • Tree shaking
  • 插件反对

能够看到,这份列表中反对的资源类型、工程化个性非常少,甚至并不足以撑持一个大型项目的开发需要。在这之外,官网明确申明将来没有打算反对如下个性:

  • Elm, Svelte, Vue, Angular 等代码文件格式
  • Ts 类型查看
  • AST 相干操作 API
  • Hot Module Replace
  • Module Federation

而且,Esbuild 所设计的插件零碎也无心笼罩以上这些场景,这就意味着第三方开发者无奈通过 插件 这种无侵入的形式实现上述性能,emmm,能够预感将来可能会呈现很多魔改版本。

Esbuild 只解决一部分问题,所以它的架构复杂度绝对较小,绝对地编码复杂度也会小很多,绝对于 Webpack、Rollup 等大一统的工具,也天然更容易把性能做到极致。节制的功能设计还能带来另外一个益处:齐全为性能定制的各种附加工具。

定制

回顾一下,在 Webpack、Rollup 这类工具中,咱们不得不应用很多额定的第三方插件来解决各种工程需要,比方:

  • 应用 babel 实现 ES 版本转译
  • 应用 eslint 实现代码查看
  • 应用 TSC 实现 ts 代码转译与代码查看
  • 应用 less、stylus、sass 等 css 预处理工具

咱们曾经齐全习惯了这种形式,甚至感觉事件就应该是这样的,大多数人可能基本没有意识到事件能够有另一种解决方案。Esbuild 起了个头,抉择齐全!齐全重写整套编译流程所须要用到的所有工具!这意味着它须要重写 js、ts、jsx、json 等资源文件的加载、解析、链接、代码生成逻辑。

开发成本很高,而且可能被动陷入关闭的危险,但收益也是微小的,它能够一路贯彻准则,以性能为最高优先级定制编译的各个阶段,比如说:

  • 重写 ts 转译工具,齐全摈弃 ts 类型查看,只做代码转换
  • 大多数打包工具把词法剖析、语法分析、符号申明等步骤拆解为多个高内聚低耦合的处理单元,各个模块职责明显,可读性、可维护性较高。而 Esbuild 则保持性能第一准则,不惜采纳反直觉的设计模式,将多个解决算法混合在一起升高编译过程数据流转所带来的性能损耗
  • 统一的数据结构,以及衍生出的高效缓存策略,下一节细讲

这种深度定制一方面升高了设计老本,可能放弃编译链条的架构一致性;一方面可能贯彻性能第一的准则,确保每个环节以及环节之间交互性能的最优。尽管随同着性能、可读性、可维护性层面的的就义,但在编译性能方面简直做到了极致。

构造一致性

上一节咱们讲到 Esbuild 抉择重写包含 js、ts、jsx、css 等语言在内的转译工具,所以它更能保障源代码在编译步骤之间的构造一致性,比方在 Webpack 中应用 babel-loader 解决 JavaScript 代码时,可能须要通过屡次数据转换:

  • Webpack 读入源码,此时为字符串模式
  • Babel 解析源码,转换为 AST 模式
  • Babel 将源码 AST 转换为低版本 AST
  • Babel 将低版本 AST generate 为低版本源码,字符串模式
  • Webpack 解析低版本源码
  • Webpack 将多个模块打包成最终产物

源码须要经验 string => AST => AST => string => AST => string,在字符串与 AST 之间重复横跳。

而 Esbuild 重写大多数转译工具之后,可能在多个编译阶段共用类似的 AST 构造,尽可能减少字符串到 AST 的构造转换,晋升内存应用效率。

总结

单纯从编译性能的维度看,Esbuild 的确完胜世面上所有打包框架,差距甚至能在百倍之大:

耗时 性能差别 速度 产物大小
esbuild 0.11s 1x 1198.5 kloc/s 0.97mb
esbuild (1 thread) 0.40s 4x 329.6 kloc/s 0.97mb
webpack 4 19.14s 174x 6.9 kloc/s 1.26mb
parcel 1 22.41s 204x 5.9 kloc/s 1.56mb
webpack 5 25.61s 233x 5.1 kloc/s 1.26mb
parcel 2 31.39s 285x 4.2 kloc/s 0.97mb

但这是有代价的,刨除语言层面的人造劣势外,在性能层面它间接放弃对 less、stylus、sass、vue、angular 等资源的反对,放弃 MF、HMR、TS 类型查看等性能,正如作者所说:

This will involve saying “no” to requests for adding major features to esbuild itself. I don’t think esbuild should become an all-in-one solution for all frontend needs\!

在我看来,Esbuild 当下与将来都不能代替 Webpack,它不适宜间接用于生产环境,而更适宜作为一种偏底层的模块打包工具,须要在它的根底上二次封装,扩大出一套既兼顾性能又有齐备工程化能力的工具链,例如 Snowpack, Vite, SvelteKit, Remix Run 等。

总的来说,Esbuild 提供了一种新的设计思路,值得学习理解,但对大多数业务场景还不适宜间接投入生产应用。

退出移动版