前端模块化-理解Tapable与Webpack中的Hooks

前言

Webpack 中的核心架构是基于 Tapable 实现的,Tapable 是一个类似于 Node.js 的 EventEmitter 的库,专门用于实现发布-订阅模式。Webpack 中的核心组件 Compiler、Compilation、Module、Chunk、ChunkGroup、Dependency、Template 都是通过 Tapable 实现的。

Tappable 主要负责管理 Hooks,Hooks 是一系列具有特定生命周期的事件。通过 Tappable,Webpack 中的不同部分可以创建和触发 Hooks,而插件可以监听这些 Hooks,在适当的时机执行自定义的逻辑。这种设计模式使得插件开发更加灵活,能够介入 Webpack 构建流程的不同阶段。

Tappable 可以被视为 Webpack 插件系统的基石。它提供了一种机制,使得插件可以注册自己的逻辑,而这些逻辑可以被集中执行,而不需要硬编码到 Webpack 的核心逻辑中。这种松耦合的设计让插件开发者更容易理解和维护自己的代码,也让整个插件系统更容易扩展。

本节对应的 demo 可以在这里找到。

Tapable 中的核心概念

Hook

在 Tappable 中,Hook 是一个核心类,代表一个事件(或者说是一个钩子)。每个 Hook 实例都可以被订阅,订阅者可以在事件触发时执行自己的逻辑。Hook 的主要职责是管理订阅者和触发事件。

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

const myHook = new Hook(["arg1", "arg2"]);

myHook.tap("Plugin1", (arg1, arg2) => {
   
   
  console.log("Plugin1:", arg1, arg2);
});

myHook.tap("Plugin2", (arg1, arg2) => {
   
   
  console.log("Plugin2:", arg1, arg2);
});

myHook.call(42, "hello");

HookCodeFactory

HookCodeFactory 是一个工厂类,用于生成 Hook 的触发函数。每个 Hook 都有一个对应的 HookCodeFactory,HookCodeFactory 会根据 Hook 的类型和订阅者的类型生成不同的触发函数。

const {
   
    Hook, HookCodeFactory } = require("tapable");

class MyHook extends Hook {
   
   
  constructor(args) {
   
   
    super(args);
    this.compile = this.compileFactory();
  }

  compileFactory() {
   
   
    return HookCodeFactory((args) => {
   
   
      return args.map((arg) => `console.log('${
     
     arg}:', ${
     
     arg});`).join("");
    });
  }
}

const myHook = new MyHook(["arg1", "arg2"]);

const code = myHook.compile({
   
   
  tap: (tapInfo) => {
   
   
    return `console.log('Tapped by ${
     
     tapInfo.name}');`;
  },
  type: "sync",
});

console.log(code);

在上述示例中,MyHook 继承自 Hook,并通过 HookCodeFactory 生成了用于触发事件的代码。compileFactory 方法返回一个函数,该函数接受一个参数 args,并返回一个字符串,其中包含了触发 Hook 事件时执行的代码。这样的设计使得 Hook 类型可以通过不同的 HookCodeFactory 来实现不同的触发逻辑。

Hook 的类型与用途

AsyncParallelBailHook

/**
 * 类似于 AsyncParallelHook,但如果任何插件的回调函数返回除 undefined 之外的值,执行将停止,并在返回该值的情况下调用最终回调。
 * 当插件的结果可以决定是否应执行后续插件时很有用。
 * 支持异步插件注册和执行,包括回调函数和返回 Promise 的函数。
 */

class AsyncParallelBailHook {
   
   
  constructor(args) {
   
   
    this.args = args;
    this.taps = [];
  }

  tap(name, callback) {
   
   
    this.taps.push({
   
    type: "sync", callback, pluginName: name });
  }

  tapPromise(pluginName, callback) {
   
   
    this.taps.push({
   
    type: "promise", callback, pluginName });
  }

  tapAsync(pluginName, callback) {
   
   
    this.taps.push({
   
    type: "async", callback, pluginName });
  }

  callAsync(...args) {
   
   
    const finalCallback = args.pop();
    let count = 0;

    const done = (err, result) => {
   
   
      count++;
      if (err || result || count === this.taps.length) {
   
   
        finalCallback(err, result);
      }
    };

    for (const tap of this.taps) {
   
   
      const callback = tap.callback;
      if (tap.type === "sync") {
   
   
        done(null, callback(...args));
      } else if (tap.type === "promise") {
   
   
        Promise.resolve(callback(...args)).then(
          (result) => done(null, result),
          done
        );
      } else if (tap.type === "async") {
   
   
        callback(...args, done);
      }
    }
  }

  promise(...args) {
   
   
    return new Promise((resolve, reject) => {
   
   
      this.callAsync(...args, (err, result) => {
   
   
        if (err) {
   
   
          reject(err);
        } else {
   
   
          resolve(result);
        }
      });
    });
  }
}

// Demo
const asyncParallelBailHook = new AsyncParallelBailHook(["arg1", "arg2"]);

asyncParallelBailHook.tap("Plugin1", (arg1, arg2) => {
   
   
  console.log("Plugin1:", arg1, arg2);
  return "Result from Plugin1";
});

asyncParallelBailHook.tapAsync("Plugin2", (arg1, arg2, callback) => {
   
   
  console.log("Plugin2:", arg1, arg2);
  setTimeout(() => callback("Result from Plugi
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值