Webpack Tapable

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.png

如上图,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函数。

applyPluginsParallelBailResult.png

我们可做如下假设,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的基础插件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,087评论 18 139
  • GitChat技术杂谈 前言 本文较长,为了节省你的阅读时间,在文前列写作思路如下: 什么是 webpack,它要...
    萧玄辞阅读 12,609评论 7 110
  • 1.JQuery 基础 改变web开发人员创造搞交互性界面的方式。设计者无需花费时间纠缠JS复杂的高级特性。 1....
    LaBaby_阅读 1,285评论 0 2
  • 感赏自己怎么那么棒! 今天写的是第19篇博文,想不到我也写了那么多,不得不说我又进步了!自培姐点评过我的感...
    建芳_六中阅读 163评论 1 3
  • 奇缘 策划春节出游早在一个多月前就开始了,预定酒店和景点紧锣密鼓进行中。说来也奇怪,当再次选择前往浙江旅游的时候,...
    自在牛阅读 372评论 0 1