【原文:Tapable 0.2.8 入门】javascript
tapable是webpack的核心框架(4.0以上版本的API已经发生了变化),是一个基于事件流的框架,或者叫作发布订阅模式,或观察者模式,webpack的整个生命周期及其开放的自定义插件系统都离不开tapable的支持,研究其运行原理是阅读webpack源代码的第一步。html
Tapable是一个小型库,容许你添加和应用插件到一个javascript模块。它能够被继承或混入其余模块。除能够定制事件发射和操做,还能够经过回调参数访问事件的“排放者”或“生产者”。java
Tapable 有四组成员函数:node
不一样的applyPlugins*方法涵盖如下用例:webpack
var Tapable = require("tapable");
Tapable 是一个库,用于绑定和执行插件。git
在使用上,你仅仅须要继承它github
function MyClass() { Tapable.call(this) } MyClass.prototype = Object.create(Tapable.prototype) MyClass.prototype.method = function() {}
或者复制它的属性到你的类中web
function MyClass2() { EventEmitter.call(this); Tapable.call(this); } MyClass2.prototype = Object.create(EventEmitter.prototype); Tapable.mixin(MyClass2.prototype); MyClass2.prototype.method = function() {};
void apply(plugins: Plugin...) Tapable.prototype.apply = function apply() { for(var i = 0; i < arguments.length; i++) { arguments[i].apply(this); // 遍历全部参数并执行 } };
经过arguments得到全部传入的插件对象,并调用插件对象的apply方法,更改上下文为当前this,执行插件。(Webpack的插件就是Tapable对象,所以必需要提供 apply 方法)segmentfault
void plugin(names: string|string[], handler: Function) 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); };
names: 须要监听的事件名称,能够传入事件名称集合,也能够传入单个事件名称 handler: 事件的处理函数数组
tapable经过原型方法Tapable.prototype.plugin来注册事件监听。将回调函数按照事件名称进行归类存储,在tapable实例中统一调度管理。
applyPlugins
applyPluginsWaterfall
applyPluginsBailResult
applyPluginsAsync
applyPluginsAsyncWaterfall
applyPluginsAsyncSeries
applyPluginsParallel
applyPluginsParallelBailResult
hasPlugins
tapable中的事件触发方式能够按命名分为以下几个大组:
waterfall
方法会将上一个监听的执行结果传给下一个bailResult
方法只会执行到第一个返回结果不是undefined的事件流Series
方法会线性执行异步事件流,上一个结束后下一个才会开始Parallel
方法会并行执行全部异步监听void applyPlugins(name: string, args: any...) Tapable.prototype.applyPlugins = function applyPlugins(name) { 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); // 依次执行指定name事件流的apply方法 };
触发事件name,传入参数args,并行的调用全部注册在事件name上的处理函数
any applyPluginsWaterfall(name: string, init: any, args: any...) Tapable.prototype.applyPluginsWaterfall = function applyPlugins(name, init) { if(!this._plugins[name]) return init; // 若是指定事件没有注册事件流,则返回第2个参数(init) var args = Array.prototype.slice.call(arguments, 1); // 除name之外的其余参数 var plugins = this._plugins[name]; // 查找事件流数组 var current = init; for(var i = 0; i < plugins.length; i++) args[0] = current; current = plugins[i].apply(this, args); // 依次执行事件流的apply()方法, 传入的args是前执行返回值替换init初始值的参数 return current; };
触发事件name,串行的调用注册在事件name上的处理函数(先入先出),最早执行的处理函数传入init和args,后续的处理函数传入前一个处理函数的返回值和args,函数最终返回最后一个处理函数的返回结果
any applyPluginsBailResult(name: string, args: any...) Tapable.prototype.applyPluginsBailResult = function applyPluginsBailResult(name) { 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); // 依次执行事件流的apply()方法并取得返回值 if(typeof result !== "undefined") { // 若是返回一个不为undefined的结果 return result; // 则中止执行并将这个结果返回。 } } };
触发事件name,串行的调用注册在事件name上的处理函数(先入先出),传入参数args,若是其中一个处理函数返回值!== undefined,直接返回这个返回值,后续的处理函数将不被执行
void applyPluginsAsync( name: string, args: any..., callback: (err?: Error) -> void ) // 异步执行监听回调的方法。这个方法是顺序执行,等到第一个插件执行结束后才会执行下一个插件 Tapable.prototype.applyPluginsAsyncSeries = Tapable.prototype.applyPluginsAsync = function applyPluginsAsync(name) { var args = Array.prototype.slice.call(arguments, 1); // 除名称之外的其余参数 var callback = args.pop(); // 参数数组最后一个弹出,回调函数赋给callback var plugins = this._plugins[name]; // 第一个参数为事件名, 查找事件流数组 if(!plugins || plugins.length === 0) return callback(); // 监听事件name为空或没有没有注册事件流,执行回调函数 var i = 0; var _this = this args.push(copyProperties(callback, function next(err) { // copyProperties将callback原型方法复制到next中并返回next if(err) return callback(err); i++; if(i >= plugins.length) { return callback(); } plugins[i].apply(_this, args); // 将下一个插件当作回调函数传入第一个插件 })); // 利用闭包实现了一个迭代器,变量i记录在applyPluginsAsync()方法中,并在回调中函数next( )中保持了对i的引用。 plugins[0].apply(this, args); }; function copyProperties(from, to) { // 将from原型方法复制到指定对象to中 for(var key in from) to[key] = from[key]; return to; }
触发事件name,串行的调用注册在事件name上的处理函数(先入先出),假若某一个处理函数报错,则执行传入的callback(err),后续的处理函数将不被执行,不然最后一个处理函数调用callback。
applyPluginsAsyncSeries(
name: string,
args: any..., callback: (err: Error, result: any) -> void )
同applyPluginsAsync
applyPluginsAsyncWaterfall(
name: string,
init: any, callback: (err: Error, result: any) -> void )
Tapable.prototype.applyPluginsAsyncWaterfall = function applyPluginsAsyncWaterfall(name, init, callback) { if(!this._plugins[name] || this._plugins[name].length === 0) return callback(null, init); // 监听事件name为空或没有没有注册事件流,执行回调函数 var plugins = this._plugins[name]; // 第一个参数为事件名, 查找事件流数组 var i = 0; var _this = this var next = copyProperties(callback, function(err, value) {// copyProperties函数将callback函数加入了参数函数并返回参数函数 if(err) return callback(err); i++; if(i >= plugins.length) { return callback(null, value); } plugins[i].call(_this, value, next); }); plugins[0].call(this, init, next); };
触发事件name,串行的调用注册在name上的处理函数(先入先出),第一个处理函数传入参数init,后续的函数依赖于前一个函数执行回调的时候传入的参数nextValue,假若某一个处理函数报错,则执行传入的callback(err),后续的处理函数将不被执行,不然最后一个处理函数调用callback(value)
applyPluginsParallel(
name: string,
args: any..., callback: (err?: Error) -> void ) Tapable.prototype.applyPluginsParallel = function applyPluginsParallel(name) { var args = Array.prototype.slice.call(arguments, 1); var callback = args.pop(); // 参数数组最后一个弹出,回调函数赋给callback if(!this._plugins[name] || this._plugins[name].length === 0) return callback();// 监听事件name为空或没有没有注册事件流,执行回调函数 var plugins = this._plugins[name]; var remaining = plugins.length; args.push(copyProperties(callback, function(err) { if(remaining < 0) return; // ignore if(err) { remaining = -1; return callback(err); } remaining--; if(remaining === 0) { return callback(); } })); for(var i = 0; i < plugins.length; i++) { plugins[i].apply(this, args); if(remaining < 0) return; } };
触发事件name,传入参数args,并行的调用全部注册在事件name上的处理函数,假若任一处理函数执行报错,则执行callback('err'),不然当全部的处理函数都执行完的时候调用callback()
applyPluginsParallel 主要功能和最简单的 applyPlugins 方法比较类似,不管如何都会让全部注册的插件运行一遍;只是相比 applyPlugins 多了一个额外的功能,它最后提供一个 callback 函数,这个 callback 的函数比较倔强,若是全部的插件x都正常执行,且最后都cb(),则会在最后执行callback里的逻辑;不过,一旦其中某个插件运行出错,就会调用这个callback(err),以后就算插件有错误也不会再调用该callback函数;
applyPluginsParallelBailResult(
name: string,
args: any..., callback: (err: Error, result: any) -> void )
Tapable.prototype.applyPluginsParallelBailResult = function applyPluginsParallelBailResult(name) { var args = Array.prototype.slice.call(arguments, 1); var callback = args[args.length - 1]; if(!this._plugins[name] || this._plugins[name].length === 0) return callback(); var plugins = this._plugins[name]; var currentPos = plugins.length; var currentError, currentResult; var done = []; for(var i = 0; i < plugins.length; i++) { args[args.length - 1] = (function(i) { return copyProperties(callback, function(err, result) { if(i >= currentPos) return; // ignore done.push(i); if(err || result) { currentPos = i + 1; done = done.filter(function(item) { return item <= i; }); currentError = err; currentResult = result; } if(done.length === currentPos) { callback(currentError, currentResult); currentPos = 0; } }); }(i)); plugins[i].apply(this, args); } };
触发事件name,串行的执行注册在事件name上的处理函数(先入先出),每一个处理函数必须调用callback(err, result),假若任一处理函数在调用callback(err, result)的时候,err!==undefined || result!==undefined,则callback将真正被执行,后续的处理函数则不会再被执行。
它的行为和 applyPluginsParallel 很是类似,首先不管如何都会让全部注册的插件运行一遍(根据注册的顺序);为了让 callback 执行,其前提条件是每一个插件都须要调用 cb();但其中的 callback 只会执行一次(当传给cb的值不是undefined/null 的时候),这一次执行顺序是插件定义顺序有关,而跟每一个插件中的 cb() 执行时间无关的;
hasPlugins(
name: string
)
Tapable.prototype.hasPlugins = function hasPlugins(name) { var plugins = this._plugins[name]; return plugins && plugins.length > 0; };
若是事件name已经被注册了,则返回true
webpack的Tapable实例之一编译器负责编译webpack配置对象并返回一个编译实例。编译实例运行时,将建立所需的捆绑包。
node_modules/webpack/lib/Compiler.js
var Tapable = require("tapable"); // 引入Tapable function Compiler() { Tapable.call(this); // 将Tapable中的this指向Compiler } Compiler.prototype = Object.create(Tapable.prototype); // Compiler 继承 Tapable
如今在编译器上编写一个插件,my-custom-plugin.js
function CustomPlugin() {} // 定义一个CustomPlugin类 CustomPlugin.prototype.apply = function(compiler) { // CustomPlugin的原型对象上添加apply入口 compiler.plugin('emit', pluginFunction); // 传入compiler,并注册事件流pluginFunction的事件名称emit }
编译器在其生命周期的适当位置经过执行插件, node_modules/webpack/lib/Compiler.js
this.apply*("emit",options) // will fetch all plugins under 'emit' name and run them.
参考资料