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
- Customizable Components - Use built-in components or provide your own
- ⚡ Fast & Efficient - Pre-loads data during build, minimal runtime overhead
- 🛡️ XSS Protection - Built-in security using DOMPurify sanitization
npm install @grnet/rspress-plugin-terminology --save
# or
yarn add @grnet/rspress-plugin-terminology
# or
pnpm add @grnet/rspress-plugin-terminologyThe easiest way to see the plugin in action is to run the included example:
cd rspress-plugin-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 '@grnet/rspress-plugin-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 '@grnet/rspress-plugin-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 '@grnet/rspress-plugin-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-plugin-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-plugin-terminology-tooltip {
max-width: 400px;
padding: 16px;
}The plugin works in two phases:
Build Time: Scans your termsDir for markdown files, extracts frontmatter (id, title, hoverText), and generates JSON files for each term plus a master glossary.json. A remark plugin transforms markdown links into interactive <Term> components.
Runtime: Term components display tooltips on hover by fetching pre-generated JSON data. The Glossary component renders all terms in a sorted list. All HTML content is sanitized with DOMPurify to prevent XSS attacks.
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 '@grnet/rspress-plugin-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 @grnet/rspress-plugin-terminologyCommon issues and solutions:
- Terms not showing: Verify term files have required frontmatter (
id,title) and check browser console for errors - Tooltips not appearing: Ensure links use correct relative paths and check browser console
- Glossary empty: Verify
glossary.jsonis generated during build
For detailed help, see GitHub Discussions or open an issue.
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.