hn-failte's blog hn-failte's blog
首页
  • 前端文章

    • JavaScript
    • Vue
    • React
    • Webpack
    • 混合开发
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《React》笔记
    • 《TypeScript 从零实现 axios》
    • 《Git》学习笔记
    • TypeScript笔记
    • JS设计模式总结笔记
  • HTML&CSS
  • HTML
  • CSS
  • CSS预处理
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 算法
  • 数据库
  • 操作系统
  • 工具
  • 学习
  • 面试
  • 心情杂货
  • 前端相关
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

hn-failte

前端cv仔
首页
  • 前端文章

    • JavaScript
    • Vue
    • React
    • Webpack
    • 混合开发
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《Vue》笔记
    • 《React》笔记
    • 《TypeScript 从零实现 axios》
    • 《Git》学习笔记
    • TypeScript笔记
    • JS设计模式总结笔记
  • HTML&CSS
  • HTML
  • CSS
  • CSS预处理
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 算法
  • 数据库
  • 操作系统
  • 工具
  • 学习
  • 面试
  • 心情杂货
  • 前端相关
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Webpack

    • webpack 之 tapable 的讲解与使用
      • 一、tapable 是什么
      • 二、tapable 基本钩子的实现
      • 三、tapable 扩展钩子的实现
      • 三、tapable 扩展钩子的使用
      • 三、tapable 在 webpack 中的使用
      • 四、总结
    • 使用Gulp4.0搭建ts学习环境
    • eslint从愁眉苦脸到为所欲为
  • React

  • JavaScript

  • Vue

  • 混合开发

  • 学习笔记

  • 微信小程序

  • 前端
  • Webpack
hn-failte
2021-04-05

webpack 之 tapable 的讲解与使用

# tapable 在 Webpack 中的应用

tapable 是 webpack 的基石,webpack 是一套打包工具,而这套打包工具的流程管理正是用的 tapable。

# 一、tapable 是什么

首先,我们来理解下 tabable 的意义,tapable 可以拆分为 tap 和 able 的拼写,其实,含义也就是可监听的意思。

tapable 主要做的事情就是用于做各种时间的监听,提供了一系列的 hooks。

简单地说,tapable 是一个基于事件的流程管理系统。

这些 hooks 主要包括了同步和异步两大类,而同步和异步中,又分了几中不同系列的钩子。

同步类的钩子主要有:

  • SyncHook
  • SyncBailHook
  • SyncWaterfallHook
  • SyncLoopHook

异步类的钩子主要有:

  • AsyncParallelHook
  • AsyncParallelBailHook
  • AsyncSeriesHook
  • AsyncSeriesBailHook
  • AsyncSeriesWaterfallHook

除此之外,tapable 还提供了用于批量管理钩子的 Hook:

  • HookMap
  • MutiHooks

接下来,我们来看看这些钩子的一些实现。

# 二、tapable 基本钩子的实现

1、基本的 Hook 类

该类是所有其他 Hook 类的原型

注:以下源码只标注单个方法或部分重要代码的注释,具体请查看源码

const CALL_DELEGATE = function (...args) {
  // 生成同步的 call 方法
  this.call = this._createCall("sync");
  return this.call(...args);
};
const CALL_ASYNC_DELEGATE = function (...args) {
  // 生成异步回调的 call 方法
  this.callAsync = this._createCall("async");
  return this.callAsync(...args);
};
const PROMISE_DELEGATE = function (...args) {
  // 生成异步 promise 的 call 方法
  this.promise = this._createCall("promise");
  return this.promise(...args);
};
class Hook {
  constructor(args = [], name = undefined) {
    this._args = args; // 实例时携带的参数集
    this.name = name; // 该 hook 实例的 name,可为空
    this.taps = []; // 内部的 tap 实例集
    this.interceptors = []; // 挟持集,会在注册 tap 时,用于处理参数
    this._call = CALL_DELEGATE; // 调用创建的同步 call 方法
    this.call = CALL_DELEGATE; // 调用创建的同步 call 方法
    this._callAsync = CALL_ASYNC_DELEGATE; // 调用创建的异步 call 方法
    this.callAsync = CALL_ASYNC_DELEGATE; // 调用创建的异步 call 方法
    this._promise = PROMISE_DELEGATE; // 调用创建的异步 promise 方法
    this.promise = PROMISE_DELEGATE; // 调用创建的异步 promise 方法
    this._x = undefined; // 用于给其他Hook类存储参数用,Hook类未使用到

    // 若在子类有重写,则覆盖以下方法
    this.compile = this.compile; // 将抽象的 compile 方法进行覆盖
    this.tap = this.tap; // 将同步的 tap 方法进行覆盖
    this.tapAsync = this.tapAsync; // 将异步函数的 tap 方法进行覆盖
    this.tapPromise = this.tapPromise; // 将异步 promise 的 tap 方法进行覆盖
  }
  compile() {
    // 抽象方法,需子类实例实现
  }
  _createCall() {
    // 会调用 compile 方法创建一个 call 方法
  }
  _tap() {
    // 最基本的 tap 方法,其他的 tap 方法均是基本该方法封装
  }
  tap() {
    // 同步的 tap 方法
  }
  tapAsync() {
    // 异步函数的 tap 方法
  }
  tapPromise() {
    // 异步 promise 的 tap 方法
  }
  _runRegisterInterceptors() {
    // 将参数执行注册挟持器操作
  }
  withOptions() {
    // 将 tap 相关的方法重新封装一套暴露到外部的 options 中
  }
  isUsed() {
    // 该 hooks 是否有被使用(tap 或挟持器的数量不为 0 则认为未被使用)
  }
  intercept() {
    // 添加挟持,挟持方法需包括一个 register 方法用于进行挟持
  }
  _resetCompilation() {
    // 重置所有重写过的 call 方法(若_call方法也被重写则会重置为重写后的 _call 方法)
    // 包括 call、callAsync、promise
  }
  _insert() {
    // 将新增的监听插入到 taps 中,若已存在,则先删后增
  }
}

// 将 Hook 的原型设置为 null(不会继承任何Object原型的方法)
Object.setPrototypeOf(Hook.prototype, null);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

2、基本的 Hook 初始类

该类会在其他 Hook 类继承基本 Hook 类时提供用来重写 compile 方法。

该类我们重点讲解其中的几个方法。

class HookCodeFactory {
  constructor(config) {
    this.config = config;
    this.options = undefined;
    this._args = undefined; // 用于存储初始的options参数
  }

  // 核心代码:该方法会通过 new Function() 将传参 args 与字符串 code 实例化为一个函数
  create(options) {
    this.init(options);
    let fn;
    switch (this.options.type) {
      case "sync":
        fn = new Function(
          this.args(),
          '"use strict";\n' +
            this.header() +
            this.contentWithInterceptors({
              onError: (err) => `throw ${err};\n`,
              onResult: (result) => `return ${result};\n`,
              resultReturns: true,
              onDone: () => "",
              rethrowIfPossible: true,
            })
        );
        break;
      case "async":
        fn = new Function(
          this.args({
            after: "_callback",
          }),
          '"use strict";\n' +
            this.header() +
            this.contentWithInterceptors({
              onError: (err) => `_callback(${err});\n`,
              onResult: (result) => `_callback(null, ${result});\n`,
              onDone: () => "_callback();\n",
            })
        );
        break;
      case "promise":
        let errorHelperUsed = false;
        const content = this.contentWithInterceptors({
          onError: (err) => {
            errorHelperUsed = true;
            return `_error(${err});\n`;
          },
          onResult: (result) => `_resolve(${result});\n`,
          onDone: () => "_resolve();\n",
        });
        let code = "";
        code += '"use strict";\n';
        code += this.header();
        code += "return new Promise((function(_resolve, _reject) {\n";
        if (errorHelperUsed) {
          code += "var _sync = true;\n";
          code += "function _error(_err) {\n";
          code += "if(_sync)\n";
          code +=
            "_resolve(Promise.resolve().then((function() { throw _err; })));\n";
          code += "else\n";
          code += "_reject(_err);\n";
          code += "};\n";
        }
        code += content;
        if (errorHelperUsed) {
          code += "_sync = false;\n";
        }
        code += "}));\n";
        fn = new Function(this.args(), code);
        break;
    }
    this.deinit();
    return fn;
  }

  // 将实例的compile时的参数存到 _x 中(在 Hook 类中该变量有保留为空)
  // 该参数会通过 setup 的 this 带入到 compile 中
  setup(instance, options) {
    instance._x = options.taps.map((t) => t.fn);
  }

  /*
   * 由于其他大部分代码都是拼接字符串构建函数,此处就不再做更多注解
   */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

看完了基本钩子的实现,其实,扩展钩子也就是在其基础上重写了一部分方法。接下来我们来试试基于基本钩子进行了继承的扩展钩子的用法。

# 三、tapable 扩展钩子的实现

这里,我们重点对其中最复杂的 SyncWaterfallHook、AsyncSeriesWaterfallHook 的源码做分析

1、SyncWaterfallHook

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class SyncWaterfallHookCodeFactory extends HookCodeFactory {
  content({ onError, onResult, resultReturns, rethrowIfPossible }) {
    return this.callTapsSeries({
      onError: (i, err) => onError(err),
      onResult: (i, result, next) => {
        let code = "";
        code += `if(${result} !== undefined) {\n`;
        code += `${this._args[0]} = ${result};\n`;
        code += `}\n`;
        code += next();
        return code;
      },
      onDone: () => onResult(this._args[0]),
      doneReturns: resultReturns,
      rethrowIfPossible,
    });
  }
}

const factory = new SyncWaterfallHookCodeFactory();

const TAP_ASYNC = () => {
  throw new Error("tapAsync is not supported on a SyncWaterfallHook");
};

const TAP_PROMISE = () => {
  throw new Error("tapPromise is not supported on a SyncWaterfallHook");
};

const COMPILE = function (options) {
  factory.setup(this, options);
  return factory.create(options);
};

function SyncWaterfallHook(args = [], name = undefined) {
  if (args.length < 1)
    throw new Error("Waterfall hooks must have at least one argument");
  const hook = new Hook(args, name);
  hook.constructor = SyncWaterfallHook;
  hook.tapAsync = TAP_ASYNC;
  hook.tapPromise = TAP_PROMISE;
  hook.compile = COMPILE;
  return hook;
}

SyncWaterfallHook.prototype = null;

module.exports = SyncWaterfallHook;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

2、AsyncSeriesWaterfallHook

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

class AsyncSeriesWaterfallHookCodeFactory extends HookCodeFactory {
  content({ onError, onResult, onDone }) {
    return this.callTapsSeries({
      onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
      onResult: (i, result, next) => {
        let code = "";
        code += `if(${result} !== undefined) {\n`;
        code += `${this._args[0]} = ${result};\n`;
        code += `}\n`;
        code += next();
        return code;
      },
      onDone: () => onResult(this._args[0]),
    });
  }
}

const factory = new AsyncSeriesWaterfallHookCodeFactory();

const COMPILE = function (options) {
  factory.setup(this, options);
  return factory.create(options);
};

function AsyncSeriesWaterfallHook(args = [], name = undefined) {
  if (args.length < 1)
    throw new Error("Waterfall hooks must have at least one argument");
  const hook = new Hook(args, name);
  hook.constructor = AsyncSeriesWaterfallHook;
  hook.compile = COMPILE;
  hook._call = undefined;
  hook.call = undefined;
  return hook;
}

AsyncSeriesWaterfallHook.prototype = null;

module.exports = AsyncSeriesWaterfallHook;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 三、tapable 扩展钩子的使用

注意:所有相关同步的 hooks,异步 tap 均重写为了报错

1、SyncHook

使用

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

// 实例话一个Hooks类进行使用,传递的字符串参数会作为执行函数的参数
const hooks = new SyncHook(["arg1", "arg2", "arg3"]);

// 同步监听
hooks.tap("a", (...args) => {
  // 该处的args包括了arg1、agr2、arg3
  console.log(args, "a");
});

hooks.tap("b", (...args) => {
  console.log(args, "b");
});

// 同步监听
hooks.tap("c", (...args) => {
  console.log(args, "c");
});

// call的参数会传递给前面监听的事件
// 同步方法没有回调,若需要回调,直接在外边写即可
hooks.call(1, 2, 3);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

2、SyncBailHook

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

// 同步保释 Hook
const hooks = new SyncBailHook(["params"]);

hooks.tap("a", (params) => {
  console.log("hooks a", params);
  return void 0;
});

hooks.tap("b", (params) => {
  console.log("hooks b", params);
  return true;
});

hooks.tap("c", (params) => {
  // 前一个 tap 的返回值不为 undefined 时,不会再执行后续的 tap
  console.log("hooks c", params);
});

hooks.call("start");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

3、SyncWaterfallHook

const { SyncWaterfallHook } = require("../lib");

// 同步流水线 Hook
const hooks = new SyncWaterfallHook(["arg1", "arg2", "arg3"]);

hooks.tap("a", (a, b, c) => {
  const result = a + b + c;
  console.log(result);
  return result;
});

hooks.tap("b", (arg, ...args) => {
  // 后一个钩子的参数是前一个钩子的返回值,参数会替换最初传入的参数
  const result = arg + 10;
  console.log(result);
  console.log(args, "args");
  return result;
});

hooks.tap("c", (arg, ...args) => {
  const result = arg + 10;
  console.log(result);
  console.log(args, "args");
  return result;
});

hooks.call(1, 2, 3);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

4、SyncLoopHook

const { SyncLoopHook } = require("../lib");

// 同步循环 Hook
const hooks = new SyncLoopHook(["arg"]);

let count = 0;

hooks.tap("a", (arg) => {
  console.log("a", arg, count);
  // 在返回 undefined 后,该 tap 不会再自己执行(可以被后续的 tap 触发被动执行)
  return void 0;
});

hooks.tap("b", (arg) => {
  // b 是一个可以循环的 tap
  // 在 b 之前的 tap 每次在 b 执行时,都会再次执行
  console.log("b", arg, count);
  return count > 10 ? void 0 : count++;
});

hooks.tap("c", (arg) => {
  // 在 b 执行完成之前,c 都不会执行
  console.log("c", arg, count);
  return count > 10 ? void 0 : count++;
});

hooks.call("loop");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

5、AsyncParallelHook

const { AsyncParallelHook } = require("tapable");

// 并行的异步 Hook
const hooks = new AsyncParallelHook(["params"]);

hooks.tapPromise("a", () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks a");
      resolve();
    }, 1000);
  });
});

hooks.tapPromise("b", () => {
  // a、b 同步执行,b 会先完成
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks b");
      resolve();
    }, 500);
  });
});

hooks.callAsync("start", () => {
  console.log("done");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

6、AsyncParallelBailHook

const { AsyncParallelBailHook } = require("../lib");

// 异步并行保释 Hook
const hooks = new AsyncParallelBailHook(["params"]);

hooks.tapPromise("a", (params, callback) => {
  // tapPromise 不带有回调,因此无法使用promise进行保释
  console.log(callback, "callback");
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks a", params);
      resolve(void 0);
    }, 800);
  });
});

hooks.tapAsync("b", (params, callback) => {
  setTimeout(() => {
    console.log("hooks b", params);
    // 进行保释,该操作会使回调在所以 tap 运行完成后执行
    callback(true);
  }, 300);
});

hooks.tapAsync("c", (params, callback) => {
  setTimeout(() => {
    console.log("hooks c", params);
  }, 500);
});

hooks.tap("d", (params, callback) => {
  // tap 同样不带有回调,因此无法使用promise进行保释
  console.log(callback, "callback");
  console.log("hooks d", params);
});

hooks.callAsync("start", () => {
  console.log("done");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

7、AsyncSeriesHook

const { AsyncSeriesHook } = require("tapable");

// 串行的异步 Hook
const hooks = new AsyncSeriesHook(["params"]);

hooks.tapPromise("a", () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks a");
      resolve();
    }, 1000);
  });
});

hooks.tapPromise("b", () => {
  // 先执行完 a 后,才会执行 b
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks b");
      resolve();
    }, 500);
  });
});

hooks.callAsync("start", () => {
  console.log("done");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

8、AsyncSeriesBailHook

const { AsyncSeriesBailHook } = require("../lib");

// 异步串行保释 Hook
const hooks = new AsyncSeriesBailHook(["params"]);

hooks.tapPromise("a", (params) => {
  // tapPromise 没有回调,但可以把resolve的值当做返回值
  // promise 最后的值为非 undefined 时会执行最后的回调
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks a", params);
      // 若未进行 resolve 或 reject 操作,则后续的 tap 将无法正常执行
      resolve();
    }, 800);
  }).then((res) => res);
});

hooks.tapAsync("b", (params, callback) => {
  setTimeout(() => {
    console.log("hooks b", params);
    // 进行保释,该操作会使回调在所以 tap 运行完成后执行
    // 若未执行回调,则后续的 tap 将无法正常执行
    callback();
  }, 300);
});

hooks.tapAsync("c", (params, callback) => {
  setTimeout(() => {
    console.log("hooks c", params);
    callback();
  }, 500);
});

hooks.tap("d", (params) => {
  // tap 同样不带有回调,因此无法使用promise进行保释
  console.log("hooks d", params);
});

hooks.callAsync("start", () => {
  // 若所有的 tap 均已执行完,则正常执行回调
  console.log("done");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

9、AsyncSeriesWaterfallHook

const { AsyncSeriesWaterfallHook } = require("../lib");

// 异步串行流水线 Hook
const hooks = new AsyncSeriesWaterfallHook(["params"]);

hooks.tapPromise("a", (params) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks a", params);
      resolve("a");
    }, 800);
  }).then((res) => res);
});

hooks.tapAsync("b", (params, callback) => {
  setTimeout(() => {
    console.log("hooks b", params);
    // 当返回值为 undefined 时,后续的 tap 将会执行,且可携带参数到下一个函数中
    // 若未携带参数,则默认为上一 tap 传递的参数
    callback(void 0, "b");
  }, 300);
});

hooks.tapAsync("c", (params, callback) => {
  setTimeout(() => {
    console.log("hooks c", params);
    // 当返回值不为 undefined 时,后续的 tap 将不会执行
    callback(true, "c");
  }, 500);
});

hooks.tap("d", (params) => {
  console.log("hooks d", params);
});

hooks.callAsync("start", () => {
  // 若所有可执行的 tap 均已执行完,则正常执行回调
  console.log("done");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

10、AsyncSeriesLoopHook

const { AsyncSeriesLoopHook } = require("../lib");

// 异步串行循环 Hook
// 该 Hook 是异步串行 Hook 与 同步循环 Hook 的结合
const hooks = new AsyncSeriesLoopHook(["params"]);

let count = 0;

hooks.tapPromise("a", () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks a");
      resolve(count > 2 ? void 0 : count++);
    }, 500);
  });
});

hooks.tapPromise("b", () => {
  // 先执行完 a 后,才会执行 b
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("hooks b");
      resolve(count > 3 ? void 0 : count++);
    }, 500);
  });
});

hooks.callAsync("start", () => {
  console.log("done");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

11、HookMap

const { HookMap, SyncHook, SyncWaterfallHook } = require("../lib");

// HookMap 的传参是一个函数,函数需要返回一个 Hook 实例
// HookMap 可以包含不同的 Hook
const hooks = new HookMap((key) => {
  if (key === "one") return new SyncHook(["arg"]);
  else return new SyncWaterfallHook(["arg"]);
});

{
  // 通过 for 方法可以创建一个对应该 key 的 hook
  hooks.for("one").tap("a", (arg) => {
    console.log("a", arg);
  });

  // 通过 get 方法可以取出一个对应该 key 的 hook
  hooks.get("one").call("one");
}

{
  const two = hooks.for("two");

  two.tap("a", (arg) => {
    console.log("a", arg);
    return "a";
  });

  two.tap("b", (arg) => {
    console.log("b", arg);
  });

  two.call("two");
}

{
  // 挟持器,可以在创建 Hook 的时候挟持到 key 和 hook
  hooks.intercept({
    factory(key, hook) {
      // key 是传入时的 key,hook 是未加挟持时即将生成的 hook 实例
      return new SyncHook();
    },
  });

  let three = hooks.for("three");

  three = hooks.get("three");

  three.tap("a", (arg) => {
    console.log("a", arg);
    return "a";
  });

  three.tap("b", (arg) => {
    console.log("b", arg);
  });

  three.call("three");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

12、MultiHook

const { MultiHook, SyncHook, SyncBailHook } = require("../lib");

const sync = new SyncHook(["arg"]);
const syncBail = new SyncBailHook(["arg"]);

// 将需要批量操作的 hook 放入该类中可以实现批量 tap
const hooks = new MultiHook([sync, syncBail]);

// 在进行 tap 时,会批量的进行 注册
hooks.tap("a", (arg) => {
  console.log("a", arg);
  // SyncBailHook 在返回非 undefined 后,下一个 tap 将不会执行
  return true;
});

hooks.tap("b", (arg) => {
  console.log("b", arg);
});

sync.call("start");

syncBail.call("start");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 三、tapable 在 webpack 中的使用

tapable 贯穿了 webpack 的整个构建流程

1、简述

webpack 的编译流程都是由一个一个的 tap hook 组成的

webpack 的插件通过 tap 某个流程可以实现注册事件的功能

2、单实例构建

class Compiler {
  constructor(context) {
    this.hooks = Object.freeze({
      initialize: new SyncHook([]),
      shouldEmit: new SyncBailHook(["compilation"]),
      done: new AsyncSeriesHook(["stats"]),
      afterDone: new SyncHook(["stats"]),
      additionalPass: new AsyncSeriesHook([]),
      beforeRun: new AsyncSeriesHook(["compiler"]),
      run: new AsyncSeriesHook(["compiler"]),
      emit: new AsyncSeriesHook(["compilation"]),
      assetEmitted: new AsyncSeriesHook(["file", "info"]),
      afterEmit: new AsyncSeriesHook(["compilation"]),
      thisCompilation: new SyncHook(["compilation", "params"]),
      compilation: new SyncHook(["compilation", "params"]),
      normalModuleFactory: new SyncHook(["normalModuleFactory"]),
      contextModuleFactory: new SyncHook(["contextModuleFactory"]),
      beforeCompile: new AsyncSeriesHook(["params"]),
      compile: new SyncHook(["params"]),
      make: new AsyncParallelHook(["compilation"]),
      finishMake: new AsyncSeriesHook(["compilation"]),
      afterCompile: new AsyncSeriesHook(["compilation"]),
      watchRun: new AsyncSeriesHook(["compiler"]),
      failed: new SyncHook(["error"]),
      invalid: new SyncHook(["filename", "changeTime"]),
      watchClose: new SyncHook([]),
      shutdown: new AsyncSeriesHook([]),
      infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
      environment: new SyncHook([]),
      afterEnvironment: new SyncHook([]),
      afterPlugins: new SyncHook(["compiler"]),
      afterResolvers: new SyncHook(["compiler"]),
      entryOption: new SyncBailHook(["context", "entry"]),
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

可以清楚的看到构建通过 tapable 为构建创建了 29 个钩子,可以对这 29 个钩子订阅事件,从而在构建时进行触发。

webpack 的插件就是通过该方式进行编写的,我们可以通过该类方式使得我们的自定义插件可以起到不同的作用。

3、webpack 多实例构建

class MultiCompiler {
  constructor() {
    /* 省略 */
    this.hooks = Object.freeze({
      done: new SyncHook(["stats"]),
      invalid: new MultiHook(compilers.map((c) => c.hooks.invalid)),
      run: new MultiHook(compilers.map((c) => c.hooks.run)),
      watchClose: new SyncHook([]),
      watchRun: new MultiHook(compilers.map((c) => c.hooks.watchRun)),
      infrastructureLog: new MultiHook(
        compilers.map((c) => c.hooks.infrastructureLog)
      ),
    });
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可以清晰的看到 webpack 通过 tapable 对所有的编译做了统一的钩子进行管理

# 四、总结

1、优缺点

tapable 可以将一个流程划分为较细的一些步骤进行管理,在每个步骤中都能通过不同的方式进行管理,适用于比较大型的流程管理

但是,也正是因为如此,tapable 在小型的项目中显得臃肿,小型项目更适合简单的事件订阅。

2、适用场景

通常的,对于一些业务比较复杂的项目,流程较为麻烦时,可以使用 tapable 对项目进行管理。

通过 tapable,可以将项目的各个流程进行钩子的插入,从而更方便的进行管理。

3、适用项目

主要适用于构建工具、后端项目、前端离线管理项目、配置化表单等。

编辑 (opens new window)
#Webpack
上次更新: 2021/08/05, 12:37:41
使用Gulp4.0搭建ts学习环境

使用Gulp4.0搭建ts学习环境→

最近更新
01
基于 Taro 的微信小程序优化指南
02-16
02
搭建一个极简混合开发架构
08-03
03
使用State Hook
04-06
更多文章>
Theme by Vdoing | Copyright © 2017-2023 hn-failte | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式