Skip to content

构建能力扩展

Rspress 提供强大的构建扩展系统,允许您自定义和增强文档站点的构建过程。本章探讨如何使用插件、自定义构建配置和高级功能扩展 Rspress。

扩展系统概述

什么是构建扩展?

构建扩展允许您:

  • 添加功能: 通过插件扩展核心功能
  • 自定义构建: 修改构建过程
  • 集成工具: 连接外部工具和服务
  • 优化输出: 增强构建输出

扩展类型

插件系统:

  • 官方插件
  • 社区插件
  • 自定义插件

构建配置:

  • Vite 配置
  • Rsbuild 配置
  • 自定义转换

插件系统

使用插件

在配置中添加插件:

typescript
// rspress.config.ts
import { defineConfig } from 'rspress/config';
import pluginSitemap from '@rspress/plugin-sitemap';
import pluginRss from '@rspress/plugin-rss';

export default defineConfig({
  plugins: [
    pluginSitemap({
      domain: 'https://example.com',
    }),
    pluginRss({
      siteUrl: 'https://example.com',
      feed: {
        id: 'https://example.com',
        title: 'My Documentation',
        copyright: '2024 My Company',
      },
    }),
  ],
});

官方插件

Sitemap 插件

生成 XML 站点地图:

typescript
import pluginSitemap from '@rspress/plugin-sitemap';

export default defineConfig({
  plugins: [
    pluginSitemap({
      domain: 'https://example.com',
      // 排除特定路径
      exclude: ['/admin', '/internal'],
      // 自定义优先级
      priority: (url) => {
        if (url === '/') return 1.0;
        if (url.startsWith('/guide')) return 0.9;
        return 0.7;
      },
      // 更改频率
      changefreq: 'weekly',
    }),
  ],
});

RSS 插件

生成 RSS 源:

typescript
import pluginRss from '@rspress/plugin-rss';

export default defineConfig({
  plugins: [
    pluginRss({
      siteUrl: 'https://example.com',
      feed: {
        id: 'https://example.com',
        title: 'My Documentation',
        description: 'Latest documentation updates',
        link: 'https://example.com',
        language: 'zh-CN',
        copyright: '2024 My Company',
        author: {
          name: 'Your Name',
          email: 'your@email.com',
          link: 'https://example.com',
        },
      },
      // 包含的路径
      include: ['/blog', '/updates'],
      // 项目数量限制
      limit: 20,
    }),
  ],
});

预览插件

添加代码预览功能:

typescript
import pluginPreview from '@rspress/plugin-preview';

export default defineConfig({
  plugins: [
    pluginPreview({
      // 支持的语言
      languages: ['jsx', 'tsx', 'vue'],
      // 预览组件
      components: {
        Button: () => import('./components/Button'),
        Card: () => import('./components/Card'),
      },
    }),
  ],
});

搜索插件

增强搜索功能:

typescript
import pluginSearch from '@rspress/plugin-search';

export default defineConfig({
  plugins: [
    pluginSearch({
      // 索引模式
      mode: 'local', // 或 'remote'
      // 搜索字段
      searchFields: ['title', 'content', 'headers'],
      // 最大结果数
      maxResults: 10,
      // 自定义评分
      customScore: (result) => {
        if (result.type === 'title') return 100;
        if (result.type === 'header') return 50;
        return 10;
      },
    }),
  ],
});

创建自定义插件

插件结构

创建自定义插件:

typescript
// plugins/my-plugin.ts
import type { RspressPlugin } from '@rspress/core';

export function myPlugin(options = {}): RspressPlugin {
  return {
    name: 'my-plugin',

    // 配置钩子
    config(config) {
      // 修改配置
      return {
        ...config,
        // 您的修改
      };
    },

    // 构建开始
    buildStart() {
      console.log('构建开始...');
    },

    // 页面数据加载
    async loadPage(filePath) {
      // 处理页面数据
    },

    // 转换内容
    async transform(code, id) {
      // 转换代码
      return code;
    },

    // 构建结束
    buildEnd() {
      console.log('构建完成!');
    },
  };
}

实际示例: 阅读时间插件

创建估算阅读时间的插件:

typescript
// plugins/reading-time.ts
import type { RspressPlugin } from '@rspress/core';

interface ReadingTimeOptions {
  wordsPerMinute?: number;
  includeCodeBlocks?: boolean;
}

export function pluginReadingTime(
  options: ReadingTimeOptions = {}
): RspressPlugin {
  const { wordsPerMinute = 200, includeCodeBlocks = true } = options;

  return {
    name: 'rspress-plugin-reading-time',

    async extendPageData(pageData) {
      const { content } = pageData;

      // 移除代码块(如果需要)
      let textContent = content;
      if (!includeCodeBlocks) {
        textContent = content.replace(/```[\s\S]*?```/g, '');
      }

      // 计算字数
      const words = textContent
        .replace(/\s+/g, ' ')
        .split(' ')
        .length;

      // 计算阅读时间
      const minutes = Math.ceil(words / wordsPerMinute);

      // 添加到页面数据
      return {
        ...pageData,
        readingTime: {
          minutes,
          words,
          text: `${minutes} 分钟阅读`,
        },
      };
    },
  };
}

使用插件:

typescript
// rspress.config.ts
import { pluginReadingTime } from './plugins/reading-time';

export default defineConfig({
  plugins: [
    pluginReadingTime({
      wordsPerMinute: 200,
      includeCodeBlocks: false,
    }),
  ],
});

在页面中显示:

tsx
// components/ReadingTime.tsx
import { usePageData } from 'rspress/runtime';

export function ReadingTime() {
  const pageData = usePageData();
  const { readingTime } = pageData;

  if (!readingTime) return null;

  return (
    <div className="reading-time">
      📖 {readingTime.text}
    </div>
  );
}

实际示例: 目录生成插件

生成文档目录:

typescript
// plugins/toc-generator.ts
import type { RspressPlugin } from '@rspress/core';
import fs from 'fs-extra';
import path from 'path';

export function pluginTocGenerator(): RspressPlugin {
  return {
    name: 'rspress-plugin-toc-generator',

    async buildEnd(config) {
      const pages = await this.getPages();

      // 构建目录结构
      const toc = pages.map(page => ({
        title: page.title,
        path: page.routePath,
        headers: page.toc,
      }));

      // 写入 JSON 文件
      const outputPath = path.join(config.outDir, 'toc.json');
      await fs.writeJSON(outputPath, toc, { spaces: 2 });

      console.log('✅ 目录已生成');
    },
  };
}

实际示例: Markdown 增强插件

添加自定义 Markdown 语法:

typescript
// plugins/markdown-enhance.ts
import type { RspressPlugin } from '@rspress/core';

export function pluginMarkdownEnhance(): RspressPlugin {
  return {
    name: 'rspress-plugin-markdown-enhance',

    // 转换 Markdown
    async markdown(content, filePath) {
      let enhanced = content;

      // 添加自定义容器
      enhanced = enhanced.replace(
        /::: note\s+([\s\S]*?):::/g,
        '<div class="note">$1</div>'
      );

      // 添加高亮标记
      enhanced = enhanced.replace(
        /==([^=]+)==/g,
        '<mark>$1</mark>'
      );

      // 添加键盘快捷键
      enhanced = enhanced.replace(
        /\[\[([^\]]+)\]\]/g,
        '<kbd>$1</kbd>'
      );

      return enhanced;
    },
  };
}

使用:

markdown
::: note
这是一个注释!
:::

这是 ==高亮文本==

按 [[Ctrl]] + [[S]] 保存

构建配置

Vite 配置

自定义 Vite 构建:

typescript
import { defineConfig } from 'rspress/config';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  builderConfig: {
    // Vite 配置
    vite: {
      plugins: [vue()],

      // 构建优化
      build: {
        // 代码分割
        rollupOptions: {
          output: {
            manualChunks: {
              'vendor': ['react', 'react-dom'],
              'utils': ['lodash', 'date-fns'],
            },
          },
        },

        // 压缩
        minify: 'terser',
        terserOptions: {
          compress: {
            drop_console: true,
          },
        },

        // Chunk 大小警告
        chunkSizeWarningLimit: 1000,
      },

      // 开发服务器
      server: {
        port: 3000,
        proxy: {
          '/api': {
            target: 'http://localhost:8080',
            changeOrigin: true,
          },
        },
      },

      // 解析别名
      resolve: {
        alias: {
          '@': '/src',
          '@components': '/src/components',
        },
      },
    },
  },
});

Rsbuild 配置

使用 Rsbuild 进行高级构建:

typescript
export default defineConfig({
  builderConfig: {
    // HTML 配置
    html: {
      tags: [
        {
          tag: 'link',
          attrs: {
            rel: 'preconnect',
            href: 'https://fonts.googleapis.com',
          },
        },
        {
          tag: 'script',
          attrs: {
            src: 'https://cdn.example.com/analytics.js',
            async: true,
          },
        },
      ],
      // 自定义模板
      template: './template.html',
    },

    // 输出配置
    output: {
      // 静态资源路径
      assetPrefix: 'https://cdn.example.com/',
      // 文件名模式
      filename: {
        js: '[name].[contenthash:8].js',
        css: '[name].[contenthash:8].css',
        image: 'images/[name].[hash:8][ext]',
      },
      // 清理输出目录
      cleanDistPath: true,
    },

    // 性能优化
    performance: {
      // 代码分割策略
      chunkSplit: {
        strategy: 'split-by-experience',
        override: {
          chunks: 'all',
          minSize: 20000,
          maxSize: 244000,
        },
      },
      // 预加载
      preload: true,
      prefetch: true,
      // 移除 console
      removeConsole: ['log', 'warn'],
    },

    // 源映射
    sourceMap: {
      js: 'source-map',
      css: true,
    },
  },
});

资源处理

图片优化

优化图片资源:

typescript
import imagemin from 'imagemin';
import imageminWebp from 'imagemin-webp';

export default defineConfig({
  builderConfig: {
    plugins: [
      {
        name: 'optimize-images',
        async buildEnd() {
          // 转换为 WebP
          await imagemin(['doc_build/assets/images/*.{jpg,png}'], {
            destination: 'doc_build/assets/images',
            plugins: [
              imageminWebp({ quality: 80 }),
            ],
          });
        },
      },
    ],
  },
});

字体优化

优化字体加载:

typescript
export default defineConfig({
  builderConfig: {
    html: {
      tags: [
        {
          tag: 'link',
          attrs: {
            rel: 'preload',
            as: 'font',
            type: 'font/woff2',
            href: '/fonts/custom-font.woff2',
            crossorigin: 'anonymous',
          },
        },
      ],
    },
  },
});

代码转换

Babel 插件

添加 Babel 转换:

typescript
export default defineConfig({
  builderConfig: {
    vite: {
      esbuild: {
        jsxFactory: 'h',
        jsxFragment: 'Fragment',
      },
    },
  },
});

PostCSS 插件

添加 PostCSS 处理:

typescript
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';

export default defineConfig({
  builderConfig: {
    vite: {
      css: {
        postcss: {
          plugins: [
            autoprefixer(),
            cssnano({
              preset: 'default',
            }),
          ],
        },
      },
    },
  },
});

环境变量

定义变量

在构建中定义变量:

typescript
export default defineConfig({
  builderConfig: {
    vite: {
      define: {
        __APP_VERSION__: JSON.stringify('1.0.0'),
        __BUILD_TIME__: JSON.stringify(new Date().toISOString()),
        __IS_PRODUCTION__: JSON.stringify(process.env.NODE_ENV === 'production'),
      },
    },
  },
});

使用变量:

typescript
// 在代码中
console.log('Version:', __APP_VERSION__);
console.log('Built at:', __BUILD_TIME__);

if (__IS_PRODUCTION__) {
  // 生产特定代码
}

环境文件

使用 .env 文件:

bash
# .env
VITE_API_URL=https://api.example.com
VITE_GA_ID=GA-XXXXX

访问:

typescript
const apiUrl = import.meta.env.VITE_API_URL;
const gaId = import.meta.env.VITE_GA_ID;

构建钩子

生命周期钩子

使用构建生命周期钩子:

typescript
export function myPlugin(): RspressPlugin {
  return {
    name: 'my-plugin',

    // 配置解析前
    config(config) {
      console.log('配置加载');
      return config;
    },

    // 构建开始
    buildStart() {
      console.log('构建开始');
    },

    // 页面收集
    async collectPages() {
      console.log('收集页面');
    },

    // 页面处理
    async loadPage(filePath) {
      console.log('加载页面:', filePath);
    },

    // 内容转换
    async transform(code, id) {
      console.log('转换:', id);
      return code;
    },

    // 构建结束
    buildEnd() {
      console.log('构建完成');
    },
  };
}

最佳实践

插件开发

  1. 命名约定
typescript
// ✅ 好的命名
export function pluginReadingTime() {}
export function pluginSitemap() {}

// ❌ 避免
export function readingTime() {}
export function generateSitemap() {}
  1. 错误处理
typescript
export function myPlugin(): RspressPlugin {
  return {
    name: 'my-plugin',

    async buildEnd() {
      try {
        // 插件逻辑
      } catch (error) {
        console.error('插件错误:', error);
        // 不要中断构建
      }
    },
  };
}
  1. 性能优化
typescript
export function myPlugin(): RspressPlugin {
  // 缓存
  const cache = new Map();

  return {
    name: 'my-plugin',

    async transform(code, id) {
      // 使用缓存
      if (cache.has(id)) {
        return cache.get(id);
      }

      const result = processCode(code);
      cache.set(id, result);
      return result;
    },
  };
}

构建优化

  1. 代码分割
typescript
export default defineConfig({
  builderConfig: {
    performance: {
      chunkSplit: {
        strategy: 'split-by-experience',
      },
    },
  },
});
  1. 资源压缩
typescript
export default defineConfig({
  builderConfig: {
    output: {
      minify: true,
    },
  },
});
  1. 缓存优化
typescript
export default defineConfig({
  builderConfig: {
    output: {
      filename: {
        js: '[name].[contenthash:8].js',
      },
    },
  },
});

故障排除

插件不工作

问题: 插件未执行

检查:

  1. 插件正确导入
  2. 在配置中添加插件
  3. 钩子名称正确
  4. 返回正确的值

构建错误

问题: 构建失败

调试:

typescript
export default defineConfig({
  builderConfig: {
    vite: {
      // 启用详细日志
      logLevel: 'info',
    },
  },
});

性能问题

问题: 构建缓慢

优化:

  1. 启用缓存
  2. 减少文件监视
  3. 优化插件
  4. 使用并行处理

下一步


最佳实践

  • 保持插件简单和专注
  • 使用 TypeScript 实现类型安全
  • 彻底测试插件
  • 记录插件选项
  • 处理错误优雅地

性能

  • 避免在热路径上进行昂贵的操作
  • 在适当的时候使用缓存
  • 最小化文件系统操作
  • 使用异步操作
  • 监控构建时间