tapable基本概念

tapable 是一个类似于 Node.js 中的 EventEmitter的库,但更专注于自定义事件的触发和处理。webpack 通过 tapable 将实现与流程解耦,所有具体实现通过插件的形式存在。

Tapable分类

  • 按照同步和异步分类可以分成:
    • 同步和异步
    • 异步又可以分为异步串行和异步并行
  • 按照返回值分成:
    • basic 不关心返回值,有没有返回值都无所谓
      • SyncHook 同步
      • AsyncParallelHook 异步并行
      • AsyncSeriesHook 异步串行
    • Waterfall 当前事件参数是上一个事件的返回值,如果上一个事件没有返回值会取在上一层的
      • SyncWaterfallHook 同步瀑布
      • AsyncSeriesWaterfallHook 异步串行瀑布
    • bail 返回值如果非undefined就终止下一个事件的执行
      • SyncBailHook 同步带保险
      • AsyncParallelBailHook 异步并行带保险
      • AsyncSeriesBailHook 异步串行带保险
    • loop 循环执行直到返回值是非undefined,如果返回值不是undefined,就会从头继续向下执行,再次遇到非undefined则继续从头执行
      • SyncLoopHook 同步循环

tapable钩子函数的基本使用

1. SyncHook(同步)

  • SyncHook是一个类(构造函数)
  • 参数是一个数组,用来指定事件回调函数的参数列表,里面变量名没有用,但是数组的长度是有用的(如果数组只有一项,但是实际的时候传递两项的话,后面的值就无法接收,为undefined)
  • 如果没有参数,可以传一个空数组
  • 回调函数的执行是按照注册顺序执行的,先注册先执行
  1. const { SyncHook } = require('tapable');
  2. const hook = new SyncHook(['name', 'age']);
  3. hook.tap('1', (name, age) => { console.log(name, age, 1) }) // => 'test name', 'test age', 1; 可以没有返回值,不关心返回值
  4. hook.tap('2', (name, age) => { console.log(name, age, 2) }) // => 'test name', 'test age', 2;
  5. hook.call('test name', 'test age');

2. SyncBailHook(同步带保险)

  • 当事件的回调函数返回值是undefined的话,就会继续执行后续事件函数
  • 如果事件的回调函数返回的是非undefined的话,就会停止后续的事件函数执行
  1. const { SyncBailHook } = require('tabable');
  2. const hook = new SyncBailHook(['name', 'age']);
  3. hook.tap('1', (name, age) => { console.log(name, age, 1); return undefined }); // => 'test name', 'test age', 1;
  4. hook.tap('2', (name, age) => { console.log(name, age, 2); return 2}; // => 'test name', 'test age', 2; 当返回值非undefined的时候,就会停止后续事件函数的执行
  5. hook.tap('3', (name, age) => { console.log(name, age, 3); return 3});
  6. hook.call('test name', 'test age');

3. SyncWaterfaillHoos(同步瀑布)

  • 下一次的参数会采用上一次的返回值,如果上一次没有返回值会采用再上一次的返回值用来当参数 ```javascript const { SyncWaterfaillHook } = require(‘tapable’); const hook = new SyncWaterfaillHoos([‘name’, ‘age’]);

hook.tap(‘1’, (name, age) => { console.log(name, age, 1); return ‘A’}); // => ‘test name’, ‘test age’, 1; hook.tap(‘2’, (name, age) => { console.log(name, age, 2); }); // => ‘A’, ‘test age’, 2; hook.tap(‘3’, (name, age) => { console.log(name, age, 3); return ‘C’}); // => ‘A’, ‘test age’, 1;

hook.call(‘test name’, ‘test age’);

  1. <a name="vqRAc"></a>
  2. ### 4. SyncLoopHook(同步循环)
  3. - 遇到返回值非false的情况下就会从头执行
  4. ```javascript
  5. const { SyncLoopHook } = require('tapable');
  6. const hook = new SyncLoopHook();
  7. let counter1 = 0, counter2 = 0, counter3 = 0;
  8. hook.tap('1', () => {
  9. console.log('counter1', counter1);
  10. if (++counter1 === 1) {
  11. counter1 = 0;
  12. return
  13. }
  14. return true
  15. })
  16. hook.tap('2', () => {
  17. console.log('counter2', counter2);
  18. if (++counter2 === 2) {
  19. counter2 = 0;
  20. return
  21. }
  22. return true
  23. })
  24. hook.tap('3', () => {
  25. console.log('counter3', counter3);
  26. if (++counter3 === 3) {
  27. counter3 = 0;
  28. return
  29. }
  30. return true
  31. })
  32. hook.call('test,', '29')
  33. // => 最终输出15次, 00010, 00011,00012

5. AsyncParallelHoos(异步并行)

  • 异步并行,类似于Promise.all执行时长取决于耗时最长的任务
  • 有两种写法,第一种hook.tapAsync和hook.callAsync,第二种是hook.tapPromise和hook.promise

第一种写法

const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name']);

console.time('cost')
hook.tapAsync('1', (name, callback) => {
  console.log(name, 1);
  setTimeout(() => {
    callback();
  }, 1000);
})
hook.tapAsync('1', (name, callback) => {
  console.log(name, 2);
  setTimeout(() => {
    callback();
  }, 2000);
})
hook.tapAsync('1', (name, callback) => {
  console.log(name, 3);
  setTimeout(() => {
    callback();
  }, 3000);
});

hook.callAsync('test', () => {
  console.timeEnd('cost')
});

第二种写法

const { AsyncParallelHook } = require('tapable');
const hook = new AsyncParallelHook(['name']);

console.time('cost')
hook.tapPromise('1', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(1, name);
      resolve();
    }, 1000);
  })
});

hook.tapPromise('2', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(2, name);
      resolve();
    }, 2000);
  })
});

hook.tapPromise('3', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(3,  name);
      resolve();
    }, 3000);
  })
});

hook.promise('test name').then(res => {
  console.timeEnd('cost')
})

6. AsyncParallelBailHoos(异步并行保险)

  • 如果resolve中传递参数,就终止后面的事件执行 ```javascript const { AsyncParallelBailHook } = require(‘tapable’); const hook = new AsyncParallelBailHook([‘name’]);

console.time(‘cost’)

hook.tapPromise(‘1’, (name) => { return new Promise((resolve) => { setTimeout(() => { resolve(1); }, 1000); }) });

hook.tapPromise(‘2’, (name) => { return new Promise((resolve) => { setTimeout(() => { resolve(); }, 2000); }) });

hook.tapPromise(‘3’, (name) => { return new Promise((resolve) => { setTimeout(() => { resolve(); }, 3000); }) });

hook.promise(‘test name’).then(res => { console.timeEnd(‘cost’) })


<a name="q3APT"></a>
### 7. AsyncSeriesHook(异步串行)

- 与并行不同,会一个一个执行,先执行第一个在执行第二个在执行第三个,耗时取决于所有事件时间加到一起
```javascript
const { AsyncSeriesHook } = require('tapable');
const hook = new AsyncSeriesHook(['name']);

console.time('cost')

hook.tapPromise('1', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(1, name);
      resolve();
    }, 1000);
  })
});

hook.tapPromise('2', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(2, name);
      resolve();
    }, 2000);
  })
});

hook.tapPromise('3', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(3,  name);
      resolve();
    }, 3000);
  })
});

hook.promise('test name').then(res => {
  console.timeEnd('cost')
})

8. AsyncSeriesBailHook(异步串行保险)

  • 和上面的类型,只要有返回值,就终止下面事件的执行 ```javascript const { AsyncSeriesBailHook } = require(‘tapable’); const hook = new AsyncSeriesBailHook([‘name’]);

console.time(‘cost’)

hook.tapPromise(‘1’, (name) => { return new Promise((resolve) => { setTimeout(() => { console.log(1, name); resolve(); }, 1000); }) });

hook.tapPromise(‘2’, (name) => { return new Promise((resolve) => { setTimeout(() => { console.log(2, name); resolve(1); }, 2000); }) });

hook.tapPromise(‘3’, (name) => { return new Promise((resolve) => { setTimeout(() => { console.log(3, name); resolve(); }, 3000); }) });

hook.promise(‘test name’).then(res => { console.timeEnd(‘cost’) });

// => 1 ‘test name’ 2 ‘test name’ cost: 3021.173ms

<a name="qTw9i"></a>
### <br />
<a name="DguUQ"></a>
### 9. AsyncSeriesWaterfallHook(异步串行瀑布)

- 串行执行,事件时间取合集
- 参数会根据返回值来做下一个事件函数的参数
```javascript
const { AsyncSeriesWaterfallHook } = require('tapable');
const hook = new AsyncSeriesWaterfallHook(['name']);

console.time('cost')

hook.tapPromise('1', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(1, name);
      resolve();
    }, 1000);
  })
});

hook.tapPromise('2', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(2, name);
      resolve(1);
    }, 2000);
  })
});

hook.tapPromise('3', (name) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(3,  name);
      resolve();
    }, 3000);
  })
});

hook.promise('test name').then(res => {
  console.timeEnd('cost')
});

// => 1 'test name'
      2 'test name'
      3 1
      cost: 6017.594ms

tapable钩子函数的基本实现

/SyncHook.js(同步)

const Hook = require('./Hook');
const HookCodeFactory = require('./HookCodeFactory');

const factory = new HookCodeFactory();
class SyncHook extends Hook {
    compile(options) {
      factory.setup(this, options);
    return facrory.create(options);
  }
}

module.export = SyncHook

/AsyncParallelHook.js(异步并行)

const Hook = require("./hook");
const HookCodeFactory = require('./HookCodeFactory');


class AsyncParallelHookCodeFactory extends HookCodeFactory {
  // 子类实现的
  // onDone 是为了方便里面处理不同的钩子的执行函数 比如_callback或_resolve
  content({ onDone }) {
    return this.callTapsParallel({ onDone });
  }
}

let factory = new AsyncParallelHookCodeFactory();

class AsyncParallelHook extends Hook {
  compile(options) {
    factory.setup(this, options);
    return factory.create(options);
  }
}

module.exports = AsyncParallelHook;

/Hook.js


class Hook {
  constructor(args) {
    // 事件回调参数的函数列表数组
    this.args = args;
    // 放置所有的事件函数对象
    this.taps = [];
    this._x = null;

    // 同步
    this.call = CALL_DELEGATE;
    // 异步
    this.callAsync = ASYNC_CALL_DELEGATE;
    // Promise
    this.promise = PROMISE_DELEGATE;
  }

  // 同步
  tap(options, fn) {
    this._tap('sync', options, fn);
  }

  // 异步
  tapAsync(options, fn) {
       this._tap('async', options, fn);
  }

  // promise
  tapPromise(options, fn) {
      this._tap('promise', options, fn);
  }

  _tap(type, options, fn) {
    if (typeof options === 'string') {
      options = { name: options }
    }

    let tapInfo = { ...options, type, fn };

    this._insert(tapInfo);
  }

  _insert (tapInfo) {
    this.taps.push(tapInfo)
  }

  _createCall(type) {
    // compail 是子类实现的方法
    return this.compile({
      taps: this.taps,
      args: this.args,
      type
    });
  }
}

// 动态创建call方法
const CALL_DELEGATE = function(...args) {
  this.call = this._createCall('sync');
  return this.call(...args);
}

const ASYNC_CALL_DELEGATE = function(...args) {
  this.callAsync = this._createCall('async');
  return this.callAsync(...args);
}

const PROMISE_DELEGATE = function(...args) {
  this.promise = this._createCall('promise');
  return this.promise(...args);
}

module.exports = Hook

/HookCodeFactory.js


class HookCodeFactory {
  setup(hook, options) {
    hook._x = options.taps.map(tap => tap.fn);
  }

  init(options) {
    this.options = options;
  }

  deInit() {
    this.options = null;
  }

  args(options = {}) {
    let { before, after } = options;
    let allArgs = this.options.args;

    if (before) allArgs = [ before, ...allArgs ];
    if (after) allArgs = [ ...allArgs, after ];

    return allArgs.join(',')
  }

  header() {
    let code = '';
    code += `var _x = this._x;\n`;
    return code
  }

  create(options) {
    this.init(options);

    let { type } = options;
    let fn;
    switch (type) {
      case 'sync':
        fn = new Function(
          this.args(),
          this.header() + this.content()
        )
        break;
      case 'async':
        fn = new Function(
          this.args({ after: '_callback' }),
          // 这里的content是子类实现的
          this.header() + this.content({ onDone: () => `_callback(); \n` })
        )
        break;
      case 'promise':
        let tapsCount = this.content({ onDone: () => `_resolve(); \n` });
        let content = `
          return new Promise(function(_resolve){
            ${tapsCount}
          })`;

        fn = new Function(
          this.args(),
          this.header() + content;
        )
        break;
      default:
        break;
    }
    return fn
  }

  // 同步生成函数
  callTapsSeries() {
    let { taps } = this.options;
    if (taps.length === 0) return '';

    let code = '';
    for (let i = 0; i < taps.length; i++) {
      let content = this.callTap(i);
      code += content;
    }

    return code
  }

  // 异步生成函数
  callTapsParallel({ onDone }) {
    let taps = this.options.taps;
    let code = `var _counter = ${taps.length}; \n`;
    code += `
      var _done = (function(){
           ${onDone()}
      })
    `;

    for (let i = 0; i < taps.length; i++) {
      let content = this.callTap(i);
      code += content;
    }
    return code
  }

  callTap(tapIndex) {
    let code = '';
    code += `var _fn${tapIndex} = _x[${tapIndex}];\n`;
    let tapInfo = this.options.taps[tapIndex];
    switch (tapInfo.type) {
      // 如果是同步
      case 'sync':
        code += `_fn${tapIndex}(${this.args()});\n`;
        break;
      case 'async':
        code += `
          _fn${tapIndex}(${this.args()}, (function(){
            if (--_counter === 0) _done();
          })); `;
        break;
      case 'promise':
        code += `
          var _promise${tapIndex} = _fn${tapIndex}(${this.args()});
          _promise${tapIndex}.then((function() {
            if (--_counter === 0) _done();
          }));`;
        break;
      default:
        break;
    }
    return code
  }
}

module.exports = HookCodeFactory;

生成的函数文件

SyncHook 执行最终生成的执行文件

(function anonymous(name,age
) {
  var _x = this._x;
  var _fn0 = _x[0];
  _fn0(name,age);
  var _fn1 = _x[1];
  _fn1(name,age);
  var _fn2 = _x[2];
  _fn2(name,age);
})

AsyncParallelHook 执行最终生成的执行文件(简化后的,去除了报错处理)

(function anonymous(name, _callback) {
  var _x = this._x;
  var _counter = 3;

  var _done = (function () {
    _callback();
  })
  var _fn0 = _x[0];

  _fn0(name, (function () {
    if (--_counter === 0) _done();
  })); var _fn1 = _x[1];

  _fn1(name, (function () {
    if (--_counter === 0) _done();
  })); var _fn2 = _x[2];

  _fn2(name, (function () {
    if (--_counter === 0) _done();
  }));
})

AsyncParallelHook promise 写法生成最终的执行文件(简化后的,去除了报错处理)

(function anonymous(name) {
  var _x = this._x;
  return new Promise((function (_resolve, _reject) {

    var _counter = 3;
    var _done = (function () {
      _resolve();
    });

    var _fn0 = _x[0];
    var _promise0 = _fn0(name);

    _promise0.then((function () {
      if (--_counter === 0) _done();
    }));

    var _fn1 = _x[1];
    var _promise1 = _fn1(name);

    _promise1.then((function () {
      if (--_counter === 0) _done();
    }));
    var _fn2 = _x[2];
    var _promise2 = _fn2(name);

    _promise2.then((function () {
      if (--_counter === 0) _done();
    }));
  }));

})

拦截器 - interceptor

基本概念

所有的钩子都提供额外的拦截器API

  • call: (…args) => void
    • 当你的钩子也就是call函数触发前触发此函数,你可以访问钩子的参数,多个钩子执行一次
  • tap: (tap: Tap) => void
    • 每个钩子函数执行前触发此函数,多个钩子触发多次
  • register: (tap: Tap) => Tap | undefined
    • 每添加一个Tap都会触发此函数,你下一个拦截器的register 函数得到的参数 取决于你上一个register返回的值,所以你最好返回一个 tap 钩子

基本使用

const { SyncHook } = require('tapable');
const hook = new SyncHook(['name', 'age']);

hook.intercept({
  register: () => {}  // => 当你注册一个tap回调函数触发
  call: () => {}     // => 当执行call方法之前触发
    tap: () => {}  // => 每个tap函数会触发一次
});

hook.tap('name', () => {});
hook.call();

基本实现

/Hook.js

实现注册拦截器方法

class Hook {
    constructor {
      ...
    this.interceptors = [];
  }

    // 注册一个拦截器就放入数组中一个
    intercept(interceptor) {
      this.interceptors.push(interceptor);
  }

    _tap(tapInfo) {
      ...
    // 实现注册拦截器函数
    this._runRegisterInterceptors(tapInfo) // => tapInfo = { type: 'sync', fn: fn(), options: {name: options}}
  }

    // 执行注册的拦截器方法
    _runRegisterInterceptors(tapInfo) {
      for (const intercept of this.interceptors) {
        if (intercept.register) {
        // 如果拦截器中有注册拦截器就执行此函数 并且把参数传进去
          intercept.register(tapInfo)
      }
    }
  }

    _create(type) {
       return this.compile({
      taps: this.taps,
      args: this.args,
      type,
      // 把拦截器对象传递给工厂类
      interceptors: this.interceptors
    });
  }
}

/HookCodeFactory.js

  • 实现call拦截器方法和tap拦截器方法
  • 本质还是拼接成一个可以执行的函数
class HookCodeFactory {
    header() {
    let code = '';
    code += `var _x = this._x;\n`;

    // 添加call拦截器函数
    if (this.options.interceptors.length) {
      code += `var _taps = this.taps;\n`;
      code += `var _interceptors = this.interceptors;\n`;

      for (let i = 0; i < this.options.interceptors.length; i++) {
        const interceptor=this.options.interceptors[i];
        if (interceptor.call) {
          code += `_interceptors[${i}].call(${this.args()});\n`;
        }
      }
    }

    return code
  }

  callTap(tapIndex) {
    let code = '';

    // 添加tap拦截器函数
    if (this.options.interceptors.length) {
      code += `var _tap${tapIndex} = _taps[${tapIndex}];`;

      for (let i = 0; i < this.options.interceptors.length; i++) {
        const interceptor=this.options.interceptors[i];
        if (interceptor.tap) {
          code += `_interceptors[${i}].tap(_tap${tapIndex});`;
        }
      }
    }

    return code
  }
}

HookMap

HookMap的基本使用

const { SyncHook, HookMap } = require('tapable');
const hook = new HookMap(() => new SyncHook(['name']));

hook.for('first').tap('name', () => {
  console.log('第一个');
})

hook.for('first').tap('name', () => {
  console.log(‘第二个’);
})

const map1 = hook.get('first');
map1.call('zz');

=> 第一个
     第二个

HookMap的基本实现

class HookMap {
    constructor(createHookFactory) {
      this._createHookFactory = createHookFactory;
    this._map = new Map();
  }

  for(key) {
    // 先从map对象中取出,如果有直接返回
      const hook = this._map.get(key);
    if (hook) return hook;
    // 没有创建对应的Hook实例,并存到map中,并且返回 
    let newHook = this._createHookFactory();
    this._map.set(key, newHook);
    return newHook
  }

  get(key) {
      return this._map.get(key)
  }
}