构建能力扩展
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('构建完成');
},
};
}最佳实践
插件开发
- 命名约定
typescript
// ✅ 好的命名
export function pluginReadingTime() {}
export function pluginSitemap() {}
// ❌ 避免
export function readingTime() {}
export function generateSitemap() {}- 错误处理
typescript
export function myPlugin(): RspressPlugin {
return {
name: 'my-plugin',
async buildEnd() {
try {
// 插件逻辑
} catch (error) {
console.error('插件错误:', error);
// 不要中断构建
}
},
};
}- 性能优化
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;
},
};
}构建优化
- 代码分割
typescript
export default defineConfig({
builderConfig: {
performance: {
chunkSplit: {
strategy: 'split-by-experience',
},
},
},
});- 资源压缩
typescript
export default defineConfig({
builderConfig: {
output: {
minify: true,
},
},
});- 缓存优化
typescript
export default defineConfig({
builderConfig: {
output: {
filename: {
js: '[name].[contenthash:8].js',
},
},
},
});故障排除
插件不工作
问题: 插件未执行
检查:
- 插件正确导入
- 在配置中添加插件
- 钩子名称正确
- 返回正确的值
构建错误
问题: 构建失败
调试:
typescript
export default defineConfig({
builderConfig: {
vite: {
// 启用详细日志
logLevel: 'info',
},
},
});性能问题
问题: 构建缓慢
优化:
- 启用缓存
- 减少文件监视
- 优化插件
- 使用并行处理
下一步
最佳实践
- 保持插件简单和专注
- 使用 TypeScript 实现类型安全
- 彻底测试插件
- 记录插件选项
- 处理错误优雅地
性能
- 避免在热路径上进行昂贵的操作
- 在适当的时候使用缓存
- 最小化文件系统操作
- 使用异步操作
- 监控构建时间