A plugin for Rspress that enables terminology management with hover tooltips and auto-generated glossaries.
This plugin is a port of @grnet/docusaurus-terminology for the Rspress static site generator.
- 🔤 Term Definitions - Define terms in markdown files with frontmatter
- 🎯 Hover Tooltips - Display term definitions when hovering over links
- 📚 Auto-Generated Glossary - Automatically create a glossary page with all terms
- 🔗 Link Transformation - Automatically transform markdown links to interactive components
- 🎨 Customizable Components - Use built-in components or provide your own
- ⚡ Fast & Efficient - Pre-loads data during build, minimal runtime overhead
- 🐛 Debug Logging - Built-in debug utility with namespace-based logging for troubleshooting
- 🛡️ Security - Built-in XSS protection using DOMPurify sanitization
npm install rspress-terminology --save
# or
yarn add rspress-terminology
# or
pnpm add rspress-terminologyThe easiest way to see the plugin in action is to run the included example:
cd rspress-terminology
# Build the plugin
npm run build
# Install example dependencies
cd example
npm install
# Start the dev server
npm run devThen open http://localhost:3000 and:
- Hover over linked terms to see tooltips
- Visit the Glossary page
- Explore the source code in
docs/terms/
Add the plugin to your rspress.config.ts:
import { defineConfig } from '@rspress/core';
import { terminologyPlugin } from 'rspress-terminology';
export default defineConfig({
// ... other config
plugins: [
terminologyPlugin({
termsDir: './docs/terms', // Directory containing term definitions
docsDir: './docs/', // Root documentation directory
glossaryFilepath: './docs/glossary.md' // Path to glossary page
})
]
});Create markdown files in your docs/terms/ directory:
---
id: api-key
title: API Key
hoverText: A unique identifier used to authenticate a user or application.
---
An API key is a secret token that identifies the calling application or user. API keys are used to track and control how the API is being used, prevent malicious use, and calculate usage fees.Create docs/glossary.md:
---
title: Glossary
---
# Glossary
Welcome to the glossary. All terms from the documentation are listed below.
The plugin will automatically inject the <Glossary /> component into this file.
Reference terms using standard markdown link syntax:
To use the API, you need an [API Key](./terms/api-key).This will automatically be transformed into an interactive link with a hover tooltip.
| Option | Type | Required | Description |
|---|---|---|---|
termsDir |
string |
Yes | Directory containing term definition files (e.g., './docs/terms') |
docsDir |
string |
Yes | Root documentation directory (e.g., './docs/') |
glossaryFilepath |
string |
Yes | Path to glossary markdown file (e.g., './docs/glossary.md') |
basePath |
string |
No | Base path for the site (e.g., '/my-site'). Useful when hosting in a subdirectory. |
termPreviewComponentPath |
string |
No | Custom path to Term preview component |
glossaryComponentPath |
string |
No | Custom path to Glossary view component |
debug |
boolean | object |
No | Enable debug logging. See Debug Configuration below. |
terminologyPlugin({
termsDir: './docs/terms',
docsDir: './docs/',
glossaryFilepath: './docs/glossary.md',
termPreviewComponentPath: './components/CustomTerm.tsx',
glossaryComponentPath: './components/CustomGlossary.tsx'
})If your site is hosted in a subdirectory (e.g., https://example.com/docs/), use basePath:
terminologyPlugin({
termsDir: './docs/terms',
docsDir: './docs/',
glossaryFilepath: './docs/glossary.md',
basePath: '/docs' // Links will be /docs/terms/term instead of /terms/term
})The plugin includes a built-in debug logging utility to help troubleshoot issues.
terminologyPlugin({
termsDir: './docs/terms',
docsDir: './docs/',
glossaryFilepath: './docs/glossary.md',
debug: true // Enable all debug logs
})terminologyPlugin({
termsDir: './docs/terms',
docsDir: './docs/',
glossaryFilepath: './docs/glossary.md',
debug: {
enabled: true,
timestamps: true, // Include timestamps in logs
namespaces: [ // Only log specific namespaces
'build:*', // All build operations
'plugin:load' // Plugin loading
]
}
})You can also control debug logging via environment variable:
# Enable all debug logs
RSPRESS_TERMINOLOGY_DEBUG=1 npm run build
# Enable specific namespaces
RSPRESS_TERMINOLOGY_DEBUG=build:* npm run build
# Enable multiple namespaces (comma-separated)
RSPRESS_TERMINOLOGY_DEBUG=plugin:load,build:index npm run buildplugin- Main plugin lifecycle eventsplugin:load- Loading glossary JSONplugin:build- Build phase eventsplugin:page- Page data extensionplugin:inject- HTML injection events
build- Build-time operationsbuild:index- Term indexing operationsbuild:glossary- Glossary JSON generationbuild:inject- Component injectionbuild:copy- File copying operations
For more examples and detailed usage, see DEBUG_EXAMPLES.md.
Each term file should include:
---
id: unique-term-id # Required: Unique identifier
title: Term Title # Required: Display title
hoverText: Short definition shown on hover # Optional: Hover text
---
Full term explanation and details here...| Field | Type | Required | Description |
|---|---|---|---|
id |
string |
Yes | Unique identifier for the term |
title |
string |
Yes | Display title of the term |
hoverText |
string |
No | Short description shown on hover (supports markdown) |
Create a custom term preview component:
// components/CustomTerm.tsx
import React from 'react';
import type { TermMetadata } from 'rspress-terminology';
interface CustomTermProps {
pathName: string;
children?: React.ReactNode;
}
export default function CustomTerm({ pathName, children }: CustomTermProps) {
const [term, setTerm] = React.useState<TermMetadata | null>(null);
React.useEffect(() => {
fetch(`${pathName}.json`)
.then(res => res.json())
.then(setTerm);
}, [pathName]);
return (
<span className="custom-term">
<a href={pathName}>{children || term?.title}</a>
{/* Custom tooltip implementation */}
</span>
);
}// components/CustomGlossary.tsx
import React from 'react';
import type { TermMetadata } from 'rspress-terminology';
export default function CustomGlossary() {
const [terms, setTerms] = React.useState<Record<string, TermMetadata>>({});
React.useEffect(() => {
fetch('/docs/glossary.json')
.then(res => res.json())
.then(setTerms);
}, []);
return (
<div className="custom-glossary">
<h1>Glossary</h1>
{Object.entries(terms).map(([path, term]) => (
<div key={path}>
<a href={path}>{term.title}</a>
</div>
))}
</div>
);
}The plugin includes default styles. To customize, override these CSS classes:
.term-link- Base term link style.term-link:hover- Hover state.term-link-loading- Loading state.term-link-error- Error state
.rspress-terminology-tooltip- Tooltip container.term-tooltip-content- Tooltip content wrapper.term-title- Term title in tooltip.term-hover-text- Hover text content
.glossary-container- Glossary wrapper.glossary-item- Individual glossary entry.glossary-term- Term title.glossary-definition- Term definition
Add custom styles in your Rspress theme:
/* src/styles/custom.css */
.term-link {
text-decoration-style: dotted;
color: #3b82f6;
}
.rspress-terminology-tooltip {
max-width: 400px;
padding: 16px;
}-
beforeBuild Hook
- Scans
termsDirfor markdown files - Parses frontmatter from each term file
- Builds term index
- Generates
glossary.json - Creates individual
.jsonfiles for each term
- Scans
-
extendPageData Hook
- Attaches term index to page data
- Makes terms available via
usePageData()
-
Remark Plugin
- Transforms
[term](path/to/term.md)links - Converts to
<Term pathName="...">text</Term>components - Uses AST transformation for reliability
- Transforms
- Term Component - Fetches term data from JSON or uses pre-loaded data
- Glossary Component - Displays all terms in a sorted list
- Tooltip - Shows hover text using
rc-tooltip
If you're migrating from @grnet/docusaurus-terminology:
Docusaurus:
module.exports = {
plugins: [
['@grnet/docusaurus-terminology', {
termsDir: './docs/terms',
docsDir: './docs/',
glossaryFilepath: './docs/glossary.md'
}]
]
};Rspress:
import { terminologyPlugin } from 'rspress-terminology';
export default defineConfig({
plugins: [
terminologyPlugin({
termsDir: './docs/terms',
docsDir: './docs/',
glossaryFilepath: './docs/glossary.md'
})
]
});@docusaurus/BrowserOnly→ No longer needed (client-side fetching handled internally)@docusaurus/useBaseUrl→usePageData()fromrspress/runtime@docusaurus/Link→ Standard<a>tags (Rspress handles routing)
npm run buildnpm run devThe project includes comprehensive security tests to verify XSS prevention:
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverageSee SECURITY.md for detailed security information.
# Link your local version
npm link
# In your rspress project
npm link rspress-terminology- Check that term files are in the correct directory (
termsDir) - Verify each term has required frontmatter (
id,title) - Check browser console for fetch errors
- Ensure
glossary.jsonis generated in your output
- Check that
mdxRs: falseis set (required for remark plugins) - Verify links use correct relative paths
- Check browser console for JavaScript errors
- Ensure
glossary.mdexists at specified path - Check that
<Glossary />component is injected - Verify
glossary.jsonis generated during build
BSD-2-Clause
Ported from @grnet/docusaurus-terminology
Original implementation for Docusaurus by GRNET Developers.
Contributions are welcome! Please feel free to submit issues or pull requests.
Note: This is the Rspress version of the terminology plugin. For Docusaurus, see @grnet/docusaurus-terminology.