VitePress Plugin Extensions
CmdWise documentation site is built with VitePress, incorporating icons, flowcharts, and diagrams in Markdown. This article documents how to extend VitePress plugins to achieve these requirements.
Why Extend?
Mermaid Flowcharts
Documentation already contains multiple «```mermaid» code blocks, but default VitePress doesn't render them—readers only see source code. We need graphical display.Interface Icon Consistency
CmdWise App already uses Font Awesome and Lucide icons extensively. When writing user manuals, if Markdown can directly insert the same SVG icons, it reduces screenshots and keeps UI and documentation synchronized.
Goal: Make
```mermaid,<font-awesome-icon>,<LucideIcon>work directly without changing user content syntax.
Solution Overview
| Requirement | Solution | Key Packages |
|---|---|---|
| Mermaid rendering | Use community plugin vitepress-plugin-mermaid, wrap existing config with one line | vitepress-plugin-mermaid, mermaid |
| Font Awesome icons | Globally register font-awesome-icon, and library.add() popular icons on demand | @fortawesome/vue-fontawesome + three packages |
| Lucide icons | Wrap generic component <LucideIcon>, internally dynamic lookup lucide-vue-next components | lucide-vue-next |
Core Code
1. .vitepress/config.ts
ts
import { defineConfig } from 'vitepress'
import { withMermaid } from 'vitepress-plugin-mermaid'
export default withMermaid(defineConfig({
// Rest of configuration remains unchanged
}))2. package.json (docs package)
jsonc
{
"devDependencies": {
"vitepress-plugin-mermaid": "^2.0.17",
"mermaid": "^10.9.1",
"@fortawesome/vue-fontawesome": "^3.0.3",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"lucide-vue-next": "^0.525.0",
"vue": "^3.4.21"
}
}3. Custom Theme theme/index.ts
ts
import DefaultTheme from 'vitepress/theme'
import type { Theme } from 'vitepress'
import './custom.css'
// Font Awesome
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faTerminal, faBolt, faClipboard, faFile } from '@fortawesome/free-solid-svg-icons'
library.add(faTerminal, faBolt, faClipboard, faFile)
// Lucide
import * as LucideIcons from 'lucide-vue-next'
import { h, defineComponent } from 'vue'
const LucideIcon = defineComponent({
name: 'LucideIcon',
props: {
name: { type: String, required: true },
size: { type: [Number, String], default: 24 },
color: String,
strokeWidth: { type: [Number, String], default: 2 },
},
setup(props) {
const normalize = (n: string) => n.split(/[-_]/).map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('')
return () => {
const key = (LucideIcons as any)[props.name] ? props.name : normalize(props.name as string)
const Icon = (LucideIcons as any)[key]
return Icon ? h(Icon, { size: props.size, color: props.color, strokeWidth: props.strokeWidth }) : null
}
}
})
const theme: Theme = {
...DefaultTheme,
enhanceApp({ app }) {
(DefaultTheme as any).enhanceApp?.({ app })
app.component('FontAwesomeIcon', FontAwesomeIcon)
app.component('LucideIcon', LucideIcon)
},
}
export default themeIssues Encountered & Solutions
| Problem | Cause | Solution |
|---|---|---|
| Startup white screen / Sidebar missing | Once tried batch registration of all Lucide components, conflicted with VitePress built-in component names | Abandoned batch registration, switched to <LucideIcon> dynamic loading to avoid naming conflicts |
| Dependency resolution failed | Failed to sync install @fortawesome/vue-fontawesome, vue etc. packages | Added dependencies to docs package.json, restored after pnpm install |
| Mermaid code blocks still show source | Forgot to wrap config with withMermaid | Wrap defineConfig() with withMermaid() in outermost layer of config.ts |
| Icon case inconsistency | Developer writes name="file", library actually has File | normalize() converts kebab/snake → PascalCase then lookup |
Results
- ````mermaid` code blocks auto-render to SVG diagrams
- Markdown can directly write
<font-awesome-icon icon="fa-solid fa-terminal" /> - Lucide supports two syntaxes:md
<LucideIcon name="file" size="20" /> <lucide-icon name="camera" color="red" /> - Unused icons won't be included in final bundle, keeping build size controllable