这个是探究 Webpack 本质的系列文章,会详细讲解如何手写一些源码如 Webpack, loader, plugin 等等,本文主要讲解的是如何手写 tapbable 库
tapable 是个啥
由之前写的文章可以了解到,webpack 本质上是基于 事件流 的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 tapable
。tapable
就有点类似于 Node.js 的 events
库,核心原理也依赖于 发布订阅 模式
以下是 tapable
几个常用钩子
const { |
以
Sync
开头的都是同步钩子,以Async
开头的都是异步钩子
这些钩子基本上都是通过 tap
来绑定事件,然后使用 call
来触发事件,比如如下的代码
const { SyncHook } = require('tapable') |
执行下打印结果是
test1 strugglebak |
由以上可以看出,tapable
是将一堆 tap
中订阅的函数放到一个数组中去,调用 call
时分别按顺序去执行
实现 SyncHook
由以上的论证,代码不难实现
class SyncHook { |
测试代码
const hook = new SyncHook(['name']) |
打印结果为
test1 strugglebak |
实现 SyncBailHook
这个钩子的作用是,只要有任何的监听函数返回了一个非 undefined 的结果,那么该监听函数里面的逻辑在执行完成后会就不会继续向下执行了
class SyncBailHook { |
测试代码
const hook = new SyncBailHook(['name']) |
打印结果
test1 strugglebak |
实现 SyncWaterfallHook
这个钩子本质上就是,在调用函数的时候,将上一个函数的返回值作为下一个函数的参数,这样一个传递的流程
class SyncWaterfallHook { |
测试代码
const hook = new SyncWaterfallHook(['name']) |
打印结果
test1 strugglebak |
实现 SyncLoopHook
这个钩子的作用就是 当在同步执行时,遇到某个不返回 undefined 的函数会多次执行
class SyncLoopHook { |
测试代码
const hook = new SyncLoopHook(['name']) |
打印结果
test1 strugglebak |
实现 AsyncParallelHook
首先需要说明的是,这是一个 异步并行 的钩子,所谓 “异步并行”,在这里的表现就是 需要等待所有并发的异步事件执行完成后再执行回调方法
比如如下的代码
const { AsyncParallelHook } = require('tapable') |
打印输出为
test1 strugglebak |
以上代码的意思是,只有当 tapAsync
里面的回调函数中,cb
都执行完了,最后才会调用 callAsync
里的回调函数,即输出 end
原理大致就是,每次执行完一个异步操作,就会调用 cb
,而这个 cb
里面会有个计数器,如果计数器的总数等于当前回调函数注册的总数,就说明所有的异步操作执行完成,接着就可以执行最后的 callAsync
里面的回调了,所以代码可以这么写
class AsyncParallelHook { |
测试代码为
const hook = new AsyncParallelHook(['name']) |
打印输出为
test1 strugglebak |
当然,以上的思想跟 Promise.all
很像,那么这种异步也可以用 Promise
来改写
class AsyncParallelHook { |
测试代码为
const hook = new AsyncParallelHook(['name']) |
测试结果为
test1 strugglebak |
实现 AsyncSeriesHook
注意这个是 异步串行 的钩子,而 异步串行 表示只有执行完 异步 1 之后才可以执行 异步 2,能够这么实现的就是使用回调函数
class AsyncSerieslHook { |
测试代码为
const hook = new AsyncSerieslHook(['name']) |
打印输出为
test1 strugglebak |
当然,有异步的地方也可以改写成 Promise
的形式,如下
class AsyncSerieslHook { |
测试代码为
const hook = new AsyncSerieslHook(['name']) |
打印输出为
test1 strugglebak |
实现 AsyncSeriesWaterfallHook
这里顾名思义,就是 异步 + 串行 + waterfall 钩子,那么代码应该这么写
class AsyncSeriesWaterfalllHook { |
测试代码为
const hook = new AsyncSeriesWaterfalllHook(['name']) |
打印结果为
test1 strugglebak |
当然,这里也可以用 Promise
改写
class AsyncSeriesWaterfalllHook { |
测试代码为
const hook = new AsyncSeriesWaterfalllHook(['name']) |
打印结果为
test1 strugglebak |
总结
由以上的描述我们可以看出一个规律,那就是
- tapable 库中有 3 种注册方法: tap(同步注册)、tapAsync(cb) (异步注册)、tapPromise(注册时是 promise)
- tapable 库中有 3 种调用方法: call(同步调用)、callAsync(cb)(异步调用)、promise(调用时是 promise 用 .then)
以上三者两两对应
同时,我们也知道了,对于 tapable
这个库的钩子来说,分同步钩子和异步钩子,同时钩子还分并行和串行钩子
对于异步并行钩子来说,异步的操作可以是同时发生的,即哪个执行快就先执行
对于异步串行钩子来说,异步的操作是依赖于上个异步的结果的,只有等上个异步执行了,才能执行下个异步