摘要:開始對進行遍歷,當遇到等一些調用表達式時,觸發事件的執行,收集依賴,并。
1、Tapable
Tap 的英文單詞解釋,除了最常用的 點擊 手勢之外,還有一個意思是 水龍頭 —— 在 webpack 中指的是后一種;
Webpack 可以認為是一種基于事件流的編程范例,內部的工作流程都是基于 插件 機制串接起來;
而將這些插件粘合起來的就是webpack自己寫的基礎類 Tapable 是,plugin方法就是該類暴露出來的;
后面我們將看到核心的對象 Compiler、Compilation 等都是繼承于該對象
基于該類規范而其的 Webpack 體系保證了插件的有序性,使得整個系統非常有彈性,擴展性很好;然而有一個致命的缺點就是調試、看源碼真是很痛苦,各種跳來跳去;(基于事件流的寫法,和程序語言中的 goto 語句很類似)
把這個倉庫下載,使用 Webstorm 進行調試,test 目錄是很好的教程入口;
Tapable.plugin():相當于把對象歸類到名為 name 的對象下,以array的形式;所有的插件都存在私有變量 _plugin 變量中;
接下來我們簡單節選幾個函數分析一下:
1.1、apply 方法該方法最普通也是最常用的,看一下它的定義:
Tapable.prototype.apply = function apply() { for(var i = 0; i < arguments.length; i++) { arguments[i].apply(this); } };
毫無懸念,就是 挨個順序 執行傳入到該函數方法中對象的 apply 方法;通常傳入該函數的對象也是 Tapable 插件 對象,因此必然也存在 apply 方法;(Webpack 的插件就是Tapable對象,因此必須要提供 apply 方法 )
只是更改上下文為當前 this
因此當前這里最大的作用就是傳入當前 Tapable 的上下文
1.2、 applyPluginsAsync(name,...other,callback)// 模擬兩個插件 var _plugins = { "emit":[ function(a,b,cb){ setTimeout(()=>{ console.log("1",a,b); cb(); },1000); }, function(a,b,cb){ setTimeout(()=>{ console.log("2",a,b); cb(); },500) } ] } applyPluginsAsync("emit","aaaa","bbbbb",function(){console.log("end")}); // 輸出結果: // 1 aaaa bbbbb // 2 aaaa bbbbb // end
我們看到,雖然第一個插件是延后 1000ms 執行,第二個則是延后 500ms,但在真正執行的時候,是嚴格按照順序執行的;每個插件需要在最后顯式調用cb()通知下一個插件的運行;
這里需要注意每個插件的形參的個數都要一致,且最后一個必須是cb()方法,用于喚起下一個插件的運行;cb的第一個參數是err,如果該參數不為空,就直接調用最后callback,中斷后續插件的運行;
1.3、 applyPluginsParallel(name,...other,callback)大部分代碼和 applyPluginsAsync 有點兒類似
這個 applyPluginsParallel 主要功能和 最簡單的 applyPlugins 方法比較相似,無論如何都會讓所有注冊的插件運行一遍;
只是相比 applyPlugins 多了一個額外的功能,它最后 提供一個 callback 函數,這個 callback 的函數比較倔強,如果所有的插件x都正常執行,且最后都cb(),則會在最后執行callback里的邏輯;不過,一旦其中某個插件運行出錯,就會調用這個callback(err),之后就算插件有錯誤也不會再調用該callback函數;
var _plugins = { "emit":[ function(a,b,cb){ setTimeout(()=>{ console.log("1",a,b); cb(null,"e222","33333"); },1000); }, function(a,b,cb){ setTimeout(()=>{ console.log("2",a,b); cb(null,"err"); },500) } ] } applyPluginsParallel("emit","aaaa","bbbbb",function(a,b){console.log("end",a,b)}); // 輸出結果: // 2 aaaa bbbbb // 1 aaaa bbbbb // end undefined undefined
上面的兩個插件都是調用了 cb,且第一個參數是 null(表示沒有錯誤),所以最后能輸出 callback 函數中的 console 內容;
如果注釋兩個插件中任何一個 cb() 調用,你會發現最后的 callback 沒有執行;
如果讓 第二個 cb()的第一個值不是 null,比如 cb("err"),則 callback 之后輸出這個錯誤,之后再也不會調用此 callback:
var _plugins = { "emit":[ function(a,b,cb){ setTimeout(()=>{ console.log("1",a,b); cb("e222","33333"); },1000); }, function(a,b,cb){ setTimeout(()=>{ console.log("2",a,b); cb("err"); },500) } ] } // 輸出結果: // 2 aaaa bbbbb // end err undefined // 1 aaaa bbbbb1.4、 applyPluginsWaterfall(name, init, callback)
顧名思義,這個方法相當于是 瀑布式 調用,給第一個插件傳入初始對象 init,然后經過第一個插件調用之后會獲得一個結果對象,該結果對象會傳給下一個插件 作為初始值,直到最后調用完畢,最后一個插件的直接結果傳給 callback 作為初始值;
1.5、 applyPluginsParallelBailResult(name,...other,callback)這個方法應該是所有方法中最難理解的;
首先它的行為和 applyPluginsParallel 非常相似,首先會 無論如何都會讓所有注冊的插件運行一遍(根據注冊的順序);
為了讓 callback 執行,其前提條件是每個插件都需要調用 cb();
但其中的 callback 只會執行一次(當傳給cb的值不是undefined/null 的時候),這一次執行順序是插件定義順序有關,而跟每個插件中的 cb() 執行時間無關的;
var _plugins = { "emit":[ function(a,b,cb){ setTimeout(()=>{ console.log("1",a,b); cb(); },1000); }, function(a,b,cb){ setTimeout(()=>{ console.log("2",a,b); cb(); },500) }, function(a,b,cb){ setTimeout(()=>{ console.log("3",a,b); cb(); },1500) } ] } applyPluginsParallelBailResult("emit","aaaa","bbbbb",function(a,b){console.log("end",a,b)}); // 運行結果 // 2 aaaa bbbbb // 1 aaaa bbbbb // 3 aaaa bbbbb // end undefined undefined
這是最普通的運行情況,我們稍微調整一下(注意三個插件運行的順序2-1-3),分別給cb傳入有效的值:
var _plugins = { "emit":[ function(a,b,cb){ setTimeout(()=>{ console.log("1",a,b); cb("1"); },1000); }, function(a,b,cb){ setTimeout(()=>{ console.log("2",a,b); cb("2"); },500) }, function(a,b,cb){ setTimeout(()=>{ console.log("3",a,b); cb("3"); },1500) } ] } applyPluginsParallelBailResult("emit","aaaa","bbbbb",function(a,b){console.log("end",a,b)}); // 運行結果 // 2 aaaa bbbbb // 1 aaaa bbbbb // end 1 undefined // 3 aaaa bbbbb
可以發現第1個插件 cb("1") 執行了,后續的 cb("2") 和 cb("3") 都給忽略了;
這是因為插件注冊順序是 1-2-3,雖然運行的時候順序是 2-1-3,但所運行的還是 1 對應的 cb;所以,就算1執行的速度最慢(比如把其setTimeout的值設置成 2000),運行的 cb 仍然是1對應的cb;
其中涉及的魔法是 閉包,傳入的i就是和注冊順序綁定了1.6、總結這樣一說明,你會發現 applyPluginsParallel 的 cb 執行時機是和執行時間有關系的,你可以自己驗證一下;
總結一下,Tapable 就相當于是一個 事件管家,它所提供的 plugin 方法類似于 addEventListen 監聽事件,apply 方法類似于事件觸發函數 trigger;
2、Webpack 中的事件流既然 Webpack 是基于 Tapable 搭建起來的,那么我們看一下 Webpack 構建一個模塊的基本事件流是如何的;
我們在 Webpack 庫中的 Tapable.js 中每個方法中新增 console 語句打出日志,就能找出所有關鍵的事件名字:
打印結果:(這里只列舉了簡單的事件流程,打包不同的入口文件會有所差異,但 事件出現的先后順序是固定的 )
類型 | 名字 | 事件名 |
---|---|---|
[C] | applyPluginsBailResult | entry-option |
[A] | applyPlugins | after-plugins |
[A] | applyPlugins | after-resolvers |
[A] | applyPlugins | environment |
[A] | applyPlugins | after-environment |
[D] | applyPluginsAsyncSeries | run |
[A] | applyPlugins | normal-module-factory |
[A] | applyPlugins | context-module-factory |
[A] | applyPlugins | compile |
[A] | applyPlugins | this-compilation |
[A] | applyPlugins | compilation |
[F] | applyPluginsParallel | make |
[E] | applyPluginsAsyncWaterfall | before-resolve |
[B] | applyPluginsWaterfall | factory |
[B] | applyPluginsWaterfall | resolver |
[A] | applyPlugins | resolve |
[A] | applyPlugins | resolve-step |
[G] | applyPluginsParallelBailResult | file |
[G] | applyPluginsParallelBailResult | directory |
[A] | applyPlugins | resolve-step |
[G] | applyPluginsParallelBailResult | result |
[E] | applyPluginsAsyncWaterfall | after-resolve |
[C] | applyPluginsBailResult | create-module |
[B] | applyPluginsWaterfall | module |
[A] | applyPlugins | build-module |
[A] | applyPlugins | normal-module-loader |
[C] | applyPluginsBailResult | program |
[C] | applyPluginsBailResult | statement |
[C] | applyPluginsBailResult | evaluate CallExpression |
[C] | applyPluginsBailResult | var data |
[C] | applyPluginsBailResult | evaluate Identifier |
[C] | applyPluginsBailResult | evaluate Identifier require |
[C] | applyPluginsBailResult | call require |
[C] | applyPluginsBailResult | evaluate Literal |
[C] | applyPluginsBailResult | call require:amd:array |
[C] | applyPluginsBailResult | evaluate Literal |
[C] | applyPluginsBailResult | call require:commonjs:item |
[C] | applyPluginsBailResult | statement |
[C] | applyPluginsBailResult | evaluate MemberExpression |
[C] | applyPluginsBailResult | evaluate Identifier console.log |
[C] | applyPluginsBailResult | call console.log |
[C] | applyPluginsBailResult | expression console.log |
[C] | applyPluginsBailResult | expression console |
[A] | applyPlugins | succeed-module |
[E] | applyPluginsAsyncWaterfall | before-resolve |
[B] | applyPluginsWaterfall | factory |
[A] | applyPlugins | build-module |
[A] | applyPlugins | succeed-module |
[A] | applyPlugins | seal |
[A] | applyPlugins | optimize |
[A] | applyPlugins | optimize-modules |
[A] | applyPlugins | after-optimize-modules |
[A] | applyPlugins | optimize-chunks |
[A] | applyPlugins | after-optimize-chunks |
[D] | applyPluginsAsyncSeries | optimize-tree |
[A] | applyPlugins | after-optimize-tree |
[C] | applyPluginsBailResult | should-record |
[A] | applyPlugins | revive-modules |
[A] | applyPlugins | optimize-module-order |
[A] | applyPlugins | before-module-ids |
[A] | applyPlugins | optimize-module-ids |
[A] | applyPlugins | after-optimize-module-ids |
[A] | applyPlugins | record-modules |
[A] | applyPlugins | revive-chunks |
[A] | applyPlugins | optimize-chunk-order |
[A] | applyPlugins | before-chunk-ids |
[A] | applyPlugins | optimize-chunk-ids |
[A] | applyPlugins | after-optimize-chunk-ids |
[A] | applyPlugins | record-chunks |
[A] | applyPlugins | before-hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | hash |
[A] | applyPlugins | hash-for-chunk |
[A] | applyPlugins | chunk-hash |
[A] | applyPlugins | after-hash |
[A] | applyPlugins | before-chunk-assets |
[B] | applyPluginsWaterfall | global-hash-paths |
[C] | applyPluginsBailResult | global-hash |
[B] | applyPluginsWaterfall | bootstrap |
[B] | applyPluginsWaterfall | local-vars |
[B] | applyPluginsWaterfall | require |
[B] | applyPluginsWaterfall | module-obj |
[B] | applyPluginsWaterfall | module-require |
[B] | applyPluginsWaterfall | require-extensions |
[B] | applyPluginsWaterfall | asset-path |
[B] | applyPluginsWaterfall | startup |
[B] | applyPluginsWaterfall | module-require |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | module |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | package |
[B] | applyPluginsWaterfall | module |
[B] | applyPluginsWaterfall | render |
[B] | applyPluginsWaterfall | package |
[B] | applyPluginsWaterfall | modules |
[B] | applyPluginsWaterfall | render-with-entry |
[B] | applyPluginsWaterfall | asset-path |
[B] | applyPluginsWaterfall | asset-path |
[A] | applyPlugins | chunk-asset |
[A] | applyPlugins | additional-chunk-assets |
[A] | applyPlugins | record |
[D] | applyPluginsAsyncSeries | additional-assets |
[D] | applyPluginsAsyncSeries | optimize-chunk-assets |
[A] | applyPlugins | after-optimize-chunk-assets |
[D] | applyPluginsAsyncSeries | optimize-assets |
[A] | applyPlugins | after-optimize-assets |
[D] | applyPluginsAsyncSeries | after-compile |
[C] | applyPluginsBailResult | should-emit |
[D] | applyPluginsAsyncSeries | emit |
[B] | applyPluginsWaterfall | asset-path |
[D] | applyPluginsAsyncSeries | after-emit |
[A] | applyPlugins | done |
內容較多,依據源碼內容的編排,可以將上述進行分層;大粒度的事件流如下:
而其中 make、 seal 和 emit 階段比較核心(包含了很多小粒度的事件),后續會繼續展開講解;
這里羅列一下關鍵的事件節點:
entry-option:初始化options
run:開始編譯
make:從entry開始遞歸的分析依賴,對每個依賴模塊進行build
before-resolve - after-resolve: 對其中一個模塊位置進行解析
build-module :開始構建 (build) 這個module,這里將使用文件對應的loader加載
normal-module-loader:對用loader加載完成的module(是一段js代碼)進行編譯,用 acorn 編譯,生成ast抽象語法樹。
program: 開始對ast進行遍歷,當遇到require等一些調用表達式時,觸發 call require 事件的handler執行,收集依賴,并。如:AMDRequireDependenciesBlockParserPlugin等
seal: 所有依賴build完成,下面將開始對chunk進行優化,比如合并,抽取公共模塊,加hash
optimize-chunk-assets:壓縮代碼,插件 UglifyJsPlugin 就放在這個階段
bootstrap: 生成啟動代碼
emit: 把各個chunk輸出到結果文件
3、參考文章本系列的源碼閱讀,以下幾篇文章給了很多啟發和思路,其中 webpack 源碼解析 和 細說 webpack 之流程篇 尤為突出,推薦閱讀;
webpack 源碼解析
細說 webpack 之流程篇
WebPack學習:WebPack內置Plugin
如何寫一個webpack插件
plugins官方文檔:
下面的是我的公眾號二維碼圖片,歡迎關注。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81118.html
摘要:它的行為和的方法相似,用來注冊一個處理函數監聽器,來在信號事件發生時做一些事情他最終還是調用進行存儲。而就全部取出來執行。總結上面這些知識是理解插件和運行原理的前置條件更多內容待下次分解參考源碼版本說明參考鏈接 引言 去年3月的時候當時寫了一篇webpack2-update之路,到今天webpack已經到了4.2,更新挺快的,功能也在不斷的完善,webpack4特性之一就是零配置, w...
摘要:流程劃分縱觀整個打包過程,可以流程劃分為四塊。核心類關系圖功能實現模塊通過將源碼解析為樹并拆分,以及直至基礎模塊。通過的依賴和切割文件構建出含有和包含關系的對象。通過模版完成主入口文件的寫入,模版完成切割文件的寫入。 前言 插件plugin,webpack重要的組成部分。它以事件流的方式讓用戶可以直接接觸到webpack的整個編譯過程。plugin在編譯的關鍵地方觸發對應的事件,極大的...
摘要:打開是個構造函數,定義了一些靜態屬性和方法我們先看在插件下地址上面寫的解釋就跟沒寫一樣在文件下我們看到輸出的一些對象方法每一個對應一個模塊而在下引入的下面,我們先研究引入的對象的英文單詞解釋,除了最常用的點擊手勢之外,還有一個意思是水龍頭進 打開compile class Compiler extends Tapable { constructor(context) { ...
摘要:小尾巴最終返回了屬性掛載把引入的函數模塊全部暴露出來下面暴露了一些插件再通俗一點的解釋比如當你你能調用文件下的方法這個和上面的不同在于上面的是掛在函數對象上的正題要想理解必須要理解再寫一遍地址我們先簡單的理解它為一個通過注冊插件是插件的事 webpack.js小尾巴 const webpack = (options, callback) => { //... if (...
摘要:引入定義一個自己的插件。一個最基礎的的代碼是這樣的在構造函數中獲取用戶給該插件傳入的配置會調用實例的方法給插件實例傳入對象導出在使用這個時,相關配置代碼如下和在開發時最常用的兩個對象就是和,它們是和之間的橋梁。 本文示例源代碼請戳github博客,建議大家動手敲敲代碼。 webpack本質上是一種事件流的機制,它的工作流程就是將各個插件串聯起來,而實現這一切的核心就是Tapable,w...
閱讀 1231·2021-11-23 09:51
閱讀 678·2021-11-19 09:40
閱讀 1337·2021-10-11 10:58
閱讀 2347·2021-09-30 09:47
閱讀 3726·2021-09-22 15:55
閱讀 2160·2021-09-03 10:49
閱讀 1250·2021-09-03 10:33
閱讀 698·2019-08-29 17:12