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