这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
引言
Webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 tapable
,Webpack 中最核心的,负责编译的 Compiler
和负责创建 bundles
的 Compilation
都是 tapable
构造函数的实例。
1. 什么是Tapable
Tapable就是webpack用来创建钩子
的库
。
Tapable
是webpack内部使用的一个流程管理工具,主要用来串联插件,完善事件流执行。
简单来说,Tapable
就是webpack用来创建钩子
的,那么为什么webapck要创建钩子呢?我们先看下面一句话:
webpack的核心功能是通过抽离出很多
插件
来实现的。可以说插件就是webpack的基石,这些基石又影响着流程的走向。这些钩子是通过Tapable
串起来的,可以类比Vue框架的生命周期,webpack也有自己的生命周期,在周期里边会顺序地触发一些钩子,挂载在这些钩子上的插件的方法
得以执行,从而进行一些特定的逻辑处理。在插件里边,构建的实体或构建出来的数据结果都是可触达的,这样做实现了webpack的高度可扩展。
我们先简单理解一下插件,插件
就是功能的扩展,不会影响原有的数据。
在webpack整个运行流程中,我们需要很多插件放在特定的时期去扩展我们想要的功能。插件是如何实现自己代码的功能呢?就是通过在webpack的各种钩子
函数上挂载
自己的方法
。webapck运行时会自己执行
钩子上挂载的这些插件的方法。所以,webapck需要在运行的各个时期暴露
出钩子
,好让插件在这些钩子上挂载他们要执行的方法。webapck的钩子都是通过Tapable
创建的。
我们可以把webpack实例理解为一个长的衣柜
,从上到下代表着webpack运行期间不同的流程。webpack从上到下创建了不同时期的衣架(钩子),有挂鞋的,挂衣服的,挂裤子的,那么我们就可以在这些钩子上挂载我们想要挂载的东西(插件的各种方法)。
2. Tapable的基本概念
Tapable 中主要提供了同步
与异步
两种钩子。
同步钩子:
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
异步钩子:
-
异步并行钩子:
AsyncParallelHook, AsyncParallelBailHook,
-
异步串行钩子:
AsyncSeriesHook, AsyncSeriesBailHook, AsyncSeriesWaterfallHook,
hook类型解析
- 基本的钩子(没有Bail/Waterfall/Loop):只会简单的调用每个tap传进去的函数。
- 带Waterfall的钩子(瀑布流):也会调用每个tap传进去的函数,但会把每一个函数的返回值传给下一个函数参数。
- 带Bail的钩子(保释):允许更早的退出。如果有某个函数返回值不是undefined, bail类会停止执行后面其他的函数执行,(保释出来了)。
- 带Loop的钩子:如果某个函数的返回值不是undefined,这循环(重复)调用这个回调函数,直到它的返回值为undefined。
插件3种注册方式
- tap: 生产同步钩子对应的(事件)goods。
- tapAsync: 生产带callback回调的异步钩子对应的goods。
- tapPromise: 生产带promise回调的异步钩子对应的goods。
与注册对应的3种调用方式
- call : 调用注册的同步钩子。
- callAsync: 调用注册的有callback回调函数的异步钩子。
- promise: 调用注册的有promise回调的异步钩子。
3. Tapable的基本使用示例
同步钩子的使用:
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
SyncHook:
// 同步钩子
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
} = require ('tapable');
// 初始化hook,确定参数
const syncHK = new SyncHook(['name', 'age'])
// 生产商品,注册事件
syncHK.tap( 'plugin1', (name, age)=> {
console.log('plugin1:', name, age);
} )
syncHK.tap('plugin2:', (name, age) => {
console.log('plugin2:', name, age);
})
// 消费
syncHK.call('zack', 18)
SyncBailHook:
const bailHook = new SyncBailHook(['name', 'age'])
// 注册事件
bailHook.tap('a', (name, age) => {
console.log('a:', name, age);
})
bailHook.tap('b', (name, age) => {
console.log('b:', name, age);
return 'b'
})
bailHook.tap('c', (name, age) => {
console.log('c:', name, age);
})
// 消费
bailHook.call('lili', 20)
// result2
// a: lili 20
// b: lili 20
SyncWaterfallHook:
const waterFallHK = new SyncWaterfallHook(['name', 'age'])
// 注册事件
waterFallHK.tap('one', (name, age) => {
console.log('one:', name, age);
return age
})
waterFallHK.tap('two', (age) => {
console.log('two:', age);
})
// 消费
waterFallHK.call('pilipili', 25)
// result3
// one: pilipili 25
// two: 25
SyncLoopHook:
let num = 20
const loopHK = new SyncLoopHook (['name', 'age'])
// 生产商品
loopHK.tap('1', (name, age) => {
console.log('1:', name, age);
if(num>18) {
num--;
console.log('num:', num);
return num
}
})
loopHK.tap('2', (name, age) => {
console.log('2', name, age);
})
// 消费
loopHK.call('kiki', 21)
// result4:
// 1: kiki 21
// num: 19
// 1: kiki 21
// num: 18
// 1: kiki 21
// 2 kiki 21
异步并行钩子的使用:
异步钩子注册一般不使用tap,不然最后还是会串行,使用后两种。
所有任务一起执行,谁先完成谁先输出,所有任务执行完后执行回调。
AsyncParallelHook,
AsyncParallelBailHook,
AsyncParallelHook:
异步并行执行,当所有注册事件都执行完成后,才执行callAsync
或者promise
。
const asyncPHK = new AsyncParallelHook(['name'])
asyncPHK.tapAsync('print1', (name, callbackName) => {
setTimeout(() => {
console.log('1', name);
callbackName('hhhh', '11')
}, 2000)
})
asyncPHK.callAsync('lili', (err, res) => {
console.log('err', err);
console.log('res', res);
})
// 1 lili
// err hhhh
// res undefined
const asyncPHK2 = new AsyncParallelHook(['name'])
asyncPHK2.tapPromise('1', (name) => {
// tapPromise需要返回一个Promise
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name, 1);
resolve('11')
}, 2000)
})
})
asyncPHK2.tapPromise('2', (name) => {
// tapPromise需要返回一个Promise
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name, 2);
resolve('22')
}, 1000)
})
})
// 触发
asyncPHK2.promise('tapPromise')
.then(res => {
console.log('over', res);
})
.catch(err => {
console.log('error', err);
})
// tapPromise 2
// tapPromise 1
// over undefined
AsyncParallelBailHook:
const asyncPBailHK = new AsyncParallelBailHook(['name'])
asyncPBailHK.tapAsync('1', (name, callBK) => {
setTimeout(() => {
console.log(name, 01);
callBK('001')
}, 2000)
})
asyncPBailHK.tapAsync('2', (name, callBK) => {
setTimeout(() => {
console.log(name, 02);
callBK('002')
}, 1000)
})
asyncPBailHK.callAsync('bail', (res) => {
console.log(res);
})
// bail 2
// bail 1
// 001
const asyncPBailHK2 = new AsyncParallelBailHook(['name'])
asyncPBailHK2.tapPromise('2', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name, 22);
resolve('222')
}, 2000)
})
})
asyncPBailHK2.tapPromise('2', (name) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(name, 23);
resolve('223')
}, 1000)
})
})
asyncPBailHK2.promise('bail222')
.then(res => {
console.log('over', res);
})
.catch(err => {
console.log('err', err);
})
异步串行钩子的使用:
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook,
与上相同。