From e65ad50415774e8a2f032d4bdd33e307e6f9850e Mon Sep 17 00:00:00 2001 From: zoolsher Date: Fri, 20 Jun 2025 21:18:03 +0800 Subject: [PATCH 1/4] doc: add rsbuild plugin start doc --- website/docs/zh/guide/_meta.json | 5 + website/docs/zh/guide/plugin/_meta.json | 1 + website/docs/zh/guide/plugin/start.mdx | 221 ++++++++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 website/docs/zh/guide/plugin/_meta.json create mode 100644 website/docs/zh/guide/plugin/start.mdx diff --git a/website/docs/zh/guide/_meta.json b/website/docs/zh/guide/_meta.json index 28d0473032..e132bdc6c5 100644 --- a/website/docs/zh/guide/_meta.json +++ b/website/docs/zh/guide/_meta.json @@ -44,6 +44,11 @@ "name": "debug", "label": "调试" }, + { + "type": "dir", + "name": "plugin", + "label": "开发 Rsbuild 插件" + }, { "type": "dir", "name": "faq", diff --git a/website/docs/zh/guide/plugin/_meta.json b/website/docs/zh/guide/plugin/_meta.json new file mode 100644 index 0000000000..49cdeab83a --- /dev/null +++ b/website/docs/zh/guide/plugin/_meta.json @@ -0,0 +1 @@ +["start"] \ No newline at end of file diff --git a/website/docs/zh/guide/plugin/start.mdx b/website/docs/zh/guide/plugin/start.mdx new file mode 100644 index 0000000000..1c7439cd88 --- /dev/null +++ b/website/docs/zh/guide/plugin/start.mdx @@ -0,0 +1,221 @@ +# Rsbuild 插件开发指南 + +## 插件开发 + +启动一个基本的插件项目: + +```typescript +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)中,就可以引入这个插件了: + +```typescript +import { defineConfig } from '@rsbuild/core'; +import customPlugin from './plugin'; +export default defineConfig({ + plugins: [customPlugin()], +}); +``` + +## 为插件增加配置选项 PluginOptions + +如下,增加一个 PluginOptions 的 inteface,可以作为参数传给 plugin 函数。建议同时导出 PluginOptions 类型,方便插件使用者阅读类型信息 + +```typescript +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 来判断环境: + +```typescript +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 的基本配置 + +### 思路 + +如果需求可以通过配置 Rsbuild 配置来达到,都推荐使用 modifyRsbuildConfig 来实现,并且最终通过 mergeRsbuildConfig 来更新 Rsbuild 的配置。 + +1. 通过阅读 [RsbuildConfig](https://rsbuild.rs/config/) 了解哪个配置项目可以满足插件的需求 +2. 通过 `api.modifyRsbuildConfig` 方法修改 Rsbuild 的基本配置 + +### 例子 1: 修改输出目录 + +例如,我们需要开发一个插件,修改输出目录的位置: + +1. 在 Rsbuild 配置中了解可以修改输出目录的配置项目,找到为 output.distPath 可以[参考文档](https://rsbuild.rs/config/output/dist-path) +2. 编写插件,通过 `api.modifyRsbuildConfig` 方法修改输出目录的位置,代码示例如下: + +```typescript +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: 增加 devServer 能力 + +同样的方法可以开发一个插件来为 devServer 增加一些服务处理能力: + +1. 在 Rsbuild 配置中了解可以修改 devServer 的配置项目,找到为 devServer.setupMiddlewares 可以[参考文档](https://rsbuild.rs/config/dev/setup-middlewares) +2. 编写插件,通过 `api.modifyRsbuildConfig` 方法修改 devServer 的配置,代码示例如下: + +```typescript +import { RsbuildPlugin, RequestHandler } from '@rsbuild/core'; +interface DevServerOptions { + middleware: RequestHandler; +} +export default function plugin(options: DevServerOptions): RsbuildPlugin { + return { + name: 'plugin', + setup(api) { + if(options.middleware) { + api.modifyRsbuildConfig((userConfig, { mergeRsbuildConfig }) => { + const devServerConfig: RsbuildConfig = { + dev: { + setupMiddlewares: [ + (middlewares, devServer) => { + // 可以拿到 middlewares 数组,可以对数组进行操作,例如在尾部增加一个处理函数 + middlewares.push(options.middleware) + + // server 参数的使用可以[参考文档](https://rsbuild.rs/config/dev/setup-middlewares#server-api) + // server 内只有,[environments](https://rsbuild.rs/config/dev/setup-middlewares#environments) 和 [sockWrite](https://rsbuild.rs/config/dev/setup-middlewares#sockwrite) 两个属性 + + // 其中 environments 为:https://rsbuild.rs/api/javascript-api/environment-api#environment-api 可以基于 rsbuild 的 environment 来处理不同环境的请求 + } + ] + }, + }; + + return mergeRsbuildConfig(userConfig, devServerConfig); + }) + } + } + } +} +``` + +该 Plugin 的使用方法如下: + +```typescript +import MiddlewarePlugin from 'middleware-plugin'; + +export default { + plugins: [ + MiddlewarePlugin({ + middleware: (req, res, next) => { + // req 是 http IncomingMessage + // res 是 http ServerResponse + // 可以在这里处理请求和响应 + // 例如,针对获取 json 的内容返回一个 404 响应 + if(req.url === '/json'){ + res.writeHead(404); + res.end('Not Found'); + return; + } else { + next() + } + } + }) + ] +} + +``` + + + From 593455585b9b09c2eea521e690baa3234098326b Mon Sep 17 00:00:00 2001 From: zoolsher Date: Fri, 20 Jun 2025 22:36:05 +0800 Subject: [PATCH 2/4] feat: update doc --- website/docs/zh/guide/plugin/start.mdx | 104 +++++++++++++------------ 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/website/docs/zh/guide/plugin/start.mdx b/website/docs/zh/guide/plugin/start.mdx index 1c7439cd88..b5fc2d398f 100644 --- a/website/docs/zh/guide/plugin/start.mdx +++ b/website/docs/zh/guide/plugin/start.mdx @@ -104,8 +104,6 @@ export default function plugin(): RsbuildPlugin { ### 思路 -如果需求可以通过配置 Rsbuild 配置来达到,都推荐使用 modifyRsbuildConfig 来实现,并且最终通过 mergeRsbuildConfig 来更新 Rsbuild 的配置。 - 1. 通过阅读 [RsbuildConfig](https://rsbuild.rs/config/) 了解哪个配置项目可以满足插件的需求 2. 通过 `api.modifyRsbuildConfig` 方法修改 Rsbuild 的基本配置 @@ -147,75 +145,83 @@ export default function plugin(options: OutputOptions): RsbuildPlugin { }; } ``` +### 例子 2: 自定义页面入口 -### 例子 2: 增加 devServer 能力 - -同样的方法可以开发一个插件来为 devServer 增加一些服务处理能力: +例如,我们需要开发一个插件,自定义页面入口: -1. 在 Rsbuild 配置中了解可以修改 devServer 的配置项目,找到为 devServer.setupMiddlewares 可以[参考文档](https://rsbuild.rs/config/dev/setup-middlewares) -2. 编写插件,通过 `api.modifyRsbuildConfig` 方法修改 devServer 的配置,代码示例如下: +1. 在 Rsbuild 配置中了解可以修改页面入口的配置项目,找到为 source.entry 可以[参考文档](https://rsbuild.rs/config/source/entry) +2. 编写插件,通过 `api.modifyRsbuildConfig` 方法修改页面入口的位置,代码示例如下: ```typescript -import { RsbuildPlugin, RequestHandler } from '@rsbuild/core'; -interface DevServerOptions { - middleware: RequestHandler; +import { RsbuildPlugin } from '@rsbuild/core'; + +interface EntryOptions { + entry: string; } -export default function plugin(options: DevServerOptions): RsbuildPlugin { +export default function plugin(options: EntryOptions): RsbuildPlugin { return { - name: 'plugin', + name: 'rsbuild-source-plugin', setup(api) { - if(options.middleware) { + if(options.entry) { api.modifyRsbuildConfig((userConfig, { mergeRsbuildConfig }) => { - const devServerConfig: RsbuildConfig = { - dev: { - setupMiddlewares: [ - (middlewares, devServer) => { - // 可以拿到 middlewares 数组,可以对数组进行操作,例如在尾部增加一个处理函数 - middlewares.push(options.middleware) - - // server 参数的使用可以[参考文档](https://rsbuild.rs/config/dev/setup-middlewares#server-api) - // server 内只有,[environments](https://rsbuild.rs/config/dev/setup-middlewares#environments) 和 [sockWrite](https://rsbuild.rs/config/dev/setup-middlewares#sockwrite) 两个属性 - - // 其中 environments 为:https://rsbuild.rs/api/javascript-api/environment-api#environment-api 可以基于 rsbuild 的 environment 来处理不同环境的请求 - } - ] + let entry = "./src/index.ts"; // config you own entry, custom your logic here + const sourceConfig: RsbuildConfig = { + source:{ + entry: entry || options.entry, }, }; - return mergeRsbuildConfig(userConfig, devServerConfig); - }) + return mergeRsbuildConfig(userConfig, sourceConfig); + }); } } - } + }; +} +``` + + +## 自定义 DevServer 的行为 + +可以通过 [onBeforeStartDevServer](https://rsbuild.rs/zh/plugins/dev/hooks#onbeforestartdevserver) 来自定义 DevServer 的行为。 + +例如,我们需要自定义 DevServer 来处理一些请求,代码示例如下: + +```typescript + +import { RsbuildPlugin } from '@rsbuild/core'; +import { RequestHandler } from 'express'; + +type DevServerOptions = Record; + +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(); + } + }); + }); + }, + }; } ``` -该 Plugin 的使用方法如下: +该插件的使用例子: ```typescript -import MiddlewarePlugin from 'middleware-plugin'; +import staticContent from './plugin'; export default { plugins: [ - MiddlewarePlugin({ - middleware: (req, res, next) => { - // req 是 http IncomingMessage - // res 是 http ServerResponse - // 可以在这里处理请求和响应 - // 例如,针对获取 json 的内容返回一个 404 响应 - if(req.url === '/json'){ - res.writeHead(404); - res.end('Not Found'); - return; - } else { - next() - } - } + staticContent({ + '/json': '{"name": "rsbuild"}', }) ] } - ``` - - - From 64589e7014628ba20cb965ab5b8a60e09151d3d4 Mon Sep 17 00:00:00 2001 From: zoolsher Date: Fri, 20 Jun 2025 22:49:04 +0800 Subject: [PATCH 3/4] fix: use logger from @rsbuild/core --- website/docs/zh/guide/plugin/start.mdx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/docs/zh/guide/plugin/start.mdx b/website/docs/zh/guide/plugin/start.mdx index b5fc2d398f..93a820b61b 100644 --- a/website/docs/zh/guide/plugin/start.mdx +++ b/website/docs/zh/guide/plugin/start.mdx @@ -225,3 +225,13 @@ export default { ] } ``` + +## 插件的日志打印 + +默认情况下插件可以直接使用 `console.log` 和 `console.error` 等标准方法进行日志输出。如果你希望打印跟 Rsbuild 主题更接近的 log 可以使用 logger。 + +```typescript +import { logger } from "@rsbuild/core"; + +logger.log("hello world"); +``` \ No newline at end of file From da6132deb8ab7b2fdf0d6da9aa34b789992218c8 Mon Sep 17 00:00:00 2001 From: zoolsher Date: Mon, 23 Jun 2025 14:30:09 +0800 Subject: [PATCH 4/4] docs: add section for custom resolver --- website/docs/zh/guide/plugin/start.mdx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/website/docs/zh/guide/plugin/start.mdx b/website/docs/zh/guide/plugin/start.mdx index 93a820b61b..62d612a267 100644 --- a/website/docs/zh/guide/plugin/start.mdx +++ b/website/docs/zh/guide/plugin/start.mdx @@ -234,4 +234,19 @@ export default { import { logger } from "@rsbuild/core"; logger.log("hello world"); +``` + +## 自定义 resolver + +当插件有需求要自定义 resolver 的行为时,可以使用 @rspack/resolver 来创建新的 resolver,可以在[这里](https://github.com/web-infra-dev/rspack-resolver/blob/main/README.md)了解更多的信息 + +```typescript +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. ``` \ No newline at end of file