Conversation
WalkthroughAdds Mermaid diagram support to VitePress: markdown fence detection and renderer, a ClientOnly Changes
Sequence DiagramsequenceDiagram
participant MD as Markdown Processor
participant CFG as VitePress Config
participant MB as MermaidBlock Component
participant RE as Render Engine
participant DOM as DOM / MutationObserver
MD->>CFG: pass fenced code token
CFG->>CFG: isMermaidFence()? -> true
CFG->>MD: renderMermaidFence() -> ClientOnly + MermaidBlock
MD->>DOM: render page with MermaidBlock
MB->>MB: mount, decode graph, compute id and isDark
MB->>RE: request renderMermaidSvg(id, code, isDark)
RE->>RE: enqueue render, load mermaid (lazy), init with theme vars
RE-->>MB: return SVG or error
MB->>DOM: display SVG or show error state
DOM->>DOM: theme/dark-mode class change
DOM->>MB: MutationObserver triggers
MB->>RE: re-render with updated isDark
RE-->>MB: updated SVG
MB->>DOM: update displayed SVG
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.vitepress/theme/style.css (1)
405-415:⚠️ Potential issue | 🟡 Minor
infois not a valid HTML type selector — should this be.info?Both Stylelint and Biome flag
infoas an unknown type selector. If this is meant to style elements with classinfo, it should be.info. As-is, this rule will never match anything.Same issue at line 631 (
.dark info).Proposed fix
-info { +.info { display: inline-block;-.dark info { +.dark .info { background: `#1a1a1a`;
🧹 Nitpick comments (5)
.vitepress/theme/style.css (1)
756-823: New Mermaid styles look good.The loading, diagram, and error states are well-structured with appropriate use of CSS variables for theme integration. Minor stylelint nits:
- Line 807:
margin: 0 0 8px 0→margin: 0 0 8px(redundant value).- Line 790:
geometricPrecision→geometricprecisionpervalue-keyword-caserule..vitepress/theme/utils/mermaid.ts (2)
174-192: Re-initializing mermaid on every render call could be simplified, but is functionally correct given the serialized queue.Since
mermaid.initialize()is called inside the serializedrenderQueue, there's no race between init and render. However, callinginitializeon every single render is slightly wasteful when multiple diagrams share the same theme. Consider caching the last config and skipping re-init when unchanged — but this is optional and can be deferred.
148-157: Lazy-loading pattern is clean. The singleton promise for dynamic import avoids redundant loads.One minor note: if the dynamic
import('mermaid')fails (e.g., network issue in dev),mermaidPromisewill cache the rejected promise permanently. Consider resetting it on failure so retries are possible.Proposed fix
const loadMermaid = async () => { if (!mermaidPromise) { - mermaidPromise = import('mermaid') + mermaidPromise = import('mermaid').catch((err) => { + mermaidPromise = null + throw err + }) } const module = await mermaidPromise return module.default }.vitepress/theme/components/MermaidBlock.vue (2)
101-109: Consider debouncing the MutationObserver callback.The observer fires on any
classattribute change on<html>, which could happen frequently (not just dark mode toggles). Each fire triggers a full mermaid re-render. A simple debounce would prevent unnecessary render churn.Proposed fix
+let debounceTimer: ReturnType<typeof setTimeout> | null = null + onMounted(() => { mutationObserver = new MutationObserver(() => { - renderDiagram() + if (debounceTimer) clearTimeout(debounceTimer) + debounceTimer = setTimeout(renderDiagram, 50) }) mutationObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'], }) renderDiagram() })
46-68:getIsDarkMode()is called 6 times inreadThemePalette.Each call re-evaluates
document.documentElement.classList.contains('dark'). Cache the result in a local variable.Proposed fix
const readThemePalette = (): MermaidPalette => { const styles = getComputedStyle(document.documentElement) + const isDark = getIsDarkMode() const read = (name: string, fallback: string) => { const value = styles.getPropertyValue(name).trim() return value || fallback } return { - bg: read('--vp-c-bg', getIsDarkMode() ? '#0b1220' : '#ffffff'), - bgSoft: read('--vp-c-bg-soft', getIsDarkMode() ? '#1f2937' : '#f6f8fa'), - text1: read('--vp-c-text-1', getIsDarkMode() ? '#f8fafc' : '#1f2937'), - text2: read('--vp-c-text-2', getIsDarkMode() ? '#cbd5e1' : '#475569'), - border: read('--vp-c-border', getIsDarkMode() ? '#64748b' : '#94a3b8'), - brand: read('--vp-c-brand-1', getIsDarkMode() ? '#60a5fa' : '#2563eb'), + bg: read('--vp-c-bg', isDark ? '#0b1220' : '#ffffff'), + bgSoft: read('--vp-c-bg-soft', isDark ? '#1f2937' : '#f6f8fa'), + text1: read('--vp-c-text-1', isDark ? '#f8fafc' : '#1f2937'), + text2: read('--vp-c-text-2', isDark ? '#cbd5e1' : '#475569'), + border: read('--vp-c-border', isDark ? '#64748b' : '#94a3b8'), + brand: read('--vp-c-brand-1', isDark ? '#60a5fa' : '#2563eb'), } }
b45ba52 to
93786ca
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.vitepress/theme/style.css (1)
405-415:⚠️ Potential issue | 🟡 MinorUnknown type selector
info— likely should be a class.Stylelint flags
infoas an unknown HTML element (line 405 and similarly at line 631 for.dark info). This should probably be.infoor a custom element needs to be declared.Proposed fix
-info { +.info {And similarly at line 631:
-.dark info { +.dark .info {
🤖 Fix all issues with AI agents
In @.vitepress/theme/utils/mermaid.ts:
- Around line 148-157: The dynamic import of Mermaid is being cached in
mermaidPromise even when it rejects, causing permanent failures; update
loadMermaid to wrap the import call in a try/catch so that if import('mermaid')
rejects you set mermaidPromise back to null and rethrow the error; specifically
modify the loadMermaid function (and the mermaidPromise setter logic) so a
failed import clears mermaidPromise to allow retries on subsequent calls while
still returning module.default on success.
🧹 Nitpick comments (1)
.vitepress/theme/style.css (1)
756-823: Mermaid styles look solid overall — a couple of minor stylelint fixes.The new
.vp-mermaid-*classes are well-structured, use VitePress CSS variables appropriately, and handle loading/error/diagram states cleanly.Two stylelint issues in the new code:
- Line 790:
geometricPrecision→geometricprecision(value-keyword-case)- Line 807:
margin: 0 0 8px 0→margin: 0 0 8px(shorthand-property-no-redundant-values)Proposed fixes
.vp-mermaid-diagram svg text { - text-rendering: geometricPrecision; + text-rendering: geometricprecision; font-family: var(--vp-font-family-base) !important; }.vp-mermaid-error-title { - margin: 0 0 8px 0; + margin: 0 0 8px; color: var(--vp-c-danger-1);
| let mermaidPromise: Promise<typeof import('mermaid')> | null = null | ||
| let renderQueue = Promise.resolve() | ||
|
|
||
| const loadMermaid = async () => { | ||
| if (!mermaidPromise) { | ||
| mermaidPromise = import('mermaid') | ||
| } | ||
| const module = await mermaidPromise | ||
| return module.default | ||
| } |
There was a problem hiding this comment.
Mermaid import failure is permanently cached.
If the dynamic import('mermaid') fails (e.g., network issue in a lazy-loaded chunk scenario), mermaidPromise retains the rejected promise forever, and all subsequent calls to loadMermaid will fail without retrying.
Proposed fix: reset on failure
const loadMermaid = async () => {
if (!mermaidPromise) {
- mermaidPromise = import('mermaid')
+ mermaidPromise = import('mermaid').catch((err) => {
+ mermaidPromise = null
+ throw err
+ })
}
const module = await mermaidPromise
return module.default
}🤖 Prompt for AI Agents
In @.vitepress/theme/utils/mermaid.ts around lines 148 - 157, The dynamic import
of Mermaid is being cached in mermaidPromise even when it rejects, causing
permanent failures; update loadMermaid to wrap the import call in a try/catch so
that if import('mermaid') rejects you set mermaidPromise back to null and
rethrow the error; specifically modify the loadMermaid function (and the
mermaidPromise setter logic) so a failed import clears mermaidPromise to allow
retries on subsequent calls while still returning module.default on success.
【特性】新增 mermaid 渲染支持
【社区插件】
vitepress-plugin-mermaid
优点:start 171
缺点:该插件最近更新时间是2年前,更新不活跃(或者已经被弃坑了?)
vitepress-mermaid-renderer
优点:支持放大、全屏等高级特性
缺点:该插件 license 为 gpl 3.0,有开源传染风险。
【实现方案】自定义 markdown-it 插件 + Vue 组件
理由:当前实现简单,后续可根据自己的业务需求进行特性添加或者维护。(使用大模型进行编码维护也降低了自维护的成本。)
【实现截图】
Summary by CodeRabbit
New Features
Style
Chores