close
  • 中文
  • 插件 API

    上一节我们介绍了插件的基本结构,本节我们来介绍插件的 API,帮助你更细致地了解插件功能。

    globalStyles

    • 类型string

    用于添加全局样式,传入一个样式文件的绝对路径,使用方式如下:

    plugin.ts
    import type { RspressPlugin } from '@rspress/core';
    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 type { RspressPlugin } from '@rspress/core';
    
    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/core';
    import type { RspressPlugin } from '@rspress/core';
    
    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',
            },
          ],
        ],
      };
    }

    当你注册了全局组件之后,Rspress 会自动将这些 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/core/runtime';
    
    // 需要默认导出一个组件
    export default function PluginSideEffect() {
      const { pathname } = useLocation();
      useEffect(() => {
        // 组件初次渲染时执行
      }, []);
    
      useEffect(() => {
        // 路由变化时执行
      }, [pathname]);
      return null;
    }

    这样,在主题页面中会执行组件的副作用。比如以下的一些需要副作用的场景:

    • 针对某些页面路由进行重定向操作。
    • 对页面的 img 标签进行事件监听,实现图片放大功能。
    • 路由变化时,上报不同页面的 PV 数据。
    • ......

    builderConfig

    • 类型RsbuildConfig

    Rspress 使用 Rsbuild 作为构建工具。通过 builderConfig 可以对 Rsbuild 进行配置,具体的配置项可以参考 Rsbuild - 配置

    当然,如果你想要直接配置 Rspack,也可以通过 builderConfig.tools.rspack 进行配置。

    plugin.ts
    import type { RspressPlugin } from '@rspress/core';
    
    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 type { RspressPlugin } from '@rspress/core';
    
    export function pluginForDoc(): RspressPlugin {
      return {
        // 插件名称
        name: 'plugin-name',
        // 扩展 Rspress 本身的配置
        config(config) {
          return {
            ...config,
            title: '新的文档标题',
          };
        },
      };
    }

    如果涉及到添加和删除插件,需要通过 addPluginremovePlugin 来实现:

    plugin.ts
    import type { RspressPlugin } from '@rspress/core';
    
    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 type { RspressPlugin } from '@rspress/core';
    
    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 type { RspressPlugin } from '@rspress/core';
    
    export function pluginForDoc(): RspressPlugin {
      return {
        // 插件名称
        name: 'plugin-name',
        // 扩展 Markdown/MDX 编译能力
        markdown: {
          remarkPlugins: [
            // 添加自定义的 remark 插件
          ],
          rehypePlugins: [
            // 添加自定义的 rehype 插件
          ],
          globalComponents: [
            // 为 MDX 注册全局组件
          ],
        },
      };
    }

    extendPageData

    • 类型(pageData: PageData) => void | Promise<void>

    用于扩展页面数据,比如你想要在页面数据中添加一些自定义的属性,可以通过 extendPageData 来实现:

    plugin.ts
    import type { RspressPlugin } from '@rspress/core';
    
    export function pluginForDoc(): RspressPlugin {
      return {
        // 插件名称
        name: 'plugin-name',
        // 扩展页面数据
        extendPageData(pageData, isProd) {
          // 你可以往 pageData 对象上添加或者修改属性
          pageData.a = 1;
        },
      };
    }

    在扩展页面数据之后,你可以在主题中通过 usePageData 这个 hook 来访问页面数据。

    import { usePageData } from '@rspress/core/runtime';
    
    export function MyComponent() {
      const { page } = usePageData();
      // page.a === 1
      return <div>{page.a}</div>;
    }

    addPages

    • 类型(config: UserConfig) => AdditionalPage[] | Promise<AdditionalPage[]>

    其中,configrspress.config.ts 配置文件中导出的 doc 属性值,AdditionalPage 的类型定义如下:

    interface AdditionalPage {
      routePath: string;
      filepath?: string;
      content?: string;
    }

    主要用来添加额外的页面,你可以在 addPages 函数中返回一个数组,数组中的每一项都是一个页面的配置,你可以通过 routePath 来指定页面的路由,通过 filepath 或者 content 来指定页面的内容。比如:

    import path from 'path';
    import type { RspressPlugin } from '@rspress/core';
    
    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 type { RspressPlugin } from '@rspress/core';
    
    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 type { RspressPlugin } from '@rspress/core';
    
    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 之后执行。

    i18nSource

    • 类型(source: Record<string, Record<string, string>>) => Record<string, Record<string, string>> | Promise<Record<string, Record<string, string>>>

    用于添加或修改国际化文案数据。你可以通过这个 hook 来扩展或修改主题的国际化文案。

    参数 source 是一个对象,其结构为:

    {
      [textKey: string]: {
        [locale: string]: string;
      }
    }

    其中第一层的 textKey 是文案的键名,第二层的 locale 是语言代码(如 zhen),值是对应语言的翻译文本。

    使用示例:

    plugin.ts
    import type { RspressPlugin } from '@rspress/core';
    
    export function pluginForDoc(): RspressPlugin {
      return {
        // 插件名称
        name: 'plugin-name',
        // 添加或修改国际化文案
        i18nSource(source) {
          // 添加新的文案
          return {
            ...source,
            customKey: {
              zh: '自定义文案',
              en: 'Custom Text',
            },
            anotherKey: {
              zh: '另一个文案',
              en: 'Another Text',
            },
          };
        },
      };
    }

    如果你的插件同时提供了运行时组件,你可以通过 useI18n hook 来使用这些文案:

    import { useI18n } from '@rspress/core/runtime';
    
    export function MyComponent() {
      const t = useI18n();
      return <div>{t('customKey')}</div>;
    }