中無主而不止,外無正而不行。——莊子javascript
如今前端開發基本上都會用到react
、vue
,用到了前端mvc
、mvvm
框架,基本上都會涉及到打包發佈,打包經常使用的工具就是webpack
、gulp
等等。常常使用天然也要了解一些他大體的流程也會方便使用。 首先要理解webpack中比較核心的概念:前端
coding split
的產物,咱們能夠對一些代碼打包成一個單獨的chunk
,好比某些公共模塊,去重,更好的利用緩存。或者按需加載某些功能模塊,優化加載時間。在webpack3
及之前咱們都利用CommonsChunkPlugin
將一些公共代碼分割成一個chunk
,實現單獨加載。在webpack4
中CommonsChunkPlugin
被廢棄,使用SplitChunksPlugin
webpack 執行流程和事件流以下圖所示:vue
webpack編譯過程當中一個比較重要的概念compiler、compilation,以下:java
Compiler
實例中包含了完整的 webpack
配置,全局只有一個 Compiler
實例。webpack
以開發模式運行時,每當檢測到文件變化,一次新的 Compilation
將被建立。一個 Compilation
對象包含了當前的模塊資源、編譯生成資源、變化的文件等。Compilation
對象也提供了不少事件回調供插件作擴展。Webpack的運行流程是一個串行的過程,從啓動到結束依次執行如下流程:react
若是隻執行一次構建,以上階段將會按照順序各執行一次。但在開啓監聽模式下,流程將變爲以下: webpack
下面具體介紹一下 webpack
的三個大階段具體的小步。git
初始化階段大體分爲:github
shell
和配置文件文件的參數而且實例化Complier對象。事件名 | 解釋 |
---|---|
初始化參數 | 從配置文件和 Shell 語句中讀取與合併參數,得出最終的參數。 這個過程當中還會執行配置文件中的插件實例化語句 new Plugin()。 |
實例化 Compiler | 用上一步獲得的參數初始化 Compiler 實例,Compiler 負責文件監聽和啓動編譯。Compiler 實例中包含了完整的 Webpack 配置,全局只有一個 Compiler 實例。 |
加載插件 | 依次調用插件的 apply 方法,讓插件能夠監聽後續的全部事件節點。同時給插件傳入 compiler 實例的引用,以方便插件經過 compiler 調用 Webpack 提供的 API 。 |
environment | 開始應用 Node.js 風格的文件系統到 compiler 對象,以方便後續的文件尋找和讀取。 |
entry-option | 讀取配置的 Entrys ,爲每一個 Entry 實例化一個對應的 EntryPlugin ,爲後面該 Entry 的遞歸解析工做作準備。 |
after-plugins | 調用完全部內置的和配置的插件的 apply 方法。 |
after-resolvers | 根據配置初始化完 resolver ,resolver 負責在文件系統中尋找指定路徑的文件。 |
事件名 | 解釋 |
---|---|
before-run | 清除緩存 |
run | 啓動一次新的編譯。 |
watch-run | 和 run 相似,區別在於它是在監聽模式下啓動的編譯,在這個事件中能夠獲取到是哪些文件發生了變化致使從新啓動一次新的編譯。 |
compile | 該事件是爲了告訴插件一次新的編譯將要啓動,同時會給插件帶上 compiler 對象。 |
compilation | 當 Webpack 以開發模式運行時,每當檢測到文件變化,一次新的 Compilation 將被建立。一個 Compilation 對象包含了當前的模塊資源、編譯生成資源、變化的文件等。Compilation 對象也提供了不少事件回調供插件作擴展。 |
make | 一個新的 Compilation 建立完畢,即將從 Entry 開始讀取文件,根據文件類型和配置的 Loader 對文件進行編譯,編譯完後再找出該文件依賴的文件,遞歸的編譯和解析。 |
after-compile | 一次 Compilation 執行完成。這裏會根據編譯結果 合併出咱們最終生成的文件名和文件內容。 |
invalid | 當遇到文件不存在、文件編譯錯誤等異常時會觸發該事件,該事件不會致使 Webpack 退出。 |
這裏主要最重要的就是compilation
過程,compilation
實際上就是調用相應的 loader
處理文件生成 chunks
並對這些 chunks
作優化的過程。幾個關鍵的事件(Compilation
對象this.hooks
中):web
事件名 | 解釋 |
---|---|
build-module | 使用對應的 Loader 去轉換一個模塊。 |
normal-module-loader | 在用 Loader 對一個模塊轉換完後,使用 acorn 解析轉換後的內容,輸出對應的抽象語法樹(AST ),以方便 Webpack 後面對代碼的分析。 |
program | 從配置的入口模塊開始,分析其 AST ,當遇到 require 等導入其它模塊語句時,便將其加入到依賴的模塊列表,同時對新找出的依賴模塊遞歸分析,最終搞清全部模塊的依賴關係。 |
seal | 全部模塊及其依賴的模塊都經過 Loader 轉換完成後,根據依賴關係開始生成 Chunk 。 |
事件名 | 解釋 |
---|---|
should-emit | 全部須要輸出的文件已經生成好,詢問插件哪些文件須要輸出,哪些不須要。 |
emit | 肯定好要輸出哪些文件後,執行文件輸出,能夠在這裏獲取和修改輸出內容。 |
after-emit | 文件輸出完畢。 |
done | 成功完成一次完成的編譯和輸出流程。 |
failed | 若是在編譯和輸出流程中遇到異常致使 Webpack 退出時,就會直接跳轉到本步驟,插件能夠在本事件中獲取到具體的錯誤緣由。 |
Webpack
能夠將其理解是一種基於事件流的編程範例,一個插件合集。而將這些插件控制在webapck
事件流上的運行的就是webpack
本身寫的基礎類Tapable
。Webpack
的事件流機制應用了觀察者模式,和 Node.js
中的 EventEmitter
很是類似。 Tapable 有四組成員函數:shell
Tapable
實例 的事件中。它的行爲和 EventEmitter
的 on()
方法類似,用來註冊一個處理函數/監聽器,來在信號/事件發生時作一些事情。(AnyPlugin|function)[]):AnyPlugin
應該是一個擁有 apply
方法的類(也能夠是一個對象,可是不常見),或者只是一個包含註冊代碼的函數。這個方法只調用插件的定義,從而將真正的事件監聽器能夠註冊到 Tapable
實例的註冊列表中。Tapable
實例能夠經過使用這些函數,在指定的 hash
下應用全部的插件。這一組方法的行爲和 EventEmitter
的 emit()
方法類似,使用多種策略細緻地控制事件的觸發。Tapable
的原型。上面核心的對象 Compiler
、Compilation
等都是繼承於Tabable
類。能夠直接在 Compiler
和 Compilation
對象上廣播和監聽器,方法以下:
/** * 廣播出事件 * event-name 爲事件名稱,注意不要和現有的事件重名 * params 爲附帶的參數 */
compiler.apply('event-name',params);
/** * 監聽名稱爲 event-name 的事件,當 event-name 事件發生時,函數就會被執行。 * 同時函數中的 params 參數爲廣播事件時附帶的參數。 */
compiler.plugin('event-name',function(params) {
doSomeThing();
});
複製代碼
同理,compilation.apply
和 compilation.plugin
使用方法和上面一致。
tapable庫暴露了不少Hook(鉤子)類,爲插件提供掛載的鉤子。
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
複製代碼
以下圖所示tapable上的鉤子:
tabable的提供了兩類綁定鉤子的方式:tapAsync
或 tapPromise
(以及 tap
),執行經過 callAsync
、promise
;tap
,執行經過 call
;具體的用法請看Tapable。
Webpack 的運行流程是一個串行的過程,從啓動到結束會依次執行如下流程:
同時咱們也瞭解了webpack中比較核心的幾個概念compiler
、compilation
、tapable
。