webpack核心模块tapable源码解析

上一篇文章我写了tapable的基本用法,我们知道他是一个增强版版的发布订阅模式,本文想来学习下他的源码。tapable的源码我读了一下,发现他的抽象程度比较高,直接扎进去反而会让人云里雾里的,所以本文会从最简单的SyncHook和发布订阅模式入手,再一步一步抽象,慢慢变成他源码的样子。

本文可运行示例代码已经上传GitHub,大家拿下来一边玩一边看文章效果更佳:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/tapable-source-code

SyncHook的基本实现

上一篇文章已经讲过SyncHook的用法了,我这里就不再展开了,他使用的例子就是这样子:

const { SyncHook } = require("tapable"); // 实例化一个加速的hook const accelerate = new SyncHook(["newSpeed"]); // 注册第一个回调,加速时记录下当前速度 accelerate.tap("LoggerPlugin", (newSpeed) => console.log("LoggerPlugin", `加速到${newSpeed}`) ); // 再注册一个回调,用来检测是否超速 accelerate.tap("OverspeedPlugin", (newSpeed) => { if (newSpeed > 120) { console.log("OverspeedPlugin", "您已超速!!"); } }); // 触发一下加速事件,看看效果吧 accelerate.call(500);

其实这种用法就是一个最基本的发布订阅模式,我之前讲发布订阅模式的文章讲过,我们可以仿照那个很快实现一个SyncHook:

class SyncHook { constructor(args = []) { this._args = args; // 接收的参数存下来 this.taps = []; // 一个存回调的数组 } // tap实例方法用来注册回调 tap(name, fn) { // 逻辑很简单,直接保存下传入的回调参数就行 this.taps.push(fn); } // call实例方法用来触发事件,执行所有回调 call(...args) { // 逻辑也很简单,将注册的回调一个一个拿出来执行就行 const tapsLength = this.taps.length; for(let i = 0; i < tapsLength; i++) { const fn = this.taps[i]; fn(...args); } } }

这段代码非常简单,是一个最基础的发布订阅模式,使用方法跟上面是一样的,将SyncHook从tapable导出改为使用我们自己的:

// const { SyncHook } = require("tapable"); const { SyncHook } = require("./SyncHook");

运行效果是一样的:

image-20210323153234354

注意: 我们构造函数里面传入的args并没有用上,tapable主要是用它来动态生成call的函数体的,在后面讲代码工厂的时候会看到。

SyncBailHook的基本实现

再来一个SyncBailHook的基本实现吧,SyncBailHook的作用是当前一个回调返回不为undefined的值的时候,阻止后面的回调执行。基本使用是这样的:

const { SyncBailHook } = require("tapable"); // 使用的是SyncBailHook const accelerate = new SyncBailHook(["newSpeed"]); accelerate.tap("LoggerPlugin", (newSpeed) => console.log("LoggerPlugin", `加速到${newSpeed}`) ); // 再注册一个回调,用来检测是否超速 // 如果超速就返回一个错误 accelerate.tap("OverspeedPlugin", (newSpeed) => { if (newSpeed > 120) { console.log("OverspeedPlugin", "您已超速!!"); return new Error('您已超速!!'); } }); // 由于上一个回调返回了一个不为undefined的值 // 这个回调不会再运行了 accelerate.tap("DamagePlugin", (newSpeed) => { if (newSpeed > 300) { console.log("DamagePlugin", "速度实在太快,车子快散架了。。。"); } }); accelerate.call(500);

他的实现跟上面的SyncHook也非常像,只是call在执行的时候不一样而已,SyncBailHook需要检测每个回调的返回值,如果不为undefined就终止执行后面的回调,所以代码实现如下:

class SyncBailHook { constructor(args = []) { this._args = args; this.taps = []; } tap(name, fn) { this.taps.push(fn); } // 其他代码跟SyncHook是一样的,就是call的实现不一样 // 需要检测每个返回值,如果不为undefined就终止执行 call(...args) { const tapsLength = this.taps.length; for(let i = 0; i < tapsLength; i++) { const fn = this.taps[i]; const res = fn(...args); if( res !== undefined) return res; } } }

然后改下SyncBailHook从我们自己的引入就行:

// const { SyncBailHook } = require("tapable"); const { SyncBailHook } = require("./SyncBailHook");

运行效果是一样的:

image-20210323155857678

抽象重复代码

现在我们只实现了SyncHook和SyncBailHook两个Hook而已,上一篇讲用法的文章里面总共有9个Hook,如果每个Hook都像前面这样实现也是可以的。但是我们再仔细看下SyncHook和SyncBailHook两个类的代码,发现他们除了call的实现不一样,其他代码一模一样,所以作为一个有追求的工程师,我们可以把这部分重复的代码提出来作为一个基类:Hook类。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfpxj.html