Skip to content

Umi 插件概述

什么是插件

Umi 的核心就在于它的插件机制。那什么是插件机制呢?如果你有 webpack 基础,那你应该知道 webpack 整个架构很大程度上是基于事件的,每个 webpack 插件基本上都是一组在编译阶段挂钩不同事件的监听器。webpack 在底层使用了一个叫做 tapable 的库来封装“发布-订阅”的实现。

tapable 提供了不同的“钩子”类(SyncBailHook、AsyncParallelHook 等)来“钩起”具有一些额外丰富功能(例如拦截或跨侦听器集成)的事件。umi 的插件机制也是基于 tapable 实现的。

如果你听不懂上面的表达,也没有关系,以下我用一些伪代码来让你明白什么是插件,插件是如何运行的。当然,这并不是真正的插件运行机制,我只是为了便于理解做了大量的简化。

js
class Umi {
  constructor(plugins) {
    this.plugins = plugins;
  }
  init() {
    console.log("umi core init");
    this.plugins.map((i) => {
      i?.init?.();
    });
  }
  generateFile() {
    console.log("umi core generateFile");
    this.plugins.map((i) => {
      i?.generateFile?.();
    });
  }
  build() {
    console.log("umi core build");
    this.plugins.map((i) => {
      i?.build?.();
    });
  }
  run() {
    this.init();
    this.generateFile();
    this.build();
  }
}
const plugin1 = {
  init: () => {
    console.log("[plugin1] init");
  },
  build: () => {
    console.log("[plugin1] build");
  },
};
const plugin2 = {
  generateFile: () => {
    console.log("[plugin2] generateFile");
  },
  build: () => {
    console.log("[plugin2] build");
  },
};
const plugins = [plugin1, plugin2];
const umiCore = new Umi(plugins);
umiCore.run();

run 里面的内容就是定义好的生命周期,当执行 run 的时候,我们会按顺序执行 initgenerateFilebuild,并且在内部生命周期函数中,使用 plugins.map 来触发每个插件中定义的钩子函数。

上面的代码 运行结果

js

umi core init
[plugin1] init
umi core generateFile
[plugin2] generateFile
umi core build
[plugin1] build
[plugin2] build

如果你理解了上面的代码,那加入一个自定义的 EventEmitter 来模拟 tapable 库的“发布-订阅”逻辑。 我们就得到了下面的代码。

js
class EventEmitter {
  constructor() {
    this.subscriptions = new Set();
  }
  emit = (val) => {
    for (const subscription of this.subscriptions) {
      subscription(val);
    }
  };

  useSubscription = (callback) => {
    function subscription(val) {
      if (callback) {
        callback(val);
      }
    }
    this.subscriptions.add(subscription);
  };
}

const initEmitter = new EventEmitter();
const buildEmitter = new EventEmitter();

class Umi {
  constructor(plugins) {
    plugins.map((i) => i());
  }
  init() {
    console.log("umi core init");
    initEmitter.emit();
  }
  build() {
    console.log("umi core build");
    buildEmitter.emit();
  }
  run() {
    this.init();
    this.build();
  }
}

const plugin1 = () => {
  initEmitter.useSubscription(() => {
    console.log("[plugin1] init");
  });
  buildEmitter.useSubscription(() => {
    console.log("[plugin1] build");
  });
};

const plugin2 = () => {
  buildEmitter.useSubscription(() => {
    console.log("[plugin2] build");
  });
};

const plugins = [plugin1, plugin2];
const umiCore = new Umi(plugins);
umiCore.run();

同样的复制上面的代码,在控制台中运行查看结果。

ts

umi core init
[plugin1] init
umi core build
[plugin1] build
[plugin2] build

在这次的生命周期函数中,使用 emit 来“发布”事件。在每个插件中使用 useSubscription 来“订阅”事件。当执行“发布”时,所有的“订阅”都将被响应。

以上为了说明仅仅展示了两个生命周期,在 umi 生态中,有一套更加完整丰富的生命周期,并且全部通过 umi 的 api 暴露给插件。

查看项目所有的插件

ts
npx umi plugin list