尝试了解webpack的工作机制的时候,发现webpack大量使用了tapable这个核心库来组织代码,
tapable 提供了很多中钩子注册,执行的机制。让webpack可以灵活的管理模块编译的各个阶段,灵活的在处理的不同阶段触发plugin的预先定义的钩子。
仓库地址:github
github readme里有详细的使用文档
本文通过自己使用的demo详细讲解
一:tapable 提供的常规的N种钩子
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
先用一个最简单的demo来感受下,使用方法是:
let ahook = new SyncHook() // 通过new 创建一个钩子的实例
ahook.tap('name', () => {
console.log('xxx')}) // 通过tap来挂载一个函数到钩子实例上
ahook.call() // 通过call来触发钩子函数执行,钩子执行会触发所有挂载函数的执行
// xxx 调用call后,输出xxx
这是最简单的一个执行流程,不同的钩子,声明,挂载,执行机制,会有 差异,
下面将写一个完整的类来定义所有的钩子和钩子对应的触发方法,然后一一讲解
class LearnTabpable {
constructor () {
this.hooks = {
testSyncHook: new SyncHook(),
testSyncBailHook: new SyncBailHook(),
testSyncWaterfallHook: new SyncWaterfallHook(['wa']),
testSyncLoopHook: new SyncLoopHook(),
testAsyncParalleHook: new AsyncParallelHook(),
testAsyncParalleBailHook: new AsyncParallelBailHook(),
testAsyncSeriesHook: new AsyncSeriesHook(),
testAsyncSeriesBailHook: new AsyncSeriesBailHook(),
testAsyncSeriesWaterfallHook: new AsyncSeriesWaterfallHook(['aa'])
}
}
}
var a = new LearnTabpable()
a.hooks.testSyncHook.tap('test sync hook', () => {
console.log('test sync hook console1')})
a.hooks.testSyncHook.tap('test sync hook', () => {
console.log('test sync hook console2')})
a.hooks.testSyncHook.call()
运行结果
test sync hook console1
test sync hook console2
a.hooks.testSyncBailHook.tap('test ', () => {
console.log('test sync bail hook console')})
a.hooks.testSyncBailHook.tap('test ', () => {
console.log('return 1'); return 1})
// return 一个非undefined的内容,就会停止
a.hooks.testSyncBailHook.tap('test ', () => {
console.log('test sync bail hook console2')})
a.hooks.testSyncBailHook.call()
同步钩子函数上的挂载函数按顺序执行,当某一个函数返回了一个非undefined的值的时候,回停止向下继续执行
执行结果
test sync bail hook console
return 1
a.hooks.testSyncWaterfallHook.tap('f', (water) => {
console.log(water);
return water + 1
})
a.hooks.testSyncWaterfallHook.tap('f', (water) => {
console.log(water);
return water + 1
})
a.hooks.testSyncWaterfallHook.tap('f', (water) => {
console.log(water)
})
a.hooks.testSyncWaterfallHook.call(10)
10 将作为参数在挂载函数中作为入口流转因子,再接下来的挂载函数中,会将上一次的返回值作为新的流转因子进行流转。
执行结果
10
11
12
let index = 0
a.hooks.testSyncLoopHook.tap('f', () => {
console.log(index)
if (index < 5) {
index++
return index
}
})
a.hooks.testSyncLoopHook.tap('f', () => {
console.log('结束了')
})
a.hooks.testSyncLoopHook.call()
此钩子会不停的触发挂载函数,直到挂载函数返回内容为undefined的时候,继续执行下一个挂载函数。直到结束
0
1
2
3
4
5
结束了
a.hooks.testAsyncParalleHook.tapAsync('f', (callback) => {
setTimeout(() => {
console.log('p1')
callback()
}, 1000);
})
a.hooks.testAsyncParalleHook.tapAsync('f2', (callback) => {
setTimeout(() => {
console.log('p2')
callback()
}, 2000);
})
a.hooks.testAsyncParalleHook.callAsync(() => {
console.log('并行走结束了')
})
运行结果
p1
p2
并行走结束了
a.hooks.testAsyncParalleBailHook.tapAsync('f', (callback) => {
setTimeout(() => {
console.log('delay 1')
callback(1)// 这里会导致直接触发钩子函数的回调函数
}, 1000);
})
a.hooks.testAsyncParalleBailHook.tapAsync('f1', (callback) => {
setTimeout(() => {
console.log('delay 2')
callback(1)
}, 2000);
})
a.hooks.testAsyncParalleBailHook.callAsync((result) => {
console.log(result)
})
运行结果
delay1
1
delay2
a.hooks.testAsyncSeriesHook.tapPromise('f1', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p1')
resolve()
}, 1000);
})
})
a.hooks.testAsyncSeriesHook.tapPromise('f2', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('p2')
resolve()
}, 2000);
})
})
a.hooks.testAsyncSeriesHook.promise().then(()=> {
console.log('p3')
})
运行结果
p1 // 1秒后
p2 // 3秒后
p3 // p2 执行完成立即触发
a.hooks.testAsyncSeriesBailHook.tapPromise('f1', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('pB1')
resolve(1)
}, 1000);
})
})
a.hooks.testAsyncSeriesBailHook.tapPromise('f2', () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('pB2')
resolve()
}, 2000);
})
})
a.callTestAsyncSeriesBailHook().then(()=> {
console.log('pB3')
})
运行结果
pB1 // 1秒后运行
pB3
// 串行参数传递
a.hooks.testAsyncSeriesWaterfallHook.tapPromise('f1', (result) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('wf1' , result)
resolve('param 1')
}, 1000);
})
})
a.hooks.testAsyncSeriesWaterfallHook.tapPromise('f2', (result) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('wf2', result)
resolve('xxxx')
}, 2000);
})
})
a.hooks.testAsyncSeriesWaterfallHook.promise('param 0').then((result)=> {
console.log('wfcb', result)
})
运行结果
wf1 param 0
wf2 param 1
wfcb xxxx
二:tapable 其他的API
const keyedHook = new HookMap(key => new SyncHook(["arg"]))
keyedHook.for("some-key").tap("MyPlugin", (arg) => {
/* ... */ });
keyedHook.for("some-key").tapAsync("MyPlugin", (arg, callback) => {
/* ... */ });
keyedHook.for("some-key").tapPromise("MyPlugin", (arg) => {
/* ... */ });
const hook = keyedHook.get("some-key");
if(hook !== undefined) {
hook.callAsync("arg", err => {
/* ... */ });
}
2.MultiHook
我们可以基于MultiHook来创建一个新的hook,这个hook tap后,会同时挂载在两个hook上
const {
MultiHook } = require("tapable");
this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]);
this.hooks.allHooks.tap('f', () => {
})