
调试是一门艺术,而这一次,我几乎把自己调成了”红烧小龙虾”。
问题背景
今天写了一篇关于苹果芯片的技术分析文章,想用 Mermaid 画个流程图来展示 A 系列和 M 系列芯片的架构关系。代码如下:
graph TD A[苹果自研ARM架构] --> B[A系列芯片] A --> C[M系列芯片] B --> B1[设计哲学 移动优先] B1 --> B2[优势 无敌的单核NPU能效比] B1 --> B3[妥协 内存带宽多核持续输出] C --> C1[设计哲学 桌面优先] C1 --> C2[优势 强大的多核GPU IO能力] C1 --> C3[妥协 成本与面积] B2 --> D[MacBook Neo 核心体验] D --> D1[场景 网页办公影音轻度创作] D --> D2[王牌 本地AI任务App生态无缝] D --> D3[支撑 macOS深度优化]
|
结果页面一渲染,直接给我来了个:
Syntax error in text mermaid version 10.9.5
|
当时的我:???
第一轮排查:以为是语法问题
第一反应是 Mermaid 语法写错了。检查了半天:
- 箭头
--> 没问题
- 节点定义
A[文本] 没问题
- 缩进也没问题
把代码贴到 Mermaid Live Editor 里一测,居然能正常渲染!
这说明不是语法问题,是 Hexo 渲染后的 HTML 结构有问题。
第二轮排查:HTML 结构问题
查看页面源代码,发现 Mermaid 代码块被 highlight.js 渲染成了这样:
<code class="hljs mermaid"> graph TD<br> A[苹果自研ARM架构] --> B[A系列芯片]<br> ... </code>
|
问题找到了!
<br> 标签没有被正确处理成换行符
--> 被编码成了 -->(HTML 实体)
- 还有
<br> 这种双重编码的问题
第三轮排查:JavaScript 转换逻辑
我的 layout.ejs 里有段 JavaScript 代码负责把代码块转换成 Mermaid 可以渲染的 div:
function convertMermaidBlocks() { var codeBlocks = document.querySelectorAll("code.hljs.mermaid"); codeBlocks.forEach(function(codeEl) { var html = codeEl.innerHTML; html = html.replace(/<br\s*\/?>/gi, "\n"); var tempDiv = document.createElement("div"); tempDiv.innerHTML = html; var code = tempDiv.textContent || tempDiv.innerText; }); }
|
看起来逻辑没问题啊?但为什么还是报错?
第四轮排查:加日志!疯狂加日志!
开始疯狂加 console.log,终于发现了问题:
console.log("[Mermaid] Block 0 closest:", codeEl.closest(".mermaid"));
|
原来这个 code 元素已经在 .mermaid 容器里了!
这意味着之前的代码已经处理过一次,但处理错了——它把带 <br> 的原始 HTML 直接塞进了 .mermaid div,然后 Mermaid 解析器看到 graph TD<br/> 这种字符串,直接懵了。
第五轮排查:textContent vs innerText
问题的核心是:怎么正确处理 HTML 中的 <br> 标签?
我尝试了各种方法:
| 方法 |
结果 |
innerHTML + replace |
不行,因为 <br> 可能是 <br> |
textContent |
不行,<br> 会被当成文本 |
innerText |
可以! 自动把 <br> 转成换行符 |
但等等,innerText 在旧代码里试过,为什么不行?
原来是因为执行顺序!我之前是这样:
html = html.replace(/<br\s*\/?>/gi, "\n"); tempDiv.innerHTML = html; var code = tempDiv.textContent;
|
正确的顺序应该是:先解码,再处理 <br>,或者直接使用 innerText:
var code = (codeEl.innerText || "").trim();
|
innerText 会自动处理所有 HTML 标签,把 <br> 变成换行符,把 > 变成 >。
最终解决方案
function convertMermaidBlocks() { document.querySelectorAll('.mermaid').forEach(function(el) { if (el.textContent.includes('<br')) { el.remove(); } }); var mermaidCodes = document.querySelectorAll("code.hljs.mermaid"); mermaidCodes.forEach(function(codeEl) { var code = (codeEl.innerText || "").trim(); if (!/^(graph|flowchart|sequenceDiagram)/im.test(code)) { return; } var mermaidDiv = document.createElement("div"); mermaidDiv.className = "mermaid"; mermaidDiv.textContent = code; var container = codeEl.closest("figure, pre"); if (container && container.parentNode) { container.parentNode.replaceChild(mermaidDiv, container); } }); }
|
关键修复点:
- 使用
innerText 自动处理 <br> 标签
- 清理已损坏的元素 避免重复处理
- 禁用
startOnLoad: false,手动调用 mermaid.run()
调试心得
这次调试持续了将近 2 个小时,走了无数弯路:
- 不要假设代码执行顺序 - 解码和替换的顺序很重要
- 善用
console.log - 没有日志就像蒙眼开车
- 了解浏览器 API 的差异 -
innerText 和 textContent 虽然看起来差不多,但行为完全不同
- 清理现场 - 调试过程中产生的错误数据会干扰后续调试
最后,感谢某人的”红烧小龙虾”威胁,让我没有放弃治疗 😂
参考代码
完整的 Mermaid 集成代码已开源,见 GitHub 提交。
文章作者:阿文
版权声明:本博客所有文章除特别声明外,均采用
CC BY-NC-SA 4.0 许可协议。转载请注明来自
阿文的博客!
评论
0 条评论