不知大家是否有遇到这个问题,
<--- Last few GCs --->
[59757:0x103000000] 32063 ms: Mark-sweep 1393.5 (1477.7) -> 1393.5 (1477.7) MB, 109.0 / 0.0 ms allocation failure GC in old space requested
<--- JS stacktrace --->
==== JS stack trace =========================================
Security context: 0x24d9482a5ec1 <JSObject>
...
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
1: node::Abort() [/Users/xxx/.nvm/versions/node/v10.13.1/bin/node]
2: ...
或者在 92% 的进度里卡很久,
● Webpack █████████████████████████ chunk asset optimization (92%) TerserPlugin
随着产物越来越大,编译上线和 CI 的时间都越来越长,而其中 1/3 及更多的时间则是在做压缩的部分。OOM 的问题也通常来源于压缩,我们推出的 UglifyCache 和 autoExternal 方案其实大部分也是在解决产物大了之后压缩慢从而可能导致 OOM的问题。
如何解决压缩慢和占内存的问题,一直很令人头疼。
esbuild
An extremely fast JavaScript bundler and minifier.
特点就是快,
为啥快?作者给了几个原因,但其中最主要的应该是用 go 写,然后编译为 Native 代码。然后 npm 安装时动态去下对应平台的二进制包,支持 mac、linux 和 windows,比如 esbuild-darwin-64。
相同思路的还有 es-module-lexer、swc 等,都是用编译成 Native 代码的方式进行提速。
esbuild 有两个功能,bundler 和 minifier。bundler 的功能和 babel 以及 webpack 相比肯定差很多,直接上风险太大;而 minifier 倒是可以试试,在 webpack 和 babel 产物的基础上做一次压缩,坑应该相对较少。
我先直接用 esbuild 压一个 5M 多的文件到 1.5M,效果明显。
$ esbuild --minify --outfile=esbuild-minify-test.js dist-uncompressed/umi.b9ab1e60.js
Wrote to esbuild-minify-test.js (1.5mb)
Done in 294ms
esbuild-webpack-plugin
为进一步验证效果,写了 webpack 插件和 Umi 插件。以依赖了全量 antd 和 bizcharts 的项目为例,在禁用 Babel 缓存和 Terser 缓存的基础上进行了测试。
大家感兴趣的可以自行 clone 仓库,在 yarn install
后分别执行,
// 用默认的 terser 压缩
$ yarn build:example
// 用 esbuild 压缩
$ yarn build:example:esbuild
// 不压缩
$ yarn build:example:nocompress
跑的结果,
结论如下:
- 编译时间减少近 1/3,产物越大越明显
- Gzip 前的尺寸变化不明显,Gzip 后的尺寸略有增加
- 内存消耗降低很多,基本可忽略(拿
process.memoryUsage().heapUsed
的值,因为有子进程,可能不准)
未来
不管是 Bundled 还是 Unbundled,其中肯定会有一些重计算的部分,这些应该是会转向 Native 代码实现,比如压缩、Babel 编译、TS 编译、模块分析等。
至于应用场景,一开始步子应该迈不大,可以先局部尝试起来,比如压缩、组件库开发时的 TS 编译等。Umi 会在 3.2 内置这种压缩方式,通过 minifier: { type: 'esbuild' }
提供,打 beta 使用警告⚠️。
在 Umi/Bigfish 中使用
通过插件 @umijs/plugin-esbuild 一键开启。
先安装依赖,
$ yarn add @umijs/plugin-esbuild
然后配置开启,
export default {
esbuild: {},
};