webpack笔记——hook执行时call的是什么
2014 年 1 月 11 日
我们一般使用的插件都是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 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, interceptors: Array }} 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, 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 {\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()
了。