最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • tapable源码浅读

    正文概述 掘金(Junyang1023)   2021-01-31   613

    概念

    官方链接

    tapable 类似于一个发布订阅中心,基本流程就是

    1. 注册自己需要的一个或多个类型的一个或多个 hook 的实例
    2. 为该实例注册消费者
    3. 在某个时机触发该 hook 实例,执行消费者

    最终是让我们使用各种各样的 hook,tapable 是这个库的名字,我们实际使用的是这些 hook

    tapable 库输出的有 9 种类型的 hook 类,如下index.js:

    "use strict";
    
    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 简介

    执行效率

    The Hook will compile a method with the most efficient way of running your plugins. hook 实例会自行判断执行注册的 plugin 消费者的最有效率的方法。 它依赖如下因素产生最终执行的逻辑:

    1. 注册的 plugin 的数目
    2. 注册的 plugin 的类型
    3. 执行的方法 sync async promise
    4. 参数的数目
    5. 有无使用注入【intercept api】

    Hook 类型

    执行顺序

    每一个 hook 可以注册(被 tap tapAsync tapPromise)多个函数【消费者】。如何执行依赖于 hook 本身的类型:

    1. Basic hook 只是简单执行所有注册的函数
    2. Waterfall 把上一个函数的返回值传入下一个函数
    3. Bail【迅速离开】允许早结束, 只要有一个函数有返回值, 后续的函数都不再执行
    4. Loop 只要有一个 plugin 有返回值,就从第一个 plugin 再开始执行,直到所有 plugin 都返回 undefined

    同步异步

    另外 hook 可以是同步或异步的,有如下 3 种:

    1. Sync 只能用 tap 方法注册同步的函数
    2. AsyncSeries 可以用 tap tapAsync tapPromise 方法对应注册 同步的、回调的、promise 的函数。注册的函数逐行调用【类似async await】
    3. AsyncParallel 可以用 tap tapAsync tapPromise 方法对应注册 同步的、回调的、promise 的函数。注册的函数并发调用。

    【注:只不过,用 tapPromise 方法定义的消费者必须返回一个 promise】

    组合结果及举例

    最终 hook 的类型有上面2个方面组合而成, 共 9 种【可在 tapable 库的 index.js 看到罗列】,不到 12 种 , 比如没有 AsyncParallelWaterfallHook 类型,有 AsyncSeriesWaterfallHook。举2个例子如下:

    1. AsyncSeriesWaterfallHook 允许异步函数,并且按照顺序执行,并且把上一个函数的返回值传入下一个函数。
    2. AsyncSeriesHook 执行 call 的时候如果某一步不是 通过 tapPromise 注册的消费者,就不再往后执行了,因为它应该用的是 类似 aa,会一直在等待中。

    intercept api 注入

    所有的hook 都提供了 intercept api 该 api 传入一个对象,包含如下函数字段: context【boolean】 true 时可以用来传递任意值,暂时还不清楚具体用途。 call hook 触发的时候触发,代入 hook 触发时的参数 tap 注册函数的时候触发。传入的 tapobject 不可修改 loop loop hook 进入 loop 的时候触发 register 注册函数的时候触发。传入的 tapobject 可修改

    注册消费者

    共 3 种方式

    tap tapPromise tapAsync 3 种方式。

    1. 同步的 hook 只能用 tap 方法添加 plugin
    2. 异步的 hook 同时支持异步的 plugin,即支持用 tap tapPromise tapAsync 3 种方法添加 plugin

    关于tapPromise tapAsync

    tapPromise 方法调用返回 promise。 tapAsync 方法可以在内部执行异步函数,不过需要传入callback去执行回调

    触发消费

    触发这些在 tap 等函数中定义的 plugin 需要用 call 等一系列方法

    1. tap 对应 call 方法
    2. tapPromise 对应 promise 方法
    3. tapAsync 对应 callAsync 方法

    不过,AsyncParallelHook AsyncSeriesHook 等类型的 hook 没有 call 方法,只有 promise callAsync 方法,虽然可以用 tap 方法定义消费者。即:异步类型的 hook 可以用同步的 tap 方式添加消费者,不过该 hook 在被触发的时候不能用同步的 call 方法。【下方源码简介部分有解释】

    源码简介

    以 SyncHook 为例

    注册消费者简析

    tap tapPromise tapAsync 这几个如何注册消费者的函数是如何定义的?

    1. 通过 options = this._runRegisterInterceptors(options); 改造了下 options 参数。不过如果没有给 hook 实例配置 intercept apiinterceptor.register 的话,什么也不做。
    2. 通过 this._insert(options); 把新的消费者加到该 hook 实

    例上。判断有没有设置 before stage 等参数,有的话重新排序,没有的话把新的消费者直接加到后面。

    触发消费简析

    /node_modules/_tapable@1.1.3@tapable/lib/Hook.jscall promise callAsync 这几个触发 hook 执行的函数如何定义的?

    1. 在上面 的 constructor 写的 this.call=this._call this.promise = this._promise; this.callAsync = this._callAsync;并不真的 执行,只是在实例化时执行。
    2. 而在最下面 Object.defineProperties 定了 _call _promise _callAsync 方法,它们的值是函数。【不过为什么不直接放 class 里面呢?】
    3. _call 是调用 this._createCall 生成的函数,而 this._createCall 中调用的是 compile 方法,而 syncHook 类覆盖了 Hook 基类的 compile 方法【该基类方法本身也要求被改写】
    4. SyncHook 类的 compile 方法执行了 factory.setupfactory.create
    5. factory 是实例化的 SyncHookCodeFactory【继承自 HookCodeFactory】 得到的一个实例,
    6. factory.setup 执行的效果是 把 taps 数组中的对象上的 fn 返回成一个数组赋值给 SyncHook 实例的 _x 属性【在 getTapFn 函数中被使用,用于拼成生成的执行函数的字符串】,
    7. factory.create 使用的是 new Function【里面拼字符串作为函数功能】 的方式定义的函数,根据 type 为 sync asyncpromise 来决定使用不同的字符串,里面约定了这几种不同的触发 hook 的方式如何调用注册的消费者。

    factory.create 生成的这几个被字符串拼接的函数是比较重要的,是在他们这决定如何执行被注册的消费者的。这里消费在上面 注册消费者简析 中介绍的排好序的被注册的消费者】

    以 AsyncParallelHook 为例

    为什么他触发执行消费者的方法中没有 call 方法呢? 他是在 /node_modules/_tapable@1.1.3@tapable/lib/AsyncParallelHook.js 中在最下面通过 Object.defineProperties(AsyncParallelHook.prototype 给 给 _call 设置的 valueundefined,从而覆盖了 /node_modules/_tapable@1.1.3@tapable/lib/Hook.js 基类中定义的 _call 方法,那 undefined 肯定不能被执行啊。

    结合官方 demo 写一个小 demo 用于跑断点测试

    略长,有耐心可以细看

    代码如下:

    // https://github.com/webpack/tapable
    var {SyncHook, AsyncParallelHook, AsyncSeriesHook} = require('tapable')
    
    /**
     * 这里传入的数组,就是个占位符,或者语义化的值,
     * 表示这个实例化的 hook 在 tap时候的回调函数写几个参数,
     * 也即在被注册消费者、以及被调用的时候传入多少个参数。
     */
    // const hook = new SyncHook(["arg1", "arg2", "arg3"]);
    // console.log('hook', hook)
    
    
    
    class Car {
      constructor() {
        /**
         * You won't get returned value from SyncHook or AsyncParallelHook,
         * to do that, use SyncWaterfallHook and AsyncSeriesWaterfallHook respectively
         *
         * SyncHook AsyncParallelHook 没有返回值,SyncWaterfallHook 和 AsyncSeriesWaterfallHook 有
         **/
    
        // 把实例化的 hook 放一块
        this.hooks = {
          accelerate: new SyncHook(["newSpeed"]),
          brake: new SyncHook(),
          calculateRoutes: new AsyncParallelHook(["source", "target", "callback"])
        };
      }
    
      /* 下面的方法都是这么触发 hook,从而让其上的消费者执行 */
      brake(newSpeed) {
        // following call returns undefined even when you returned values
        // brake 是个 SyncHook,下面这个 call 返回 undefined
        this.hooks.brake.call();
      }
    
      setSpeed(newSpeed) {
        // following call returns undefined even when you returned values
        // accelerate 是个 SyncHook,下面这个 call 返回 undefined
        this.hooks.accelerate.call(newSpeed);
      }
    
      useNavigationSystemPromise(source, target) {
        // 用 promise 方法可以触发所有用 tapPromise tapAsync tap 注册的消费者
        return this.hooks.calculateRoutes.promise(source, target, ()=>{
          console.log('useNavigationSystemPromise 执行1')
        }).then((res) => {
          // 虽然所有方法都能触发,但是只有所有消费者都是用 tapPromise 注册的话,才会执行到这里
          // res is undefined for AsyncParallelHook
          console.log('useNavigationSystemPromise 执行2 res', res)
        });
      }
    
      // 用 callAsync 方法可以触发所有用 tapPromise tapAsync tap 注册的消费者
      useNavigationSystemAsync(source, target) {
        this.hooks.calculateRoutes.callAsync(source, target, err => {
          console.log('useNavigationSystemAsync 执行')
        });
      }
    }
    
    const myCar = new Car();
    
    
    /**
     * 下面是给 hook 们注册消费者
     * 用 tap 方法给 hook 添加一个消费者
     * tap 要求必须要传入一个 name 来标记是什么 plugin 或触发原因
     *
     */
    myCar.hooks.brake.tap("WarningLampPlugin", () => {
      console.log('刹车灯 on')
    });
    myCar.hooks.brake.tap("speeddown", () => {
      console.log('speeddown on')
    });
    
    // 回调函数可以接受参数【可不止一个】
    myCar.hooks.accelerate.tap('loggerPlugin', newSpeed => {
      console.log(`正则加速到 ${newSpeed}`)
    })
    
    
    /**
     * calculateRoutes 的消费者1
     *
     * calculateRoutes 是个 AsyncParallelHook
     * 可以用 tap tapAsync tapPromise 方法
     * 这里用的 tapPromise 方法注册,后面的函数返回值应该是 promise
     * 如果不返回一个 promise 的话,会报 Error: Tap function (tapPromise) did not return promise (returned undefined)
     *
     */
    myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, callback) => {
      // return a promise
      console.log('in GoogleMapsPlugin')
      callback()
      return Promise.resolve('google')
    });
    /**
     * calculateRoutes 的消费者2
     *
     * calculateRoutes 是个 AsyncParallelHook
     * 可以用 tap tapAsync tapPromise 方法
     * 这里用的 tapAsync 方法注册,后面的函数中用的是回调函数的方式
     *
     */
    myCar.hooks.calculateRoutes.tapAsync({
      name: 'BingMapsPlugin',
      before: 'GoogleMapsPlugin', // 消费者被调用的顺序。before 可以是字符串或数组, 是在 node_modules/_tapable@1.1.3@tapable/lib/Hook.js:117 的 _insert 函数中处理的顺序关系
    }, (source, target, callback) => {
      console.log('in BingMapsPlugin')
      callback()
    });
    
    /**
     * calculateRoutes 的消费者3
     *
     * calculateRoutes 是个 AsyncParallelHook
     * 可以用 tap tapAsync tapPromise 方法
     * 这里用的 tap 方法注册,后面的函数中用的是回调函数的方式
     *
     * You can still use sync plugins
     * AsyncParallelHook 依然可以用同步方法 tap 来添加 plugin
     *
     * 最终 calculateRoutes 在被触发的时候会并发执行注册的这 3 个方法
     *
     */
    myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, callback) => {
      console.log('in CachedRoutesPlugin')
      callback()
    })
    
    // 这里是实际调用 car 的方法,brake 方法中执行了 hook 的 call,去触发该 hook
    myCar.brake()
    // myCar.setSpeed(100)
    // myCar.useNavigationSystemPromise('source', 'target')
    myCar.useNavigationSystemAsync('source', 'target')
    

    起源地下载网 » tapable源码浅读

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元