一、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就会退出循环 |
1、Basic钩子
执行每一个事件函数,不关心函数的返回值,有SyncHook、AsyncParalleHook、AsyncSeriesHook
2、Bail钩子
执行每一个事函数,遇到第一个返回结果不为undefined则返回,不再执行。有SyncBailHook、AsyncSeriesBailHook、AsyncParalleBailHook。
3、Waterfall钩子
如果前一个事件函数的结果不为undefined时,则返回的结果作为后一个事件函数的第一个参数。有SyncWaterfallHook、AsyncSeriesWaterfallHook。
4、Loop钩子
不停循环执行事件函数,知道所有的返回结果为undefined时。有SyncLoopHook、AsyncSeriesLoopHook。
本文只介绍tapable相关钩子函数的使用,不注重原理实现。
三、使用
1、同步钩子
1.1、SyncHook - 同步普通钩子
所有构造函数都接受一个可选参数,参数是一个参数名的字符串数组。
所有参数的名字可以任意填写,但是参数数组的长度必须要根实际接受的参数个数相同,如果回调函数不接受参数可以传入空数组。
执行call时,参数个数和实例化时的数组长度有关
回调的时候是按先入先出的顺序执行的,先放的先执行
const { SyncHook } = require('tapable');const hook = new SyncHook(['name', 'age']);hook.tap('test', (name, age) => { console.log(1, name, age);})hook.tap('test', (name, age) => { console.log(2, name, age);})hook.call('wz', 18);// 打印结果1 'wz' 182 'wz' 18
1.2、SyncBailHook - 同步保险钩子
BailHook中的回调函数是顺序执行的,调用call时传入的参数可以传递给回调函数,当回调函数返回非undefined的时候会停止后续的回调调用。
const { SyncBailHook } = require('tapable');const hook = new SyncHook(['name', 'age']);hook.tap('test', (name, age) => { console.log(7, name, age); // return 123; 返回123})hook.tap('test', (name, age) => { console.log(8, name, age);})hook.call('wz', 18);// 打印结果1、如果没有返回结果7 'wz' 188 'wz' 182、返回1237 'wz' 183、返回undefined7 'wz' 188 'wz' 18
1.3、SyncWaterfallHook - 同步瀑布钩子
SyncWaterfallHook表示如果上一个回调函数的结果不为undefined,则可以作为下一个回调函数的第一个参数。回调函数接受的参数来自于上一个函数的结果。调用call传入的第一个参数,会被上一个函数的非undefined结果替代。当回调函数返回非undefined不会停止回调函数执行。
const { SyncWaterfallHook } = require("tapable");const hook = new SyncWaterfallHook(["name", "age"]);hook.tap("1", (name, age) => { console.log(1, name, age); return 234});hook.tap("2", (name, age) => { console.log(2, name, age); return 123});hook.tap("3", (name, age) => { console.log(3, name, age);});hook.call("wz", 10);// 执行结果1 'wz' 102 234 103 123 10
1.4、SyncLoopHook钩子
SyncLoopHook的特点是不停的循环执行回调函数,直到函数 结果等于undefined,并且每次循环都是从头开始的。
const { SyncLoopHook } = require("tapable");//当回调函数返回非undefined值的时候会停止调用后续的回调let hook = new SyncLoopHook(["name", "age"]);let counter1 = 0;let counter2 = 0;let counter3 = 0;hook.tap("1", (name, age) => { console.log(1, "counter1", counter1); if (++counter1 == 1) { counter1 = 0; return; } return true;});hook.tap("2", (name, age) => { console.log(2, "counter2", counter2); if (++counter2 == 2) { counter2 = 0; return; } return true;});hook.tap("3", (name, age) => { console.log(3, "counter3", counter3); if (++counter3 == 3) { counter3 = 0; return; } return true;});hook.call("wz", 10);// 执行结果1 counter1 02 counter2 01 counter1 02 counter2 13 counter3 01 counter1 02 counter2 01 counter1 02 counter2 13 counter3 11 counter1 02 counter2 01 counter1 02 counter2 13 counter3 2
2、异步钩子
2.1、AsyncParallelHook - 异步并行钩子
使用tap方式进行同步注册
const { AsyncParallelHook } = require('tapable');const hook = new AsyncParallelHook(['name', 'age']);hook.tap('1', (name, age) => { console.log(1, name, age);})hook.tap('2', (name, age) => { console.log(2, name, age);})hook.callAsync('wz', 18, () => { console.log(3, 'success')})// 执行结果1 'wz' 182 'wz' 183 'success'
使用tapAsync方式进行异步注册,全部任务执行完成后执行最终的回调。如果注册的函数中callback中有参数,会直接跳过后面的注册的函数,直接执行最终回调后再去执行其他注册的函数。
const { AsyncParallelHook } = require('tapable');const hook = new AsyncParallelHook(['name', 'age']);console.time("cost");hook.tapAsync('1', (name, age, callback) => { setTimeout(() => { console.log(1, name, age); callback(); }, 1000)})hook.tapAsync('2', (name, age, callback) => { setTimeout(() => { console.log(2, name, age); callback(); }, 2000)})hook.callAsync('wz', 18, (err) => { console.log(3, 'success', err); console.timeEnd("cost");})// 执行结果1 'wz' 182 'wz' 183 'success' undefinedcost: 2004.567ms
使用tapPromise方式进行注册,使用pormise方式调用,全部执行完成后执行最后的then方法
const { AsyncParallelHook } = require('tapable');const hook = new AsyncParallelHook(['name', 'age']);console.time("cost");hook.tapPromise('1', (name, age) => { return new Promise((resolve) => { setTimeout(() => { console.log(1, name, age); resolve(3) }, 1000) })})hook.tapPromise('2', (name, age) => { return new Promise((resolve) => { setTimeout(() => { console.log(2, name, age); resolve(5) }, 2000) })})hook.promise('wz', 18).then(res => { console.timeEnd("cost");})// 执行结果1 'wz' 182 'wz' 18cost: 2003.845ms
2.2、AsyncParallelBailHook - 异步并行保险钩子
带有保险的异步钩子,有一个任务返回不会空就直接结束。
tap方式注册,如果有一个任务返回值则就执行最终的回调。
let { AsyncParallelBailHook } = require("tapable");let queue = new AsyncParallelBailHook(["name"]);console.time("cost");queue.tap("1", function (name) { console.log(1, name); return "Wrong";});queue.tap("2", function (name) { console.log(2, name);});queue.tap("3", function (name) { console.log(1, name);});queue.callAsync("zwz", (err) => { console.log(err); console.timeEnd("cost");});// 执行结果1 'zwz'nullcost: 4.540ms
tapAsync方式注册,如果回调中参数不为空,会立即执行最终的回调后执行后面注册的方法。
let { AsyncParallelBailHook } = require("tapable");let queue = new AsyncParallelBailHook(["name"]);console.time("cost");queue.tapAsync("1", function (name, callback) { setTimeout(() => { console.log(1, name); callback("Wrong"); }, 1000)});queue.tapAsync("2", function (name, callback) { setTimeout(() => { console.log(2, name); callback(); }, 2000)});queue.callAsync("wz", (err) => { console.log(err); console.timeEnd("cost");});// 执行结果1 'wz'Wrongcost: 1011.024ms2 '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