

webpack笔记——hook执行时call的是什么
source link: https://www.daozhao.com/8845.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我们一般使用的插件都是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()
了。
Recommend
-
35
建设银行时光网
-
8
分析解决脚本执行时总是报出command not found |坐而言不如起而行! 二丫讲梵 > 迎刃而解 > 分析解决脚本执行时总是报出command not found 本文预计...
-
4
旅行时,你一定管不住自己的钱包 2021-06-25 0 评论 18 浏览...
-
7
同一条sql在navicate中执行很快,用mybatis执行时约3s。 ...
-
7
在空中飞行时所想在空中飞行时所想2021 年 9 月 26 日Poems...
-
10
在空中飞行时所想 | enoch2090 enoch2090 Welcome to Nowhere's Land.Enoch2090Daydreaming the whole day through.35Posts| 51Visitors
-
6
广东东莞:市民市内出行时无需查验核酸检测阴性证明 11 小时前 · 亿欧 广东省东莞市新型冠状病毒肺炎疫情防控指挥部办公室发布第56号通告:当前,东莞市疫情防控形势依然复杂,但基本可控。通告提到,广大市民非必要不离莞...
-
6
← 今日好价 0430majer @ 2022.04.30 , 11:15 0
-
2
东航回应客机飞行时舱内飞出小鸟:正在调查 2022-06-30 16:04 出处/作者:快科技 整合编辑:佚名 0 ...
-
9
← 白宫:最晚2025年末,全体公众可免费获取美国政府资助的研究论文majer @ 2022.08.27 , 21:...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK