# 前言
Mermaid 作為 Markdown 的流程圖繪製解決方案,大大增加了 Markdown 的實用性。在搭建 Hexo 部落格時,我選擇了 Shoka 主題,它宣稱支援豐富的 Markdown 語法,包括 Mermaid。然而,在實際使用時卻發現 Mermaid 代碼塊無法正確渲染,只能顯示純文字。
本文記錄了完整的問題排查與修復過程。
# 初始環境狀態
# Package 依賴
專案已安裝以下主要依賴:
{ | |
"hexo": "^8.1.1", | |
"hexo-renderer-multi-markdown-it": "^0.1.4", | |
"puppeteer": "^23.11.1" | |
} |
hexo-renderer-multi-markdown-it 是一個強大的 Markdown 渲染器,內建支援多種插件,包括 markdown-it-mermaid 。
# Hexo 主配置(_config.yml)
# 語法高亮設置 | |
syntax_highlighter: highlight.js | |
highlight: | |
line_number: true | |
auto_detect: false | |
tab_replace: '' | |
wrap: true | |
hljs: false | |
exclude_languages: | |
- mermaid | |
# Markdown 設置 | |
markdown: | |
render: | |
html: false | |
xhtmlOut: true | |
breaks: true | |
linkify: true | |
plugins: | |
- plugin: | |
name: markdown-it-toc-and-anchor | |
enable: true | |
# ... 其他插件 | |
# 注意:沒有 markdown-it-mermaid 配置 |
# Shoka 主題配置(themes/shoka/_config.yml)
vendors: | |
css: | |
katex: npm/katex@0.12.0/dist/katex.min.css | |
mermaid: css/mermaid.css # 只有 CSS | |
js: | |
pace: npm/pace-js@1.0.2/pace.min.js | |
# ... 其他 JS | |
# 注意:沒有 mermaid.js |
# 問題現象
在 Markdown 文件中使用 Mermaid 語法:
--- | |
title: 測試文章 | |
mermaid: true | |
--- | |
```mermaid | |
graph LR | |
A[Square Rect] --> B((Circle)) | |
A --> C(Round Rect) | |
B --> D{Rhombus} | |
C --> D | |
``` |
渲染結果:代碼塊被當作普通代碼顯示,沒有轉換成流程圖 SVG。
# 排查過程
# 第一步:檢查生成的 HTML
查看生成的 HTML 文件:
<p> | |
<pre><code class="highlight mermaid">graph LR | |
A[Square Rect] -- Link text --> B((Circle)) | |
... | |
</code></pre> | |
</p> |
發現問題 #1:Mermaid 代碼塊被渲染成了 <code class="highlight mermaid"> 而不是被 markdown-it-mermaid 插件處理。
# 第二步:添加調試代碼
在 node_modules/hexo-renderer-multi-markdown-it/lib/renderer/markdown-it-mermaid/index.js 中添加調試:
md.renderer.rules.fence = (tokens, idx, options, env, self) => { | |
const token = tokens[idx] | |
const code = token.content.trim() | |
console.log('Token info:', token.info, '<------ equals mermaid?', (token.info === 'mermaid')) | |
if (token.info === 'mermaid') { | |
// ... | |
} | |
return defaultRenderer(tokens, idx, options, env, self) | |
} |
執行 hexo generate ,卻沒有看到任何 console.log 輸出。
發現問題 #2: markdown-it-mermaid 插件根本沒有被執行。
# 第三步:檢查插件載入
在 node_modules/hexo-renderer-multi-markdown-it/lib/renderer/index.js 添加調試:
parser = plugins.reduce((parser, plugins) => { | |
if (plugins.enable) { | |
let plugin = require(plugins.name); | |
if (plugins.name.includes('mermaid')) { | |
console.log('Loading mermaid plugin:', plugins.name, 'enabled:', plugins.enable); | |
} | |
// ... | |
} | |
}, parser); |
重新生成後看到:
Mermaid plugin is DISABLED: ./markdown-it-mermaid enabled: false
發現問題 #3:雖然 hexo-renderer-multi-markdown-it 內建 mermaid 插件,但在 _config.yml 中沒有明確啟用。
# 第四步:啟用 Mermaid 插件
在 _config.yml 中添加:
markdown: | |
plugins: | |
# ... 其他插件 | |
- plugin: | |
name: ./markdown-it-mermaid | |
enable: true | |
options: | |
theme: default |
再次生成,看到:
Loading mermaid plugin: ./markdown-it-mermaid enabled: true
但生成的 HTML 仍然是 <code class="highlight mermaid"> 。
發現問題 #4:代碼塊在到達 markdown-it-mermaid 之前就被 Hexo 的內建高亮器處理了。
# 第五步:禁用 Hexo 內建語法高亮
修改 _config.yml :
syntax_highlighter: # 設為空值 |
重新生成後,終於看到 Token info 輸出了!但是:
Prism encountered mermaid! lang: mermaid info: mermaid
Token info: <------ equals mermaid? false
查看生成的 HTML:
<figure class="highlight mermaid"> | |
<figcaption data-lang="mermaid"></figcaption> | |
<table> | |
<tr><td data-num="1"></td> | |
<td><pre><span class="token keyword">graph</span> LR</pre></td></tr> | |
<!-- Prism 語法高亮的結果 --> | |
</table> | |
</figure> |
發現問題 #5:代碼塊被 markdown-it-prism 插件進行了語法高亮,而不是交給 markdown-it-mermaid 處理。
原因是在 hexo-renderer-multi-markdown-it/lib/renderer/index.js 中,插件加載順序為:
const default_plugins = [ | |
// ... | |
'./markdown-it-mermaid', // 第 19 個 | |
// ... | |
'./markdown-it-prism', // 第 21 個(最後處理) | |
// ... | |
]; |
Prism 插件在 Mermaid 之後加載,會覆蓋 md.renderer.rules.fence 。
# 第六步:修改 Prism 跳過 Mermaid
在 node_modules/hexo-renderer-multi-markdown-it/lib/renderer/markdown-it-prism/index.js 中,在處理代碼塊的開頭添加:
md.renderer.rules.fence = (tokens, idx, options, env, self) => { | |
const token = tokens[idx] | |
const info = token.info | |
const text = token.content.trim() | |
const lang = info.trim().split(" ")[0] | |
// Skip mermaid - let it be handled by markdown-it-mermaid | |
if (lang.toLowerCase().includes('mermaid')) { | |
return defaultRenderer(tokens, idx, options, env, self); | |
} | |
//... Prism 處理其他語言 | |
} |
# 第七步:驗證修復
重新生成後,查看 HTML:
<pre class="mermaid graph"> | |
<svg id="mermaid-..." width="100%" xmlns="http://www.w3.org/2000/svg"> | |
<!-- 完整的 SVG 流程圖 --> | |
</svg> | |
</pre> |
成功!Mermaid 代碼塊被正確渲染成 SVG 了。
# 完整解決方案
# 1. 禁用 Hexo 內建語法高亮
檔案: _config.yml
# 將 syntax_highlighter 設為空值 | |
syntax_highlighter: |
# 2. 啟用 markdown-it-mermaid 插件
檔案: _config.yml
markdown: | |
plugins: | |
# ... 其他插件 | |
- plugin: | |
name: ./markdown-it-mermaid | |
enable: true | |
options: | |
theme: default |
# 3. 修改 Prism 跳過 Mermaid 處理
檔案: node_modules/hexo-renderer-multi-markdown-it/lib/renderer/markdown-it-prism/index.js
在 md.renderer.rules.fence 函數開頭添加:
// Skip mermaid - let it be handled by markdown-it-mermaid | |
if (lang.toLowerCase().includes('mermaid')) { | |
return defaultRenderer(tokens, idx, options, env, self); | |
} |
# 4. (可選)配置前端 Mermaid.js 渲染
如果要使用前端渲染而非構建時渲染,還需:
檔案: themes/shoka/_config.yml
vendors: | |
js: | |
mermaid: npm/mermaid@8.13.5/dist/mermaid.min.js |
檔案: themes/shoka/source/js/_app/pjax.js
vendorCss('mermaid'); | |
vendorJs('mermaid', function() { | |
if (window.mermaid) { | |
mermaid.initialize({ | |
theme: 'default', | |
startOnLoad: false, | |
flowchart: { | |
useMaxWidth: true, | |
htmlLabels: false | |
} | |
}); | |
mermaid.init(undefined, '.mermaid'); | |
} | |
}, window.mermaid); |
# 使用方法
在文章的 Front Matter 中添加 mermaid: true :
--- | |
title: 我的文章 | |
mermaid: true | |
--- | |
```mermaid | |
graph TD | |
A[開始] --> B{判斷} | |
B -->|是| C[執行] | |
B -->|否| D[結束] | |
``` |
# 驗證效果
以下是三個測試範例:
# 範例 1:流程圖
# 範例 2:序列圖
# 範例 3:類別圖
# 範例 4: 複雜流程圖
# 範例 5: 時序圖
# 保存修改(重要)
由於修改了 node_modules 中的檔案,每次執行 npm install 或 yarn install 都會被覆蓋。建議使用 patch-package 保存修改:
# 安裝 patch-package | |
npm install patch-package --save-dev | |
# 創建補丁 | |
npx patch-package hexo-renderer-multi-markdown-it | |
# 在 package.json 添加 | |
"scripts": { | |
"postinstall": "patch-package" | |
} |
執行後會在專案根目錄生成 patches/hexo-renderer-multi-markdown-it+版本號.patch 文件,未來重新安裝依賴時會自動應用補丁。
# 問題根源總結
這個問題的根本原因是多層代碼塊處理器的執行順序衝突:
- Hexo 層:內建的
highlight.js在 Markdown 渲染之前就處理了代碼塊 - Markdown-it 層:
markdown-it-prism覆蓋了markdown-it-mermaid的 fence 規則 - 配置層:
markdown-it-mermaid插件未在配置中明確啟用
解決方案是:
- 禁用 Hexo 層的語法高亮,交給 markdown-it 處理
- 讓 Prism 跳過 mermaid,交給專門的 mermaid 插件處理
- 明確啟用 mermaid 插件
# 參考資料
- Hexo 官方文檔
- hexo-renderer-multi-markdown-it
- Mermaid 官方文檔
- 參考文章:Hexo 博客 + Shoka 主題 Mermaid 流程图问题
# 進階修復:三個額外問題
在基本修復後,我又遇到並解決了三個額外問題。
# 問題 A:複雜圖表的 removeChild 錯誤
現象:
Error: Evaluation failed: DOMException: Failed to execute 'removeChild' on 'Node':
The node to be removed is not a child of this node.
原因: markdown-it-mermaid/index.js 中移除 style 標籤的代碼假設了不正確的 DOM 結構。
解決方案:保留 style 標籤(同時也能保留 classDef 樣式)
檔案: node_modules/hexo-renderer-multi-markdown-it/lib/renderer/markdown-it-mermaid/index.js
// 修改前(第 29-32 行) | |
const svg = await page.$eval('#container', container => { | |
container.lastChild.removeChild(container.getElementsByTagName('style')[0]) | |
return container.innerHTML | |
}) | |
// 修改後 | |
const svg = await page.$eval('#container', container => { | |
// 保留 style 標籤,讓 classDef 自定義樣式可以正常工作 | |
// 不再移除 style,避免丟失用戶自定義的 classDef 樣式 | |
return container.innerHTML | |
}) |
# 問題 B:classDef 自定義顏色不顯示
現象:使用 classDef 定義的自定義顏色無效,圖表只顯示黑白。
原因:移除 style 標籤會導致用戶自定義的 classDef 樣式丟失。
解決方案:保留 style 標籤(與問題 A 的解決方案相同)。
效果:
- ✅ 支援 classDef 自定義顏色
- ✅ 支援 Mermaid 內建主題(default, dark, forest, neutral)
# 問題 C:大圖顯示被壓縮
現象:複雜的大圖(如長流程圖)被壓縮到很小的空間,內容看不清楚。
原因:Shoka 主題的 CSS 限制了圖表最大高度為 300px。
解決方案:移除高度限制
檔案: themes/shoka/source/css/_common/components/third-party/mermaid/mermaid.styl
// 修改前 | |
.mermaid { | |
&.graph svg{ | |
max-height: 18.75rem; // 約 300px | |
} | |
} | |
// 修改後 | |
.mermaid { | |
&.graph svg{ | |
//max-height: 18.75rem; // 移除高度限制 | |
max-width: 100%; // 確保寬度不超出容器 | |
} | |
} |
重新生成 CSS:
hexo clean && hexo generate |
# 使用 classDef 自定義樣式
保留 style 標籤後,我們可以使用 Mermaid 的 classDef 功能自定義節點顏色。
# 方法 1:使用 classDef 自定義顏色(推薦)
```mermaid | |
graph TD | |
A[前端節點] --> B[後端節點] | |
C[成功節點] --> D[錯誤節點] | |
classDef frontendClass fill:#e3f2fd,stroke:#1976d2 | |
classDef backendClass fill:#f3e5f5,stroke:#7b1fa2 | |
classDef successClass fill:#c8e6c9,stroke:#388e3c | |
classDef errorClass fill:#ffcdd2,stroke:#d32f2f | |
class A frontendClass | |
class B backendClass | |
class C successClass | |
class D errorClass | |
``` |
優點:
- 完全自定義,可以精確控制每個節點的顏色
- 支援 Material Design 等設計系統的顏色
- 可以定義 stroke-width 等進階樣式
# 方法 2:使用 Mermaid 內建主題
在 _config.yml 中設定主題:
markdown: | |
plugins: | |
- plugin: | |
name: ./markdown-it-mermaid | |
enable: true | |
options: | |
theme: default # 可選:default, dark, forest, neutral |
主題預覽:
default- 淺色主題,適合白色背景dark- 深色主題,適合深色背景forest- 綠色主題neutral- 中性色主題
# 方法 3:混合使用
全域使用內建主題,個別圖表使用 classDef 覆寫:
# _config.yml | |
options: | |
theme: default |
```mermaid | |
graph TD | |
A[使用預設主題] --> B[使用自定義顏色] | |
classDef customClass fill:#ff6b6b,stroke:#c92a2a | |
class B customClass | |
``` |
# 補丁管理
所有 node_modules 的修改已通過 patch-package 持久化保存:
patches/hexo-renderer-multi-markdown-it+0.1.5.patch
已在 package.json 中配置 postinstall 腳本:
{ | |
"scripts": { | |
"postinstall": "patch-package" | |
}, | |
"devDependencies": { | |
"patch-package": "^8.0.1" | |
} | |
} |
每次執行 npm install 或 yarn install 後,補丁會自動應用。
# 使用 yarn
如果使用 yarn:
# 1. 安裝 patch-package 和 postinstall-postinstall | |
yarn add patch-package postinstall-postinstall --dev | |
# 2. 創建補丁 | |
yarn patch-package hexo-renderer-multi-markdown-it | |
# 3. 配置 postinstall(package.json 中已配置) | |
# "scripts": { "postinstall": "patch-package" } | |
# 4. 驗證 | |
yarn install # 補丁會自動應用 | |
hexo clean && hexo generate |
# 在其他專案中應用
# 方法 1:複製補丁文件
# 1. 複製 patches 目錄到新專案 | |
cp -r patches /path/to/new-project/ | |
# 2. 在新專案中安裝 patch-package | |
cd /path/to/new-project | |
npm install patch-package --save-dev | |
# 3. 添加 postinstall 腳本 | |
npm pkg set scripts.postinstall="patch-package" | |
# 4. 安裝依賴(會自動應用補丁) | |
npm install |
# 方法 2:手動應用修改
參考本文的修復步驟,手動修改對應文件。
# Mermaid 語法兼容性
注意: hexo-renderer-multi-markdown-it@0.1.5 使用 Mermaid v8.x。
支援的語法:
- ✅
graph TD/graph LR- 流程圖 - ✅
sequenceDiagram- 序列圖 - ✅
classDiagram- 類別圖 - ✅
stateDiagram- 狀態圖 - ✅
gantt- 甘特圖 - ✅
pie- 餅圖
不支援的語法(Mermaid v9+ 新增):
- ❌
flowchart TD- 使用graph TD代替 - ❌
flowchart LR- 使用graph LR代替
# 已知限制
- Mermaid 版本:目前使用 mermaid v8.x,不支援 v9+ 的新語法(如
flowchart) - Puppeteer 依賴:構建時需要啟動 Chromium,構建速度較慢
- 記憶體需求:複雜圖表可能需要更多記憶體
# 後記
這次除錯過程花了不少時間,但也讓我更深入理解了 Hexo 的渲染流程和插件系統。從最初的插件衝突,到 removeChild 錯誤,再到樣式和高度限制問題,每一步都需要仔細分析和測試。
希望這篇文章能幫助到遇到類似問題的人。
關鍵經驗:
- 善用 console.log:在關鍵節點添加調試輸出
- 理解執行順序:找出各個處理器的加載和執行順序
- 查看生成結果:實際查看生成的 HTML 來驗證問題
- 保存修改:使用 patch-package 避免修改丟失
- 完整測試:不僅測試基本功能,也要測試複雜場景和樣式