插件 API
上一节我们介绍了插件的基本结构,本节我们来介绍插件的 API,帮助你更细致地了解插件功能。
提示
为了获得更好的类型提示,你可以在项目中安装 @rspress/shared
,然后通过 import { RspressPlugin } from '@rspress/shared'
来引入 RspressPlugin
类型。
globalStyles
用于添加全局样式,传入一个样式文件的绝对路径,使用方式如下:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
import path from 'path';
export function pluginForDoc(): RspressPlugin {
// 样式路径
const stylePath = path.join(__dirname, 'some-style.css');
return {
// 插件名称
name: 'plugin-name',
// 全局样式的路径
globalStyles: path.join(__dirname, 'global.css'),
};
}
比如你想修改主题色,可以通过添加全局样式来实现:
global.css
:root {
--rp-c-brand: #ffa500;
--rp-c-brand-dark: #ffa500;
--rp-c-brand-darker: #c26c1d;
--rp-c-brand-light: #f2a65a;
--rp-c-brand-lighter: #f2a65a;
}
globalUIComponents
- 类型:
(string | [string, object])[]
用于添加全局组件,传入一个数组,数组中的每一项都是一个组件的绝对路径,使用方式如下:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
// 组件路径
const componentPath = path.join(__dirname, 'foo.tsx');
return {
// 插件名称
name: 'plugin-comp',
// 全局组件的路径
globalUIComponents: [componentPath],
};
}
globalUIComponents
的每一项可以是一个字符串,代表组件的文件路径;也可以是一个数组,第一项为组件的文件路径,第二项为组件的 props 对象,比如:
rspress.config.ts
import { defineConfig } from 'rspress/config';
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
// component path
const componentPath = path.join(__dirname, 'foo.tsx');
return {
// plugin name
name: 'plugin-comp',
globalUIComponents: [
[
path.join(__dirname, 'components', 'MyComponent.tsx'),
{
foo: 'bar',
},
],
],
};
}
当你注册了全局组件之后,框架会自动将这些 React 组件在主题中进行渲染,而不用你手动引入。
通过全局组件,你可以完成诸多自定义的功能,比如:
compUi.tsx
import React from 'react';
// 需要默认导出一个组件
// 通过 props 来拿到配置中传入的 props 数据
export default function PluginUI(props?: { foo: string }) {
return <div>This is a global layout component</div>;
}
这样,在主题页面中会渲染组件的内容,比如添加回到顶部按钮。
同时,你也可以通过全局组件来注册全局副作用。比如:
compSideEffect.tsx
import { useEffect } from 'react';
import { useLocation } from 'rspress/runtime';
// 需要默认导出一个组件
export default function PluginSideEffect() {
const { pathname } = useLocation();
useEffect(() => {
// 组件初次渲染时执行
}, []);
useEffect(() => {
// 路由变化时执行
}, [pathname]);
return null;
}
这样,在主题页面中会执行组件的副作用。比如以下的一些需要副作用的场景:
builderConfig
Rspress 底层基于 Rsbuild 来进行文档构建。通过 builderConfig
可以对 Rsbuild 进行配置,具体的配置项可以参考 Rsbuild - 配置。
当然,如果你想要直接配置 Rspack,也可以通过 builderConfig.tools.rspack
进行配置。
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(slug: string): RspressPlugin {
return {
// 插件名称
name: 'plugin-name',
// 构建阶段的全局变量定义
builderConfig: {
source: {
define: {
SLUG: JSON.stringify(slug),
},
},
tools: {
rspack(options) {
// 修改 rspack 的配置
},
},
},
};
}
config
- 类型:
(config: DocConfig, utils: ConfigUtils, isProd: boolean) => DocConfig | Promise<DocConfig>
其中,ConfigUtils
的类型定义如下:
interface ConfigUtils {
addPlugin: (plugin: RspressPlugin) => void;
removePlugin: (pluginName: string) => void;
}
用于修改 Rspress 本身的配置,比如你想要修改文档的标题,可以通过 config
来实现:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
return {
// 插件名称
name: 'plugin-name',
// 扩展 Rspress 本身的配置
config(config) {
return {
...config,
title: '新的文档标题',
};
},
};
}
如果涉及到添加和删除插件,需要通过 addPlugin
和 removePlugin
来实现:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
return {
// 插件名字
name: 'plugin-name',
// 扩展 Rspress 本身的配置
config(config, utils) {
// 添加插件
utils.addPlugin({
name: 'plugin-name',
// ...插件的其他配置
});
// 通过插件名称来删除插件
utils.removePlugin('plugin-name');
return config;
},
};
}
beforeBuild/afterBuild
- 类型:
(config: DocConfig, isProd: boolean) => void | Promise<void>
用于在文档构建之前/之后执行一些操作,第一个参数是文档的配置,第二个参数是当前是否是生产环境。使用方式如下:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
return {
// 插件名称
name: 'plugin-name',
// 在构建之前执行的钩子
async beforeBuild(config, isProd) {
// 这里可以执行一些操作
},
// 在构建之后执行的钩子
async afterBuild(config, isProd) {
// 这里可以执行一些操作
},
};
}
提醒
在 beforeBuild
钩子执行时,已经经过了所有插件的 config
插件处理,因此 config 参数已经代表了最终的文档配置。
markdown
- 类型:
{ remarkPlugins?: Plugin[]; rehypePlugins?: Plugin[]; globalComponents?: string[] }
用于扩展 Markdown/MDX 的编译能力,如果你想要添加自定义的 remark/rehype 插件以及 MDX 里的全局组件,可以通过 markdown
配置来实现:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
return {
// 插件名称
name: 'plugin-name',
// 扩展 Markdown/MDX 编译能力
markdown: {
// 启用 JS 版本的编译器以支持外部插件
mdxRs: false,
remarkPlugins: [
// 添加自定义的 remark 插件
],
rehypePlugins: [
// 添加自定义的 rehype 插件
],
globalComponents: [
// 为 MDX 注册全局组件
],
},
};
}
extendPageData
- 类型:
(pageData: PageData) => void | Promise<void>
用于扩展页面数据,比如你想要在页面数据中添加一些自定义的属性,可以通过 extendPageData
来实现:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
return {
// 插件名称
name: 'plugin-name',
// 扩展页面数据
extendPageData(pageData, isProd) {
// 你可以往 pageData 对象上添加或者修改属性
pageData.a = 1;
},
};
}
在扩展页面数据之后,你可以在主题中通过 usePageData
这个 hook 来访问页面数据。
import { usePageData } from 'rspress';
export function MyComponent() {
const { page } = usePageData();
// page.a === 1
return <div>{page.a}</div>;
}
addPages
- 类型:
(config: UserConfig) => AdditionalPage[] | Promise<AdditionalPage[]>
其中,config
为 rspress.config.ts
配置文件中导出的 doc
属性值,AdditionalPage
的类型定义如下:
interface AdditionalPage {
routePath: string;
filepath?: string;
content?: string;
}
主要用来添加额外的页面,你可以在 addPages
函数中返回一个数组,数组中的每一项都是一个页面的配置,你可以通过 routePath
来指定页面的路由,通过 filepath
或者 content
来指定页面的内容。比如:
import path from 'path';
import { RspressPlugin } from '@rspress/shared';
export function docPluginDemo(): RspressPlugin {
return {
name: 'add-pages',
addPages(config, isProd) {
return [
// 支持真实文件的绝对路径(filepath),这样会读取磁盘中的 md(x) 内容
{
routePath: '/filepath-route',
filepath: path.join(__dirname, 'blog', 'index.md'),
},
// 支持通过 content 参数直接传入 md(x) 内容
{
routePath: '/content-route',
content: '# Demo2',
},
];
},
};
}
addPages
接受两个参数,config
为当前文档站的配置,isProd
表示是否为生产环境。
routeGenerated
- 类型:
(routeMeta: RouteMeta[]) => void | Promise<void>
这这个钩子中,你可以拿到所有的路由信息,每一项路由信息的结构如下:
export interface RouteMeta {
// 路由
routePath: string;
// 文件绝对路径
absolutePath: string;
// 页面名称,作为打包产物文件名的一部分
pageName: string;
// 语言
lang: string;
}
例子:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
return {
// 插件名称
name: 'plugin-routes',
// 在构建之后执行的钩子
async routeGenerated(routes, isProd) {
// 这里可以拿到 routes 数组,执行一些操作
},
};
}
addRuntimeModules
- 类型:
(config: UserConfig, isProd: boolean) => Record<string, string> | Promise<Record<string, string>>;
用于添加额外的运行时模块,比如你想要在文档中使用到某些编译时的信息,可以通过 addRuntimeModules
来实现:
plugin.ts
import { RspressPlugin } from '@rspress/shared';
export function pluginForDoc(): RspressPlugin {
return {
// 插件名称
name: 'plugin-name',
// 添加额外的运行时模块
async addRuntimeModules(config, isProd) {
const fetchSomeData = async () => {
// 模拟异步请求
return { a: 1 };
};
const data = await fetchSomeData();
return {
'virtual-foo': `export default ${JSON.stringify(data)}`,
};
},
};
}
这样你就可以在运行时组件中使用 virtual-foo
模块了:
import myData from 'virtual-foo';
export function MyComponent() {
return <div>{myData.a}</div>;
}
提醒
该 hook 在 routeGenerated
之后执行。