在閱讀 webpack4.x
源碼的過程當中,參考了《深刻淺出webpack》一書和衆多大神的文章,結合本身的一點體會,總結以下。前端
webpack
就像一條生產線,要通過一系列處理流程後才能將源文件轉換成輸出結果。 這條生產線上的每一個處理流程的職責都是單一的,多個流程之間有存在依賴關係,只有完成當前處理後才能交給下一個流程去處理。 插件就像是一個插入到生產線中的一個功能,在特定的時機對生產線上的資源作處理。 webpack
經過 Tapable
來組織這條複雜的生產線。 webpack
在運行過程當中會廣播事件,插件只須要監聽它所關心的事件,就能加入到這條生產線中,去改變生產線的運做。 webpack
的事件流機制保證了插件的有序性,使得整個系統擴展性很好。 --吳浩麟《深刻淺出webpack》webpack
entry
,loader
,plugin
,module
,chunk
不論文檔仍是相關的介紹都不少了,不贅述,有疑問的移步文檔。git
webpack
的運行流程是一個串行的過程,從啓動到結束會依次執行如下流程:github
Shell
語句中讀取與合併參數,得出最終的參數;Compiler
對象,加載全部配置的插件,執行對象的 run
方法開始執行編譯;Chunk
,再把每一個 Chunk
轉換成一個單獨的文件加入到輸出列表,這步是能夠修改輸出內容的最後機會;webpack
會在特定的時間點廣播出特定的事件,插件在監聽到感興趣的事件後會執行特定的邏輯,而且插件能夠調用 webpack
提供的 API 改變 webpack
的運行結果。Compile
對象:負責文件監聽和啓動編譯。Compiler
實例中包含了完整的 webpack
配置,全局只有一個 Compiler
實例。compilation
對象:當 webpack
以開發模式運行時,每當檢測到文件變化,一次新的 Compilation
將被建立。一個 Compilation
對象包含了當前的模塊資源、編譯生成資源、變化的文件等。Compilation
對象也提供了不少事件回調供插件作擴展。Compile
爲例const {
Tapable,
SyncHook,
SyncBailHook,
AsyncParallelHook,
AsyncSeriesHook
} = require("tapable");
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
//全部須要輸出的文件已經生成好,詢問插件哪些文件須要輸出,哪些不須要。
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
//成功完成一次完成的編譯和輸出流程。
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
//啓動一次新的編譯
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
// 肯定好要輸出哪些文件後,執行文件輸出,能夠在這裏獲取和修改輸出內容。
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
// 輸出完畢
afterEmit: new AsyncSeriesHook(["compilation"]),
// 以上幾個事件(除了run,beforerun爲編譯階段)其他爲輸出階段的事件
/** @type {SyncHook<Compilation, CompilationParams>} */
// compilation 建立以前掛載插件的過程
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
// 建立compilation對象
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<NormalModuleFactory>} */
// 初始化階段:初始化compilation參數
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<ContextModuleFactory>} */
// 初始化階段:初始化compilation參數
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<CompilationParams>} */
// 該事件是爲了告訴插件一次新的編譯將要啓動,同時會給插件帶上 compiler 對象
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<Compilation>} */
//一個新的 Compilation 建立完畢,即將從 Entry 開始讀取文件,根據文件類型和配置的 Loader 對文件進行編譯,編譯完後再找出該文件依賴的文件,遞歸的編譯和解析。
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
// 一次Compilation執行完成
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
//監聽模式下啓動編譯(經常使用於開發階段)
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
// 如名字所述
watchClose: new SyncHook([]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
//初始化階段:開始應用 Node.js 風格的文件系統到compiler 對象,以方便後續的文件尋找和讀取。
environment: new SyncHook([]),
/** @type {SyncHook} */
// 參照上文
afterEnvironment: new SyncHook([]),
/** @type {SyncHook<Compiler>} */
// 調用完內置插件以及配置引入插件的apply方法,完成了事件訂閱
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<string, EntryOptions>} */
// 讀取配置的 Entrys,爲每一個 Entry 實例化一個對應的 EntryPlugin,爲後面該 Entry 的遞歸解析工做作準備。
entryOption: new SyncBailHook(["context", "entry"])
};
複製代碼
在 webpack
執行的過程當中,會按順序廣播一系列事件--this.hooks
中的一系列事件(相似於咱們經常使用框架中的生命週期),而這些事件的訂閱者該按照怎樣的順序來組織,來執行,來進行參數傳遞... 這就是 Tapable
要作的事情。
關於 Tapable
給你們推薦一篇比較好(可是閱讀量點贊評論都很少2333)的科普文web
流程細節參照我在引用的Compile
對象中的註釋,有一點須要注意,做者hooks
的書寫順序並非調用順序。 有些沒註釋的有幾種狀況:bash
compilation
實際上就是調用相應的 loader
處理文件生成 chunks
並對這些 chunks
作優化的過程。幾個關鍵的事件(Compilation對象this.hooks中):app
buildModule
使用對應的 Loader
去轉換一個模塊;normalModuleLoader
在用 Loader
對一個模塊轉換完後,使用 acorn
解析轉換後的內容,輸出對應的抽象語法樹(AST),以方便 webpack
後面對代碼的分析。seal
全部模塊及其依賴的模塊都經過 Loader
轉換完成後,根據依賴關係開始生成 Chunk
。最後從參考文章中摘了一張圖片以便於對整個過程有更清晰的認知 框架
本文發佈於薄荷前端週刊,歡迎Watch & Star ★,轉載請註明出處。post