Rsbuild 插件开发指南

插件开发

启动一个基本的插件项目:

import { RsbuildPlugin } from '@rsbuild/core';
export default function plugin(): RsbuildPlugin {
  return {
    name: 'plugin',
    setup(api) {
      // api 对象上的方法即是 plugin hooks,存在的方法可以再这里进行查找
      // https://rsbuild.rs/plugins/dev/hooks
    }
  };
}

这时候在 rsbuild 配置(rsbuild.config.ts)中,就可以引入这个插件了:

import { defineConfig } from '@rsbuild/core';
import customPlugin from './plugin';
export default defineConfig({
  plugins: [customPlugin()],
});

为插件增加配置选项 PluginOptions

如下,增加一个 PluginOptions 的 inteface,可以作为参数传给 plugin 函数。建议同时导出 PluginOptions 类型,方便插件使用者阅读类型信息

import { RsbuildPlugin } from '@rsbuild/core';
export interface PluginOptions {
  foo: string;
}
export default function plugin(options: PluginOptions): RsbuildPlugin {
  return {
    name: 'plugin',
    setup(api) {
      // 这里可以通过 options 拿到用户传入的配置
      console.log(options.foo);
    }
  }
}

了解插件的 API

Rsbuild api 一共提供了以下属性,这些 hook 会按照顺序执行。

  • modifyRsbuildConfig
  • modifyEnvironmentConfig
  • onBeforeStartDevServer
  • modifyBundlerChain
  • modifyRspackConfig
  • onBeforeCreateCompiler
  • onAfterCreateCompiler
  • onBeforeEnvironmentCompile
  • onAfterStartDevServer
  • modifyHTMLTags
  • modifyHTML
  • onAfterEnvironmentCompile
  • onDevCompileDone
  • onCloseDevServer
  • onExit

一些注意事项:

  1. 真实的 Rsbuild 实例会在 modifyRsbuildConfig modifyEnvironmentConfig onBeforeStartDevServer 之后创建,所以在这三个 API 内并没有明确的 mode 是 development 还是 production。在这三个 API 内可以通过 process.node.NODE_ENV 来判断是否为 development,在此之后的 API 中可以通过 hooks 参数中的 isDev isProd 来判断
  2. Rsbuild 依赖的 Rspack 实例是在 modifyRsbuildConfig modifyEnvironmentConfig onBeforeStartDevServer modifyRspackConfig modifyBundlerChain onBeforeCreateCompiler 这些 API 之后创建的。所以在此之后的 API 才可以有 rspack 的实例

判断环境是 development 还是 production

可以通过 api.modifyRsbuildConfig 方法拿到 Rsbuild 的配置,然后根据配置的 mode 来判断环境:

import { RsbuildPlugin } from '@rsbuild/core';
export default function plugin(): RsbuildPlugin {
  return {
    name: 'plugin',
    setup(api) {
      // 在 modifyRsbuildConfig 中还没有最终的 mode
      api.modifyRsbuildConfig((config) => {
        const isDev = process.env.NODE_ENV === 'development';
        const isProd = process.env.NODE_ENV === 'production';
      });
      // 在 modifyRspackConfig 中可以通过 isDev 和 isProd 来判断环境
      api.modifyRspackConfig((config, { isDev, isProd }) => {
        if (isDev) {
          console.log('development');
        } else if (isProd) {
          console.log('production');
        }
      });
    }
  }
}

修改 Rsbuild 的基本配置

思路

  1. 通过阅读 RsbuildConfig 了解哪个配置项目可以满足插件的需求
  2. 通过 api.modifyRsbuildConfig 方法修改 Rsbuild 的基本配置

例子 1: 修改输出目录

例如,我们需要开发一个插件,修改输出目录的位置:

  1. 在 Rsbuild 配置中了解可以修改输出目录的配置项目,找到为 output.distPath 可以参考文档
  2. 编写插件,通过 api.modifyRsbuildConfig 方法修改输出目录的位置,代码示例如下:
import { RsbuildPlugin } from '@rsbuild/core';
interface OutputOptions {
  path: string;
}
export default function plugin(options: OutputOptions): RsbuildPlugin {
  return {
    name: 'plugin',
    setup(api) {
      if(options.path) {
        // userConfig 即为 Rsbuild 的配置
        // 可以在这里查看配置的全部内容 https://rsbuild.rs/config/
        api.modifyRsbuildConfig((userConfig, { mergeRsbuildConfig }) => {
          const outputConfig: RsbuildConfig = {
            output: {
              distPath: {
                root: options.path,
              }
            },
          };

          // outputConfig 会覆盖用户的配置
          // 如果不希望覆盖用户的配置,可以使用 `mergeRsbuildConfig` 方法反过来传递
          // 例如: mergeRsbuildConfig(outputConfig, userConfig)
          return mergeRsbuildConfig(userConfig, outputConfig);
        });
      }
    }
  };
}

例子 2: 自定义页面入口

例如,我们需要开发一个插件,自定义页面入口:

  1. 在 Rsbuild 配置中了解可以修改页面入口的配置项目,找到为 source.entry 可以参考文档
  2. 编写插件,通过 api.modifyRsbuildConfig 方法修改页面入口的位置,代码示例如下:
import { RsbuildPlugin } from '@rsbuild/core';

interface EntryOptions {
  entry: string;
}
export default function plugin(options: EntryOptions): RsbuildPlugin {
  return {
    name: 'rsbuild-source-plugin',
    setup(api) {
      if(options.entry) {
        api.modifyRsbuildConfig((userConfig, { mergeRsbuildConfig }) => {
          let entry = "./src/index.ts"; // config you own entry, custom your logic here
          const sourceConfig: RsbuildConfig = {
            source:{
              entry: entry || options.entry,
            },
          };

          return mergeRsbuildConfig(userConfig, sourceConfig);
        });
      }
    }
  };
}

自定义 DevServer 的行为

可以通过 onBeforeStartDevServer 来自定义 DevServer 的行为。

例如,我们需要自定义 DevServer 来处理一些请求,代码示例如下:


import { RsbuildPlugin } from '@rsbuild/core';
import { RequestHandler } from 'express';

type DevServerOptions = Record<string, string>;

export default function plugin(options: DevServerOptions): RsbuildPlugin {
  return {
    name: 'plugin',
    setup(api) {
      api.onBeforeStartDevServer(({ server }) => {
        server.middlewares.use((req, res, next) => {
          const urls = Object.keys(options);
          if(urls.includes(req.url)) {
            res.end(options[req.url]);
          }else{
            next();
          }
        });
      });
    },
  };
}

该插件的使用例子:

import staticContent from './plugin';

export default {
  plugins: [
    staticContent({
      '/json': '{"name": "rsbuild"}',
    })
  ]
}

插件的日志打印

默认情况下插件可以直接使用 console.logconsole.error 等标准方法进行日志输出。如果你希望打印跟 Rsbuild 主题更接近的 log 可以使用 logger。

import { logger } from "@rsbuild/core";

logger.log("hello world");

自定义 resolver

当插件有需求要自定义 resolver 的行为时,可以使用 @rspack/resolver 来创建新的 resolver,可以在这里了解更多的信息

import { ResolverFactory } from "@rspack/resolver";

const resolver = new ResolverFactory({
  alias: [],
  mainFields: ["main", "module"],
});

const indexJsPath = resolver.async(__dirname, "./index.js").path; // resolve specifier at an absolute path to a directory.