https://zhuanlan.zhihu.com/p/139219361 支付寶@陳成
不知大家是否有遇到這個問題,
<--- 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 中使用
通過插件 @umijs/plugin-esbuild 一鍵開啟。
先安裝依賴,
$ yarn add @umijs/plugin-esbuild
然后配置開啟,
export default {
esbuild: {},
};