完成webpack默認參數注入後,下一步雖然是 new Compiler() ,可是這東西不是一下能夠講完的,複雜的一批。webpack
不如先從工具入手,分塊講解compiler,首先來看看事件流執行器Tapable工具。web
tips:這裏的Tapable源碼來自於webpack內部自帶的tapable,若是經過npm i tapable查看會發現徹底不同。npm
出現地點以下:app
class Compiler extends Tapable { // ... }
class Compilation extends Tapable { // ... }
能夠看到核心對象基本上都繼承於該工具,用於處理事件流,Tapable源碼整理以下(用ES6的class複寫了一遍,看起來比較清晰):異步
// 原型方法混入 Tapable.mixin = function mixinTapable(pt) { /**/ }; function copyProperties(from, to) { /**/ } // 服務於某些apply function fastFilter(fun /*, thisArg*/ ) { /*...*/ } class Tapable { constructor() { this._plugins = {}; } plugin(name, fn) { /*...*/ } hasPlugins(name) { /*...*/ } apply() { /*...*/ } applyPlugins(name) { /*...*/ } applyPlugins0(name) { /*...*/ } applyPlugins1(name, param) { /*...*/ } applyPlugins2(name, param1, param2) { /*...*/ } applyPluginsWaterfall(name, init) { /*...*/ } applyPluginsWaterfall0(name, init) { /*...*/ } applyPluginsWaterfall1(name, init, param) { /*...*/ } applyPluginsWaterfall2(name, init, param1, param2) { /*...*/ } applyPluginsBailResult(name) { /*...*/ } applyPluginsBailResult1(name, param) { /*...*/ } applyPluginsBailResult2(name, param1, param2) { /*...*/ } applyPluginsBailResult3(name, param1, param2, param3) { /*...*/ } applyPluginsBailResult4(name, param1, param2, param3, param4) { /*...*/ } applyPluginsBailResult5(name, param1, param2, param3, param4, param5) { /*...*/ } applyPluginsAsyncSeries(name) { /*...*/ } applyPluginsAsyncSeries1(name, param, callback) { /*...*/ } applyPluginsAsyncSeriesBailResult(name) { /*...*/ } applyPluginsAsyncSeriesBailResult1(name, param, callback) { /*...*/ } applyPluginsAsyncWaterfall(name, init, callback) { /*...*/ } applyPluginsParallel(name) { /*...*/ } applyPluginsParallelBailResult(name) { /*...*/ } applyPluginsParallelBailResult1(name, param, callback) { /*...*/ } } module.exports = Tapable;
構造函數只是簡單的聲明瞭一個_plugins對象,外部函數包括有一個混入函數、一個工具函數,原型上則是大量apply...函數
先從簡單的入手,看看混入函數:工具
// 將Tapable原型方法複製到指定對象中 function copyProperties(from, to) { for (var key in from) to[key] = from[key]; return to; } // 傳入對象 Tapable.mixin = function mixinTapable(pt) { copyProperties(Tapable.prototype, pt); };
很是簡單,用一個小案例說明:ui
const Tapable = require('./Tapable'); var sourObj = { ownKey: null }; Tapable.mixin(sourObj);
經過mixin方法的調用,sourObj會變成:this
至於另一個工具函數,單獨講沒有任何意義,因此在用到的時候再作分析。spa
接下來分析原型函數,其中有兩個函數是基本操做函數,其他的都是用不一樣方式執行指定名字的事件流。
先看基本的。
plugin
Tapable.prototype.plugin = function plugin(name, fn) { // 將函數注入多個事件流中 if (Array.isArray(name)) { name.forEach(function(name) { this.plugin(name, fn); }, this); return; } // 若是不存在該事件流 新建並將函數插入 if (!this._plugins[name]) this._plugins[name] = [fn]; // 存在就添加執行函數 else this._plugins[name].push(fn); };
這是Tapable最基本的操做,給指定的事件流注入新函數。
hasPlugins
Tapable.prototype.hasPlugins = function hasPlugins(name) { // 嘗試獲取對應事件流 var plugins = this._plugins[name]; // 存在事件流且有可執行函數 return plugins && plugins.length > 0; };
has判斷,沒啥好講的。
接下來看看全部的事件流執行方式。(源碼中儘可能使用ES6進行改寫以加強可讀性,留個註釋在那)
首先是一個比較特殊的原型函數:
apply
Tapable.prototype.apply = function apply(...fns) { // 遍歷全部參數並執行 for (var i = 0; i < fns.length; i++) { fns[i].apply(this); } };
該函數並不直接關聯於_plugins對象,而是按照參數傳入順序依次執行。
applyPlugins
這個方式很是簡單暴力,依次遍歷指定name的事件流,不一樣名字的函數可接受參數數量不同。
// 不接受傳參 Tapable.prototype.applyPlugins0 = function applyPlugins0(name) { var plugins = this._plugins[name]; if (!plugins) return; for (var i = 0; i < plugins.length; i++) plugins[i].call(this); }; // 接受一個參數 Tapable.prototype.applyPlugins1 = function applyPlugins1(name, param) { var plugins = this._plugins[name]; if (!plugins) return; for (var i = 0; i < plugins.length; i++) plugins[i].call(this, param); }; // 接受兩個參數 Tapable.prototype.applyPlugins2 = function applyPlugins2(name, param1, param2) { var plugins = this._plugins[name]; if (!plugins) return; for (var i = 0; i < plugins.length; i++) plugins[i].call(this, param1, param2); }; // 接受任意數量參數 Tapable.prototype.applyPlugins = function applyPlugins(name, ...args) { if (!this._plugins[name]) return; // var args = Array.prototype.slice.call(arguments, 1); var plugins = this._plugins[name]; for (var i = 0; i < plugins.length; i++) plugins[i].apply(this, args); };
語義化滿分,0表明不接受參數,1表明1個...而s表明任意數量的參數。
applyPluginsWaterfall
這種方式的特色是:事件流執行過程當中,每一次執行的返回值會做爲下一次的參數(僅限於第一個參數)。
Tapable.prototype.applyPluginsWaterfall0 = function applyPluginsWaterfall0(name, init) { var plugins = this._plugins[name]; if (!plugins) return init; var current = init; for (var i = 0; i < plugins.length; i++) current = plugins[i].call(this, current); return current; }; // ...1 // ...2 Tapable.prototype.applyPluginsWaterfall = function applyPluginsWaterfall(name, init, ...args) { if (!this._plugins[name]) return init; // var args = Array.prototype.slice.call(arguments, 1); var plugins = this._plugins[name]; var current = init; for (var i = 0; i < plugins.length; i++) { current = plugins[i].call(this, current, ...args); } return current; };
applyPluginsBailResult
這種方式的特色是:事件流執行過程當中,返回第一個不是undefined的值,後續函數不執行。
Tapable.prototype.applyPluginsBailResult = function applyPluginsBailResult(name, ...args) { if (!this._plugins[name]) return; // var args = Array.prototype.slice.call(arguments, 1); var plugins = this._plugins[name]; for (var i = 0; i < plugins.length; i++) { var result = plugins[i].apply(this, args); if (typeof result !== "undefined") { return result; } } }; // 1,2,3,4,5
applyPluginsAsync...
帶有Async的均爲異步調用方式,特色是事件流會在回調中依次進行,區別主要在於回調函數的參數處理,具體的使用方式還須要在實際應用中來看。
Tapable.prototype.applyPluginsAsyncSeries = Tapable.prototype.applyPluginsAsync = function applyPluginsAsyncSeries(name, ...args) { // var args = Array.prototype.slice.call(arguments, 1); // 最後一個參數爲回調函數 其他爲普通參數 var callback = args.pop(); var plugins = this._plugins[name]; if (!plugins || plugins.length === 0) return callback(); var i = 0; // var _this = this; // 包裝 args.push(copyProperties(callback, (err) => { if (err) return callback(err); i++; if (i >= plugins.length) { return callback(); } plugins[i].apply(this, args); })); // 內部繼續使用此方式可依次執行事件流 plugins[0].apply(this, args); }; // ..1 // applyPluginsAsyncSeriesBailResult => 回調函數傳了參數就直接執行回調並返回終止事件流 // ..1 // applyPluginsAsyncWaterfall => 回調函數每次取給定的參數
剩下的3個比較複雜,幹講也不知道怎麼解釋,等到後面的代碼有用到的時候再組具體分析。