本文使用「署名 4.0 國際 (CC BY 4.0)」 許可協定,歡迎轉載、或重新修改使用,但需要注明來源。
作者: 百應前端團隊 @雙魚
entry:入口。webpack是基于子產品的,使用webpack首先需要指定子產品解析入口(entry),webpack從入口開始根據子產品間依賴關系遞歸解析和處理所有資源檔案。
output:輸出。源代碼經過webpack處理之後的最終産物。
loader:子產品轉換器。本質就是一個函數,在該函數中對接收到的内容進行轉換,傳回轉換後的結果。因為 Webpack 隻認識 JavaScript,是以 Loader 就成了翻譯官,對其他類型的資源進行轉譯的預處理工作。
plugin:擴充插件。基于事件流架構 Tapable,插件可以擴充 Webpack 的功能,在 Webpack 運作的生命周期中會廣播出許多事件,Plugin 可以監聽這些事件,在合适的時機通過 Webpack 提供的 API 改變輸出結果。
module:子產品。除了js範疇内的es module、commonJs、AMD等,css @import、url(...)、圖檔、字型等在webpack中都被視為子產品。
另外webpack4開始 mode 變成一個重要概念,webpack為不同 mode提供了一些預設值,附上阮一峰老師的吐槽

不同mode的預設配置如下:
初始化參數:從配置檔案和 Shell 語句中讀取與合并參數,得出最終的參數;
初始化編譯:用上一步得到的參數初始化 Compiler 對象,注冊插件并傳入 Compiler 執行個體(挂載了衆多webpack事件api供插件調用);
AST & 依賴圖:從入口檔案(entry)出發,調用AST引擎(acorn)生成抽象文法樹AST,根據AST構模組化塊的所有依賴;
遞歸編譯子產品:調用所有配置的 Loader 對子產品進行編譯;
輸出資源:根據入口和子產品之間的依賴關系,組裝成一個個包含多個子產品的 Chunk,再把每個 Chunk 轉換成一個單獨的檔案加入到輸出清單,這步是可以修改輸出内容的最後機會;
輸出完成:在确定好輸出内容後,根據配置确定輸出的路徑和檔案名,把檔案内容寫入到檔案系統;
在以上過程中,Webpack 會在特定的時間點廣播出特定的事件,插件在監聽到相關事件後會執行特定的邏輯,并且插件可以調用 Webpack 提供的 API 改變 Webpack 的運作結果
Tapable:一個基于釋出訂閱的事件流工具類,Compiler 和 Compilation 對象都繼承于 Tapable
Compiler:webpack編譯貫穿始終的核心對象,在編譯初始化階段被建立的全局單例,包含完整配置資訊、loaders、plugins以及各種工具方法
Compilation:代表一次 webpack 建構和生成編譯資源的的過程,在watch模式下每一次檔案變更觸發的重新編譯都會生成新的 Compilation 對象,包含了目前編譯的子產品 module, 編譯生成的資源,變化的檔案, 依賴的狀态等
更加細化的建構流程圖:
看大圖點這裡????
流程圖出處:淘系前端團隊-細說 webpack 之流程篇
loader就像一個翻譯官,将源檔案經過轉換後生成目标檔案并交由下一流程處理
每個loader職責都是單一的,就像流水線上的勞工
順序很關鍵(從右往左)
簡單【Simple】loader隻做單一任務,多個loader > 一個多功能loader
鍊式【Chaining】遵循鍊式調用原則
無狀态【Stateless】即函數式裡的Pure Function,無副作用
使用工具庫【Loader Utilities】充分利用 loader-utils 包
在webpack編譯整個生命周期的特定節點執行特定功能
一個命名JS函數或者JS類
在prototype上定義一個apply方法(供webpack調用,并且在調用時注入 compiler 對象)
在 apply 函數中需要有通過 compiler 對象挂載的 webpack 事件鈎子(鈎子函數中能拿到目前編譯的 compilation 對象)
處理 webpack 内部執行個體的特定資料
功能完成後調用 webpack 提供的回調
compiler.hooks 上挂載了不同時期觸發的webpack事件函數(類似于React生命周期),可以在編譯的各個階段執行其他邏輯或者改變輸出結果,具體支援的事件清單可以看這裡:compiler-hooks
webpack 的插件架構主要基于 Tapable 實作的,Tapable 是 webpack 項目組的一個内部庫,主要是抽象了一套插件機制。它類似于 NodeJS 的 EventEmitter 類,專注于自定義事件的觸發和操作。
Tapable事件類型分為同步和異步,内部又以不同的規則分為不同類型,上述事件的具體差別可以看 這篇文章,了解這些事件的差別和應用場景有助于我們了解webpack源碼和編寫Plugin
在webpack啟動時被初始化一次,全局唯一,可以了解為webpack編譯執行個體,它包含了webpack原始配置、Loader、Plugin引用、各種鈎子
部分源碼:https://github.com/webpack/webpack/blob/10282ea20648b465caec6448849f24fc34e1ba3e/lib/webpack.js
使用 speed-measure-webpack-plugin 測量打包速度
使用 webpack-bundle-analyzer 進行體積分析
從某項目的分析圖可以看出一個很明顯的優化空間就是 BizCharts 沒有按需引入,這時候我們可以import路徑再執行一次打包分析看效果。
另外圖中每個子產品都有三種Size,分别是 Stat Size、Parsed Size、Gzipped Size,這三者的分别代表什麼含義可以看下插件的github issue
思路主要是優化搜尋時間、縮小檔案搜尋範圍、減少不必要的編譯工作,具體做法可以看以下配置檔案
合理使用DLLPlugin将更改頻率較低的代碼(三方庫)移到單獨的編譯中,我了解大部分場景下和配置 externals 作用是差不多的(都不用打包三方庫),但是 externals 在某些場景下會存在失效問題,具體可以看 這篇文章,另外 DLLPlugin 具體使用 參考這裡
多程序陣營裡有幾位知名選手:
thread-loader(v4以後的官方推薦)
happypack(不怎麼維護了)
parallel-webpack(不怎麼維護了)
這裡隻介紹一下 thread-loader ,使用 thread-loader 将開銷較大的 loader(例如babel-loader)放到獨立程序中(官方描述 worker pool)處理,使用上有以下注意事項
将其放在需要單獨加載的loader的前面,順序很關鍵
worker pool中的loader使用上是有限制的,例如無法使用自定義 loader api,無法擷取webpack 配置項
目前項目在用的插件是 hard-source-webpack-plugin,效果較為顯著,不過缺點有3
生成的緩存檔案較大,比較占用磁盤空間(之前還出現過釋出的時候誤把緩存檔案上傳到伺服器導緻釋出特别慢的情況 =。=,是以最好還是指定緩存檔案路徑為 node_modules 内部)
這個倉庫也很久沒更新了
現有項目偶爾會出現更改代碼不觸發重新編譯的情況,猜測可能與此插件有關
另外 webpack5 是否有自帶的緩存政策或者官方維護的緩存插件還需要去了解一下
webpack3配置optimization.minimize = true會預設啟用 UglifyJsPlugin,其多程序版本為 ParallelUglifyPlugin
webpack4 中 webpack.optimize.UglifyJsPlugin 已被廢棄,預設内置使用 terser-webpack-plugin 插件壓縮優化代碼,原生支援多程序(這裡想起官方文檔 Build Performance 章節中列舉的優化措施第一點:Stay Up to Date,最香的還是最新的webpack版本)
官方文檔描述的code splitting的3種姿勢:
多entry配置(多entry是天然的code splitting,但是基本沒人會因為性能優化的點去把一個單頁應用改成多entry)
使用 SplitChunksPlugin 進行重複資料删除和提取
使用 Dynamic Import 指定子產品拆分,并且可以結合 preload、prefetch做更多使用者體驗上的優化
HMR的原理?
Tree shaking原理,為什麼需要es module的寫法?
webpack5的Module Federation有哪些優勢,在與http2.0的結合上有哪些有趣的事情,在微前端上的應用?
為什麼說rollup比webpack更适合打包元件庫?