阅读 100

Tapable部分hook源码实现分析

介绍

Webpack如何和各个插件配合实现打包的?

主要就是借助于 tapable 这个库。 下面我参考 tapable 的源码,简单实现其中的部分 hook.

SyncHook 同步勾子

使用示例

const SyncHook = require("./lib/syncHook").default;

const hook = new SyncHook(["name", "age"]);

hook.tap("fn1", (name, age) => {
  console.log("fn1->>>", name, age);
});

hook.tap("fn2", (name, age) => {
  console.log("fn2->>>", name, age);
});

hook.call("kww", 16);

// 控制台打印
fn1->>> kww 16
fn2->>> kww 16
复制代码

源码实现

抽象基类 Hook

我先简单实现一个抽象类 Hook,tapable中的syncHook、asyncParallelHook等hook都继承于这个 Hook。

/**
 tap 结构
 {
     name: string;
     fn: Function;
     type: 'sync' | 'async;
 }
*/

abstract class Hook {
  // 形式参数列表, 如 new SyncHook(["name", "age"]) 中的 ["name", "age"]
  public args: string[];
  // 注册的订阅对象列表
  public taps: object[];
  // 订阅对象的 callback 函数列表
  protected _x: Function[];

  constructor(args = []) {
    this.args = args;
    this.taps = [];
    this._x = undefined;
  }
    
  // hook.tap('fn1', () => {})
  tap(options, fn) {
    if (typeof options === "string") {
      options = {
        name: options,
      };
    }
    // tap = { fn: f(){}, name: "fn1" }
    const tap = Object.assign(
      {
      // 源码里tap对象还有 type: "sync",以及 拦截器 interceptors,我这里就省略了
        fn,
      },
      options
    );
    this._insert(tap);
  }

  // 注册 tap
  private _insert(tap) {
    this.taps[this.taps.length] = tap; // this.taps.push(tap);
  }

  call(...args) {
    // 创建将来要具体执行的函数代码结构
    let callFn = this._createCall();
    // 调用上述的函数
    return callFn.apply(this, args);
  }

  private _createCall(type) {
    return this.compile({
      taps: this.taps,
      args: this.args,
      type,
    });
  }

  abstract compile(data: { taps: any[]; args: string[]; type: "sync" | "async""promise" }): Function;
}

export default Hook;
复制代码

实现子类 SyncHook

最终的执行函数,是通过不同的codeFactory动态生成出来的。先实现 SyncHookCodeFactory,再实现 SyncHook

import Hook from "./hook";
// 源码中 SyncHookCodeFactory 还继承于名为 HookCodeFactory 的基类工厂
// 这里就简化一下直接写到这里了
class SyncHookCodeFactory {
  public options: {
    taps: any[]; // [{ name:'fn1', fn: f(){} }, { name:'fn2', fn: f(){} },]
    args: string[]; // ["name", "age"]
  };

  setup(instance, options) {
    // 这里的操作在源码中是通过 init 方法实现的
    this.options = options;
    // 获取所有注册的tap的回调函数
    instance._x = options.taps.map((o) => o.fn);
  }

  /**
   * 创建一段可以执行的代码题,然后返回
   *
   * 源码中是写在基类工厂中的,通过switch(this.options.type) 执行不同的逻辑,
   * this.options.type 就是 sync、async、promise
   * @param options
   */
  create(options) {
    let fn;
    // 使用 new Function 将字符串转成函数
    fn = new Function(
      // ["name", "age"] 转成  "name,age",作为生成的函数的形式参数, 如 f(name, age) {...}
      this.args(),
      `
        ${this.head()}
        ${this.content()}
    `
    );
    return fn;
  }

  // 基类中固定的方法
  head() {
    return "var _x = this._x;";
  }

  // 子类实现方法,生成类似下面的字符串
  // 
  // var _fn0 = _x[0]; _x 是注册的所有tap的回调函数
  // _fn0(name, age);
  //
  content() {
    let code = "";
    for (var i = 0; i < this.options.taps.length; i++) {
      code += `
          var _fn${i} = _x[${i}]; 
          _fn${i}(${this.args()});
          `;
    }
    return code;
  }

  args() {
    return this.options.args.join(",");
  }
}

let factory = new SyncHookCodeFactory();

class SyncHook extends Hook {
  constructor(args) {
    super(args);
  }

  compile(options) {
    factory.setup(this, options);
    return factory.create(options);
  }
}

export default SyncHook;
复制代码

AsyncParallelHook 异步并行勾子

使用示例

const AsyncParallelHook = require("./src/asyncParallelHook").default;

const hook = new AsyncParallelHook(["name", "age"]);

console.time('time')
hook.tapAsync("fn1", (name, age, callback) => {
  console.log("fn1->>>", name, age);
  setTimeout(()=>{
    callback();
  },1000)
});

hook.tapAsync("fn2", (name, age, callback) => {
  console.log("fn2->>>", name, age);
  setTimeout(()=>{
    callback();
  },2000)
});

hook.callAsync("kww", 16, function () {
  console.log("end");
  console.timeEnd('time')
});

// 终端打印
fn1->>> kww 16
fn2->>> kww 16
end
time: 2009.890ms
复制代码

源码实现

实现子类 AsyncParallelHook

AsyncParallelHookSyncHook的没有太大区别,主要是各自的 codeFactory有些差异。

class AsyncParallelHookCodeFactory {
...
   create(options) {
    let fn;
    // f(name,age, _callback){...}
    fn = new Function(
      this.args({ after: "_callback" }), // "name,age,_callback"
      `
        ${this.head()}
        ${this.content()}
    `
    );
    return fn;
  }
  
  // 生成类似如下代码
  // var _counter = 2;
  // var _done = function(){_callback();};
  //
  // var _fn0 = _x[0];
  // _fn0(name, age, function() {
  //   if(--_counter === 0) _done();
  // });
  //
  // var _fn1 = _x[1];
  // _fn1(name, age, function() {
  //   if(--_counter === 0) _done();
  // });
  //
  content() {
    let code = `var _counter = ${this.options.taps.length}; 
    var _done = function(){_callback();};`;
    
    for (var i = 0; i < this.options.taps.length; i++) {
      code += `
          var _fn${i} = _x[${i}]; 
          _fn${i}(${this.args()}, function(){
              if(--_counter === 0) _done();
          });
          `;
    }
    return code;
  }
  
  args(data?: { before?: string; after?: string }) {
    let args = this.options.args.slice();
    //if (data?.before) {
    //  args = [data.before].concat(args);
    //}
    if (data?.after) {
      args = args.concat([data.after]); // ["name", "age", "_callback"]
    }
    return args.join(",");
  }

...

}

var factory = new AsyncParallelHookCodeFactory();

class AsyncParallelHook extends Hook {
  compile(options) {
    factory.setup(this, options);
    return factory.create(options);
  }
}

复制代码
文章分类
前端
文章标签