Plugin Core

本章节描述了 Builder 插件核心的类型定义和 API。

BuilderPlugin

插件对象的类型,插件对象包含以下属性:

  • name:插件的名称,唯一标识符。
  • setup:插件逻辑的主入口函数,可以是一个异步函数。该函数仅会在初始化插件时执行一次。
  • pre:指定当前插件的前置插件有哪些,可以传入前置插件 name 的数组,当前插件会被调整到这些插件之后执行。
  • post:指定当前插件的后置插件有哪些,可以传入后置插件 name 的数组,当前插件会被调整到这些插件之前执行。
  • remove:指定需要移除的插件,可以传入插件 name 的数组。
type BuilderPlugin<API = BuilderPluginAPI> = {
  name: string;
  pre?: string[];
  post?: string[];
  remove?: string[];
  setup: (api: API) => Promise<void> | void;
};

你可以从 @modern-js/builder 中导入该类型:

import type { BuilderPlugin } from '@modern-js/builder';

export default (): BuilderPlugin => ({
  name: 'builder-plugin-foo',

  pre: ['builder-plugin-bar'],

  setup: api => {
    api.onAfterBuild(() => {
      console.log('after build!');
    });
  },
});

前置插件

默认情况下,插件会按照添加顺序依次执行,通过 pre 字段可以声明前置执行的插件。

比如有下面两个插件:

const builderPluginFoo = {
  name: 'builder-plugin-foo',
};

const builderPluginBar = {
  name: 'builder-plugin-bar',
  pre: ['builder-plugin-foo'],
};

Bar 插件在 pre 字段中配置了 Foo 插件,因此 Foo 插件一定会在 Bar 插件之前执行。

后置插件

同样的,通过 post 字段可以声明后置执行的插件。

const builderPluginFoo = {
  name: 'builder-plugin-foo',
};

const builderPluginBar = {
  name: 'builder-plugin-bar',
  post: ['builder-plugin-foo'],
};

Bar 插件在 post 字段中配置了 Foo 插件,因此 Foo 插件一定会在 Bar 插件之后执行。

移除插件

通过 remove 字段可以在一个插件中移除其他插件。

const builderPluginFoo = {
  name: 'builder-plugin-foo',
};

const builderPluginBar = {
  name: 'builder-plugin-bar',
  remove: ['builder-plugin-foo'],
};

比如同时注册上述的 Foo 和 Bar 插件,由于 Bar 插件声明 remove 了 Foo 插件,因此 Foo 插件不会生效。

BuilderPluginAPI

插件 API 对象的类型。插件 API 对象上挂载了提供给插件使用的上下文数据、工具函数和注册生命周期钩子的函数。

关于生命周期钩子的完整介绍,请阅读 Plugin Hooks 章节。

在使用 webpack-provider 或 rspack-provider 时,BuilderPluginAPI 的类型定义有一定不同,你可以根据插件的使用场景来引入对应的类型。

适用于 webpack-provider 的插件

开发适用于 webpack-provider 的插件时,请从 @modern-js/builder-webpack-provider 中引入 BuilderPluginAPI

import type { BuilderPlugin } from '@modern-js/builder';
import type { BuilderPluginAPI } from '@modern-js/builder-webpack-provider';

export default (): BuilderPlugin<BuilderPluginAPI> => ({
  name: 'builder-plugin-foo',

  setup: api => {
    api.modifyWebpackConfig(config => {
      config.devtool = false;
    });
  },
});

适用于 rspack-provider 的插件

开发适用于 rspack-provider 的插件时,请从 @modern-js/builder-rspack-provider 中引入 BuilderPluginAPI

import type { BuilderPlugin } from '@modern-js/builder';
import type { BuilderPluginAPI } from '@modern-js/builder-rspack-provider';

export default (): BuilderPlugin<BuilderPluginAPI> => ({
  name: 'builder-plugin-foo',

  setup: api => {
    api.modifyRspackConfig(config => {
      config.devtool = false;
    });
  },
});

同时适用于 webpack-provider 和 rspack-provider 的插件

想要开发同时适用于 webpack-provider 和 rspack-provider 的插件时,请从 @modern-js/builder-shared 中引入 DefaultBuilderPlugin

需要注意的是,开发 webpack 和 Rspack 同时支持的插件,意味着不能使用任何带有 webpack 或 Rspack 标识的 api,如 modifyWebpackConfigmodifyRspackConfig

可通过 modifyBundlerChain 修改同时适用于 webpack 和 Rspack 的配置。

import type { BuilderPlugin } from '@modern-js/builder';
import type { DefaultBuilderPlugin } from '@modern-js/builder-shared';

export default (): BuilderPlugin<DefaultBuilderPlugin> => ({
  name: 'builder-plugin-foo',

  setup: api => {
    api.modifyBundlerChain(chain => {
      chain.devtool(false);
    });
  },
});

api.context

api.context 是一个只读对象,提供一些上下文信息。

api.context 的内容与 builder.context 完全一致,请参考 builder.context

  • Example
const builderPluginFoo = () => ({
  setup(api) {
    console.log(api.context.distPath);
  },
});

api.getBuilderConfig

获取 Builder 配置,该方法必须在 modifyBuilderConfig 钩子执行完成后才能被调用。

  • 类型
function GetBuilderConfig(): Readonly<BuilderConfig>;
  • Example
const builderPluginFoo = () => ({
  setup(api) {
    const config = api.getBuilderConfig();
    console.log(config.html?.title);
  },
});

api.getNormalizedConfig

获取归一化后的 Builder 配置,该方法必须在 modifyBuilderConfig 钩子执行完成后才能被调用。

相较于 getBuilderConfig 方法,该方法返回的配置经过了归一化处理,配置的类型定义会得到收敛,比如 config.htmlundefined 类型将被移除。

推荐优先使用该方法获取配置。

  • 类型
function GetNormalizedConfig(): Readonly<NormalizedConfig>;
  • Example
const builderPluginFoo = () => ({
  setup(api) {
    const config = api.getNormalizedConfig();
    console.log(config.html.title);
  },
});

api.isPluginExists

判断某个插件是否已经被注册。

  • 类型
function IsPluginExists(pluginName: string): boolean;
  • Example
export default () => ({
  setup: api => {
    console.log(api.isPluginExists('builder-plugin-foo'));
  },
});

api.getHTMLPaths

获取所有 HTML 产物的路径信息。

该方法会返回一个对象,对象的 key 为 entry 名称,value 为 HTML 文件在产物目录下的相对路径。

  • 类型
function GetHTMLPaths(): Record<string, string>;
  • Example
const builderPluginFoo = () => ({
  setup(api) {
    api.modifyWebpackChain(() => {
      const htmlPaths = api.getHTMLPaths();
      console.log(htmlPaths); // { main: 'html/main/index.html' };
    });
  },
});