Skip to content

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:

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',
      },
    }),
  ],
});

Official Plugins

Sitemap Plugin

Generate XML sitemap:

typescript
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:

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: '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:

typescript
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:

typescript
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:

typescript
// 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:

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;

      // 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:

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

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

Display in page:

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>
  );
}

Real Example: TOC Generator Plugin

Generate table of contents:

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();

      // 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:

typescript
// 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:

markdown
::: note
This is a note!
:::

This is ==highlighted text==

Press [[Ctrl]] + [[S]] to save

Build Configuration

Vite Configuration

Customize Vite build:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

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

PostCSS Plugins

Add PostCSS processing:

typescript
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:

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'),
      },
    },
  },
});

Use variables:

typescript
// 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:

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

Access them:

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

Build Hooks

Lifecycle Hooks

Use build lifecycle hooks:

typescript
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

  1. Naming Conventions
typescript
// ✅ Good naming
export function pluginReadingTime() {}
export function pluginSitemap() {}

// ❌ Avoid
export function readingTime() {}
export function generateSitemap() {}
  1. Error Handling
typescript
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
      }
    },
  };
}
  1. Performance
typescript
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

  1. Code Splitting
typescript
export default defineConfig({
  builderConfig: {
    performance: {
      chunkSplit: {
        strategy: 'split-by-experience',
      },
    },
  },
});
  1. Asset Compression
typescript
export default defineConfig({
  builderConfig: {
    output: {
      minify: true,
    },
  },
});
  1. Cache Optimization
typescript
export default defineConfig({
  builderConfig: {
    output: {
      filename: {
        js: '[name].[contenthash:8].js',
      },
    },
  },
});

Troubleshooting

Plugin Not Working

Issue: Plugin not executing

Check:

  1. Plugin imported correctly
  2. Plugin added to config
  3. Hook names correct
  4. Returning correct values

Build Errors

Issue: Build failing

Debug:

typescript
export default defineConfig({
  builderConfig: {
    vite: {
      // Enable verbose logging
      logLevel: 'info',
    },
  },
});

Performance Issues

Issue: Slow build

Optimize:

  1. Enable caching
  2. Reduce file watching
  3. Optimize plugins
  4. Use parallel processing

Next Steps


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

Content is for learning and research only.