内容简介:我们一般使用的插件都是Hook子类,比如SyncHook,没有复杂的重写基类Hook的compile方法先看Hook基类需要注意这里的
我们一般使用的插件都是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()
了。
以上所述就是小编给大家介绍的《webpack笔记——hook执行时call的是什么》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- GO笔记之详解GO的编译执行流程
- elasticsearch学习笔记高级篇(二)——filter执行原理深度剖析
- python – 阻止jupyter笔记本单元格执行,直到收到特定消息
- 印象笔记Windows客户端6.15本地文件读取和远程命令执行漏洞(CVE-2018-18524)分析
- 命令执行与代码执行的小结
- Go如何保证gorountine执行完毕后继续执行
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。