Build Extensions
Rspress provides a powerful build extension system that allows you to customize and enhance the build process of your documentation site. This chapter explores how to use plugins, custom build configurations, and advanced features to extend Rspress.
Extension System Overview
What are Build Extensions?
Build extensions allow you to:
- Add Features: Extend core functionality through plugins
- Customize Build: Modify the build process
- Integrate Tools: Connect external tools and services
- Optimize Output: Enhance build output
Extension Types
Plugin System:
- Official plugins
- Community plugins
- Custom plugins
Build Configuration:
- Vite configuration
- Rsbuild configuration
- Custom transformations
Plugin System
Using Plugins
Add plugins in your configuration:
// 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',
},
}),
],
});Official Plugins
Sitemap Plugin
Generate XML sitemap:
import pluginSitemap from '@rspress/plugin-sitemap';
export default defineConfig({
plugins: [
pluginSitemap({
domain: 'https://example.com',
// Exclude specific paths
exclude: ['/admin', '/internal'],
// Custom priority
priority: (url) => {
if (url === '/') return 1.0;
if (url.startsWith('/guide')) return 0.9;
return 0.7;
},
// Change frequency
changefreq: 'weekly',
}),
],
});RSS Plugin
Generate RSS feed:
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: 'en',
copyright: '2024 My Company',
author: {
name: 'Your Name',
email: 'your@email.com',
link: 'https://example.com',
},
},
// Include paths
include: ['/blog', '/updates'],
// Limit items
limit: 20,
}),
],
});Preview Plugin
Add code preview functionality:
import pluginPreview from '@rspress/plugin-preview';
export default defineConfig({
plugins: [
pluginPreview({
// Supported languages
languages: ['jsx', 'tsx', 'vue'],
// Preview components
components: {
Button: () => import('./components/Button'),
Card: () => import('./components/Card'),
},
}),
],
});Search Plugin
Enhanced search capabilities:
import pluginSearch from '@rspress/plugin-search';
export default defineConfig({
plugins: [
pluginSearch({
// Index mode
mode: 'local', // or 'remote'
// Search fields
searchFields: ['title', 'content', 'headers'],
// Max results
maxResults: 10,
// Custom scoring
customScore: (result) => {
if (result.type === 'title') return 100;
if (result.type === 'header') return 50;
return 10;
},
}),
],
});Creating Custom Plugins
Plugin Structure
Create a custom plugin:
// plugins/my-plugin.ts
import type { RspressPlugin } from '@rspress/core';
export function myPlugin(options = {}): RspressPlugin {
return {
name: 'my-plugin',
// Config hook
config(config) {
// Modify configuration
return {
...config,
// Your modifications
};
},
// Build start
buildStart() {
console.log('Build starting...');
},
// Page data loading
async loadPage(filePath) {
// Process page data
},
// Transform content
async transform(code, id) {
// Transform code
return code;
},
// Build end
buildEnd() {
console.log('Build complete!');
},
};
}Real Example: Reading Time Plugin
Create a plugin that estimates reading time:
// 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;
// Remove code blocks if needed
let textContent = content;
if (!includeCodeBlocks) {
textContent = content.replace(/```[\s\S]*?```/g, '');
}
// Count words
const words = textContent
.replace(/\s+/g, ' ')
.split(' ')
.length;
// Calculate reading time
const minutes = Math.ceil(words / wordsPerMinute);
// Add to page data
return {
...pageData,
readingTime: {
minutes,
words,
text: `${minutes} min read`,
},
};
},
};
}Use the plugin:
// rspress.config.ts
import { pluginReadingTime } from './plugins/reading-time';
export default defineConfig({
plugins: [
pluginReadingTime({
wordsPerMinute: 200,
includeCodeBlocks: false,
}),
],
});Display in page:
// 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>
);
}Real Example: TOC Generator Plugin
Generate table of contents:
// 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();
// Build TOC structure
const toc = pages.map(page => ({
title: page.title,
path: page.routePath,
headers: page.toc,
}));
// Write JSON file
const outputPath = path.join(config.outDir, 'toc.json');
await fs.writeJSON(outputPath, toc, { spaces: 2 });
console.log('✅ Table of contents generated');
},
};
}Real Example: Markdown Enhancement Plugin
Add custom Markdown syntax:
// plugins/markdown-enhance.ts
import type { RspressPlugin } from '@rspress/core';
export function pluginMarkdownEnhance(): RspressPlugin {
return {
name: 'rspress-plugin-markdown-enhance',
// Transform Markdown
async markdown(content, filePath) {
let enhanced = content;
// Add custom containers
enhanced = enhanced.replace(
/::: note\s+([\s\S]*?):::/g,
'<div class="note">$1</div>'
);
// Add highlight markers
enhanced = enhanced.replace(
/==([^=]+)==/g,
'<mark>$1</mark>'
);
// Add keyboard shortcuts
enhanced = enhanced.replace(
/\[\[([^\]]+)\]\]/g,
'<kbd>$1</kbd>'
);
return enhanced;
},
};
}Usage:
::: note
This is a note!
:::
This is ==highlighted text==
Press [[Ctrl]] + [[S]] to saveBuild Configuration
Vite Configuration
Customize Vite build:
import { defineConfig } from 'rspress/config';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
builderConfig: {
// Vite configuration
vite: {
plugins: [vue()],
// Build optimization
build: {
// Code splitting
rollupOptions: {
output: {
manualChunks: {
'vendor': ['react', 'react-dom'],
'utils': ['lodash', 'date-fns'],
},
},
},
// Minification
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
},
},
// Chunk size warning
chunkSizeWarningLimit: 1000,
},
// Dev server
server: {
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
// Resolve aliases
resolve: {
alias: {
'@': '/src',
'@components': '/src/components',
},
},
},
},
});Rsbuild Configuration
Use Rsbuild for advanced builds:
export default defineConfig({
builderConfig: {
// HTML configuration
html: {
tags: [
{
tag: 'link',
attrs: {
rel: 'preconnect',
href: 'https://fonts.googleapis.com',
},
},
{
tag: 'script',
attrs: {
src: 'https://cdn.example.com/analytics.js',
async: true,
},
},
],
// Custom template
template: './template.html',
},
// Output configuration
output: {
// Asset prefix
assetPrefix: 'https://cdn.example.com/',
// Filename patterns
filename: {
js: '[name].[contenthash:8].js',
css: '[name].[contenthash:8].css',
image: 'images/[name].[hash:8][ext]',
},
// Clean dist path
cleanDistPath: true,
},
// Performance optimization
performance: {
// Chunk split strategy
chunkSplit: {
strategy: 'split-by-experience',
override: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
},
},
// Preloading
preload: true,
prefetch: true,
// Remove console
removeConsole: ['log', 'warn'],
},
// Source maps
sourceMap: {
js: 'source-map',
css: true,
},
},
});Asset Processing
Image Optimization
Optimize image assets:
import imagemin from 'imagemin';
import imageminWebp from 'imagemin-webp';
export default defineConfig({
builderConfig: {
plugins: [
{
name: 'optimize-images',
async buildEnd() {
// Convert to WebP
await imagemin(['doc_build/assets/images/*.{jpg,png}'], {
destination: 'doc_build/assets/images',
plugins: [
imageminWebp({ quality: 80 }),
],
});
},
},
],
},
});Font Optimization
Optimize font loading:
export default defineConfig({
builderConfig: {
html: {
tags: [
{
tag: 'link',
attrs: {
rel: 'preload',
as: 'font',
type: 'font/woff2',
href: '/fonts/custom-font.woff2',
crossorigin: 'anonymous',
},
},
],
},
},
});Code Transformations
Babel Plugins
Add Babel transformations:
export default defineConfig({
builderConfig: {
vite: {
esbuild: {
jsxFactory: 'h',
jsxFragment: 'Fragment',
},
},
},
});PostCSS Plugins
Add PostCSS processing:
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
export default defineConfig({
builderConfig: {
vite: {
css: {
postcss: {
plugins: [
autoprefixer(),
cssnano({
preset: 'default',
}),
],
},
},
},
},
});Environment Variables
Define Variables
Define variables in build:
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'),
},
},
},
});Use variables:
// In your code
console.log('Version:', __APP_VERSION__);
console.log('Built at:', __BUILD_TIME__);
if (__IS_PRODUCTION__) {
// Production-specific code
}Environment Files
Use .env files:
# .env
VITE_API_URL=https://api.example.com
VITE_GA_ID=GA-XXXXXAccess them:
const apiUrl = import.meta.env.VITE_API_URL;
const gaId = import.meta.env.VITE_GA_ID;Build Hooks
Lifecycle Hooks
Use build lifecycle hooks:
export function myPlugin(): RspressPlugin {
return {
name: 'my-plugin',
// Before config resolved
config(config) {
console.log('Config loading');
return config;
},
// Build start
buildStart() {
console.log('Build starting');
},
// Page collection
async collectPages() {
console.log('Collecting pages');
},
// Page processing
async loadPage(filePath) {
console.log('Loading page:', filePath);
},
// Content transformation
async transform(code, id) {
console.log('Transforming:', id);
return code;
},
// Build end
buildEnd() {
console.log('Build complete');
},
};
}Best Practices
Plugin Development
- Naming Conventions
// ✅ Good naming
export function pluginReadingTime() {}
export function pluginSitemap() {}
// ❌ Avoid
export function readingTime() {}
export function generateSitemap() {}- Error Handling
export function myPlugin(): RspressPlugin {
return {
name: 'my-plugin',
async buildEnd() {
try {
// Plugin logic
} catch (error) {
console.error('Plugin error:', error);
// Don't break the build
}
},
};
}- Performance
export function myPlugin(): RspressPlugin {
// Caching
const cache = new Map();
return {
name: 'my-plugin',
async transform(code, id) {
// Use cache
if (cache.has(id)) {
return cache.get(id);
}
const result = processCode(code);
cache.set(id, result);
return result;
},
};
}Build Optimization
- Code Splitting
export default defineConfig({
builderConfig: {
performance: {
chunkSplit: {
strategy: 'split-by-experience',
},
},
},
});- Asset Compression
export default defineConfig({
builderConfig: {
output: {
minify: true,
},
},
});- Cache Optimization
export default defineConfig({
builderConfig: {
output: {
filename: {
js: '[name].[contenthash:8].js',
},
},
},
});Troubleshooting
Plugin Not Working
Issue: Plugin not executing
Check:
- Plugin imported correctly
- Plugin added to config
- Hook names correct
- Returning correct values
Build Errors
Issue: Build failing
Debug:
export default defineConfig({
builderConfig: {
vite: {
// Enable verbose logging
logLevel: 'info',
},
},
});Performance Issues
Issue: Slow build
Optimize:
- Enable caching
- Reduce file watching
- Optimize plugins
- Use parallel processing
Next Steps
- Explore Custom Search
- Learn about Custom Theme
- Read about Deployment strategies
Best Practices
- Keep plugins simple and focused
- Use TypeScript for type safety
- Test plugins thoroughly
- Document plugin options
- Handle errors gracefully
Performance
- Avoid expensive operations in hot paths
- Use caching where appropriate
- Minimize file system operations
- Use async operations
- Monitor build times