tapable是webpack使用的事件处理模块,npm上可以看到一共提供了九种事件处理方式,分别是
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
这里选其中的几个研究一下
SyncHook
从lib/index.js点进去,该类代码只有数行:
const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
const factory = new SyncHookCodeFactory();
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncHook");
};
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
SyncHook.prototype = null;
module.exports = SyncHook;
实例化了一个Hook类,然后赋予tapAsync,tapPromise,compile函数。因为是同步钩子,所以tapAsync和tapPromise方法都只是抛出了一个错误。这里看不到call、tap方法,显然在Hook类里面。Hook代码如下:
...
class Hook {
constructor(args = [], name = undefined) {
this._args = args;
this.name = name;
this.taps = [];
this.interceptors = [];
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
this._callAsync = CALL_ASYNC_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
this._promise = PROMISE_DELEGATE;
this.promise = PROMISE_DELEGATE;
this._x = undefined;
this.compile = this.compile;
this.tap = this.tap;
this.tapAsync = this.tapAsync;
this.tapPromise = this.tapPromise;
}
...
module.exports = Hook;
构造函数这里面最后四句我有点疑惑
this.compile = this.compile;
this.tap = this.tap;
this.tapAsync = this.tapAsync;
this.tapPromise = this.tapPromise;
说实话不知道有什么用,省去查找原型的时间?好像也没必要啊。从tap看起,tap里面调用了_tap
,如下:
_tap(type, options, fn) {
if (typeof options === "string") {
options = {
name: options.trim()
};
} else if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
if (typeof options.name !== "string" || options.name === "") {
throw new Error("Missing name for tap");
}
if (typeof options.context !== "undefined") {
deprecateContext();
}
options = Object.assign({ type, fn }, options);
console.log('option', options);
options = this._runRegisterInterceptors(options);
this._insert(options);
}
前面几行主要是生成option,从这里看到,tap的时候直接传入的第一个参数如果是个对象,它的fn属性是会覆盖第三个参数的。deprecateContext()是提醒context参数已经被取消了。然后执行_runRegisterInterceptors,如下:
_runRegisterInterceptors(options) {
for (const interceptor of this.interceptors) {
if (interceptor.register) {
const newOptions = interceptor.register(options);
if (newOptions !== undefined) {
options = newOptions;
}
}
}
return options;
}
tapable的hook类提供intercept方法注册一些interceptors,_runRegisterInterceptors
就是绑定监听(tap、tapPromise、tapAsync)的时候,把option依次交给所有的interceptors.register处理,最后生成一个新的option。最后使用_insert
方法把option放到队列里:
_insert(item) {
this._resetCompilation();
let before;
if (typeof item.before === "string") {
before = new Set([item.before]);
} else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let stage = 0;
if (typeof item.stage === "number") {
stage = item.stage;
}
let i = this.taps.length;
while (i > 0) {
i--;
const x = this.taps[i];
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (before) {
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
i++;
break;
}
this.taps[i] = item;
}
}
一开始先跑一次_resetCompilation
,然后根据before和stage参数寻找位置插入到taps数组里面。x.name其实就是使用tap的时候的第一个参数:
hook.tap('name', function () {})
// 或者
hook.tap({
name: 'name'
}, function () {})
before可以是字符串或则数组,最终插入的位置会在所有before的项的前面。stage参数则是越大排越前面。为什么要跑_resetCompilation
呢?_resetCompilation
代码如下:
_resetCompilation() {
this.call = this._call;
this.callAsync = this._callAsync;
this.promise = this._promise;
}
const CALL_DELEGATE = function(...args) {
this.call = this._createCall("sync");
return this.call(...args);
};
const CALL_ASYNC_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);
};
this._call
、this._callAsync
、this._promise
在构造函数中和this.call
、this.callAsync
、this.promise
一样,都等于CALL_DELEGATE
、CALL_ASYNC_DELEGATE
、PROMISE_DELEGATE
。当调用call等方法,this.call会被重新赋值。_createCall
的作用就是根据传入的参数生成一个函数,如下:
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
compile(options) {
throw new Error("Abstract: should be overridden");
}
compile是个抽象方法,具体的实现在子类,也就是最开头的那段代码。调用了
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
很显然,重新调用的原因就是因为这个工厂方法create会根据参数生成不同的处理逻辑,所以每次tap都要更新一次call函数。
为啥要重新生成call呢?在我的想法里,tap的时候把fn都放到一个list里面,然后call的时候逐个判断触发,所以call是不需要改变的。查阅后发现,call的执行代码是动态生成的,根据webpack成员的回复,是为了能更快的执行 github.com/webpack/tap…
看下factory.create的代码:
create(options) {
this.init(options);
let fn;
switch (this.options.type) {
case "sync":
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.contentWithInterceptors({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
case "async":
...
case "promise":
...
}
this.deinit();
return fn;
}
deinit() {
this.options = undefined;
this._args = undefined;
}
使用new Function的方式生成一个新函数,先用this.args函数将参数用逗号隔开,这里的参数就是指new Hook(['param1', 'param2'])里面的['param1', 'param2']。然后用this.header()在代码开头声明一些变量,如下:
header() {
let code = "";
if (this.needContext()) {
code += "var _context = {};\n";
} else {
code += "var _context;\n";
}
code += "var _x = this._x;\n";
if (this.options.interceptors.length > 0) {
code += "var _taps = this.taps;\n";
code += "var _interceptors = this.interceptors;\n";
}
return code;
}
needContext() {
for (const tap of this.options.taps) if (tap.context) return true;
return false;
}
其中this._x在调用COMPILE的时候在factory.setup里赋值
setup(instance, options) {
instance._x = options.taps.map(t => t.fn);
}
上面只是一些变量的初始化。继续看contentWithInterceptors:
contentWithInterceptors(options) {
if (this.options.interceptors.length > 0) {
const onError = options.onError;
const onResult = options.onResult;
const onDone = options.onDone;
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.call) {
code += `${this.getInterceptor(i)}.call(${this.args({
before: interceptor.context ? "_context" : undefined
})});\n`;
}
}
code += this.content(
Object.assign(options, {
onError:
onError &&
(err => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.error) {
code += `${this.getInterceptor(i)}.error(${err});\n`;
}
}
code += onError(err);
return code;
}),
onResult:
onResult &&
(result => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.result) {
code += `${this.getInterceptor(i)}.result(${result});\n`;
}
}
code += onResult(result);
return code;
}),
onDone:
onDone &&
(() => {
let code = "";
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.done) {
code += `${this.getInterceptor(i)}.done();\n`;
}
}
code += onDone();
return code;
})
})
);
return code;
} else {
return this.content(options);
}
}
第一段代码拼接先加了拦截器call方法的调用,第二次拼接了this.content(options)的结果,content是子类上声明的方法:
content({ onError, onDone, rethrowIfPossible }) {
var code = this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
return code;
}
callTapsSeries 后面就是根据taps数组开始拼凑代码。因为拼凑的过程我也看不懂,我这里直接看拼出来的代码:
// 源码
var tapable = require('tapable');
class Person {
constructor (name) {
this.name = name;
this.hooks = {
intro: new tapable.SyncHook(['name'])
}
}
}
var man = new Person('lujiajian');
man.hooks.intro.tap('introduce', (name) => {
console.log('tap:' + name);
});
man.hooks.intro.tap('introduce2', (name) => {
console.log('tap2:' + name);
});
man.hooks.intro.intercept({
call: (source, target, routesList) => {
console.log("Starting to calculate routes");
},
register: (tapInfo) => {
console.log(`${tapInfo.name} is doing its job`);
return tapInfo; // may return a new tapInfo object
}
})
man.hooks.intro.call('lujiajian');
// sync执行函数
"use strict";
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
_interceptors[0].call(name);
var _fn0 = _x[0];
_fn0(name);
var _fn1 = _x[1];
_fn1(name);
直接拼出来的代码不需要用for!
SyncBailHook
因为大部分都是一样的,也是直接看拼出来的代码。
// 源码
var tapable = require('tapable');
class Person {
constructor (name) {
this.name = name;
this.hooks = {
intro: new tapable.SyncHook(['name'])
}
}
}
var man = new Person('lujiajian');
man.hooks.intro.tap('introduce', (name) => {
console.log('tap:' + name);
});
man.hooks.intro.tap('introduce2', (name) => {
console.log('tap2:' + name);
});
man.hooks.intro.intercept({
call: (source, target, routesList) => {
console.log("Starting to calculate routes");
},
register: (tapInfo) => {
console.log(`${tapInfo.name} is doing its job`);
return tapInfo; // may return a new tapInfo object
}
})
man.hooks.intro.call('lujiajian');
// sync执行函数
"use strict";
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
_interceptors[0].call(name);
var _fn0 = _x[0];
var _result0 = _fn0(name);
if (_result0 !== undefined) {
return _result0;;
} else {
var _fn1 = _x[1];
var _result1 = _fn1(name);
if (_result1 !== undefined) {
return _result1;;
} else {}
}
就是一直用else if嵌套下去,返回值非undefined就打断。
SyncWaterfallHook
// 源码
var tapable = require('tapable');
class Person {
constructor (name) {
this.name = name;
this.hooks = {
intro: new tapable.SyncWaterfallHook(['name', 'name2'])
}
}
}
var man = new Person('lujiajian');
man.hooks.intro.tap('introduce', (name1, name2) => {
console.log('tap:' + name1 + name2);
});
man.hooks.intro.tap('introduce2', (name1, name2) => {
console.log('tap2:' + name1 + name2);
});
man.hooks.intro.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
}
})
man.hooks.intro.call('lujiajian1', 'lujiajian2');
// sync执行函数
"use strict";
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
_interceptors[0].call(name, name2);
var _fn0 = _x[0];
var _result0 = _fn0(name, name2);
if (_result0 !== undefined) {
name = _result0;
}
var _fn1 = _x[1];
var _result1 = _fn1(name, name2);
if (_result1 !== undefined) {
name = _result1;
}
return name;
因为好奇他返回值的传递,参数比上面的例子多一个。结果发现name2参数貌似根本改变不了。一直只能改第一个。
AsyncSeriesWaterfallHook
// 源码
var tapable = require('tapable');
class Person {
constructor (name) {
this.name = name;
this.hooks = {
intro: new tapable.AsyncSeriesWaterfallHook(['name', 'name2'])
}
}
}
var man = new Person('lujiajian');
man.hooks.intro.tapAsync('introduce', (name1, name2, cb) => {
console.log('tap:' + name1 + name2);
cb()
});
man.hooks.intro.tapPromise('introduce2', (name1, name2) => {
console.log('tap2:' + name1 + name2);
return Promise.resolve();
});
man.hooks.intro.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
}
})
man.hooks.intro.promise('lujiajian1', 'lujiajian2');
// promise执行代码
"use strict";
var _context;
var _x = this._x;
var _taps = this.taps;
var _interceptors = this.interceptors;
return new Promise((function (_resolve, _reject) {
var _sync = true;
function _error(_err) {
if (_sync)
_resolve(Promise.resolve().then((function () {
throw _err;
})));
else
_reject(_err);
};
_interceptors[0].call(name, name2);
function _next0() {
var _fn1 = _x[1];
var _hasResult1 = false;
var _promise1 = _fn1(name, name2);
if (!_promise1 || !_promise1.then)
throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
_promise1.then((function (_result1) {
_hasResult1 = true;
if (_result1 !== undefined) {
name = _result1;
}
_resolve(name);
}), function (_err1) {
if (_hasResult1) throw _err1;
_error(_err1);
});
}
var _fn0 = _x[0];
_fn0(name, name2, (function (_err0, _result0) {
if (_err0) {
_error(_err0);
} else {
if (_result0 !== undefined) {
name = _result0;
}
_next0();
}
}));
_sync = false;
}));
Async绑定的函数是用一个_next{index}函数包裹起来的, callback的时候执行下一个next函数,promise好像只是在后面加了一些错误处理。所以用tapAsync的时候,callback必须要传参,不然运行报错。用tapPromise的时候,必须返回一个promise,不然会抛出错误。
其他的就不再打印出来了,代码都在factory.create里面。可以自行打印查看。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!