webpack 事件处理机制Tapable

webpack 的诸多核心模块都是tapable的子类, tapable提供了一套完整事件订阅和发布的机制,让webpack的执行的流程交给了订阅的插件去处理, 当然这套机制为开发者订阅流程事件去定制自己构建模式和方案提供了更多的便利性, 从基础的原理角度说,tapable就是一套观察者模式,并在此基础上提供了较为丰富的订阅和发布方式,如 call/async /promise,以此支持更多的处理场景。

  • 以下是tapable 提供的所有的hook类型
exports.__esModule = true;
exports.Tapable = require("./Tapable");
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");
复制代码

hook 分析

hook 主要分一下类型 async / sync

上图来自这里

总体介绍

在正式分析源码之前,先对每一种hook的进行功能介绍和简单源码分析

序号 钩子名称 执行方式 使用要点
1 SyncHook 同步串行 不关心监听函数的返回值
2 SyncBailHook 同步串行 只要监听函数中有一个函数的返回值不为 null,则跳过剩下所有的逻辑
3 SyncWaterfallHook 同步串行 上一个监听函数的返回值可以传给下一个监听函数
4 SyncLoopHook 同步循环 当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环
5 AsyncParallelHook 异步并发 不关心监听函数的返回值
6 AsyncParallelBailHook 异步并发 只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数
7 AsyncSeriesHook 异步串行 不关心callback()的参数
8 AsyncSeriesBailHook 异步串行 callback()的参数不为null,就会直接执行callAsync等触发函数绑定的回调函数
9 AsyncSeriesWaterfallHook 异步串行 上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

Demo验证

1 sync

const {SyncHook,SyncBailHook,SyncLoopHook,SyncWaterfallHook
} = require('tapable')class SyncHookDemo{constructor(){this.hooks = {sh: new SyncHook(['name', 'age']),sbh: new SyncBailHook(['name', 'age']),slh: new SyncLoopHook(['name']),swh: new SyncWaterfallHook(['name', 'nickname', 'user'])}}
}const hdemo = new SyncHookDemo();复制代码
  1. SyncHook
hdemo.hooks.sh.tap('record', (name, age)=>{console.log(`record: ${name}, ${age}`);return name;
});hdemo.hooks.sh.tap('save', (name, age)=>{console.log(`save: ${name}, ${age} `)
});hdemo.hooks.sh.call('张三', ~~(Math.random() * 100))// 结果
record: 张三, 75
save: 张三, 75复制代码

synchook就是很简单的订阅 同步发布 不关心订阅函数返回值, 一口气把把所有订阅者执行一遍

原理 就是简单的订阅和发布

class SyncHook{constructor(){this.subs = [];}tap(fn){this.sub.push(fn);}call(...args){this.subs.forEach(fn=>fn(...args));}
}
复制代码
  1. SyncBailHook

hdemo.hooks.sbh.tap('cache', (name, age)=>{console.log(`get data from cache: ${name}, ${age}`)return name;
})hdemo.hooks.sbh.tap('db', (name, age)=>{console.log(`get data from db: ${name}, ${age}`)
})hdemo.hooks.sbh.call('李四',  ~~(Math.random() * 100))// 结果
get data from cache: 李四, 36复制代码

遇到订阅函数返回不为空的情况下 就会停止执行剩余的callback

原理

class SyncBailHook{constructor(){this.subs = [];}tap(fn){this.sub.push(fn);}call(...args){for(let i=0; i< this.subs.length; i++){const result = this.subs[i](...args);result && break;}
}
复制代码
  1. SyncWaterHook

hdemo.hooks.swh.tap('getName', (name)=>{console.log('getName', name);return 'OK' + name;
})hdemo.hooks.swh.tap('getNikename', (name)=>{console.log('preName:', name);
})hdemo.hooks.swh.call('车库');// 结果
getName 车库
preName: OK车库复制代码

上一个监听函数的返回值可以传给下一个监听函数

原理:


class SyncWaterHook{constructor(){this.subs = [];}tap(fn){this.sub.push(fn);}call(...args){let result = null;for(let i = 0, l = this.hooks.length; i < l; i++) {let hook = this.hooks[i];result = i == 0 ? hook(...args): hook(result); }}
}
复制代码
  1. SyncLoopHook

let count = 5;
hdemo.hooks.slh.tap('loop', (name) => {console.log('count: ', count--, name);if (count > 0) {return true;}return;
})
hdemo.hooks.slh.call('测试loop');// 结果
count:  5 测试loop
count:  4 测试loop
count:  3 测试loop
count:  2 测试loop
count:  1 测试loop
复制代码

当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环

原理

class SyncLooHook{constructor(){this.subs = [];}tap(fn){this.sub.push(fn);}call(...args){let result;do {result = this.hook(...arguments);} while (result!==undefined)}
}
复制代码

2. async

const {AsyncParallelHook,AsyncParallelBailHook,AsyncSeriesHook,AsyncSeriesBailHook,AsyncSeriesWaterfallHook} = require('tapable')class AsyncHookDemo{constructor(){this.hooks = {aph: new AsyncParallelHook(['name']),apbh: new AsyncParallelBailHook(['name']),ash: new AsyncSeriesHook(['name']),asbh: new AsyncSeriesBailHook(['name']),aswh: new AsyncSeriesWaterfallHook(['name'])}}}const shdemo = new AsyncHookDemo();
复制代码
  1. AsyncParallelHook 异步并行
shdemo.hooks.aph.tap('req-1', (name)=>{console.log(name, 'req-1');// throw new Error('0000')// callback()
})shdemo.hooks.aph.tap('req-2', (name)=>{console.log(name, 'req-2');// callback('reqcallback2')
})shdemo.hooks.aph.tapAsync('areq-1', (name, cb)=>{console.log('areq-1', name);cb();
})shdemo.hooks.aph.tapAsync('areq-2', (name, cb)=>{console.log('areq-2', name);cb();
})shdemo.hooks.aph.tapAsync('areq-3', (name, cb)=>{console.log('areq-3', name);cb(null ,'over');
})shdemo.hooks.aph.tapAsync('areq-4', (name, cb)=>{console.log('areq-4', name);cb();
})shdemo.hooks.aph.callAsync('<---------90---->', (err, result)=>{if(err){return console.log('err', err);}console.log('jieshu', result)
});// promise  不在验证 特别简单
<---------90----> req-1
<---------90----> req-2
areq-1 <---------90---->
areq-2 <---------90---->
areq-3 <---------90---->
areq-4 <---------90---->
jieshu undefined
复制代码

结论:

  • hook 不在乎callback的返回值
  • callback 第一个参数给给值 表示异常 就会结束
  • 监听函数throw 一个异常会被最后的callback捕获
  1. AsyncParallelBailHook

复制代码

可能看起来有点懵, 为什么是这样,我们还是从源码入手,看看各类hook源码
然后在demo验证 分析的对否
如果想自己想试试的,可以直接使用参考的资料的第一个链接,
先从基类

Hook源代码

"use strict";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) {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 chainsoptions = 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);};
}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}
});
复制代码

hook 实现的思路是: 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;}
复制代码
  • taps 订阅函数集合
  • interceptors 拦截集合 配置在执行派发之前的拦截
  • call 同步触发对象
  • promise promise方式触发的对象
  • callSync 异步触发对象
  • _x 应用于生成执行函数 监听集合 后续详细介绍这个_x 的使用意义 (这个名字起得有点怪)

既然hook是观察者模式实现的,我们就顺着观察者模式的思路去逐步解析hook的实现方法

hook之订阅

  • 同步订阅 tap 方法
     tap(options, fn) {//传入的参数是 字符串 整理options = { name: options} if (typeof options === "string") options = { name: options };// options 如果传入不是字符串和Object就扔出异常  当然从订阅角度来说 有个唯一性名称就行 Oif (typeof options !== "object" || options === null)throw new Error("Invalid arguments to tap(options: Object, fn: function)");// 加入类型和callback 很显然这里对我们传入的callback 没有进行任何处理options = Object.assign({ type: "sync", fn: fn }, options);if (typeof options.name !== "string" || options.name === "")throw new Error("Missing name for tap");// 如果option中存有拦截器 注入进去即可 拦截器分成 before/after类型options = this._runRegisterInterceptors(options);// 保存订阅this._insert(options);}
// 下面将详细分析 如何处理我们的订阅函数  this._insert
复制代码
  • _insert 方法

insert 依赖方法

_resetCompilation

// 定义方法委托 让hook[name]方法 = hook._createCall(type) 产生
//  sync 调用call
// promise 调用promise
// async 调用 callAsync
function createCompileDelegate(name, type) {return function lazyCompileHook(...args) {// 这里很妙 将生成函数的指向上下文 给定给this 也就是说 模板代码中// this 可以获取hook的属性和方法 this._x 就可以排上用途了 具体的在 //factory 里面分析this[name] = this._createCall(type);return this[name](...args);};
}
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}
});// 重置 call / callAsync promise
// 为什么要重置_resetCompilation() {this.call = this._call;this.callAsync = this._callAsync;this.promise = this._promise;
}复制代码

// 根据源码可以知道 重置就是让call/callAsync/promise方法来自原型上的 _call/_promise/_callAsync 同时赋值

insert 源码

 _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);}// 通过item 如果配置了before 和 stage 来控制item在 taps的位置 // 如果监听函数没有配置这两个参数就会执行 this.taps[i] = item  // 最后位置保存新加入的订阅 从此完成订阅//如果配置了before 就会移动位置 根据name值  将item放在相应的位置//stage 同理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;}
复制代码

在上面分析到的 call/callAsync/promise 方法中 使用到了createCall 方法和 _compile

  • 异步订阅tapAsync
    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)");// 只是将type改成了 async 其余同tapoptions = 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
   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);}
复制代码

// 其实源码订阅方法有重构的空间 好多代码冗余

hook之发布


// 这个方法交给子类自己实现 也就是说 怎么发布订阅由子类自己实现
compile(options) {throw new Error("Abstract: should be overriden");}_createCall(type) {return this.compile({taps: this.taps,interceptors: this.interceptors,args: this._args,type: type});
}
复制代码
问题 ?

this._x 没有做任何处理 ? 带着这个问题我们去分析

也就是说hook将自己的发布交给了子类去实现

HookCodeFactory

hook的发布方法(compile)交给 子类自己去实现,同时提供了代码组装工程类,这个类为所有类别的hook的提供了代码生成基础方法,下面我们详细分析这个类的代码组成

HookCodeFactory 最终生成可执行的代码片段和普通的模板编译方法差不多


class HookCodeFactory {/*** config 配置options = 就是{taps: this.taps,interceptors: this.interceptors,args: this._args,type: type}*/constructor(config) {this.config = config;this.options = undefined;this._args = undefined;}/*** options = {taps: this.taps,interceptors: this.interceptors,args: this._args,type: type}*/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`,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":// promise代码模板let code = "";code += '"use strict";\n';code += "return new Promise((_resolve, _reject) => {\n";code += "var _sync = true;\n";code += this.header();code += this.content({onError: err => {let code = "";code += "if(_sync)\n";code += `_resolve(Promise.resolve().then(() => { throw ${err}; }));\n`;code += "else\n";code += `_reject(${err});\n`;return code;},onResult: result => `_resolve(${result});\n`,onDone: () => "_resolve();\n"});code += "_sync = false;\n";code += "});\n";fn = new Function(this.args(), code);break;}// 重置 options和argsthis.deinit();return fn;}setup(instance, options) {// 安装实例 让模板代码里的this._x 给与值 // 这里可以解释 hook源码中 定义未赋值_x的疑问了// _x 其实就是taps 监听函数的集合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;// 赋值args的参数this._args = options.args.slice();}deinit() {this.options = undefined;this._args = undefined;}// 代码header部分// 这里定义了 _X的值// interceptors 的执行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) {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;}// 触发订阅/*** 构建发布方法* 分sync async promise*/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`;// 需要返回promise 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;}// 调用串行callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {if (this.options.taps.length === 0) return onDone();const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");const next = i => {if (i >= this.options.taps.length) {return onDone();}const done = () => next(i + 1);const doneBreak = skipDone => {if (skipDone) return "";return onDone();};return this.callTap(i, {onError: error => onError(i, error, done, doneBreak),onResult:onResult &&(result => {return onResult(i, result, done, doneBreak);}),onDone:!onResult &&(() => {return done();}),rethrowIfPossible:rethrowIfPossible && (firstAsync < 0 || i < firstAsync)});};return next(0);}// 触发循环调用callTapsLooping({ onError, onDone, rethrowIfPossible }) {if (this.options.taps.length === 0) return onDone();const syncOnly = this.options.taps.every(t => t.type === "sync");let code = "";if (!syncOnly) {code += "var _looper = () => {\n";code += "var _loopAsync = false;\n";}code += "var _loop;\n";code += "do {\n";code += "_loop = false;\n";for (let i = 0; i < this.options.interceptors.length; i++) {const interceptor = this.options.interceptors[i];if (interceptor.loop) {code += `${this.getInterceptor(i)}.loop(${this.args({before: interceptor.context ? "_context" : undefined})});\n`;}}code += this.callTapsSeries({onError,onResult: (i, result, next, doneBreak) => {let code = "";code += `if(${result} !== undefined) {\n`;code += "_loop = true;\n";if (!syncOnly) code += "if(_loopAsync) _looper();\n";code += doneBreak(true);code += `} else {\n`;code += next();code += `}\n`;return code;},onDone:onDone &&(() => {let code = "";code += "if(!_loop) {\n";code += onDone();code += "}\n";return code;}),rethrowIfPossible: rethrowIfPossible && syncOnly});code += "} while(_loop);\n";if (!syncOnly) {code += "_loopAsync = true;\n";code += "};\n";code += "_looper();\n";}return code;}// 并行callTapsParallel({onError,onResult,onDone,rethrowIfPossible,onTap = (i, run) => run()}) {if (this.options.taps.length <= 1) {return this.callTapsSeries({onError,onResult,onDone,rethrowIfPossible});}let code = "";code += "do {\n";code += `var _counter = ${this.options.taps.length};\n`;if (onDone) {code += "var _done = () => {\n";code += onDone();code += "};\n";}for (let i = 0; i < this.options.taps.length; i++) {const done = () => {if (onDone) return "if(--_counter === 0) _done();\n";else return "--_counter;";};const doneBreak = skipDone => {if (skipDone || !onDone) return "_counter = 0;\n";else return "_counter = 0;\n_done();\n";};code += "if(_counter <= 0) break;\n";code += onTap(i,() =>this.callTap(i, {onError: error => {let code = "";code += "if(_counter > 0) {\n";code += onError(i, error, done, doneBreak);code += "}\n";return code;},onResult:onResult &&(result => {let code = "";code += "if(_counter > 0) {\n";code += onResult(i, result, done, doneBreak);code += "}\n";return code;}),onDone:!onResult &&(() => {return done();}),rethrowIfPossible}),done,doneBreak);}code += "} while(false);\n";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}]`;}
}
复制代码

有点长 下一篇具体分析每一种类型的hook 暂时先分析这么多

参考资料
  • webpack4.0源码分析之Tapable
  • tapable 官方源码

转载于:https://juejin.im/post/5c25d6706fb9a049a81f6488

tapable源码分析相关推荐

  1. webpack 源码分析(四)——complier模块

    webpack 源码分析(四)--complier模块 上一篇我们看到,webpack-cli 通过 yargs 对命令行传入的参数和配置文件里的配置项做了转换包装,然后传递给 webpack 的 c ...

  2. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  3. SpringBoot-web开发(四): SpringMVC的拓展、接管(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) SpringBoot-web开发(二): 页面和图标定制(源码分析) SpringBo ...

  4. SpringBoot-web开发(二): 页面和图标定制(源码分析)

    [SpringBoot-web系列]前文: SpringBoot-web开发(一): 静态资源的导入(源码分析) 目录 一.首页 1. 源码分析 2. 访问首页测试 二.动态页面 1. 动态资源目录t ...

  5. SpringBoot-web开发(一): 静态资源的导入(源码分析)

    目录 方式一:通过WebJars 1. 什么是webjars? 2. webjars的使用 3. webjars结构 4. 解析源码 5. 测试访问 方式二:放入静态资源目录 1. 源码分析 2. 测 ...

  6. Yolov3Yolov4网络结构与源码分析

    Yolov3&Yolov4网络结构与源码分析 从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗. 文章目录 论文汇总 ...

  7. ViewGroup的Touch事件分发(源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,View的touch事件分发相对比较简单,可参考 View的Touch事件分发(一.初步了解) View的Touch事 ...

  8. View的Touch事件分发(二.源码分析)

    Android中Touch事件的分发又分为View和ViewGroup的事件分发,先来看简单的View的touch事件分发. 主要分析View的dispatchTouchEvent()方法和onTou ...

  9. MyBatis原理分析之四:一次SQL查询的源码分析

    上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的. 准备工作 Mybatis完成一次SQL查询需要使用的代码如下: Java代码   String res ...

最新文章

  1. 设计模式-拦截器模式
  2. js源码 模仿 jquery的ajax的获取数据(get,post )的请求封装
  3. Java 策略模式和状态模式
  4. Linux 系统服务管理和控制程序(初始化系统/Init System) -- systemd 介绍
  5. 数据挖掘算法之关联规则挖掘(一)apriori算法
  6. 一次性从linux发送多个文件到windows
  7. About_PHP_验证码的生成
  8. 菜刀php教程,Weevely(php菜刀)工具使用详解
  9. c语言空白字符的aci,c语言的保留字符有32个是那些啊???代表什么于是啊??...
  10. mybatis存在就不插入_mybatis只能进行查找,不能进行删除,插入,更新解决方案...
  11. C 数据结构与算法 散列表
  12. Flutter 2.5 的新特性
  13. pringboot+vue 基于springboot房屋租赁管理系统#毕业设计
  14. SuperMap地图系列:坐标系的那些事
  15. 爬虫碰到状态码412的解决办法
  16. windows蓝屏解决方式SYSTEM_THREAD_EXCEPTION_NOT_HANDLED,失败的操作wdf01000.sys
  17. SCI论文的Highlights怎么写(正经的教你怎么写)
  18. Typora高亮颜色设置
  19. 【数字信号处理】线性常系数差分方程 ( 使用递推解法求解 “ 线性常系数差分方程 “ | “ 线性常系数差分方程 “ 初始条件的重要性 )
  20. GPS,RTK,PPS及网络RTK科普

热门文章

  1. 汇编语言--BIOS和DOS中断例程
  2. 数据结构: 插值查找算法
  3. linux系统管理命令,压缩命令
  4. 在C#中调用windows API函数
  5. poj 3071 Football(概率dp)
  6. [EMC++] Item 8. Prefer nullptr to 0 and NULL
  7. 使用MySql保存session
  8. Apple watch 开发指南(2) 前言
  9. 初学 Delphi 嵌入汇编[1] - 汇编语言与机器语言
  10. 将spfile从ASM里迁移到文件系统