Tapable工具
完成webpack默認參數注入后,下一步雖然是 new Compiler() ,但是這東西不是一下可以講完的,復雜的一批。
不如先從工具入手,分塊講解compiler,首先來看看事件流執行器Tapable工具。
tips:這里的Tapable源碼來自於webpack內部自帶的tapable,如果通過npm i tapable查看會發現完全不一樣。
出現地點如下:
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); };
非常簡單,用一個小案例說明:
const Tapable = require('./Tapable'); var sourObj = { ownKey: null }; Tapable.mixin(sourObj);
通過mixin方法的調用,sourObj會變成:
至於另外一個工具函數,單獨講沒有任何意義,所以在用到的時候再做分析。
接下來分析原型函數,其中有兩個函數是基本操作函數,其余的都是用不同方式執行指定名字的事件流。
先看基本的。
基本操作函數
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個比較復雜,干講也不知道怎么解釋,等到后面的代碼有用到的時候再組具體分析。