你们在看webpack源码的时候,有没有感受像再看天书,彷佛没有办法一个文件好比webpack.js
从头看到尾。感受webpack的跳跃性很强,彻底不知道程序在运行的时候,发生了什么。彻底不清楚这个事件是何时发生的,好比loader是何时执行的,plugin又是何时出现的。webpack的程序错综复杂,彻底迷失在程序之中。这到底是为何呢?其实很简单!由于webpack的灵魂Tapable!这个机制使得webpack异常的灵活,它有一句经典的话——Everything is a plugin!
。因而可知webpack是靠插件堆积起来的。而实现这个插件机制的就是Tabable!html
webpack的灵魂Tapable,有点相似于nodejs的Events,都是注册一个事件,而后到了适当的时候触发。这里的事件触发是这样绑定触发的,经过on
方法,绑定一个事件,emit
方法出发一个事件。Tapable的机制和这相似,也是tap
注册一个事件,而后call
执行这个事件。node
const EventEmitter = require('events');
const myEmitter = new EventEmitter();
//on的第一个参数是事件名,以后emit能够经过这个事件名,从而触发这个方法。
//on的第二个参数是回掉函数,也就是此事件的执行方法
myEmitter.on('newListener', (param1,param2) => {
console.log("newListener",param1,param2)
});
//emit的第一个参数是触发的事件名
//emit的第二个之后的参数是回调函数的参数。
myEmitter.emit('newListener',111,222);
复制代码
若是咱们把Tapable的实例对象比做一颗参天大树,那么的每一根树枝就是一个挂载的hook(钩子),也就是Tapable之中给每个事件分门别类的机制,好比编译(compile.js)这个对象中,又运行(run)的钩子,有构建(make)的钩子,这些钩子就像树枝同样,组成了一棵树的骨干,而后每一个树枝上的树叶就是每一个钩子上面挂载的函数方法。树枝(钩子)越多,树叶(函数)越多,此树越茂密(程序越复杂)。webpack
固然这只是一个简易的理解。实际上,webpack中不止有一棵树,每棵树之间还有错综复杂的关系。好比有些方法如compilation.js
中的一些方法,就要等compile.js
中的make
这个钩子执行以后才会执行。那么咱们就从了解Tapable中钩子的用法,来理解webpack中tapable。web
即便webpack中的每颗tapable的树之间有错综复杂的关系,整个程序都有一个逻辑线,也就是游戏中的主线剧情,咱们先构建咱们工做日的主线剧情。api
让咱们来回一下,咱们的平常工做日,应该大多数分红3个阶段,上班前,上班中和下班后,这3个时间段。这三个时间段,我用了3中钩子类型,普通型,流水型和熔断型。 按照文档他们的解释是这样的:数组
tap
的注册顺序一个个向下执行。tap
的顺序一个个向下执行,可是若是上一个tap有返回值,那么下一个tap的传入参数就是上一个tap的返回值。是否是感受一个事件的订阅发布怎么能够分出这么多类型?不要急,每一个类型都有他的做用!bash
钩子的语法通常都是new 钩子类型Hook([参数名1,参数名2,参数名3])
,这里的数组是只是提示你传入参数有几个,给了名字只是为了可读性,若是你想写一个别人看不懂的能够这样new SyncHook(["a","b","c"])
,这里要注意这个参数名的类型是字符串。若是没有提早准备号须要传入的参数,后续挂函数的时候,就没法传入参数了。这个设计应该是为了往后好打理,告诉其余开发者,我传入的参数类型。app
class MyDaily {
constructor() {
this.hooks = {
beforeWork: new SyncHook(["getUp"]),
atWork: new SyncWaterfallHook(["workTask"]),
afterWork: new SyncBailHook(["activity"])
};
}
tapTap(){
//此处是行为
}
run(){
this.hooks.beforeWork.call()
this.hooks.atWork.call()
this.hooks.afterWork.call()
}
}
复制代码
一天咱们不可能什么事都不作,因此给钩子上加点事,tap
事情。先来点必然发生的,正常的上班族,自由职业不在考虑范围内。早上咱们会作什么呢?穿衣服出门是必备的,不穿衣服无法出门,不出门无法上班。到了工做岗位,来点工做任务吧,好比咱们须要作个ppt,而后用这个ppt去开会。下班后,原本想回家的,结果佳人有约,果真不回家。异步
tapTap(){
this.hooks.beforeWork.tap("putOnCloth",()=>{
console.log("穿衣服!")
})
this.hooks.beforeWork.tap("getOut",()=>{
console.log("出门!")
})
this.hooks.atWork.tap("makePPT",()=>{
console.log("作PPT!")
return "你的ppt"
})
this.hooks.atWork.tap("meeting",(work)=>{
console.log("带着你的"+work+"开会!")
})
this.hooks.afterWork.tap("haveADate",()=>{
console.log("约会咯!")
return "约会真开心~"
})
this.hooks.afterWork.tap("goHome",()=>{
console.log("溜了溜了!")
})
}
复制代码
从上述咱们能够看到经过主演剧情了解到各类同步钩子的用法,可能难以理解就是熔断型的钩子,这个钩子的存在乎义就是,能够中断一系列的事情,好比有地方出错了,或者不须要进行下一步的操做咱们就能够及时结束。async
那么若是咱们作的事情都是异步的,每个事件之间都有联系,那么咱们就不能用同步的方法了。这个时候咱们能够将sync钩子替换成async的钩子。
async相对于sync多了一个callback的机制,就是这样的:
this.hooks.beforeWork.tapAsync("putOnCloth",(params,callback)=>{
console.log("穿衣服!")
callback();//此处无callback,则getOut这个挂载的函数便不会运行
})
this.hooks.beforeWork.tapAsync("getOut",(params,callback)=>{
console.log("出门!")
callback()//此处无callback,则beforeWork这个钩子的回调函数不会执行
})
this.hooks.beforeWork.callAsync("working",err=>{
console.log(err+" end!")//若是最后一个tap的函数没有callback则不会执行
})
复制代码
这里咱们能够将callback看成next函数,也就是下一个tap的函数的意思。以及若是当前tap的函数报错,则能够在callback中加入错误的缘由,那么接下来的函数便不会运行,也就是这样callback("errorReason")
,那么就直接回调用当前钩子的callAsync绑定的函数。
this.hooks.beforeWork.tapAsync("putOnCloth",(params,callback)=>{
console.log("穿衣服!")
callback("error");此处加入了错误缘由,那么直接callAsync,抛弃了getOut
})
this.hooks.beforeWork.tapAsync("getOut",(params,callback)=>{//直接skip了
console.log("出门!")
})
this.hooks.beforeWork.callAsync("working",err=>{
console.log(err+" end!")//error end!直接打出错误缘由。
})
复制代码
你们发现没有,Async和sync的区别在于Async经过callback来和后续的函数沟通,sync则是经过return一个值来作交流。因此,Async自带sync中bail类型的钩子。我曾经作了一个无聊的统计,由于钩子太多了,我写了一个代码遍历了webpack这个项目,得出了全部钩子的使用状况,结果以下所示:
SyncHook 69
SyncBailHook 63
SyncWaterfallHook 36
SyncLoopHook 0
AsyncParallelHook 1
AsyncParallelBailHook 0
AsyncSeriesHook 14
AsyncSeriesBailHook 0
AsyncSeriesWaterfallHook 5
复制代码
可是我发现AsyncSeriesBailHook
居然是0的时候,我很震惊,如今知道缘由了,由于从做用上来讲他和异步钩子的共能自己就重叠了,因此同理AsyncParallelBailHook
这个平行执行的bail类型的钩子也是0 。bail在Async中功能重复,因次用的不多。
言归正传,既然AsyncSeriesHook
的callback经过第一个err
参数来判断是否异步成功,不成功则直接callAsync回调。那么water类型的该如何传递参数?咱们都知道water和basic的区别就在于basic每一个异步tap之间并没有参数传递,而water则是参数传递。很简单,在err后面再加一个参数,做为下一个tap的传入值。
this.hooks.atWork.tapAsync("makePPT",(work,callback)=>{
console.log("作PPT!")
callback("没作完 ","你的ppt")//第一个参数是err,上交你的报错,第二个参数是你自定义要下一个tap处理的参数。若是有err,则忽略此参数。
})
this.hooks.atWork.tapAsync("meeting",(work,callback)=>{//由于ppt没作完,因此开不了会
console.log("带着"+work+"开会!")
callback()
})
this.hooks.atWork.callAsync("working",err=>{//没作完来这里领罚了。
console.log(err+" end!")
})
复制代码
咱们的平常生活!才不会这么单调,怎么会一路顺顺利利地走下来呢?天天都有不一样的精彩啊!小插曲确定少不了。
那么咱们就要相办法将支线剧情插入主线剧情之中了。这个时候一个MyDaily的类已经放不下咱们的精彩生活了。
如下班以后的精彩为例,咱们不必定会直接回家也有可能约会蹦迪什么的。因此这里咱们new一个名为Activity
的类。假设咱们的夜生活有两个活动,一个派对活动,一个回家。那么派对活动确定有个流程,咱们就用熔断型的,为何呢!不开心了接下来就都别执行了!回家吧!回家的这个活动就是简单的钩子。
class Activity {
constructor() {
this.hooks = {
goParty:new SyncBailHook(),
goHome:new SyncHook()
};
}
prepare(){
this.hooks.goParty.tap("happy",()=>{
console.log("happy party")
})
this.hooks.goParty.tap("unhappy",()=>{
console.log("unhappy")
return "go home"
})
this.hooks.goParty.tap("play",()=>{
console.log("continue playing")
})
this.hooks.goHome.tap("goHome",()=>{
console.log("I'm going to sleep")
})
}
start(){
this.hooks.goParty.call()
this.hooks.goHome.call()
}
}
复制代码
而后咱们要将这个单独的类挂到MyDaily的下面,毕竟这也是平常的一部分虽然非正式关卡。咱们能够在工做结束自开始准备晚上的活动,等到一下班就开始咱们丰富的夜生活。这个时候咱们能够在钩子的回调函数中触发另外一个类中的钩子状态,激活或着运行。
class MyDaily {
constructor() {
this.hooks = {
....
};
this.activity=new Activity()//实例化Activity
}
run(){
...
this.hooks.atWork.callAsync("working",res=>{
this.activity.prepare()//下班了你们能够躁动起来了
})
this.hooks.afterWork.callAsync("activity",err=>{
this.activity.start()//去浪咯!
})
}
}
复制代码
我在这里只是举了一个小例子,带你们理解tapable是什么。由于理解了tapable的特性,咱们才能在以后有办法理解webpack的机制,由于这种钩子套钩子的缘由,咱们很难看懂webpack的源代码。下一篇文章我会带你们看懂webpack的主线剧情和主要支线剧情(loader&plugin)的流程!