webpack4.0各个击破(8)—— tapable篇

> `webpack`作为前端最火的构建工具,是前端自动化工具链**最重要的部分**,使用门槛较高。本系列是笔者自己的学习记录,比较基础,希望通过**问题 + 解决方式**的模式,以前端构建中遇到的具体需求为出发点,学习`webpack`工具中相应的处理办法。(本篇中的参数配置及使用方式均基于`webpack4.0版本`) ![](?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) ![](?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) ## 一. tapable概述 `tapable`地址:[【tapable-0.2】](https://github.com/webpack/tapable/tree/tapable-0.2) `tapable`是`webpack`的核心框架(4.0以上版本的API已经发生了变化),是一个基于事件流的框架,或者叫做发布订阅模式,或观察者模式,`webpack`的整个生命周期及其开放的自定义插件系统都离不开`tapable`的支持,研究其运行原理是阅读`webpack`源代码的第一步。官方仓库`master`分支的代码是经过ES6重构的,模块化拆分非常细,且加入了很多非核心逻辑,阅读难度较大。建议先从官方仓库中0.2版本的分支开始学习,整个源码只有400行,相对容易理解。 ## 二. tapable-0.2源码解析 ### 2.1 代码结构 ```js //类定义 function Tapable() { this._plugins = {}; } //模块导出 module.exports = Tapable; //定义了许多内部方法和原型方法 ... ``` `Tapable`实际上就是一个类定义的模块。 ### 2.2 事件监听方法 `tapable`通过原型方法`Tapable.prototype.plugin`来注册事件监听。 ![](?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 这段代码并不复杂,调用`plugin`方法来注册一个事件,参考浏览器环境中的`addEventListener()`方法就很容易理解了。其逻辑就是将回调函数按照事件名称进行归类存储,在`tapable`实例中统一调度管理。 ```js //__plugin属性上挂载了各个注册事件的回调函数 tapable.__plugins = { 'click':[fn1, fn2, fn3], 'mousedown':[fn21,fn22,fn23] ... } ``` ### 2.3 事件触发方法 `tapable`提供了许多事件触发的方式,其基本功能可以参考浏览器环境中的`dispatchEvent( )`。 `tapable`中的事件触发方式可以按命名分为如下几个大组: - *waterfall*方法会将上一个监听的执行结果传给下一个 - *bailResult*方法只会执行到第一个返回结果不是undefined的监听器 - *Series*方法会线性执行异步监听器,上一个结束后下一个才会开始 - *Parallel*方法会并行执行所有异步监听 `tapable`中的典型方法如下: - `Tapable.prototype.applyPlugins( )` ![](?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 同步方法,该方法接受任意参数,以第一个参数为事件名查找监听器数组,依次执行监听器的`apply( )`方法,触发时将调用时除名称以外的其他参数传入`apply( )`方法。 - `Tapable.prototype.applyPluginsWaterfall( )` ![](?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 同步方法,该方法接受任意参数,如果指定事件没有注册监听器,则返回第二个参数(init),否则依次执行监听器的`apply( )`方法,传入的args是前一个执行前一个监听器`apply( )`方法的返回值。瀑布流这个方法名很形象。 - `Tapable.prototype.applyPluginsBailResult( )` ![](?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 同步方法,该方法接受任意参数,依次执行监听器的`apply( )`方法并取得返回值,直到某个`apply( )`返回一个不为`undefined`的结果,则停止执行并将这个结果返回。 - `Tapable.prototype.applyPluginsAsync( )` ![](?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=) 异步执行监听回调的方法。这个方法是顺序执行,等到第一个插件执行结束后才会执行下一个插件,实现的方式就是将下一个插件当做回调函数传入第一个插件,在第一个插件的`apply( )`方法的方法体最后(或是异步方法最后)来调用下一个监听插件的执行。这里利用闭包实现了一个迭代器,变量记录在`applyPluginsAsync( )`方法中(就是变量`i`),并在回调中函数`next( )`中保持了对`i`的引用。 例如需要用`applyPluginsAsync( )`方法执行的插件需要在`apply`方法中显式执行回调函数: ```js class Plugin1{ apply(info){ var callback = Array.prototype.pop.call(arguments[1]); //这里取到的callback,实际上就是源码中的具名函数next() callback(); } } ``` 其他的异步方法大同小异,不再赘述。 源码的异步方法定义中使用`copyProperties( )`来处理两个函数,笔者尝试了很多情况这个方法都并未执行,实际情况就是将next函数加入了参数数组并继续执行,希望对此有研究的读者能够点明一下。 ### 三. tapable1.0概述 `tapable`地址:[【tapable-1.0】](https://github.com/webpack/tapable) `tapable`在1.0版本做了很大改进,使用`ES6`语法重写了整个框架,除了更换了API外,在插件定义方面进行了明显升级,原来只通过`plugin( )`方法来定义插件,不阅读源码很难知道插件的规范格式,新版本的`tapable`提供了基本样例,细分的事件钩子(**\*Hook**),新的触发事件的方法(`tap`,`tapAsync`,`tapPromise`)等等,但实现的基本需求是一致的,感兴趣的读者可以自行学习。

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

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