總體
- 簡單來說:
Webpack 透過內部的 事件流機制 ,保證了外掛的有序性
Webpack 底層利用了 釋出訂閱模式,在執行過程中會廣播事件
Webpack 外掛只需要監聽它所關心的事件,在特定的時機對資源做處理 - 站在程式碼邏輯的角度:
Webpack 在編譯過程式碼程中,會觸發一系列 Tapable 鉤子事件
外掛需要找到相應的鉤子,在上面新增自己的任務(註冊事件)
當 Webpack 構建時,外掛註冊的事件,會隨著鉤子的觸發而執行
打包過程
- 版本一:
- Webpack 的編譯流程分為以下幾個階段
初始化階段(initialize):在這個階段,Webpack 會讀取配置檔案,並初始化 Compiler 和 Compilation 物件。
編譯階段(compile):在這個階段,Webpack 會從入口檔案開始,遞迴地分析模組之間的依賴關係,生成模組圖。
構建階段(make):在這個階段,Webpack 會根據模組圖,將每個模組轉換成一個或多個 Chunk,並生成對應的程式碼塊。
輸出階段(emit):在這個階段,Webpack 會將生成的 Chunk 和程式碼塊輸出到檔案系統中。
完成階段(done):在這個階段,Webpack 編譯完成,輸出資訊到控制檯。
- 版本二:
- 一次完整的 Webpack 打包,大致是這樣的過程:
將 cli 命令列引數與 Webpack 配置檔案合併、解析得到引數物件 options,用於啟用 webpack 的loader和外掛
把引數物件 options 傳給 Webpack, 會建立生成 Compiler 物件,並初始化基礎外掛
執行 Compiler 的 run 方法開始編譯。每次執行 run 編譯都會生成一個 Compilation 物件,也就是說,Compilation 物件負責一次編譯過程
觸發 Compiler 的 make 方法分析入口檔案,呼叫 compilation 的 buildModule 方法建立主模組物件
生成入口檔案 AST(抽象語法樹),透過 AST 分析和遞迴載入依賴模組
所有模組分析完成後,執行 compilation 的 seal 方法對每個 chunk 進行整理、最佳化、封裝
最後執行 Compiler 的 emitAssets 方法,把生成的檔案輸出到 output 的目錄中
- 版本三:
初始化
webpack會將cli引數、配置檔案、預設配置進行融合,形成一個最終的配置物件
對配置的處理過程是依託yargs的三方包去合併配置
可以簡單的理解為,初始化階段主要用於產生一個最終的配置
構建
chunk從入口開始分析依賴關係(形成一個chunk)
為什麼在讀取內容之後要做語法分析?因為要知道該模組依賴誰,會轉換成AST,對樹形結構的遍歷,分析裡面的依賴關係,然後會把依賴記錄記錄在一個陣列中,叫dependencies。
產生chunk assets
在經過第二步完成後,chunk中會產生一個模組列表,列表中包含了模組id和模組轉換後的程式碼。
接下來webpack會根據配置,為chunk生成一個資源列表,即chunk assets。資源列表可以理解為生成到最終檔案的檔名和資料夾。
chunk hash是根據所有chunk assets的內容生成的一個hash字串
hash是一種演算法,具體有很多分類,特點是將一個任意長度的字串轉化為一個固定長度的字串,而且可以保證原始內容不變,產生的hash串不變。
合併chunk assets
將多個chunk的assets合併在一起,併產生一個總的hash
輸出
emit階段
webpack透過node中的fs模組,建立相應的檔案,根據編譯產生的總的assets,生成相應的檔案。
總過程
術語
module:模組,分割的程式碼但願,webpack中的模組可以是任何內容的檔案,不限於js
chunk:webpack內部構件模組的塊,一個chunk中包含多個模組,這些模組是從入口模組透過依賴分析得來的。
bundle:chunk構建好模組後會生成chunk的資源清單,清單中的每一項就是一個bundle,可以認為bundle就是最終生成的檔案
hash:最終的資源清單所有內容聯合生成的hash值
chunkhash:chunk生成的資源清單內容聯合生成的hash值
chunkname:chunk的名稱,預設main
id:通常指chunk的唯一編號,如果在開發環境下構建,和chunkname相同;如果是生產環境下構建,則使用一個從0開始的數字進行編號。
Hash
var hash = crypto.createHash('sha256')
// Use update to add data
.update('I love GeeksForGeeks')
// Use digest to get the hash value
.digest('hex');
.createHash建立一個hash例項,引數是使用何種加密演算法
.update新增加密內容
.digest獲得hash值,引數返回輸出的型別。
如何提升 webpack 編譯時期計算 hash 的速度
- 使用快取:可以使用快取來避免重複計算,提高編譯速度。可以使用 Webpack 自帶的快取,也可以使用第三方外掛如 hard-source-webpack-plugin。
- 減少檔案數量:可以透過減少需要編譯的檔案數量來提高編譯速度。可以透過忽略不必要的檔案或使用 include 和 exclude 配置選項來實現。
- 使用更快的 hash 演算法:可以使用更快的 hash 演算法來提高計算速度。可以在 Webpack 配置中設定 output.hashFunction 和 output.hashDigest 來選擇不同的 hash 演算法和輸出格式。
- 升級 Webpack 版本:新版本的 Webpack 可能會有更好的效能和最佳化。
- 使用多執行緒編譯:可以使用多執行緒編譯來提高編譯速度。可以使用 thread-loader 或 happypack 來實現。
是否可以將版本號放在檔名中?
const package = require('./package.json')
const config = {
output: {
filename: `${package.version}.{hash}.js`
}
}
不可以,因為每次版本號的改變,這將「導致所有快取都失效」,而每次版本升級時,並不一定所有資源內容都會進行變更。
hash 是如何生成的
基於模組內容以及一系列元資訊生成摘要資訊
_initBuildHash(compilation) {
const hash = createHash(compilation.outputOptions.hashFunction);
if (this._source) {
hash.update("source");
this._source.updateHash(hash);
}
hash.update("meta");
hash.update(JSON.stringify(this.buildMeta));
this.buildInfo.hash = /** @type {string} */ (hash.digest("hex"));
}
SplitChunksPlugin和CommonChunksPlugin
- SplitChunksPlugin 外掛是用來提取公共模組的,它的原理是將多個入口 chunk 中相同的模組提取出來,形成一個單獨的 chunk。這樣可以避免程式碼重複,減小打包後的檔案體積,同時也可以利用瀏覽器的快取機制,提高頁面載入速度。
- SplitChunksPlugin 外掛只能處理非同步模組(即透過 import() 或動態匯入語法載入的模組)。如果需要處理同步模組,可以使用另一個外掛:CommonsChunkPlugin。
- 在 Webpack 4 中,CommonsChunkPlugin 外掛已經被廢棄,取而代之的是 optimization.splitChunks 配置選項。該選項使用了更先進的演算法來提取公共模組
常見的外掛
tree-shaking
tree-shaking 是透過靜態程式碼分析來實現的。它的原理是基於 ES6 模組化規範中的靜態特性,即模組的匯入和匯出關係是確定的,可以在編譯時確定。
當 Webpack 進行打包時,會從入口模組開始,遞迴地分析模組之間的依賴關係,生成模組圖。在這個過程中,Webpack 會標記每個模組中匯出的變數和函式,並且會記錄它們被哪些模組匯入和使用。
在生成程式碼時,Webpack 會根據這些資訊,將沒有被使用的變數和函式從打包結果中刪除。這個過程就是 tree-shaking。
需要注意的是,tree-shaking 只能刪除沒有被使用的程式碼,而不能刪除被使用的但沒有被匯出的程式碼。
- tree-shaking生效前提:
使用 ES6 模組化規範進行開發。
在配置檔案中開啟 optimization.usedExports 選項。
在生產環境中使用 UglifyJS 等工具進行程式碼壓縮。
只能處理靜態的 import 和 export 語句,無法處理動態的匯入語句(例如 import())。
Webpack 編譯速度最佳化
- 使用持久化快取:Webpack 4 引入了持久化快取,可以將快取資料儲存到磁碟上,從而避免在每次重新啟動 Webpack 時都需要重新生成快取。可以透過在 webpack.config.js 中配置 cache.type 和 cache.cacheDirectory 來啟用持久化快取。
- 使用多程序並行編譯:Webpack 5 引入了多程序並行編譯的功能,可以在多個程序中同時編譯不同的模組,從而提高編譯速度。可以透過在 webpack.config.js 中配置 parallelism 和 minimizer.parallelism 來啟用多程序並行編譯。
- 啟用模組熱替換(HMR):模組熱替換是一種開發時的最佳化技巧,可以在程式碼修改後只重新編譯修改的模組,從而提高開發效率。可以透過在 webpack.config.js 中配置 devServer.hot 和 plugins 來啟用模組熱替換。
- 使用快取外掛:Webpack 有一些外掛可以幫助最佳化快取,例如 hard-source-webpack-plugin 和 cache-loader。hard-source-webpack-plugin 可以在編譯過程中使用硬碟快取來加速編譯,而 cache-loader 可以在每個 loader 上使用快取來避免重複編譯。
- 避免使用絕對路徑:在 Webpack 中,使用絕對路徑會導致快取失效,因為不同的機器上的絕對路徑可能不同。可以使用相對路徑來避免這個問題。
- 避免使用動態匯入語句:在 Webpack 中,使用動態匯入語句會導致快取失效,因為無法確定匯入的模組和變數。可以使用靜態匯入語句來避免這個問題。