webpack tapable 简单了解

时间:2021-1-8 作者:admin

Tapable是何物

Tapable 承包了 webpack 最重要的事件工作机制,包括 webapck 源码中高频的两大对象(compiler , compilation)都是继承自 Tabable 类的对象。这些对象都有 Tapable 的注册调用插件的功能,并向外暴露出各自的执行顺序以及 hook 类型。

Tabable 的钩子(hook)

const {
 SyncHook,
 SyncBailHook,
 SyncWaterfallHook,
 SyncLoopHook,
 AsyncParallelHook,
 AsyncParallelBailHook,
 AsyncSeriesHook,
 AsyncSeriesBailHook,
 AsyncSeriesWaterfallHook
 } = require("tapable");
钩子类型如下:(返回值的定义请看 应用篇的触发事件的描述内容,可知返回值的来源有三种)
  1. 基础类型。hook 名称中不包含 BailWaterfall , Loop 的,都属于基本类型。也就是 SyncHook , AsyncParallelHook , AsyncSeriesHook (同步,异步并行,异步串行)。这类钩子,只会简单地调用注册的事件回调, 不关心监听函数的返回值(即,即是有返回值,也不会传递到下一个回调事件中去)。
  2. Bail 类型。SyncBailHook ,AsyncParallelBailHook ,AsyncSeriesBailHook 。当一个事件回调运行时返回的值是 undefined 时或无返回值的时候,才会继续执行后面的回调事件。否则,则停止。
  3. Waterfall 类型。SyncWaterfallHook, AsyncSeriesWaterfallHook (注意,没有 AsyncParallelWaterfallHook 并行瀑布类型)。如果一个事件回调返回值不是 undefined 的话,就会将该值传递给下一个回调事件
  4. Loop 类型。SyncLoopHook 。当注册的回调事件执行后返回值不是 undefined 的话,将会重新注册一个回调事件去执行,直到返回值undefined (或无返回值)为止

tapable

Each hook can be tapped with one or several functions. How they are executed depends on the hook type:

  • Basic hook (without “Waterfall”, “Bail” or “Loop” in its name). This hook simply calls every function it tapped in a row.
  • Waterfall. A waterfall hook also calls each tapped function in a row. Unlike the basic hook, it passes a return value from each function to the next function.
  • Bail. A bail hook allows exiting early. When any of the tapped function returns anything, the bail hook will stop executing the remaining ones.
  • Loop. When a plugin in a loop hook returns a non-undefined value the hook will restart from the first plugin. It will loop until all plugins return undefined.

Webpack 核心库 Tapable 的使用与原理解析

类型 描述
Basic 基础类型,单纯的调用注册的事件回调,并不关心其内部的运行逻辑。
Bail 保险类型,当一个事件回调在运行时返回的值不为 undefined 时,停止后面事件回调的执行。
Waterfall 瀑布类型,如果当前执行的事件回调返回值不为 undefined,那么就把下一个事件回调的第一个参数替换成这个值。
Loop 循环类型,如果当前执行的事件回调的返回值不是 undefined,重新从第一个注册的事件回调处执行,直到当前执行的事件回调没有返回值。下文有详细解释。

总而言之,以上四种类型的钩子,大概应用的场景应是:

  1. 不管有没有返回值,对预期结果都不会产生任何影响的,使用基础类型即可
  2. 预期是没有返回值的,如果有返回值,则打断执行,尽早地反馈结果的,可以使用Bail类型
  3. 如果有一份传家宝,需要代代相传的,则适合waterfall类型啦
  4. 偏执型事件专用,如果一定要结果是没有返回值的,如果有,则生生世世轮回不止的,就是Loop类型了
钩子的回调事件注册方式
  1. 同步。以 Sync 开头的钩子,只能用同步的方式去注册回调事件,如 myHook.tap()
  2. 异步串行。可以用 myHook.tap() , myHook.tapAsync() , myHook.toPromise() 注册事件,事件会串行地注册
  3. 异步并行。可以用 myHook.tap() , myHook.tapAsync() , myHook.toPromise() 注册事件,但事件执行跟异步串行不一样,它会并行地注册回调事件。

tapable

Additionally, hooks can be synchronous or asynchronous. To reflect this, there’re “Sync”, “AsyncSeries”, and “AsyncParallel” hook classes:

  • Sync. A sync hook can only be tapped with synchronous functions (using myHook.tap()).
  • AsyncSeries. An async-series hook can be tapped with synchronous, callback-based and promise-based functions (using myHook.tap(), myHook.tapAsync() and myHook.tapPromise()). They call each async method in a row.
  • AsyncParallel. An async-parallel hook can also be tapped with synchronous, callback-based and promise-based functions (using myHook.tap(), myHook.tapAsync() and myHook.tapPromise()). However, they run each async method in parallel.

应用

注册事件有3个方法:tap , tapAsync , tapPromise ,其中以 Sync 开头的钩子的回调事件只能用 tap 来注册,否则会报错。

const { SyncHook } = require('tapable');
const hook = new SyncHook();

hook.tap('first', () => {
  console.log('first');
});

hook.call();
可调整执行顺序
  1. stage 。值是数字类型,数字越大,事件回调执行时间越晚。

    // 代码来自 
    // Webpack 核心库 Tapable 的使用与原理解析
    // https://juejin.im/post/6844904037624578061#heading-21
    const { SyncHook } = require('tapable');
    const hook = new SyncHook();
    
    hook.tap('first', () => {
      console.log('first');
    });
    
    hook.tap({
        name: 'second',
      // 默认 stage 是 0,会按注册顺序添加事件回调到队列尾部
      // 顺序提前,stage 可以置为负数(比零小)
      // 顺序提后,stage 可以置为正数(比零大)
      stage: 10,
    }, () => {
      console.log('second');
    });
    
    hook.tap('third', () => {
      console.log('third');
    });
    
    hook.call('call');
    /**
     * Console output:
     * 
     * first
     * third
     * second
     */
    
  2. before 。值类型是数组或字符串。值是注册事件回调的名称。

    // 代码来自 
    // Webpack 核心库 Tapable 的使用与原理解析
    // https://juejin.im/post/6844904037624578061#heading-21
    const { SyncHook } = require('tapable');
    const hook = new SyncHook();
    
    hook.tap('first', (name) => {
      console.log('first', name);
    });
    
    hook.tap('second', (name) => {
      console.log('second', name);
    });
    
    hook.tap({
      name: 'third',
      // 把 third 事件回调放到 second 之前执行
      before: 'second',
    }, (name) => {
      console.log('third', name);
    });
    
    hook.call('call');
    
    /**
     * Console output:
     * 
     * first
     * third
     * second
     */
    
触发事件

触发事件的方法跟注册事件的方法一一对应,callcallAsyncpromise

触发事件方法设置要跟注册事件的方法对应起来,不要混用。

触发事件方法的参数需要跟实例化时传给钩子类构造函数的数组长度保持一致,也要跟注册的回调事件的参数保持一致。

  1. call

    // 代码来自 
    // Webpack 核心库 Tapable 的使用与原理解析
    // https://juejin.im/post/6844904037624578061#heading-21
    const { SyncHook } = require('tapable');
    // 1.实例化钩子类时传入的数组,实际上只用上了数组的长度,名称是为了便于维护
    const hook = new SyncHook(['name']);
    
    // 3.other 会是 undefined,因为这个参数并没有在实例化钩子类的数组中声明
    hook.tap('first', (name, other) => {
      console.log('first', name, other);
    });
    
    // 2.实例化钩子类的数组长度为 1,这里却传了 2 个传入参数
    hook.call('call', 'test');
    
    /**
     * Console output:
     * 
     * first call undefined
     */
    
  2. callAsync

    callAsynccall 不同的是,在回调事件参数的末尾,会多一个 callback 的事件,且 callback 是一定会执行的,否则,将不会执行之后的回调事件。且,如果 callback 中传递参数的话,将会阻断执行后面的回调事件,进入到 callAsync 的回调事件中。

    // 代码来自 
    // Webpack 核心库 Tapable 的使用与原理解析
    // https://juejin.im/post/6844904037624578061#heading-21
    const { AsyncSeriesHook } = require('tapable');
    const hook = new AsyncSeriesHook(['name']);
    
    hook.tapAsync('first', (name, callback) => {
      console.log('first', name, callback);
      callback(); 
    });
    
    hook.tapAsync('second', (name, callback) => {
      console.log('second', name, callback);
      callback('second'); 
    });
    
    hook.tapAsync('thrid', (name, callback) => {
      console.log('thrid', name, callback);
      callback('thrid'); 
    });
    
    hook.callAsync('callAsync', (error, result) => {
        console.log('callAsync', error, result);
    });
    /**
     * first callAsync [Function]
     * second callAsync [Function]
     * callAsync second undefined
     */
    
  3. promise

    使用 tapPromise 注册事件回调时,事件执行后必须返回一个 promise ,否则就会报错,这是为了确保事件回调能够按照顺序执行。

    // 代码来自 
    // Webpack 核心库 Tapable 的使用与原理解析
    // https://juejin.im/post/6844904037624578061#heading-21
    const { AsyncSeriesHook, AsyncSeriesWaterfallHook } = require('tapable');
    // AsyncSeriesHook 是基础类型,不关心返回值,所以即使有返回值,也不会传递给下一个回调事件的
    const hook = new AsyncSeriesHook(['name']);
    // AsyncSeriesWaterfallHook 代代相传
    // const hook = new AsyncSeriesWaterfallHook(['name']);
    
    hook.tapPromise('first', (name) => {
      console.log('first', name);
      return Promise.resolve('first');
    });
    
    hook.tapPromise('second', (name) => {
      console.log('second', name);
      return Promise.resolve('second');
    });
    
    const promise = hook.promise('promise');
    
    console.log(promise);
    
    promise.then(value => {
      // value 是 undefined,不会接收到事件回调中传入的值
      console.log('value', value);
    }, reason => {
      // 事件回调返回的 Promise 对象状态是 Rejected
      // reason 会有事件回调中传入的错误信息
      console.log('reason', reason);
    });
    /**
     * AsyncSeriesHook Console output:            
     *                                                      
     * first promise                         
     * Promise { <pending> }        
     * second promise
     * value undefined
     */
    
    /**
     * AsyncSeriesWaterfallHook Console output:            
     *                                                      
     * first promise                         
     * Promise { <pending> }        
     * second firse
     * value second
     */
    

以上触发回调的三种方法,处理返回值的方法也不一样:

  1. callreturn value;
  2. callAsynccallback('something');
  3. promise , promise.resolve('something');

暂缺拦截器和上下文的知识,待补上。

更多的对源码部分的解释,可阅读 Webpack 核心库 Tapable 的使用与原理解析 获得。

参考

Webpack 核心库 Tapable 的使用与原理解析

tapable

Webpack 的插件机制 – Tapable

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。