tapable钩子实现

吐槽君 分类:javascript

最近在阅读webpack源码的时候,发现在webpack的编译器(compiler.js)里用到了tapable,于是查资料学习了以下,这里做一个学习记录。

tapable是一个基于 发布·订阅 模式的hooks钩子库,类似node的EventEmitter,提供了很多同步/异步钩子函数供我们使用

同步hooks

SyncHook

普通同步钩子:通过tap注册事件到hook的事件列表,然后call调用依次执行注册的事件函数

const { SyncHook } = require('tapable')
class TapSyncHook {
  constructor() {
    this.hooks = {
      log: new SyncHook(['name']),
      say: new SyncHook(['name']),
    }
  }
  tap() {
    this.hooks.log.tap('node', (name) => {
      console.log('node', name)
    })
    this.hooks.say.tap('react', (name) => {
      console.log('react', name)
    })
    this.hooks.say.tap('vue', (name) => {
      console.log('vue', name)
    })
  }
  run(name) {
    this.hooks.log.call(name)
    this.hooks.say.call(name)
  }
}
const tap = new TapSyncHook()
tap.tap()
tap.run('tuya.cn')

/**
 * 执行结果:
 * node tuya.cn
   react tuya.cn
   vue tuya.cn
 */
 

SyncBailHook

带保险的同步钩子:

  • 事件回调函数返回的数据是undefined时,继续执行后面的事件回调;
  • 事件回调函数返回的数据不是undefined时,跳过后面所有事件回调执行;

即:执行事件回调列表直到拿到非undefined值或者执行完所有事件回调

const { SyncBailHook } = require('tapable')
class TapSyncBailHook {
  constructor() {
    this.hooks = {
      log: new SyncBailHook(['name']),
    }
  }
  tap() {
    this.hooks.log.tap('node', (name) => {
      console.log('node', name)
    })
    this.hooks.log.tap('react', (name) => {
      console.log('react', name)
      return 'react'
    })
    this.hooks.log.tap('vue', (name) => {
      console.log('vue', name)
    })
  }
  run(name) {
    this.hooks.log.call(name)
  }
}
const tap = new TapSyncBailHook()
tap.tap()
tap.run('tuya.cn')

/**
 * 执行结果:
 * node tuya.cn
   react tuya.cn
 */
 

SyncWaterfallHook

“瀑布”同步钩子:

  • 事件回调依次执行,上一个回调返回的数据作为下一个函数的入参被使用;
  • 如果上一个事件执行没有返回数据,则依次往上找,直到找到有返回的数据或者初始数据;
const { SyncWaterfallHook } = require('tapable')
class TapSyncWaterfallHook {
  constructor() {
    this.hooks = {
      log: new SyncWaterfallHook(['name']),
    }
  }
  tap() {
    this.hooks.log.tap('node', (name) => {
      console.log('node', name)
      return '返回数据'
    })
    this.hooks.log.tap('react', (data) => {
      console.log('react', data)
    })
    this.hooks.log.tap('vue', (data) => {
      console.log('vue', data)
      return 'vue'
    })
    this.hooks.log.tap('js', (data) => {
      console.log('js', data)
    })
  }
  run(name) {
    this.hooks.log.call(name)
  }
}
const tap = new TapSyncWaterfallHook()
tap.tap()
tap.run('tuya.cn')

/**
 * 执行结果:
 * node tuya.cn
   react 返回数据
   vue 返回数据
   js vue 
 */
 

SyncLoopHook

循环同步钩子:

  • 事件回调返回undefined时,继续执行剩下的事件回调;
  • 事件回调返回的不是undefeated时,死循环执行当前事件函数
const { SyncLoopHook } = require('tapable')
class TapSyncLoopHook {
  constructor() {
    this.hook = {
      log: new SyncLoopHook(['name']),
    }
    this.loop = 3
  }
  tap() {
    this.hook.log.tap('node', (name) => {
      console.log('node', name)
      return --this.loop === 0 ? undefined : 'node'
    })
    this.hook.log.tap('react', (name) => {
      console.log('react', name)
    })
    this.hook.log.tap('vue', (name) => {
      console.log('vue', name)
    })
  }
  run(...args) {
    this.hook.log.call(...args)
  }
}

const hook = new TapSyncLoopHook()
hook.tap()
hook.run('tuya.cn')

//第一个事件函数执行3次
/**
 * 执行结果:
 * node tuya.cn
   node tuya.cn
   node tuya.cn
   react tuya.cn
   vue tuya.cn
 */
 

异步hooks

异步钩子分为异步并发(Parallel)执行和异步串行(Series)执行,从hook名字我们就可以看出来。
所有异步钩子都有callback和promise写法,这里只举例callback写法

AsyncParallelHook

并发异步钩子:

  • 各事件函数并发执行;
  • 与同步钩子不同,采用tapAsync注册事件,callAsync执行事件函数,且回调函数多一个callback参数,用来告知hook当前回调函数执行完成了;
  • 若其中一个函数未执行完(callback未被调用),callAsync的回调都不会执行
const { AsyncParallelHook } = require('tapable')
class TapAsyncParallelHook {
  constructor() {
    this.hooks = {
      log: new AsyncParallelHook(['name']),
    }
  }
  tap() {
    this.hooks.log.tapAsync('node', (name, cb) => {
      setTimeout(() => {
        console.log('node', name)
        cb()
      }, 1000)
    })
    this.hooks.log.tapAsync('react', (data, cb) => {
      setTimeout(() => {
        console.log('react', data)
        cb()
      }, 1000)
    })
    this.hooks.log.tapAsync('vue', (data, cb) => {
      setTimeout(() => {
        console.log('vue', data)
        cb()
      }, 1000)
    })
  }
  run(name) {
    this.hooks.log.callAsync(name, () => {
      console.log('end')
    })
  }
}
const tap = new TapAsyncParallelHook()
tap.tap()
tap.run('tuya.cn')

/**
  * 执行结果:
  * node tuya.cn
    react tuya.cn
    vue tuya.cn
    end 
  */

 

ASyncSeriesHook

异步串行钩子:

  • 各事件函数串性执行,其余的特性与AsyncParallelHook一致
const { AsyncSeriesHook } = require('tapable')
class TapAsyncSeriesHook {
  constructor() {
    this.hooks = {
      log: new AsyncSeriesHook(['name']),
    }
  }
  tap() {
    this.hooks.log.tapAsync('node', (name, cb) => {
      setTimeout(() => {
        console.log('node', name)
        cb()
      }, 1000)
    })
    this.hooks.log.tapAsync('react', (data, cb) => {
      setTimeout(() => {
        console.log('react', data)
        cb()
      }, 1000)
    })
    this.hooks.log.tapAsync('vue', (data, cb) => {
      setTimeout(() => {
        console.log('vue', data)
        cb()
      }, 1000)
    })
  }
  run(name) {
    this.hooks.log.callAsync(name, () => {
      console.log('end')
    })
  }
}
const tap = new TapAsyncSeriesHook()
tap.tap()
tap.run('tuya.cn')

/**
* 执行结果:
* node tuya.cn
  react tuya.cn
  vue tuya.cn
  end 
*/

 

ASyncSeriesWaterfallHook

异步串行“瀑布”钩子:

  • 各事件函数串性执行;
  • 执行完成后调用回调callback,callback接收两个入参,第一个标记是否执行后面的事件,第二个参数是传入下一个事件函数的数据;
  • 若callback的第一个参数是“error”,直接跳过后面的所有事件函数执行,执行callAsync的callback(不传默认是error);
const { AsyncSeriesWaterfallHook } = require('tapable')
class TapAsyncSeriesWaterfallHook {
  constructor() {
    this.hooks = {
      log: new AsyncSeriesWaterfallHook(['name']),
    }
  }
  tap() {
    this.hooks.log.tapAsync('node', (name, cb) => {
      setTimeout(() => {
        console.log('node', name)
        cb(null, 'node')
      }, 1000)
    })
    this.hooks.log.tapAsync('react', (data, cb) => {
      setTimeout(() => {
        console.log('react', data)
        cb(null, 'react')
      }, 1000)
    })
    this.hooks.log.tapAsync('vue', (data, cb) => {
      setTimeout(() => {
        console.log('vue', data)
        cb()
      }, 1000)
    })
  }
  run(name) {
    this.hooks.log.callAsync(name, () => {
      console.log('end')
    })
  }
}
const tap = new TapAsyncSeriesWaterfallHook()
tap.tap()
tap.run('tuya.cn')

/**
* 执行结果:
* node tuya.cn
  react node
  vue react
  end
*/
 

总结:

  1. tapable提供tap注册同步事件,tapAsync,tapPromise注册异步事件;
  2. tapable提供call调用同步事件,callAsync,promise调用异步事件;

以上钩子均手写实现,github查看

回复

我来回复
  • 暂无回复内容