原文地址:道招網的webpack筆記——hook執行時call的是什麼
我們一般使用的外掛都是Hook子類,比如SyncHook,沒有複雜的重寫基類Hook的compile方法 先看Hook基類
// node_module/tapable/Hook.js
class Hook {
constructor(args) {
if (!Array.isArray(args)) args = [];
this._args = args;
this.taps = [];
this.interceptors = [];
this.call = this._call;
this.promise = this._promise;
this.callAsync = this._callAsync;
this._x = undefined;
}
compile(options) {
throw new Error("Abstract: should be overriden");
}
_createCall(type) {
// 呼叫對應Hook的compile方法了
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
tap(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tap(options: Object, fn: function)"
);
options = Object.assign({ type: "sync", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tap");
options = this._runRegisterInterceptors(options);
this._insert(options);
}
tapAsync(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tapAsync(options: Object, fn: function)"
);
options = Object.assign({ type: "async", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tapAsync");
options = this._runRegisterInterceptors(options);
this._insert(options);
}
tapPromise(options, fn) {
if (typeof options === "string") options = { name: options };
if (typeof options !== "object" || options === null)
throw new Error(
"Invalid arguments to tapPromise(options: Object, fn: function)"
);
options = Object.assign({ type: "promise", fn: fn }, options);
if (typeof options.name !== "string" || options.name === "")
throw new Error("Missing name for tapPromise");
options = this._runRegisterInterceptors(options);
this._insert(options);
}
_runRegisterInterceptors(options) {
for (const interceptor of this.interceptors) {
if (interceptor.register) {
const newOptions = interceptor.register(options);
if (newOptions !== undefined) options = newOptions;
}
}
return options;
}
withOptions(options) {
const mergeOptions = opt =>
Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);
// Prevent creating endless prototype chains
options = Object.assign({}, options, this._withOptions);
const base = this._withOptionsBase || this;
const newHook = Object.create(base);
(newHook.tapAsync = (opt, fn) => base.tapAsync(mergeOptions(opt), fn)),
(newHook.tap = (opt, fn) => base.tap(mergeOptions(opt), fn));
newHook.tapPromise = (opt, fn) => base.tapPromise(mergeOptions(opt), fn);
newHook._withOptions = options;
newHook._withOptionsBase = base;
return newHook;
}
isUsed() {
return this.taps.length > 0 || this.interceptors.length > 0;
}
intercept(interceptor) {
this._resetCompilation();
this.interceptors.push(Object.assign({}, interceptor));
if (interceptor.register) {
for (let i = 0; i < this.taps.length; i++)
this.taps[i] = interceptor.register(this.taps[i]);
}
}
_resetCompilation() {
this.call = this._call;
this.callAsync = this._callAsync;
this.promise = this._promise;
}
_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;
}
}
function createCompileDelegate(name, type) {
return function lazyCompileHook(...args) {
this[name] = this._createCall(type);
return this[name](...args);
// 返回的是一個匿名函式,執行該函式的話會依次執行this._x(及這裡的this.taps陣列裡面的fn)的方法們,
// 裡面會有一些判斷中斷的邏輯
};
}
Object.defineProperties(Hook.prototype, {
_call: {
value: createCompileDelegate("call", "sync"),
configurable: true,
writable: true
},
_promise: {
value: createCompileDelegate("promise", "promise"),
configurable: true,
writable: true
},
_callAsync: {
value: createCompileDelegate("callAsync", "async"),
configurable: true,
writable: true
}
});
module.exports = Hook;
複製程式碼
需要注意這裡的this.call
,this.promise
,this.callAsync
。
// node_modules/tapable/SyncHook.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();
class SyncHook extends Hook {
tapAsync() {
throw new Error("tapAsync is not supported on a SyncHook");
}
tapPromise() {
throw new Error("tapPromise is not supported on a SyncHook");
}
compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
module.exports = SyncHook;
複製程式碼
所以compile
方法是返回的HookCodeFactory例項的create的結果
// node_modules/tapable/HookCodeFactory.js
"use strict";
class HookCodeFactory {
constructor(config) {
this.config = config;
this.options = undefined;
this._args = undefined;
}
create(options) {
this.init(options);
let fn;
switch (this.options.type) {
case "sync":
fn = new Function(
this.args(),
'"use strict";\n' +
this.header() +
this.content({
onError: err => `throw ${err};\n`,
onResult: result => `return ${result};\n`,
resultReturns: true,
onDone: () => "",
rethrowIfPossible: true
})
);
break;
case "async":
fn = new Function(
this.args({
after: "_callback"
}),
'"use strict";\n' +
this.header() +
this.content({
onError: err => `_callback(${err});\n`,
onResult: result => `_callback(null, ${result});\n`,
onDone: () => "_callback();\n"
})
);
break;
case "promise":
let errorHelperUsed = false;
const content = this.content({
onError: err => {
errorHelperUsed = true;
return `_error(${err});\n`;
},
onResult: result => `_resolve(${result});\n`,
onDone: () => "_resolve();\n"
});
let code = "";
code += '"use strict";\n';
code += "return new Promise((_resolve, _reject) => {\n";
if (errorHelperUsed) {
code += "var _sync = true;\n";
code += "function _error(_err) {\n";
code += "if(_sync)\n";
code += "_resolve(Promise.resolve().then(() => { throw _err; }));\n";
code += "else\n";
code += "_reject(_err);\n";
code += "};\n";
}
code += this.header();
code += content;
if (errorHelperUsed) {
code += "_sync = false;\n";
}
code += "});\n";
fn = new Function(this.args(), code);
break;
}
this.deinit();
return fn;
}
setup(instance, options) {
instance._x = options.taps.map(t => t.fn);
}
/**
* @param {{ type: "sync" | "promise" | "async", taps: Array<tap>, interceptors: Array<interceptor> }} options
*/
init(options) {
this.options = options;
this._args = options.args.slice();
}
deinit() {
this.options = undefined;
this._args = undefined;
}
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";
}
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.call) {
console.log(' 執行interceptor.call-> ', this.options.interceptors.length, i, this.getInterceptor(i));
code += `${this.getInterceptor(i)}.call(${this.args({
before: interceptor.context ? "_context" : undefined
})});\n`;
}
}
return code;
}
needContext() {
for (const tap of this.options.taps) if (tap.context) return true;
return false;
}
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
let code = "";
let hasTapCached = false;
for (let i = 0; i < this.options.interceptors.length; i++) {
const interceptor = this.options.interceptors[i];
if (interceptor.tap) {
if (!hasTapCached) {
code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
hasTapCached = true;
}
code += `${this.getInterceptor(i)}.tap(${
interceptor.context ? "_context, " : ""
}_tap${tapIndex});\n`;
}
}
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
const tap = this.options.taps[tapIndex];
switch (tap.type) {
case "sync":
if (!rethrowIfPossible) {
code += `var _hasError${tapIndex} = false;\n`;
code += "try {\n";
}
if (onResult) {
code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} else {
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
}
if (!rethrowIfPossible) {
code += "} catch(_err) {\n";
code += `_hasError${tapIndex} = true;\n`;
code += onError("_err");
code += "}\n";
code += `if(!_hasError${tapIndex}) {\n`;
}
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
if (!rethrowIfPossible) {
code += "}\n";
}
break;
case "async":
let cbCode = "";
if (onResult) cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
else cbCode += `_err${tapIndex} => {\n`;
cbCode += `if(_err${tapIndex}) {\n`;
cbCode += onError(`_err${tapIndex}`);
cbCode += "} else {\n";
if (onResult) {
cbCode += onResult(`_result${tapIndex}`);
}
if (onDone) {
cbCode += onDone();
}
cbCode += "}\n";
cbCode += "}";
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined,
after: cbCode
})});\n`;
break;
case "promise":
code += `var _hasResult${tapIndex} = false;\n`;
code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
code += ` throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
code += `_promise${tapIndex}.then(_result${tapIndex} => {\n`;
code += `_hasResult${tapIndex} = true;\n`;
if (onResult) {
code += onResult(`_result${tapIndex}`);
}
if (onDone) {
code += onDone();
}
code += `}, _err${tapIndex} => {\n`;
code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
code += onError(`_err${tapIndex}`);
code += "});\n";
break;
}
return code;
}
// ...
args({ before, after } = {}) {
let allArgs = this._args;
if (before) allArgs = [before].concat(allArgs);
if (after) allArgs = allArgs.concat(after);
if (allArgs.length === 0) {
return "";
} else {
return allArgs.join(", ");
}
}
getTapFn(idx) {
return `_x[${idx}]`;
}
getTap(idx) {
return `_taps[${idx}]`;
}
getInterceptor(idx) {
return `_interceptors[${idx}]`;
}
}
module.exports = HookCodeFactory;
複製程式碼
所以create
方法返回的是new Function(){}
比如
(function(compilation, params
/*``*/) {
"use strict";
var _context;
var _x = this._x;
var _fn0 = _x[0];
_fn0(compilation, params);
var _fn1 = _x[1];
_fn1(compilation, params);
var _fn2 = _x[2];
_fn2(compilation, params);
var _fn3 = _x[3];
_fn3(compilation, params);
})
複製程式碼
在使用外掛時我們會對應的方法。
以this.call
為例,它的本質是執行this._call
,也就是createCompileDelegate
後的lazyCompileHook
。在呼叫this.call()時,返回的是this._createCall(type)(...arg)
的結果,也就是上面的new Function()
了。