介绍
webpack
本身仅提供前端打包的核心流程,其他具体的打包工作依赖其生态系统中的众多插件。为此 Tapable
为 webpack
提供了强大而灵活的插件管理机制。Tapable
是一个用来处理 订阅 - 发布
的工具。与常见的 dom事件
添加和触发相类似。只不过在此基础之上,添加了一些处理 订阅
的特殊逻辑。
举个例子:
import { SyncHook } from 'tapable';
// 创建钩子对象
const handleElephant = new SyncHook();
// 订阅
handleElephant.tap('step1', () => console.log('1. 把冰箱门打开'));
handleElephant.tap('step2', () => console.log('2. 大象塞进去'));
handleElephant.tap('step3', () => console.log('3. 关上冰箱门'));
// 发布
handleElephant.call();
// 1. 把冰箱门打开
// 2. 大象塞进去
// 3. 关上冰箱门
上面的例子只是最简单的一种使用方式,Tapable
的钩子可以分为两类:1. 同步钩子;2. 异步钩子。每类钩子钩子又分别具有 bail
, waterfall
, loop
。下面就来看看这些具体的钩子。
Hook
创建钩子对象。
可以选择性的传入两个参数:1. call
函数的行参 - callArgumentNameList
;2. 钩子名称 - hookName
const callArgumentNameList = ['index'];
const hookName = 'hookName';
const hook = new SyncHook(callArgumentNameList, hookName);
创建订阅回调。
需要传入两个参数:1. tap名称 - tapName
;2. tap回调函数 - tapFn
,其中 tapFn
函数的行参同上面的 callArgumentNameList
const tapName = 'tapName';
const tapFn = (...callArgumentNameList) => console.log(index);
hook.tap(tapName, tapFn);
调用发布函数。
为 tapFn
函数传入参数
const callArguments = [1];
hook.call(...callArguments);
同步钩子
同步钩子处理的场景为:tap - fn
函数都是同步函数。
SyncHook 钩子
当 syncHook.call
函数调用时,将按照 syncHook.tap
的订阅顺序,依次执行 tap - fn
函数。
实例:
const hook = new SyncHook();
hook.tap('step1', () => '1');
hook.tap('step2', () => '2');
hook.tap('step3', () => '3');
hook.call();
hook.call
实际执行代码
(function anonymous() {
"use strict";
var _context;
var _x = this._x; // tapFnList
var _fn0 = _x[0];
_fn0();
var _fn1 = _x[1];
_fn1();
var _fn2 = _x[2];
_fn2();
})
SyncBailHook 钩子
当 syncBailHook.call
函数调用时,将按照 syncBailHook.tap
的订阅顺序,依次执行 tap - fn
函数。
如果某个 tap - fn
函数的返回值不为 undefined
,则 call
函数返回值为该 tap - fn
函数的返回值,并且停止其他 tap - fn
函数调用。
实例:
const hook = new SyncBailHook();
hook.tap('tapA', () => {
console.log('通过步骤1');
});
hook.tap('tapB', () => {
console.log('通过步骤2');
return 2;
});
hook.tap('tapC', () => {
console.log('通过步骤3');
});
hook.call();
// 因为 tapB - fn 返回值为 2,不为 undefined
// 所以 hook.call 函数停止执行剩余的 tap - fn,并且返回值为 2 。
// 通过步骤1
hook.call
实际执行代码
(function anonymous() {
"use strict";
var _context;
var _x = this._x; // tapFnList
var _fn0 = _x[0];
var _result0 = _fn0();
if (_result0 !== undefined) {
return _result0;;
} else {
var _fn1 = _x[1];
var _result1 = _fn1();
if (_result1 !== undefined) {
return _result1;;
} else {
var _fn2 = _x[2];
var _result2 = _fn2();
if (_result2 !== undefined) {
return _result2;;
} else {}
}
}
})
SyncWaterfallHook 钩子
当 syncWaterfallHook.call
函数调用时,将按照 syncWaterfallHook.tap
的订阅顺序,依次执行 tap - fn
函数。
syncWaterfallHook.call
函数的返回值为该函数的第一个参数: returnValue
。
如果某个 tap - fn
函数的返回值不为 undefined
时,则修改 returnValue
为该函数的返回值,并且停止执行剩余的 tap - fn
函数。
实例:
const hook = new SyncWaterfallHook(['speed']);
hook.tap('tapA', (speed) => {});
hook.tap('tapB', (speed) => speed + 50);
hook.tap('tapC', (speed) => speed + 50);
hook.call(50); // 100
// 因为 tapB - fn 返回值为 100(50 + 50),不为 undefined
// 所以 hook.call 函数停止执行剩余的 tap - fn,并且返回值为 100 。
hook.call
实际执行代码
(function anonymous(speed) {
"use strict";
var _context;
var _x = this._x; // tapFnList
var _fn0 = _x[0];
var _result0 = _fn0(speed, total);
if (_result0 !== undefined) {
speed = _result0;
}
var _fn1 = _x[1];
var _result1 = _fn1(speed, total);
if (_result1 !== undefined) {
speed = _result1;
}
var _fn2 = _x[2];
var _result2 = _fn2(speed, total);
if (_result2 !== undefined) {
speed = _result2;
}
return speed;
})
SyncLoopHook 钩子
当 syncLoopHook.call
函数调用时,将按照 syncLoopHook.tap
的订阅顺序,依次执行 tap - fn
函数。
如果某个 tap - fn
函数返回值不为 undefined
时,则停止剩余 tap - fn
函数执行,从第一个 tap - fn
函数开始重新开始一轮执行。
实例:
const hook = new SyncLoopHook();
let index = 0;
hook.tap('tapA', () => {});
hook.tap('tapB', () => {
if (index++ < 3) return index;
});
hook.tap('tapC', () => {});
hook.call();
// 因为在 tapB - fn 函数中 index++ < 3 需要循环 3 次
// 所以 tapA - fn 函数 和 tapB - fn 函数 依次执行 3 次
// 最后 tapA - fn 函数、tapB - fn 函数、tapC - fn 函数依次执行 1 次
hook.call
实际执行代码
(function anonymous() {
"use strict";
var _context;
var _x = this._x; // tapFnList
var _loop;
do {
_loop = false;
var _fn0 = _x[0];
var _result0 = _fn0();
if (_result0 !== undefined) {
_loop = true;
} else {
var _fn1 = _x[1];
var _result1 = _fn1();
if (_result1 !== undefined) {
_loop = true;
} else {
var _fn2 = _x[2];
var _result2 = _fn2();
if (_result2 !== undefined) {
_loop = true;
} else {
if (!_loop) {}
}
}
}
} while ( _loop );
})
异步钩子
异步钩子处理的场景为:tap - fn
函数都是异步函数。
异步钩子分为两大类:并行异步(parallel)、串行异步(series)。
不论并行异步还是串行异步, tap - call
的调用方式分为两种:
tapAsync - callAsync
callAsync
函数参数中的最后一个参数为 callback
回调函数,该回调函数的执行表示 callAsync
异步函数执行的结束。
tapAsync - fn
函数参数中的最后一个参数为 callback
回调函数,该回调函数的执行表示 tapAsync - fn
异步函数执行的结束。
callback
函数参数中的第一个参数为 err
,当 err
为真性值时,表示有错误发生;第二个参数为 data
。
hook.tapAsync('tapName', (...callArgumentsNameList, callback) => {
callback();
});
hook.callAsync(...callArgumentsNameList, callback)
tapPromise - promise(call)
tapPromise - fn
函数的返回值为 promise
promise(call)
函数的返回值为 promise
hook.tapPromise('tapName', (...callArgumentsNameList) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 2000);
});
});
hook.promise(...callArgumentsNameList).then(successFn, failureFn)
AsyncParallelHook 钩子
当 asyncParallelHook.call
函数调用时,将按照 tapAsync.tap
的订阅顺序,并行执行 tapAsync - fn
函数
如果某个 tapAsync - fn
函数的执行过程中,触发错误;则提前调用回调函数 callback(err)
,停止 callAsync
函数继续执行。
所有的 tapAsync - fn
函数都执行完毕之后,调用回调函数 callback()
。
实例:
const hook = new AsyncParallelHook();
hook.tapAsync('step1', (callback) => {
setTimeout(() => {
callback();
}, 1000);
});
hook.tapAsync('step2', (callback) => {
setTimeout(() => {
callback(1);
}, 2000);
});
hook.tapAsync('step3', (callback) => {
setTimeout(() => {
callback();
}, 3000);
});
hook.callAsync((err) => {
if (err) return console.log('有错误发生');
});
// 因为 tapAsync - step2 执行 callback 函数时第一个参数值为 1 ,触发了错误机制,提前结束 `callAsync` 的执行。
// 打印:有错误发生
hook.call
实际执行代码
(function anonymous(_callback) {
"use strict";
var _context;
var _x = this._x; // tapFnList
do {
var _counter = 3;
var _done = (function() {
_callback();
});
if (_counter <= 0) break;
var _fn0 = _x[0];
_fn0((function(_err0) {
if (_err0) {
if (_counter > 0) {
_callback(_err0);
_counter = 0;
}
} else {
if (--_counter === 0) _done();
}
}));
if (_counter <= 0) break;
var _fn1 = _x[1];
_fn1((function(_err1) {
if (_err1) {
if (_counter > 0) {
_callback(_err1);
_counter = 0;
}
} else {
if (--_counter === 0) _done();
}
}));
if (_counter <= 0) break;
var _fn2 = _x[2];
_fn2((function(_err2) {
if (_err2) {
if (_counter > 0) {
_callback(_err2);
_counter = 0;
}
} else {
if (--_counter === 0) _done();
}
}));
} while ( false );
})
AsyncParallelBailHook 钩子
当 asyncParallelHook.call
函数调用时,将按照 tapAsync.tap
的订阅顺序,并行执行 tapAsync - fn
函数
如果某个 tapAsync - fn
函数的执行过程中,触发错误;则提前调用回调函数 callback(err)
,停止 callAsync
函数继续执行。
如果某个 tapAsync - fn
函数的执行过程中,触发熔断;则提前调用回调函数 callback(null, data)
,停止 callAsync
函数继续执行。
所有的 tapAsync - fn
函数都执行完毕之后,调用回调函数 callback()
。
实例:
const hook = new AsyncParallelBailHook();
hook.tapAsync('step1', (callback) => {
setTimeout(() => {
callback();
}, 1000);
});
hook.tapAsync('step2', (callback) => {
setTimeout(() => {
callback(null, 2);
}, 2000);
});
hook.tapAsync('step3', (callback) => {
setTimeout(() => {
callback();
}, 3000);
});
hook.callAsync((err, data) => {
if (err) return console.log('有错误发生');
console.log(`返回值 - ${data}`);
});
// 因为 tapAsync - step2 执行 callback 函数时第二个参数值为 2 ,触发了熔断机制,提前结束 `callAsync` 的执行。
// 打印:返回值 - 2
hook.call
实际执行代码
(function anonymous(_callback) {
"use strict";
var _context;
var _x = this._x; // tapFnList
var _results = new Array(3);
var _checkDone = function() {
for (var i = 0; i < _results.length; i++) {
var item = _results[i];
if (item === undefined) return false;
if (item.result !== undefined) {
_callback(null, item.result);
return true;
}
if (item.error) {
_callback(item.error);
return true;
}
}
return false;
}
do {
var _counter = 3;
var _done = (function() {
_callback();
});
if (_counter <= 0) break;
var _fn0 = _x[0];
_fn0((function(_err0, _result0) {
if (_err0) {
if (_counter > 0) {
if (0 < _results.length && ((_results.length = 1), (_results[0] = {
error: _err0
}), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
} else {
if (_counter > 0) {
if (0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = {
result: _result0
}), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
}));
if (_counter <= 0) break;
if (1 >= _results.length) {
if (--_counter === 0) _done();
} else {
var _fn1 = _x[1];
_fn1((function(_err1, _result1) {
if (_err1) {
if (_counter > 0) {
if (1 < _results.length && ((_results.length = 2), (_results[1] = {
error: _err1
}), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
} else {
if (_counter > 0) {
if (1 < _results.length && (_result1 !== undefined && (_results.length = 2), (_results[1] = {
result: _result1
}), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
}));
}
if (_counter <= 0) break;
if (2 >= _results.length) {
if (--_counter === 0) _done();
} else {
var _fn2 = _x[2];
_fn2((function(_err2, _result2) {
if (_err2) {
if (_counter > 0) {
if (2 < _results.length && ((_results.length = 3), (_results[2] = {
error: _err2
}), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
} else {
if (_counter > 0) {
if (2 < _results.length && (_result2 !== undefined && (_results.length = 3), (_results[2] = {
result: _result2
}), _checkDone())) {
_counter = 0;
} else {
if (--_counter === 0) _done();
}
}
}
}));
}
} while ( false );
})
AsyncSeriesHook 钩子
当 asyncSeriesHook.call
函数调用时,将按照 tapAsync.tap
的订阅顺序,串行执行 tapAsync - fn
函数
如果某个 tapAsync - fn
函数的执行过程中,触发错误;则提前调用回调函数 callback(err)
,停止 callAsync
函数继续执行。
所有的 tapAsync - fn
函数都执行完毕之后,调用回调函数 callback()
。
实例:
const hook = new AsyncSeriesHook();
hook.tapAsync('step1', (callback) => {
setTimeout(() => {
console.log('step1');
callback();
}, 1000);
});
hook.tapAsync('step2', (callback) => {
setTimeout(() => {
console.log('step2');
callback();
}, 2000);
});
hook.tapAsync('step3', (callback) => {
setTimeout(() => {
console.log('step3');
callback();
}, 3000);
});
hook.callAsync(() => {});
// ... wait 1s ...
// step1
// ... wait 2s ...
// step2
// ... wait 3s ...
// step3
hook.call
实际执行代码
(function anonymous(_callback) {
"use strict";
var _context;
var _x = this._x; // tapFnList
function _next1() {
var _fn2 = _x[2];
_fn2((function(_err2) {
if (_err2) {
_callback(_err2);
} else {
_callback();
}
}));
}
function _next0() {
var _fn1 = _x[1];
_fn1((function(_err1) {
if (_err1) {
_callback(_err1);
} else {
_next1();
}
}));
}
var _fn0 = _x[0];
_fn0((function(_err0) {
if (_err0) {
_callback(_err0);
} else {
_next0();
}
}));
})
AsyncSeriesBailHook 钩子
当 asyncSeriesBailHook.call
函数调用时,将按照 tapAsync.tap
的订阅顺序,串行执行 tapAsync - fn
函数
如果某个 tapAsync - fn
函数的执行过程中,触发错误;则提前调用回调函数 callback(err)
,停止 callAsync
函数继续执行。
如果某个 tapAsync - fn
函数的执行过程中,触发熔断;则提前调用回调函数 callback(null, data)
,止其他 tapAsync - fn
函数调用。
所有的 tapAsync - fn
函数都执行完毕之后,调用回调函数 callback()
。
实例:
const hook = new AsyncSeriesBailHook();
hook.tapAsync('step1', (callback) => {
setTimeout(() => {
console.log('step1');
callback();
}, 1000);
});
hook.tapAsync('step2', (callback) => {
setTimeout(() => {
console.log('step2');
callback(null, 2);
}, 2000);
});
hook.tapAsync('step3', (callback) => {
setTimeout(() => {
console.log('step3');
callback();
}, 3000);
});
hook.callAsync((err, data) => {
if (err) return console.log('有错误发生');
console.log(`返回值 - ${data}`);
});
// 因为 tapAsync - step2 执行 callback 函数时第二个参数值为 2 ,触发了熔断机制,提前结束 `callAsync` 的执行。
// 打印:返回值 - 2
hook.call
实际执行代码
(function anonymous(_callback) {
"use strict";
var _context;
var _x = this._x; // tapFnList
function _next1() {
var _fn2 = _x[2];
_fn2((function(_err2, _result2) {
if (_err2) {
_callback(_err2);
} else {
if (_result2 !== undefined) {
_callback(null, _result2);
} else {
_callback();
}
}
}));
}
function _next0() {
var _fn1 = _x[1];
_fn1((function(_err1, _result1) {
if (_err1) {
_callback(_err1);
} else {
if (_result1 !== undefined) {
_callback(null, _result1);
} else {
_next1();
}
}
}));
}
var _fn0 = _x[0];
_fn0((function(_err0, _result0) {
if (_err0) {
_callback(_err0);
} else {
if (_result0 !== undefined) {
_callback(null, _result0);
} else {
_next0();
}
}
}));
})
AsyncSeriesLoopHook 钩子
当 asyncSeriesLoopHook.call
函数调用时,将按照 tapAsync.tap
的订阅顺序,串行执行 tapAsync - fn
函数
如果某个 tapAsync - fn
函数的执行过程中,触发错误;则提前调用回调函数 callback(err)
,停止 callAsync
函数继续执行。
如果某个 tapAsync - fn
函数的执行过程中,调用回调函数 callback(err, data)
中参数 data
不为 undefined
;则从第一个 tapAsync - fn
函数开始重新开始执行
所有的 tapAsync - fn
函数都执行完毕之后,调用回调函数 callback()
。
实例:
const hook = new AsyncSeriesLoopHook();
let index = 3;
hook.tapAsync('step1', (callback) => {
setTimeout(() => {
console.log('step1');
callback();
}, 1000);
});
hook.tapAsync('step2', (callback) => {
setTimeout(() => {
console.log('step2');
callback(null, index > 0 ? index-- : undefined);
}, 2000);
});
hook.tapAsync('step3', (callback) => {
setTimeout(() => {
console.log('step3');
callback();
}, 3000);
});
hook.callAsync((err) => {});
// 因为 tapAsync - step2 执行的 `callback - data` 值依次为 3, 2, 1, undefined
// 所以依次执行顺序如下:
//
// step1
// step2
//
// step1
// step2
//
// step1
// step2
//
// step1
// step2
// step3
hook.call
实际执行代码
(function anonymous(_callback) {
"use strict";
var _context;
var _x = this._x; // tapFnList
var _looper = (function() {
var _loopAsync = false;
var _loop;
do {
_loop = false;
function _next1() {
var _fn2 = _x[2];
_fn2((function(_err2, _result2) {
if (_err2) {
_callback(_err2);
} else {
if (_result2 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
if (!_loop) {
_callback();
}
}
}
}));
}
function _next0() {
var _fn1 = _x[1];
_fn1((function(_err1, _result1) {
if (_err1) {
_callback(_err1);
} else {
if (_result1 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
_next1();
}
}
}));
}
var _fn0 = _x[0];
_fn0((function(_err0, _result0) {
if (_err0) {
_callback(_err0);
} else {
if (_result0 !== undefined) {
_loop = true;
if (_loopAsync) _looper();
} else {
_next0();
}
}
}));
} while ( _loop );
_loopAsync = true;
});
_looper();
})
AsyncSeriesWaterfallHook 钩子
当 asyncSeriesWaterfallHook.call
函数调用时,将按照 tapAsync.tap
的订阅顺序,串行执行 tapAsync - fn
函数
如果某个 tapAsync - fn
函数的执行过程中,触发错误;则提前调用回调函数 callback(err)
,停止 callAsync
函数继续执行。
如果某个 tapAsync - fn
函数的执行过程中,调用回调函数 callback(err, data)
中参数 data
不为 undefined
;则修改 callArgumentNamelist[0]
为 data
的值。
所有的 tapAsync - fn
函数都执行完毕之后,调用回调函数 callback(null, callArgumentNamelist[0])
。
实例:
const hook = new AsyncSeriesWaterfallHook(['num']);
hook.tapAsync('step1', (num, callback) => {
setTimeout(() => {
console.log('step1');
callback(null, num + 1);
}, 1000);
});
hook.tapAsync('step2', (num, callback) => {
setTimeout(() => {
console.log('step2');
callback();
}, 2000);
});
hook.tapAsync('step3', (num, callback) => {
setTimeout(() => {
console.log('step3');
callback(null, num + 2);
}, 3000);
});
hook.callAsync(1, (err, num) => {
console.log(`返回值 - ${num}`);
});
// step1
// step2
// step3
// step3
// 返回值 - 4
hook.call
实际执行代码
(function anonymous(data, _callback) {
"use strict";
var _context;
var _x = this._x; // tapFnList
function _next1() {
var _fn2 = _x[2];
_fn2(data, (function(_err2, _result2) {
if (_err2) {
_callback(_err2);
} else {
if (_result2 !== undefined) {
data = _result2;
}
_callback(null, data);
}
}));
}
function _next0() {
var _fn1 = _x[1];
_fn1(data, (function(_err1, _result1) {
if (_err1) {
_callback(_err1);
} else {
if (_result1 !== undefined) {
data = _result1;
}
_next1();
}
}));
}
var _fn0 = _x[0];
_fn0(data, (function(_err0, _result0) {
if (_err0) {
_callback(_err0);
} else {
if (_result0 !== undefined) {
data = _result0;
}
_next0();
}
}));
})
Interception
可以为每个钩子对象添加拦截器(interception):
hook.intercept({
call(source, target, routesList) => {
console.log("Starting to calculate routes");
},
register(tapInfo) => {
// tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... }
console.log(`${tapInfo.name} is doing its job`);
return tapInfo; // may return a new tapInfo object
}
});
当为同一个 hook
添加多个 interception
时,则按照 interception
添加顺序,依次执行。
call
(...callArgumentsNameList) => void
当 hook.call
函数调用前,该 call
函数将被调用。通过该函数可以访问 callArgumentsNameList
tap
(tap: Tap) => void
当某个 tap - fn
函数调用前,该 tap
函数将被调用。为该函数提供了当前 tap - fn
函数对应的 tap
对象,该对象不应该被修改。
loop
(...callArgumentsNameList) => void
当 loop 每次执行前,该 loop
函数将被调用。
register
(tap: Tap) => Tap | undefined
当每次注册 tap
前,该 register
函数将被调用。该函数输入当前 tap
对象,该对象允许修改数据。
hook.call 中 interception 实际执行代码
(function anonymous(numA, numB) {
"use strict";
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
_interceptors[0].call(numA, numB); // call
var _loop;
do {
_loop = false;
_interceptors[0].loop(numA, numB); // loop
var _tap0 = _taps[0];
_interceptors[0].tap(_tap0); // tap
var _fn0 = _x[0];
var _result0 = _fn0(numA, numB);
if (_result0 !== undefined) {
_loop = true;
} else {
if (!_loop) {}
}
} while ( _loop );
})
Context
插件和拦截器可以选择性地连接 context
对象,这是可以传递任何值给随后的插件和拦截器的对象。
context
对象初始化时为空对象 {}
。
如果 tap.option.context
为 true
,则该函数的第一个参数为 context
对象。
如果 intercept.context
为 true
,则 call
, tap
, loop
函数的第一个参数为 context
对象。
const hook = new SyncHook();
myCar.hooks.accelerate.intercept({
context: true,
tap: (context, tapInfo) => {
// tapInfo = { type: "sync", name: "NoisePlugin", fn: ... }
console.log(`${tapInfo.name} is doing it's job`);
// `context` starts as an empty object if at least one plugin uses `context: true`.
// If no plugins use `context: true`, then `context` is undefined.
if (context) {
// Arbitrary properties can be added to `context`, which plugins can then access.
context.hasMuffler = true;
}
}
});
myCar.hooks.accelerate.tap({
name: "NoisePlugin",
context: true
}, (context, newSpeed) => {
if (context && context.hasMuffler) {
console.log("Silence...");
} else {
console.log("Vroom!");
}
});