一、webpack的插件机制

webpack插件机制大体方式是
  • 创建 - webpack在其内部compiler和compilation类上 创建各种钩子。
  • 组册 - 插件将自己的方法注册到对应的钩子上,交给webpack。
  • 调用 - webpack编译过程中,会适时出发相应的钩子,因此也触发了插件的方法。
    webpack本质是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心是Tapable,webpack中最核心负责编译的compiler和创建bundle的compilation都是Tapable的实例。都是通过事件注册和监听触发webpack生命周期中的函数方法。

    二、Tapable的分类

    Hook类型可以分为同步Sync和异步Async,异步又分为并行和串行。
    | 类型 | 要点 | | —- | —- | | Basic(普通) | 不关心监听函数是否有返回值 | | Bail(保险) | 只要监听函数中有返回值(不为undefined)就跳过之后的监听函数 | | waterFall(瀑布) | 上一步的返回值交给下一步使用 | | Loop(循环) | 如果监听函数返回true,则这个监听函数就会反复执行,如果返回undefined就会退出循环 |

image.png

1、Basic钩子

执行每一个事件函数,不关心函数的返回值,有SyncHook、AsyncParalleHook、AsyncSeriesHook

image.png

2、Bail钩子

执行每一个事函数,遇到第一个返回结果不为undefined则返回,不再执行。有SyncBailHook、AsyncSeriesBailHook、AsyncParalleBailHook。

image.png

3、Waterfall钩子

如果前一个事件函数的结果不为undefined时,则返回的结果作为后一个事件函数的第一个参数。有SyncWaterfallHook、AsyncSeriesWaterfallHook。

image.png

4、Loop钩子

不停循环执行事件函数,知道所有的返回结果为undefined时。有SyncLoopHook、AsyncSeriesLoopHook。

本文只介绍tapable相关钩子函数的使用,不注重原理实现。

三、使用

1、同步钩子

1.1、SyncHook - 同步普通钩子

所有构造函数都接受一个可选参数,参数是一个参数名的字符串数组。

所有参数的名字可以任意填写,但是参数数组的长度必须要根实际接受的参数个数相同,如果回调函数不接受参数可以传入空数组。

执行call时,参数个数和实例化时的数组长度有关

回调的时候是按先入先出的顺序执行的,先放的先执行
  1. const { SyncHook } = require('tapable');
  2. const hook = new SyncHook(['name', 'age']);
  3. hook.tap('test', (name, age) => {
  4. console.log(1, name, age);
  5. })
  6. hook.tap('test', (name, age) => {
  7. console.log(2, name, age);
  8. })
  9. hook.call('wz', 18);
  10. // 打印结果
  11. 1 'wz' 18
  12. 2 'wz' 18

1.2、SyncBailHook - 同步保险钩子

BailHook中的回调函数是顺序执行的,调用call时传入的参数可以传递给回调函数,当回调函数返回非undefined的时候会停止后续的回调调用。
  1. const { SyncBailHook } = require('tapable');
  2. const hook = new SyncHook(['name', 'age']);
  3. hook.tap('test', (name, age) => {
  4. console.log(7, name, age);
  5. // return 123; 返回123
  6. })
  7. hook.tap('test', (name, age) => {
  8. console.log(8, name, age);
  9. })
  10. hook.call('wz', 18);
  11. // 打印结果
  12. 1、如果没有返回结果
  13. 7 'wz' 18
  14. 8 'wz' 18
  15. 2、返回123
  16. 7 'wz' 18
  17. 3、返回undefined
  18. 7 'wz' 18
  19. 8 'wz' 18

1.3、SyncWaterfallHook - 同步瀑布钩子

SyncWaterfallHook表示如果上一个回调函数的结果不为undefined,则可以作为下一个回调函数的第一个参数。回调函数接受的参数来自于上一个函数的结果。调用call传入的第一个参数,会被上一个函数的非undefined结果替代。当回调函数返回非undefined不会停止回调函数执行。
  1. const { SyncWaterfallHook } = require("tapable");
  2. const hook = new SyncWaterfallHook(["name", "age"]);
  3. hook.tap("1", (name, age) => {
  4. console.log(1, name, age);
  5. return 234
  6. });
  7. hook.tap("2", (name, age) => {
  8. console.log(2, name, age);
  9. return 123
  10. });
  11. hook.tap("3", (name, age) => {
  12. console.log(3, name, age);
  13. });
  14. hook.call("wz", 10);
  15. // 执行结果
  16. 1 'wz' 10
  17. 2 234 10
  18. 3 123 10

1.4、SyncLoopHook钩子

SyncLoopHook的特点是不停的循环执行回调函数,直到函数 结果等于undefined,并且每次循环都是从头开始的。
  1. const { SyncLoopHook } = require("tapable");
  2. //当回调函数返回非undefined值的时候会停止调用后续的回调
  3. let hook = new SyncLoopHook(["name", "age"]);
  4. let counter1 = 0;
  5. let counter2 = 0;
  6. let counter3 = 0;
  7. hook.tap("1", (name, age) => {
  8. console.log(1, "counter1", counter1);
  9. if (++counter1 == 1) {
  10. counter1 = 0;
  11. return;
  12. }
  13. return true;
  14. });
  15. hook.tap("2", (name, age) => {
  16. console.log(2, "counter2", counter2);
  17. if (++counter2 == 2) {
  18. counter2 = 0;
  19. return;
  20. }
  21. return true;
  22. });
  23. hook.tap("3", (name, age) => {
  24. console.log(3, "counter3", counter3);
  25. if (++counter3 == 3) {
  26. counter3 = 0;
  27. return;
  28. }
  29. return true;
  30. });
  31. hook.call("wz", 10);
  32. // 执行结果
  33. 1 counter1 0
  34. 2 counter2 0
  35. 1 counter1 0
  36. 2 counter2 1
  37. 3 counter3 0
  38. 1 counter1 0
  39. 2 counter2 0
  40. 1 counter1 0
  41. 2 counter2 1
  42. 3 counter3 1
  43. 1 counter1 0
  44. 2 counter2 0
  45. 1 counter1 0
  46. 2 counter2 1
  47. 3 counter3 2

2、异步钩子

2.1、AsyncParallelHook - 异步并行钩子

使用tap方式进行同步注册
  1. const { AsyncParallelHook } = require('tapable');
  2. const hook = new AsyncParallelHook(['name', 'age']);
  3. hook.tap('1', (name, age) => {
  4. console.log(1, name, age);
  5. })
  6. hook.tap('2', (name, age) => {
  7. console.log(2, name, age);
  8. })
  9. hook.callAsync('wz', 18, () => {
  10. console.log(3, 'success')
  11. })
  12. // 执行结果
  13. 1 'wz' 18
  14. 2 'wz' 18
  15. 3 'success'

使用tapAsync方式进行异步注册,全部任务执行完成后执行最终的回调。如果注册的函数中callback中有参数,会直接跳过后面的注册的函数,直接执行最终回调后再去执行其他注册的函数。
  1. const { AsyncParallelHook } = require('tapable');
  2. const hook = new AsyncParallelHook(['name', 'age']);
  3. console.time("cost");
  4. hook.tapAsync('1', (name, age, callback) => {
  5. setTimeout(() => {
  6. console.log(1, name, age);
  7. callback();
  8. }, 1000)
  9. })
  10. hook.tapAsync('2', (name, age, callback) => {
  11. setTimeout(() => {
  12. console.log(2, name, age);
  13. callback();
  14. }, 2000)
  15. })
  16. hook.callAsync('wz', 18, (err) => {
  17. console.log(3, 'success', err);
  18. console.timeEnd("cost");
  19. })
  20. // 执行结果
  21. 1 'wz' 18
  22. 2 'wz' 18
  23. 3 'success' undefined
  24. cost: 2004.567ms

使用tapPromise方式进行注册,使用pormise方式调用,全部执行完成后执行最后的then方法
  1. const { AsyncParallelHook } = require('tapable');
  2. const hook = new AsyncParallelHook(['name', 'age']);
  3. console.time("cost");
  4. hook.tapPromise('1', (name, age) => {
  5. return new Promise((resolve) => {
  6. setTimeout(() => {
  7. console.log(1, name, age);
  8. resolve(3)
  9. }, 1000)
  10. })
  11. })
  12. hook.tapPromise('2', (name, age) => {
  13. return new Promise((resolve) => {
  14. setTimeout(() => {
  15. console.log(2, name, age);
  16. resolve(5)
  17. }, 2000)
  18. })
  19. })
  20. hook.promise('wz', 18).then(res => {
  21. console.timeEnd("cost");
  22. })
  23. // 执行结果
  24. 1 'wz' 18
  25. 2 'wz' 18
  26. cost: 2003.845ms

2.2、AsyncParallelBailHook - 异步并行保险钩子

带有保险的异步钩子,有一个任务返回不会空就直接结束。

tap方式注册,如果有一个任务返回值则就执行最终的回调。
  1. let { AsyncParallelBailHook } = require("tapable");
  2. let queue = new AsyncParallelBailHook(["name"]);
  3. console.time("cost");
  4. queue.tap("1", function (name) {
  5. console.log(1, name);
  6. return "Wrong";
  7. });
  8. queue.tap("2", function (name) {
  9. console.log(2, name);
  10. });
  11. queue.tap("3", function (name) {
  12. console.log(1, name);
  13. });
  14. queue.callAsync("zwz", (err) => {
  15. console.log(err);
  16. console.timeEnd("cost");
  17. });
  18. // 执行结果
  19. 1 'zwz'
  20. null
  21. cost: 4.540ms

tapAsync方式注册,如果回调中参数不为空,会立即执行最终的回调后执行后面注册的方法。
  1. let { AsyncParallelBailHook } = require("tapable");
  2. let queue = new AsyncParallelBailHook(["name"]);
  3. console.time("cost");
  4. queue.tapAsync("1", function (name, callback) {
  5. setTimeout(() => {
  6. console.log(1, name);
  7. callback("Wrong");
  8. }, 1000)
  9. });
  10. queue.tapAsync("2", function (name, callback) {
  11. setTimeout(() => {
  12. console.log(2, name);
  13. callback();
  14. }, 2000)
  15. });
  16. queue.callAsync("wz", (err) => {
  17. console.log(err);
  18. console.timeEnd("cost");
  19. });
  20. // 执行结果
  21. 1 'wz'
  22. Wrong
  23. cost: 1011.024ms
  24. 2 'wz'

tapPromise方式注册,只要有一个任务有resolve或者reject的值,都会执行最终的then方法。
let { AsyncParallelBailHook } = require("tapable");
let queue = new AsyncParallelBailHook(["name"]);
console.time("cost");
queue.tapPromise("1", function (name) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(1);
      resolve(1);
    }, 1000);
  });
});
queue.tapPromise("2", function (name) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(2);
      resolve();
    }, 2000);
  });
});
queue.promise("wz").then(
  (result) => {
    console.log("成功", result);
    console.timeEnd("cost");
  },
  (err) => {
    console.error("失败", err);
    console.timeEnd("cost");
  }
);
// 执行结果
1
成功 1
cost: 1011.363ms
2

2.3、AsyncSeriesHook - 异步串行钩子

异步串行钩子是任务一个一个执行,执行完成一个执行下一个

tap方式同步注册
let { AsyncSeriesHook } = require("tapable");
let queue = new AsyncSeriesHook(["name"]);
console.time("cost");
queue.tap("1", function (name) {
  console.log(1);
});
queue.tap("2", function (name) {
  console.log(2);
});
queue.callAsync("wz", (err) => {
  console.log(err);
  console.timeEnd("cost");
});
// 执行结果
1
2
undefined
cost: 3.864ms

tapAsync方式注册,全部任务执行完成后执行最终的回调。如果注册的函数callback有参数,就跳过后面直接执行最终的回调。
let { AsyncSeriesHook } = require("tapable");
let queue = new AsyncSeriesHook(["name"]);
console.time("cost");
queue.tapAsync("1", function (name, callback) {
  setTimeout(function () {
    console.log(1);
    callback();
  }, 1000);
});
queue.tapAsync("2", function (name, callback) {
  setTimeout(function () {
    console.log(2);
    callback();
  }, 2000);
});
queue.callAsync("wz", (err) => {
  console.log(err);
  console.timeEnd("cost");
});
// 执行结果
1
2
undefined
cost: 3014.419ms

tapPromise方式注册,通过promise方式调用。
let { AsyncSeriesHook } = require("tapable");
let queue = new AsyncSeriesHook(["name"]);
console.time("cost");
queue.tapPromise("1", function (name) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log(1, name);
      resolve();
    }, 1000);
  });
});
queue.tapPromise("2", function (name) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log(2, name);
      resolve();
    }, 2000);
  });
});
queue.promise("wz").then((data) => {
  console.log(data);
  console.timeEnd("cost");
});
// 执行结果
1 'wz'
2 'wz'
undefined
cost: 3015.984ms

2.4、AsyncSeriesBailHook - 异步串行保险钩子

只要有一个返回值不为undefined的值就直接返回结果。

tap方式注册。
let { AsyncSeriesBailHook } = require("tapable");
let queue = new AsyncSeriesBailHook(["name"]);
console.time("cost");
queue.tap("1", function (name) {
  console.log(1);
  return "Wrong";
});
queue.tap("2", function (name) {
  console.log(2);
});
queue.callAsync("wz", (err) => {
  console.log(err);
  console.timeEnd("cost");
});
// 执行结果
1
null
cost: 3.655ms

tapAsync方式注册。
let { AsyncSeriesBailHook } = require("tapable");
let queue = new AsyncSeriesBailHook(["name"]);
console.time("cost");
queue.tapAsync("1", function (name, callback) {
  setTimeout(function () {
    console.log(1);
    callback("wrong");
  }, 1000);
});
queue.tapAsync("2", function (name, callback) {
  setTimeout(function () {
    console.log(2);
    callback();
  }, 2000);
});
queue.callAsync("wz", (err) => {
  console.log(err);
  console.timeEnd("cost");
});
// 执行结果
1
wrong
cost: 1009.979ms

tapPromise方式注册,通过promise方式调用。。
let { AsyncSeriesBailHook } = require("tapable");
let queue = new AsyncSeriesBailHook(["name"]);
console.time("cost");
queue.tapPromise("1", function (name) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log(1);
      resolve("success");
    }, 1000);
  });
});
queue.tapPromise("2", function (name, callback) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(2);
      reject();
    }, 2000);
  });
});

queue.promise("wz").then(
  (data) => {
    console.log(data);
    console.timeEnd("cost");
  },
  (error) => {
    console.log(error);
    console.timeEnd("cost");
  }
);
// 执行结果
1
success
cost: 1008.633ms

2.5、AsyncSeriesWaterfallHook - 异步串行瀑布钩子

tap方式同步注册。
let { AsyncSeriesWaterfallHook } = require("tapable");
let queue = new AsyncSeriesWaterfallHook(["name", "age"]);
console.time("cost");
queue.tap("1", function (name, age) {
  console.log(1, name, age);
  return "return1";
});
queue.tap("2", function (data, age) {
  console.log(2, data, age);
  return "return2";
});
queue.callAsync("wz", 10, (err) => {
  console.log(err);
  console.timeEnd("cost");
});
// 执行结果
1 'wz' 10
2 'wz' 10
null
cost: 4.675ms

tapAsync异步方式注册。最后一个注册的callback的参数,会传递给最终的回调函数最为参数。
let { AsyncSeriesWaterfallHook } = require("tapable");
let queue = new AsyncSeriesWaterfallHook(["name", "age"]);
console.time("cost");
queue.tapAsync("1", function (name, age, callback) {
  setTimeout(function () {
    console.log(1, name, age);
    callback(null, 1);
  }, 1000);
});
queue.tapAsync("2", function (data, age, callback) {
  setTimeout(function () {
    console.log(2, data, age);
    callback(null, 2);
  }, 2000);
});
queue.callAsync("wz", 10, (err, data) => {
  console.log(err, data);
  console.timeEnd("cost");
});
// 执行结果
1 'wz' 10
2 1 10
null 2
cost: 3016.924ms

tapPromise方式注册,通过promise方式调用。
let { AsyncSeriesWaterfallHook } = require("tapable");
let queue = new AsyncSeriesWaterfallHook(["name"]);
console.time("cost");
queue.tapPromise("1", function (name) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log(name, 1);
      resolve(1);
    }, 1000);
  });
});
queue.tapPromise("2", function (data) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log(data, 2);
      resolve(2);
    }, 2000);
  });
});

queue.promise("wz").then((res) => {
  console.log(res)
  console.timeEnd("cost");
});
// 执行结果
wz 1
1 2
2
cost: 3009.502ms

3、拦截器intercept

为所有的钩子提供额外拦截器的方法。
  • register - 每一个tap都会触发
  • call - 当钩子触发之前就会触发这个方法,可以访问钩子的参数
  • tap - 每个钩子执行都会触发
    const { SyncHook } = require('tapable')
    const hook = new SyncHook(['name', 'age']);
    hook.intercept({
    register() {
      console.log('拦截器 register')
    },
    tap() {
      console.log('拦截器 tap')
    },
    call(name, age) {
      console.log('拦截器 call', name, age)
    }
    })
    hook.tap('1', (name, age) => {
    console.log(1, name, age);
    })
    hook.tap('2', (name, age) => {
    console.log(2, name, age);
    })
    hook.call('wz', 18);
    // 执行结果
    拦截器 register
    拦截器 register
    拦截器 call wz 18
    拦截器 tap
    1 'wz' 18
    拦截器 tap
    2 'wz' 18