自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Webpack 性能之多進(jìn)程打包

開發(fā) 前端
接下來我們繼續(xù)聊聊 Webpack 中一些行之有效的并行計(jì)算方案。緩存的本質(zhì)是首輪計(jì)算后將結(jié)果保存下來,下次直接復(fù)用計(jì)算結(jié)果而跳過計(jì)算過程。

[[428278]]

在上一篇《Webpack 性能系列一: 使用 Cache 提升構(gòu)建性能》中,我們討論了 Webpack 語境下如何應(yīng)用各種緩存措施提升構(gòu)建性能,接下來我們繼續(xù)聊聊 Webpack 中一些行之有效的并行計(jì)算方案。緩存的本質(zhì)是首輪計(jì)算后將結(jié)果保存下來,下次直接復(fù)用計(jì)算結(jié)果而跳過計(jì)算過程;并行的本質(zhì)則是在同一時(shí)間內(nèi)并發(fā)執(zhí)行多個(gè)運(yùn)算,提升單位時(shí)間計(jì)算效率,兩者都是計(jì)算機(jī)科學(xué)常見的提升性能優(yōu)化手段。

受限于 Node.js 的單線程架構(gòu),原生 Webpack 對所有資源文件做的所有解析、轉(zhuǎn)譯、合并操作本質(zhì)上都是在同一個(gè)線程內(nèi)串行執(zhí)行,CPU 利用率極低,因此,理所當(dāng)然地社區(qū)出現(xiàn)了一些基于多進(jìn)程方式運(yùn)行 Webpack,或 Webpack 構(gòu)建過程某部分工作的方案,例如:

  • HappyPack:多進(jìn)程方式運(yùn)行資源加載邏輯
  • Thread-loader:Webpack 官方出品,同樣以多進(jìn)程方式運(yùn)行資源加載邏輯
  • TerserWebpackPlugin:支持多進(jìn)程方式執(zhí)行代碼壓縮、uglify 功能
  • Parallel-Webpack:多進(jìn)程方式運(yùn)行多個(gè) Webpack 構(gòu)建實(shí)例

這些方案的核心設(shè)計(jì)都很類似:針對某種計(jì)算任務(wù)創(chuàng)建子進(jìn)程,之后將運(yùn)行所需參數(shù)通過 IPC 傳遞到子進(jìn)程并啟動(dòng)計(jì)算操作,計(jì)算完畢后子進(jìn)程再將結(jié)果通過 IPC 傳遞回主進(jìn)程,寄宿在主進(jìn)程的組件實(shí)例再將結(jié)果提交給 Webpack。

下面,我將展開介紹每種方案的使用方法、原理及缺點(diǎn),讀者可按需選用。

使用 HappyPack

HappyPack 是一個(gè)使用多進(jìn)程方式運(yùn)行文件加載器 —— Loader 序列,從而提升構(gòu)建性能的 Webpack 組件庫,算得上 Webpack 社區(qū)內(nèi)最先流行的并發(fā)方案,不過作者已經(jīng)明確表示不會(huì)繼續(xù)維護(hù),推薦讀者優(yōu)先使用 Webpack 官方推出的相似方案:Thread-loader。

官方鏈接:https://github.com/amireh/happypack

使用方法

基本用法

使用上,首先安裝依賴:

  1. yarn add happypack 

之后,需要將原有 loader 配置替換為 happypack/loader,如:

  1. module.exports = { 
  2.     // ... 
  3.     module: { 
  4.         rules: [{ 
  5.             test: /\.js$/, 
  6.             // 使用 happypack/loader 替換原來的 Loader 配置 
  7.             use: 'happypack/loader'
  8.             // use: [ 
  9.             //  { 
  10.             //      loader: 'babel-loader'
  11.             //      options: { 
  12.             //          presets: ['@babel/preset-env'
  13.             //      } 
  14.             //  }, 
  15.             //  'eslint-loader' 
  16.             // ] 
  17.         }] 
  18.     } 
  19. }; 

再之后,需要?jiǎng)?chuàng)建 happypack 插件實(shí)例,并將原有 loader 配置遷移到插件中,完整示例:

  1. const HappyPack = require('happypack'); 
  2.  
  3. module.exports = { 
  4.     // ... 
  5.     module: { 
  6.         rules: [{ 
  7.             test: /\.js$/, 
  8.             use: 'happypack/loader'
  9.             // use: [ 
  10.             //  { 
  11.             //      loader: 'babel-loader'
  12.             //      options: { 
  13.             //          presets: ['@babel/preset-env'
  14.             //      } 
  15.             //  }, 
  16.             //  'eslint-loader' 
  17.             // ] 
  18.         }] 
  19.     }, 
  20.     plugins: [ 
  21.         new HappyPack({ 
  22.             loaders: [ 
  23.                 { 
  24.                     loader: 'babel-loader'
  25.                     option: { 
  26.                         presets: ['@babel/preset-env'
  27.                     } 
  28.                 }, 
  29.                 'eslint-loader' 
  30.             ] 
  31.         }) 
  32.     ] 
  33. }; 

配置完畢后,再次啟動(dòng) npx webpack 命令即可使用 HappyPack 的多進(jìn)程能力提升構(gòu)建性能。以 Three.js 為例,該項(xiàng)目包含 362 份 JS 文件,合計(jì)約 3w 行代碼:

開啟 HappyPack 前,構(gòu)建耗時(shí)大約為 11000ms 到 18000ms 之間,開啟后耗時(shí)降低到 5800ms 到 8000ms 之間,提升約47%。

配置多實(shí)例

上述簡單示例只能以相同的 Loader 序列處理同種文件類型,實(shí)際應(yīng)用中還可以為不同的文件配置多個(gè) 相應(yīng)的加載器數(shù)組,例如:

  1. const HappyPack = require('happypack'); 
  2.  
  3. module.exports = { 
  4.   // ... 
  5.   module: { 
  6.     rules: [{ 
  7.         test: /\.js?$/, 
  8.         use: 'happypack/loader?id=js' 
  9.       }, 
  10.       { 
  11.         test: /\.less$/, 
  12.         use: 'happypack/loader?id=styles' 
  13.       }, 
  14.     ] 
  15.   }, 
  16.   plugins: [ 
  17.     new HappyPack({ 
  18.       id: 'js'
  19.       loaders: ['babel-loader''eslint-loader'
  20.     }), 
  21.     new HappyPack({ 
  22.       id: 'styles'
  23.       loaders: ['style-loader''css-loader''less-loader'
  24.     }) 
  25.   ] 
  26. }; 

示例中,js、less 資源都使用 happypack/loader 作為唯一 loader,并分別賦予 id = 'js' | 'styles' 參數(shù);其次,示例中創(chuàng)建了兩個(gè) HappyPack 插件實(shí)例并分別配置了用于處理 js 與 css 的 loaders 數(shù)組,happypack/loader 與 HappyPack 實(shí)例之間通過 id 值關(guān)聯(lián)起來,以此實(shí)現(xiàn)多資源配置。

共享線程池

上述多實(shí)例模式更接近實(shí)際應(yīng)用場景,但默認(rèn)情況下,HappyPack 插件實(shí)例各自管理自身所消費(fèi)的進(jìn)程,導(dǎo)致整體需要維護(hù)一個(gè)數(shù)量龐大的進(jìn)程池,反而帶來新的性能損耗。

為此,HappyPack 提供了一套簡單易用的共享進(jìn)程池功能,使用上只需創(chuàng)建 HappyPack.ThreadPool 實(shí)例并通過 size 參數(shù)限定進(jìn)程總量,之后將該實(shí)例配置到各個(gè) HappyPack 插件的 threadPool 屬性上即可,例如:

  1. const os = require('os'
  2. const HappyPack = require('happypack'); 
  3. const happyThreadPool = HappyPack.ThreadPool({ 
  4.   size: os.cpus().length - 1 
  5. }); 
  6.  
  7. module.exports = { 
  8.   // ... 
  9.   plugins: [ 
  10.     new HappyPack({ 
  11.       id: 'js'
  12.       threadPool: happyThreadPool, 
  13.       loaders: ['babel-loader''eslint-loader'
  14.     }), 
  15.     new HappyPack({ 
  16.       id: 'styles'
  17.       threadPool: happyThreadPool, 
  18.       loaders: ['style-loader''css-loader''less-loader'
  19.     }) 
  20.   ] 
  21. }; 

使用共享進(jìn)程池功能后,HappyPack 會(huì)預(yù)先創(chuàng)建好一組共享的 HappyThread 對象,所有插件實(shí)例的資源轉(zhuǎn)譯需求最終都會(huì)通過 HappyThread 對象轉(zhuǎn)發(fā)到空閑進(jìn)程做處理,從而保證整體進(jìn)程數(shù)量可控。

原理

HappyPack 的運(yùn)行過程如下圖所示:

大致可劃分為:

  • happlypack/loader 接受到轉(zhuǎn)譯請求后,從 Webpack 配置中讀取出相應(yīng) HappyPack 插件實(shí)例
  • 調(diào)用插件實(shí)例的 compile 方法,創(chuàng)建 HappyThread 實(shí)例(或從 HappyThreadPool 取出空閑實(shí)例)
  • HappyThread 內(nèi)部調(diào)用 child_process.fork 創(chuàng)建子進(jìn)程,并執(zhí)行 HappyWorkerChannel 文件
  • HappyWorkerChannel 創(chuàng)建 HappyWorker ,開始執(zhí)行 Loader 轉(zhuǎn)譯邏輯

中間流程輾轉(zhuǎn)了幾層,最終由 HappyWorker 類重新實(shí)現(xiàn)了一套與 Webpack Loader 相似的轉(zhuǎn)譯邏輯,代碼復(fù)雜度較高,讀者稍作了解即可。

缺點(diǎn)

HappyPack 雖然確實(shí)能有效提升 Webpack 的打包構(gòu)建速度,但它有一些明顯的缺點(diǎn):

  • 作者已經(jīng)明確表示不會(huì)繼續(xù)維護(hù),擴(kuò)展性與穩(wěn)定性缺乏保障,隨著 Webpack 本身的發(fā)展迭代,可以預(yù)見總有一天 HappyPack 無法完全兼容 Webpack
  • HappyPack 底層以自己的方式重新實(shí)現(xiàn)了加載器邏輯,源碼與使用方法都不如 Thread-loader 清爽簡單

不支持部分 Loader,如 awesome-typescript-loader

使用 Thread-loader

Thread-loader 也是一個(gè)以多進(jìn)程方式運(yùn)行 loader 從而提升 Webpack 構(gòu)建性能的組件,功能上與HappyPack 極為相近,兩者主要區(qū)別:

  1. Thread-loader由 Webpack 官方提供,目前還處于持續(xù)迭代維護(hù)狀態(tài),理論上更可靠
  2. Thread-loader 只提供了一個(gè)單一的 loader 組件,用法上相對更簡單
  3. HappyPack 啟動(dòng)后,會(huì)向其運(yùn)行的 loader 注入 emitFile 等接口,而 Thread-loader 則不具備這一特性,因此對 loader 的要求會(huì)更高,兼容性較差

官方鏈接:https://github.com/webpack-contrib/thread-loader

使用方法

首先,需要安裝 Thread-loader 依賴:

  1. yarn add -D thread-loader 

其次,需要將 Thread-loader 配置到 loader 數(shù)組首位,確保最先運(yùn)行,如:

  1. module.exports = { 
  2.   module: { 
  3.     rules: [{ 
  4.       test: /\.js$/, 
  5.       use: [ 
  6.         'thread-loader'
  7.         'babel-loader'
  8.         'eslint-loader' 
  9.       ], 
  10.     }, ], 
  11.   }, 
  12. }; 

配置完畢后,再次啟動(dòng) npx webpack 命令即可。依然以 Three.js 為例,開啟 Thread-loader 前,構(gòu)建耗時(shí)大約為 11000ms 到 18000ms 之間,開啟后耗時(shí)降低到 8000ms 左右,提升約37%。

原理

Webpack 將執(zhí)行 Loader 相關(guān)邏輯都抽象到 loader-runner 庫,Thread-loader 也同樣復(fù)用該庫完成 Loader 的運(yùn)行邏輯,核心步驟:

  • 啟動(dòng)時(shí),以 pitch 方式攔截 Loader 執(zhí)行鏈
  • 分析 Webpack 配置對象,獲取 thread-loader 后面的 Loader 列表
  • 調(diào)用 child_process.spawn 創(chuàng)建工作子進(jìn)程,并將Loader 列表、文件路徑、上下文等參數(shù)傳遞到子進(jìn)程
  • 子進(jìn)程中調(diào)用 loader-runner,轉(zhuǎn)譯文件內(nèi)容
  • 轉(zhuǎn)譯完畢后,將結(jié)果傳回主進(jìn)程

參考:

https://github.com/webpack/loader-runner

Webpack 原理系列七:如何編寫loader

缺點(diǎn)

Thread-loader 是 Webpack 官方推薦的并行處理組件,實(shí)現(xiàn)與使用都非常簡單,但它也存在一些問題:

  • Loader 中不能調(diào)用 emitAsset 等接口,這會(huì)導(dǎo)致 style-loader 這一類 Loader 無法正常工作,解決方案是將這類組件放置在 thread-loader 之前,如 ['style-loader', 'thread-loader', 'css-loader']
  • Loader 中不能獲取 compilation、compiler 等實(shí)例對象,也無法獲取 Webpack 配置

這會(huì)導(dǎo)致一些 Loader 無法與 Thread-loader 共同使用,讀者需要仔細(xì)加以甄別、測試。

使用 Parallel-Webpack

Thread-loader、HappyPack 這類組件所提供的并行能力都僅作用于執(zhí)行加載器 —— Loader 的過程,對后續(xù) AST 解析、依賴收集、打包、優(yōu)化代碼等過程均沒有影響,理論收益還是比較有限的。對此,社區(qū)還提供了另一種并行度更高,以多個(gè)獨(dú)立進(jìn)程運(yùn)行 Webpack 實(shí)例的方案 —— Parallel-Webpack。

官方鏈接:https://github.com/trivago/parallel-webpack

使用方法

基本用法

使用前,依然需要安裝依賴:

  1. yarn add -D parallel-webpack 

Parallel-Webpack 支持兩種用法,首先介紹的是在 webpack.config.js 配置文件中導(dǎo)出多個(gè) Webpack 配置對象,如:

  1. module.exports = [{ 
  2.     entry: 'pageA.js'
  3.     output: { 
  4.         path: './dist'
  5.         filename: 'pageA.js' 
  6.     } 
  7. }, { 
  8.     entry: 'pageB.js'
  9.     output: { 
  10.         path: './dist'
  11.         filename: 'pageB.js' 
  12.     } 
  13. }]; 

之后,執(zhí)行命令 npx parallel-webpack 即可完成構(gòu)建,上面的示例配置會(huì)同時(shí)打包出 pageA.js 與 pageB.js 兩份產(chǎn)物。

組合變量

Parallel-Webpack 還提供了 createVariants 函數(shù),用于根據(jù)給定變量組合,生成多份 Webpack 配置對象,如:

  1. const createVariants = require('parallel-webpack').createVariants 
  2. const webpack = require('webpack'
  3.  
  4.  
  5. const baseOptions = { 
  6.   entry: './index.js' 
  7.  
  8.  
  9. // 配置變量組合 
  10. // 屬性名為 webpack 配置屬性;屬性值為可選的變量 
  11. // 下述變量組合將最終產(chǎn)生 2*2*4 = 16 種形態(tài)的配置對象 
  12. const variants = { 
  13.   minified: [truefalse], 
  14.   debug: [truefalse], 
  15.   target: ['commonjs2''var''umd''amd'
  16.  
  17.  
  18. function createConfig (options) { 
  19.   const plugins = [ 
  20.     new webpack.DefinePlugin({ 
  21.       DEBUG: JSON.stringify(JSON.parse(options.debug)) 
  22.     }) 
  23.   ] 
  24.   return { 
  25.     output: { 
  26.       path: './dist/'
  27.       filename: 'MyLib.' + 
  28.                 options.target + 
  29.                 (options.minified ? '.min' : '') + 
  30.                 (options.debug ? '.debug' : '') + 
  31.                 '.js' 
  32.     }, 
  33.     plugins: plugins 
  34.   } 
  35.  
  36.  
  37. module.exports = createVariants(baseOptions, variants, createConfig) 

上述示例使用 createVariants 函數(shù),根據(jù) variants 變量搭配出 16 種不同的 minified、debug、target 組合,最終生成如下產(chǎn)物:

  1. [WEBPACK] Building 16 targets in parallel 
  2. [WEBPACK] Started building MyLib.umd.js 
  3. [WEBPACK] Started building MyLib.umd.min.js 
  4. [WEBPACK] Started building MyLib.umd.debug.js 
  5. [WEBPACK] Started building MyLib.umd.min.debug.js 
  6.  
  7. [WEBPACK] Started building MyLib.amd.js 
  8. [WEBPACK] Started building MyLib.amd.min.js 
  9. [WEBPACK] Started building MyLib.amd.debug.js 
  10. [WEBPACK] Started building MyLib.amd.min.debug.js 
  11.  
  12. [WEBPACK] Started building MyLib.commonjs2.js 
  13. [WEBPACK] Started building MyLib.commonjs2.min.js 
  14. [WEBPACK] Started building MyLib.commonjs2.debug.js 
  15. [WEBPACK] Started building MyLib.commonjs2.min.debug.js 
  16.  
  17. [WEBPACK] Started building MyLib.var.js 
  18. [WEBPACK] Started building MyLib.var.min.js 
  19. [WEBPACK] Started building MyLib.var.debug.js 
  20. [WEBPACK] Started building MyLib.var.min.debug.js 

原理

parallel-webpack 的實(shí)現(xiàn)非常簡單,基本上就是在 Webpack 上套了個(gè)殼,核心邏輯:

  • 根據(jù)傳入的配置項(xiàng)數(shù)量,調(diào)用 worker-farm 創(chuàng)建復(fù)數(shù)個(gè)工作進(jìn)程
  • 工作進(jìn)程內(nèi)調(diào)用 Webpack 執(zhí)行構(gòu)建
  • 工作進(jìn)程執(zhí)行完畢后,調(diào)用 node-ipc 向主進(jìn)程發(fā)送結(jié)束信號(hào)

到這里,所有工作就完成了。

缺點(diǎn)

雖然,parallel-webpack 相對于 Thread-loader、HappyPack 有更高的并行度,但進(jìn)程實(shí)例與實(shí)例之間并沒有做任何形式的通訊,這可能導(dǎo)致相同的工作在不同進(jìn)程 —— 或者說不同 CPU 核上被重復(fù)執(zhí)行。例如需要對同一份代碼同時(shí)打包出壓縮和非壓縮版本時(shí),在 parallel-webpack 方案下,前置的資源加載、依賴解析、AST 分析等操作會(huì)被重復(fù)執(zhí)行,僅僅最終階段生成代碼時(shí)有所差異。

這種技術(shù)實(shí)現(xiàn),對單 entry 的項(xiàng)目沒有任何收益,只會(huì)徒增進(jìn)程創(chuàng)建成本;但特別適合 MPA 等多 entry 場景,或者需要同時(shí)編譯出 esm、umd、amd 等多種產(chǎn)物形態(tài)的類庫場景。

并行壓縮

Webpack 語境下通常使用 Uglify-js、Uglify-es、Terser 做代碼混淆壓縮,三者都不同程度上原生實(shí)現(xiàn)了多進(jìn)程并行壓縮功能。

TerserWebpackPlugin 完整介紹:https://webpack.js.org/plugins/terser-webpack-plugin/

以 Terser 為例,插件 TerserWebpackPlugin 默認(rèn)已開啟并行壓縮能力,通常情況下保持默認(rèn)配置即 parallel = true 即可獲得最佳的性能收益。開發(fā)者也可以通過 parallel 參數(shù)關(guān)閉或設(shè)定具體的并行進(jìn)程數(shù)量,例如:

  1. const TerserPlugin = require("terser-webpack-plugin"); 
  2.  
  3.  
  4. module.exports = { 
  5.     optimization: { 
  6.         minimize: true
  7.         minimizer: [new TerserPlugin({ 
  8.             parallel: 2 // number | boolean 
  9.         })], 
  10.     }, 
  11. }; 

上述配置即可設(shè)定最大并行進(jìn)程數(shù)為2。

對于 Webpack 4 及之前的版本,代碼壓縮插件 UglifyjsWebpackPlugin 也有類似的功能與配置項(xiàng),此處不再贅述。

最佳實(shí)踐

理論上,并行確實(shí)能夠提升系統(tǒng)運(yùn)行效率,但 Node 單線程架構(gòu)下,所謂的并行計(jì)算都只能依托與派生子進(jìn)程執(zhí)行,而創(chuàng)建進(jìn)程這個(gè)動(dòng)作本身就有不小的消耗 —— 大約 600ms,因此建議讀者按實(shí)際需求斟酌使用上述多進(jìn)程方案。

對于小型項(xiàng)目,構(gòu)建成本可能很低,但引入多進(jìn)程技術(shù)反而導(dǎo)致整體成本增加。

對于大型項(xiàng)目,由于 HappyPack 官方已經(jīng)明確表示不維護(hù),所以建議盡量使用 Thread-loader 組件提升 Make 階段性能。生產(chǎn)環(huán)境下還可配合 terser-webpack-plugin 的并行壓縮功能,提升整體效率。

【編輯推薦】

 

責(zé)任編輯:姜華 來源: Tecvan
相關(guān)推薦

2016-10-09 20:15:30

多線程多進(jìn)程

2024-12-27 08:11:44

Python編程模式IO

2017-06-30 10:12:46

Python多進(jìn)程

2020-09-19 21:26:56

webpack

2020-07-10 12:06:28

WebpackBundleless瀏覽器

2010-07-15 12:51:17

Perl多進(jìn)程

2023-11-01 11:20:57

2024-05-27 00:00:01

2021-10-25 10:23:49

Webpack 前端Tree shakin

2012-08-08 09:32:26

C++多進(jìn)程并發(fā)框架

2022-02-10 14:23:16

WebpackJavaScript

2024-03-29 06:44:55

Python多進(jìn)程模塊工具

2016-01-11 10:29:36

Docker容器容器技術(shù)

2021-09-27 08:16:38

Webpack 前端Cache

2021-12-16 22:02:28

webpack原理模塊化

2024-08-26 08:39:26

PHP孤兒進(jìn)程僵尸進(jìn)程

2009-04-21 09:12:45

Java多進(jìn)程運(yùn)行

2021-02-25 11:19:37

谷歌Android開發(fā)者

2019-02-26 11:15:25

進(jìn)程多線程多進(jìn)程

2021-06-28 05:59:17

Webpack 前端打包與工程化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)