Tapable
Tapable 是用来实现webpack插件 binding 和 applying的类库。本文将通过源码的分析Tapable的公共函数和保护函数。
function Tapable() {
this._plugins = {};
}
公共函数(Public Function)
apply
Tapable.prototype.apply = function apply() {
for(var i = 0; i < arguments.length; i++) {
arguments[i].apply(this);
}
};
void apply(plugins: Plugin...)
函数将传入的plugins参数通过apply(this)改变为当前的作用域。
plugin
Tapable.prototype.plugin = function plugin(name, fn) {
// 若为Array, 则对每个数组元素调用plugin函数
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);
};
void plugin(names: string|string[], handler: Function)
handler为插件对应的回调函数。
保护函数 (Protected functions)
applyPlugins
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);
};
void applyPlugins(name: string,args: any ...)
applyPlugins组件将插件的作用域apply为当前作用域,并将args参数传递给插件。applyPlugins0、applyPlugins1和applyPlugins2限制函数args的参数分别为0、1和2个。
applyPluginsWaterfall
Tapable.prototype.applyPluginsWaterfall = function applyPluginsWaterfall(name, init) {
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++) {
args[0] = current;
current = plugins[i].apply(this, args);
}
return current;
};
applyPluginsWaterfall(name: string, init: any, args: any...)
applyPluginsWaterfall为瀑布式调用,将初始对象init传递给第一个插件,插件调用结束后获取结果对象,对象会传递给下一个函数作为初始值,一直到调用完毕。
var Tapable = require('Tapable')
function MyClass() {
Tapable.call(this);
}
MyClass.prototype = Object.create(Tapable.prototype);
var plugin = new MyClass()
plugin._plugins = {
"emit":[
function(parm, args0){
args0('parm', parm+1)
return (parm+1)
},
function(parm, args0){
args0('parm', parm+2)
return (parm+2)
}
]
}
plugin.applyPluginsWaterfall("emit", 2, console.log)
parm 3
parm 5
emit第一个函数接收function(2, console.log), 返回结果为(2+1)= 3,输出'parm 3'。第二个函数接收function(3, console.log),返回结果为(3 + 2) = 5,输出'parm 5'。
applyPluginsWaterfall实现瀑布式调用,按照plugins的定义顺序依次执行。applyPluginsWaterfall0、applyPluginsWaterfall1和applyPluginsWaterfall2功能与applyPluginsWaterfall类似,只是限制传递的参数为0、1、2。
applyPluginsBailResult
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);
if(typeof result !== "undefined") {
return result;
}
}
};
any applyPluginsBailResult(name: string, args: any...)
applyPluginsBailResult插件,在处理程序函数返回!==undefined时,停止继续执行其他处理函数。
var Tapable = require('Tapable')
function MyClass() {
Tapable.call(this);
}
MyClass.prototype = Object.create(Tapable.prototype);
var plugin = new MyClass()
plugin._plugins = {
"emit":[
function(parm, args0){
args0('parm1', parm+1)
},
function(parm, args0){
args0('parm2', parm+2)
return (parm+2)
},
function(parm, args0){
args0('parm3', parm+3)
return (parm+3)
}
]
}
var result = plugin.applyPluginsBailResult("emit", 2, console.log)
console.log('result', result)
parm1 3
parm2 4
result 4
emit函数执行第一个函数时,返回为undefined,继续执行第二个函数,第二个函数返回4,终止执行。applyPluginsBailResult1、applyPluginsBailResult2、applyPluginsBailResult3、applyPluginsBailResult5与applyPluginsBailResult功能类似,不过限制参数的个数为1、2、3、4、5。
applyPluginsAsyncSeries 和 applyPluginsAsync
Tapable.prototype.applyPluginsAsyncSeries = Tapable.prototype.applyPluginsAsync = function applyPluginsAsyncSeries(name) {
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, function next(err) {
if(err) return callback(err);
i++;
if(i >= plugins.length) {
return callback();
}
plugins[i].apply(_this, args);
}));
plugins[0].apply(this, args);
};
applyPluginsAsyncSeries(
name: string,
args: any...,
callback: (err: Error, result: any) -> void
)
如上图,applyPluginsAsync串行执行plugins[name]上的处理函数,每个plugins[name]上的处理函数必须执行next函数。处理函数有两种结果,若处理函数报错,则执行回调callback(err)函数,后续处理函数不执行。否则,将按照plugins函数顺序执行。
var Tapable = require('Tapable')
function MyClass() {
Tapable.call(this);
}
MyClass.prototype = Object.create(Tapable.prototype);
var plugin = new MyClass()
plugin._plugins = {
"emit":[
function(a, next){
setTimeout(()=>{
console.log('1',a);
cb();
},1000);
},
function(a, next){
setTimeout(()=>{
console.log('2',a);
cb();
},500)
}
]
}
plugin.applyPluginsAsync("emit", 'test it', function(){console.log('end')})
执行结果
1 test it
2 test it
end
测试代码中,虽然处理函数使用setTimeout延迟console.log的输出,但是由于applyPluginsAsync的处理函数是顺序执行的,所以会按照函数顺序输出。
applyPluginsAsyncSeriesBailResult
Tapable.prototype.applyPluginsAsyncSeriesBailResult = function applyPluginsAsyncSeriesBailResult(name) {
var args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
if(!this._plugins[name] || this._plugins[name].length === 0) return callback();
var plugins = this._plugins[name];
var i = 0;
var _this = this;
args.push(copyProperties(callback, function next() {
if(arguments.length > 0) return callback.apply(null, arguments);
i++;
if(i >= plugins.length) {
return callback();
}
plugins[i].apply(_this, args);
}));
plugins[0].apply(this, args);
};
功能与applyPluginsAsyncSeries类似,这里不在赘述
applyPluginsAsyncWaterfall
Tapable.prototype.applyPluginsAsyncWaterfall = function applyPluginsAsyncWaterfall(name, init, callback) {
if(!this._plugins[name] || this._plugins[name].length === 0) return callback(null, init);
var plugins = this._plugins[name];
var i = 0;
var _this = this;
var next = copyProperties(callback, function(err, value) {
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);
};
执行过程与applyPluginsAsync类似,不同点,在于第一个处理函数传递参数init,后续的参数依赖于前一个函数回调传入的参数value。
var Tapable = require('Tapable')
function MyClass() {
Tapable.call(this);
}
MyClass.prototype = Object.create(Tapable.prototype);
var plugin = new MyClass()
plugin._plugins = {
"emit":[
function(a, next){
console.log(a)
setTimeout(()=>{
var b = a + ' one'
next(null, b);
},1000);
},
function(a, next){
console.log(a)
setTimeout(()=>{
var c = a + ' two'
next(null, c);
},500)
}
]
}
plugin.applyPluginsAsyncWaterfall("emit", 'test it', function(err, result){console.log(result)})
执行结果
test it
test it one
test it one two
applyPluginsParallel
Tapable.prototype.applyPluginsParallel = function applyPluginsParallel(name) {
var args = Array.prototype.slice.call(arguments, 1);
var callback = args.pop();
if(!this._plugins[name] || this._plugins[name].length === 0) return callback();
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;
}
};
applyPluginsParallel(
name: string,
args: any...,
callback: (err?: Error) -> void
)
applyPluginsParallel插件并行执行name上所有的处理函数,若任意处理函数出错,则执行callback(err)函数,否则处理函数执行完毕,执行callback()函数。
var Tapable = require('Tapable')
function MyClass() {
Tapable.call(this);
}
MyClass.prototype = Object.create(Tapable.prototype);
var plugin = new MyClass()
plugin._plugins = {
"emit":[
function(a, next){
setTimeout(()=>{
var b = a + ' single one'
console.log(b)
next();
},1000);
},
function(a, next){
setTimeout(()=>{
var c = a + ' single two'
console.log(c)
next();
},500)
}
]
}
plugin.applyPluginsParallel("emit", 'test it', function(err){console.log('end')})
执行结果
test it single two
test it single one
end
由于applyPluginsParallel的执行顺序是并行的,并不受函数顺序的影响,因此setTimeout发挥作用,第二个函数先执行,第一个函数后执行。
applyPluginsParallelBailResult
function fastFilter(fun/*, thisArg*/) {
'use strict';
if (this === void 0 || this === null) { // 等于undefined和null throw error
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== 'function') { // fun不为函数 throw error
throw new TypeError();
}
var res = [];
var thisArg = arguments.length >= 2 ? arguments[1] : void 0; // thisArg等于第二个参数 或 undefined
for (var i = 0; i < len; i++) {
if (i in t) {
var val = t[i];
if (fun.call(thisArg, val, i, t)) {
res.push(val);
}
}
}
return res;
}
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; // 当前位置,默认为length
var currentResult; // 当前结果,默认为undefined
var done = [];
for(var i = 0; i < plugins.length; i++) {
args[args.length - 1] = (function(i) {
return copyProperties(callback, function() {
debugger
if(i >= currentPos) return; // ignore
done.push(i);
if(arguments.length > 0) {
currentPos = i + 1;
done = fastFilter.call(done, function(item) {
return item <= i;
});
currentResult = Array.prototype.slice.call(arguments);
}
// plugins next执行length次时,调用回调函数
if(done.length === currentPos) {
callback.apply(null, currentResult);
currentPos = 0;
}
});
}(i));
plugins[i].apply(this, args);
}
};
applyPluginsParallelBailResult(
name: string,
args: any...,
callback: (err: Error, result: any) -> void
)
applyPluginsParallelBailResult处理函数与 applyPluginsParallel类似,并行执行name的函数。不同点在于callback函数的执行,研究tapable的源代码发现,applyPluginsParallelBailResult执行的是按照1.2.3...plugins.length的顺序,执行第一个(arguments.length>0)的处理函数的callback函数。
我们可做如下假设,1、2、3..n(plugins.lengt-1)处理函数中,X为第一个参数个数大于0的回调函数,则可以分为三种情况
(1)X未执行时,若执行的i < X, 则执行done.push(i), 如i > X, 此时的fastFilter过滤函数,过滤的是执行顺序大于i的处理顺序索引值,由于此时X未执行,done.length 肯定小于currentPos, callback肯定不执行。
(2) X执行时,若此前小于X的处理函数全部执行,此时currentPos == done.length, 执行X的callback函数;
(3)x执行后, 则若索引i大于X,跳过(i >= currentPos), 若索引小于X,则执行done.push[i],直到currentPos == done.length,执行X的callback()函数。
测试代码
var Tapable = require('Tapable')
function MyClass() {
Tapable.call(this);
}
MyClass.prototype = Object.create(Tapable.prototype);
var plugin = new MyClass()
plugin._plugins = {
"emit":[
function(a,b,cb){
setTimeout(()=>{
console.log('1',a,b);
cb();
},1500);
},
function(a,b,cb){
setTimeout(()=>{
console.log('2',a,b);
cb();
},500);
},
function(a,b,cb){
setTimeout(()=>{
console.log('3',a,b);
cb('two');
},1000)
},
function(a,b,cb){
setTimeout(()=>{
console.log('4',a,b);
cb('three');
},500)
}
]
}
plugin.applyPluginsParallelBailResult("emit",'aaaa','bbbbb',function(a,b){console.log('end',a,b)});
2 aaaa bbbbb
4 aaaa bbbbb
3 aaaa bbbbb
1 aaaa bbbbb
end two undefined
执行结果符合我们的分析,虽然处理函数的执行结束顺序不同,但是callback执行就是按照1-n顺序的第一个参数个数大于0的callback函数。
hasPlugins
Tapable.prototype.hasPlugins = function hasPlugins(name) {
var plugins = this._plugins[name];
return plugins && plugins.length > 0;
};
hasPlugins(name: string)
hasPlugins主要是检测是否有名为name的处理函数。
总结
Tapable 为webpack的事务处理函数,是webpack的基础插件。