webpack tapable啥玩意?

832 阅读4分钟

在看webpack源码的时候,全篇布满了tapable Hooks,创建钩子、挂载钩子和调用钩子回调,看的hin~糟心,so,来吧,硬着头皮上吧!

刚才打开了webpack官网,api下发现webpack竟然有辣么多~的钩子,可以看到每一个钩子下方都会显示自己的钩子类型,这些钩子类型就是tapable提供的。而webpack也是靠这些钩子实现了复杂的功能,我们先来瞧一瞧这些钩子是咋用的,再去看webpack源码吧(🤦‍♀️)

在这里插入图片描述

一.同步钩子

  1. SyncHook 这是tapable最简单的一个钩子,它的用法非常简单:
 import { SyncHook } from 'tapable';

const hook = new SyncHook(); // 创建钩子对象
hook.tap('test', () => console.log('钩子回调')); // tap方法注册钩子回调
hook.call(); // call方法调用钩子,打印‘钩子回调’

一般来说,创建钩子、调用钩子都是在主流程代码中,注册钩子回调函数都是在插件代码中,这样就把插件想要实现的功能与主功能解耦了,插件的功能实际上是丰富了主流程的功能。

向插件传递参数 需求:我们要在调用钩子的时候向绑定钩子的插件传递参数。 以webpack中一个钩子为例:

//主流程
compilation: new SyncHook(["compilation", "params"]),
...
this.hooks.compilation.call(compilation, params);
// 插件
compiler.hooks.compilation.tap(
      "SingleEntryPlugin",
      (compilation, { normalModuleFactory }) => {
        ...
    );

首先,需要在声明钩子的时候,告知它这个钩子需要两个参数,在调用钩子的时候就将这两个参数传入,这样在绑定该钩子的插件函数里就可以接收到这两个参数了。 这样,插件就可以操作主流程的参数了。

  1. SyncBailHook bail在英文中的意思是“保释”,这里可以理解为离开,不再执行。
const this.hooks.test = new SyncBailHook()
this.hooks.test.tap('one', () => console.log(`one`));
this.hooks.test.tap('two', () => {console.log(`two`)); return 'success'}
this.hooks.test.tap('three', () => console.log(`three`));

this.hooks.test.call(); // 会打印‘one’‘two’

SyncBailHook这种类型的钩子会根据上一个钩子返回的值,决定要不要往下走,如果返回一个非undefined的值,就会停止执行之后所有的钩子回调函数。 用法:当一个模块满足A、B、C三个任意一个条件时,就将它单独打包,如果A、B、C需要按照一定的执行顺序时,就要用到SyncBailHook,如果 A 中返回为 true,那么就无须再去判断 B 和 C。

  1. SyncWaterfallHook Waterfall在英文中意思为“瀑布”。这里可以理解为下一步需要依赖上一步的执行结果。
const this.hooks.test = new SyncWaterfallHook(['num'])
this.hooks.test.tap('one', (num) => return num + 100);
this.hooks.test.tap('two', (num) => return num + 50);
this.hooks.test.tap('three', (num) => console.log(num));

this.hooks.test.call(100); // 会打印250

用法:当某一个数据需要依次经过A、B、C三个部分的处理,才能得到最后的数据

  1. SyncLoopHook 这个钩子应该比较好理解,可以理解为如果不返回非undefined值,就会一直执行这个回调函数。

二.异步钩子

异步钩子用在当插件中的回调函数是异步的时候。

  1. AsyncParallelHook 这是用来处理异步并行的钩子类型。
const {AsyncParallelHook} = require('tapable');
const FrontEnd = new AsyncParallelHook(['name']);
FrontEnd.tapAsync('webpack',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get webpack ")
    cb();
  }, 1000);
 
});
FrontEnd.tapAsync('react',(name,cb)=>{
  setTimeout(() => {
    console.log(name+" get react")
    cb();
  }, 1000);
});
  FrontEnd.callAsync('小王',()=>{
    console.log("end");
  })
// 输出结果
小王 get webpack 
小王 get react
end

这是网上的一个例子,挂载在钩子上的回调函数是并行执行的,没有任何的前后顺序,在插件的所有异步操作都完成后,会执行主流程中的回调函数。可以确保所有的插件的代码都执行完毕后,再执行某些逻辑。

  1. AsyncParallelBailHook

这个钩子功能同AsyncParallelHook类似,但是第一个插件注册的钩子执行结束后,会进行bail(熔断), 然后会调用最终的回调,无论其他插件是否执行完。

  1. AsyncSeriesHook

这是用来处理异步串行的钩子类型。 注册的异步回调函数依次执行,执行结束后,会执行主流程的回调函数。

  1. AsyncSeriesBailHook

串行执行,并且只要一个插件有返回值,立马调用最终的回调,并且不会继续执行后续的插件。

  1. AsyncSeriesWaterfallHook

串行执行,并且前一个插件的返回值,会作为后一个插件的参数。

钩子的类型大概可以分为以下几类:

  • 同步钩子。Sync开头的钩子
  • 异步串行钩子。AsyncSeries开头的钩子。
  • 异步并行钩子。AsyncParallel开头的钩子。

总结: 在webpack的整个编译打包过程中,暴露出大量的hook供内部和外部的插件使用,而webpack整个主流程的进行也依赖于这些hook,这些功能都是由Tapable实现的,从一个事件钩子到下一个事件钩子,驱动了webpack的整个编译过程。 Tapable其实就是典型的订阅发布模式,但是功能远比EventEmit强大许多,它提供了多种类型的钩子类型来满足不同插件需要的功能。且将插件的执行逻辑与主流程解耦,庞大的webpack却有很清晰的代码执行逻辑,与Tapable有不可分割的关系。

好啦,我们去看webpack源码吧~