在閱讀 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
的運行結果。web
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)的科普文app
流程細節參照我在引用的Compile
對象中的註釋,有一點須要注意,做者hooks
的書寫順序並非調用順序。
有些沒註釋的有幾種狀況:框架
這裏補充一個大從參考文章裏面找來的圖post
compilation
實際上就是調用相應的 loader
處理文件生成 chunks
並對這些 chunks
作優化的過程。幾個關鍵的事件(Compilation對象this.hooks中):優化
buildModule
使用對應的 Loader
去轉換一個模塊;normalModuleLoader
在用 Loader
對一個模塊轉換完後,使用 acorn
解析轉換後的內容,輸出對應的抽象語法樹(AST),以方便 webpack
後面對代碼的分析。seal
全部模塊及其依賴的模塊都經過 Loader
轉換完成後,根據依賴關係開始生成 Chunk
。最後從參考文章中摘了一張圖片以便於對整個過程有更清晰的認知
ui
本文發佈於薄荷前端週刊,歡迎Watch & Star ★,轉載請註明出處。