webapck核心依赖库--Tapable

298 阅读5分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

small449948afb2499091509abb6a4438d23d1621960600.jpg

引言

Webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 tapable,Webpack 中最核心的,负责编译的 Compiler 和负责创建 bundlesCompilation 都是 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种注册方式

  1. tap: 生产同步钩子对应的(事件)goods。
  2. tapAsync: 生产带callback回调的异步钩子对应的goods。
  3. tapPromise: 生产带promise回调的异步钩子对应的goods。

与注册对应的3种调用方式

  1. call : 调用注册的同步钩子。
  2. callAsync: 调用注册的有callback回调函数的异步钩子。
  3. 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,

与上相同。