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