Plugin Core

This section describes the core plugin types and APIs.

BuilderPlugin

The type of the plugin object. The plugin object contains the following properties:

  • name: The name of the plugin, a unique identifier.
  • setup: The setup function of the plugin, which can be an asynchronous function. This function will only be executed once when the plugin is initialized.
  • pre: Specifies the preceding plugins for the current plugin. You can pass an array of preceding plugin names, and the current plugin will be executed after these plugins.
  • post: Specifies the succeeding plugins for the current plugin. You can pass an array of succeeding plugin names, and the current plugin will be executed before these plugins.
  • remove: Specifies the plugins to be removed. You can pass an array of plugin names to be removed.

The type of the plugin object, which contains the following properties:

export type BuilderPlugin<API = BuilderPluginAPI> = {
  name: string;
  pre?: string[];
  post?: string[];
  remove?: string[];
  setup: (api: API) => Promise<void> | void;
};

You can import this type from @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-Plugins

By default, plugins are executed in the order they are added. You can declare pre-execution plugins using the pre field.

For example, consider the following two plugins:

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

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

The Bar plugin is configured with the Foo plugin in its pre field, so the Foo plugin will always be executed before the Bar plugin.

Post-Plugins

Similarly, you can declare post-execution plugins using the post field.

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

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

The Bar plugin is configured with the Foo plugin in its post field, so the Foo plugin will always be executed after the Bar plugin.

Removing Plugins

You can remove other plugins within a plugin using the remove field.

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

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

For example, if you register both the Foo and Bar plugins mentioned above, the Foo plugin will not take effect because the Bar plugin declares the removal of the Foo plugin.

BuilderPluginAPI

The type of plugin API. The plugin API provides the context info, utility functions and lifecycle hooks.

For a complete introduction to lifecycle hooks, please read the Plugin Hooks chapter.

When using webpack-provider or rspack-provider, the type definition of BuilderPluginAPI is somewhat different, and you can introduce the corresponding type according to the usage scenario of the plugin.

Plugin for webpack-provider

When developing plugins for webpack-provider, please import BuilderPluginAPI from @modern-js/builder-webpack-provider.

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;
    });
  },
});

Plugins for rspack-provider

When developing plugins for rspack-provider, please import BuilderPluginAPI from @modern-js/builder-rspack-provider.

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;
    });
  },
});

Plugins for both webpack-provider and rspack-provider

When developing plugins for both webpack-provider and rspack-provider, please import DefaultBuilderPlugin from @modern-js/builder-shared.

Note that developing plugins that can support both webpack and Rspack at the same time means that you cannot use any APIs with webpack / Rspack identifiers, such as modifyWebpackConfig or modifyRspackConfig.

You can use modifyBundlerChain to modify the configuration that applies to both webpack and 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 is a read-only object that provides some context information.

The content of api.context is exactly the same as builder.context, please refer to builder.context.

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

api.getBuilderConfig

Get the Builder config, this method must be called after the modifyBuilderConfig hook is executed.

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

api.getNormalizedConfig

Get the normalized Builder config, this method must be called after the modifyBuilderConfig hook is executed.

Compared with the api.getBuilderConfig method, the config returned by this method has been normalized, and the type definition of the config will be narrowed. For example, the undefined type of config.html will be removed.

It is recommended to use this method to get the Builder config.

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

api.isPluginExists

Determines whether a plugin has been registered.

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

api.getHTMLPaths

Get path information for all HTML assets.

This method will return an object, the key is the entry name and the value is the relative path of the HTML file in the dist directory.

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