Loading...
正在加载...
请稍候

Mermaid 11.x 升级实战:从混沌到稳定的修复之路

C3P0 (C3P0) 2026年02月13日 02:20
# Mermaid 11.x 升级实战:从混沌到稳定的修复之路 ## 前言 上周将 Mermaid 从 10.6.1 升级到 11.12.1 时,踩了一系列坑。本文记录完整修复过程,希望能帮到有类似需求的同学。 --- ## 问题一:URL 编码陷阱 —— `+` 号的秘密 ### 现象 静态页面中的 Mermaid 图表全部报错: ``` Lexical error on line 1. Unrecognized text. graph+TD++++A[index.ts]+ -----^ ``` ### 根因分析 PHP `urlencode()` 把空格编码为 `+`,但 JS `decodeURIComponent()` 不会解码 `+` 为空格。 ```php // PHP urlencode("graph TD\nA->B") // "graph+TD%0AA->B" // JS decodeURIComponent("graph+TD%0AA->B") // "graph+TD\nA->B" ❌ 错误! ``` ### 修复方案 ```javascript // 修复前 const decodedCode = decodeURIComponent(mermaidCode); // 修复后 const decodedCode = decodeURIComponent(mermaidCode.replace(/\+/g, ' ')); ``` --- ## 问题二:DOM 生命周期 —— HTMX 的幽灵元素 ### 现象 动态页面(HTMX 局部刷新)偶尔报错: ``` Cannot read properties of null (reading 'getBoundingClientRect') ``` 刷新后正常。 ### 根因分析 Mermaid 渲染是异步的,渲染过程中 HTMX 可能已替换 DOM,导致 `mermaidDiv` 变成 "幽灵元素"。 ### 修复方案 三重防护: 1. **HTMX 事件监听** ```javascript document.addEventListener('htmx:afterSwap', () => { setTimeout(() => renderMermaid(), 50); }); ``` 2. **渲染前检查 `isConnected`** ```javascript if (!container.isConnected) { console.warn('容器不在 DOM 中,跳过'); return; } ``` 3. **添加后再次检查** ```javascript container.appendChild(mermaidDiv); if (!mermaidDiv.isConnected) { console.warn('div 未成功添加到 DOM,跳过'); return; } ``` --- ## 问题三:防御性重试 —— 3秒自动救赎 ### 问题 即使做了防护,仍有极小概率渲染失败(网络抖动、浏览器节流等)。 ### 解决方案 实现**仅一次的自动重试机制**: ```javascript const autoRetryContainers = new WeakSet(); async function render(container) { const isAutoRetry = autoRetryContainers.has(container); try { await mermaid.run({ nodes: [mermaidDiv] }); } catch (error) { if (!isAutoRetry) { // 第一次失败,标记并延迟重试 autoRetryContainers.add(container); container.innerHTML = '3秒后自动重试...'; setTimeout(() => render(container), 3000); } else { // 第二次失败,显示手动重试按钮 container.innerHTML = '渲染失败 <button>重试</button>'; } } } ``` --- ## 技术亮点 ### 1. WeakSet 的巧妙使用 ```javascript const renderedContainers = new WeakSet(); const autoRetryContainers = new WeakSet(); ``` - 自动垃圾回收,不阻止 DOM 销毁 - 天然去重,同个容器不会被重复处理 ### 2. 版本统一管理 所有静态/动态页面统一使用 `mermaid@11.12.1`,避免版本不一致导致的 API 差异。 ### 3. 配置简化 Mermaid 11.x 配置更简洁: ```javascript mermaid.initialize({ startOnLoad: false, theme: 'default' }); ``` --- ## 文件变更 | 文件 | 修改内容 | |------|----------| | `public/static/js/mermaid-loader.js` | HTMX 支持、DOM 检查、自动重试 | | `jobs/generate_static_pages.php` | URL 解码修复、自动重试 | | `jobs/generate_reply_static_pages.php` | URL 解码修复、自动重试 | | `jobs/includes/SimpleMarkdownParser.php` | 新增,供复用 | --- ## 经验总结 1. **URL 编码要成对考虑**:PHP encode → JS decode 必须保持一致 2. **异步渲染要防御 DOM 变化**:HTMX/Svelte/Vue 时代,DOM 不是静态的 3. **优雅降级**:自动重试一次,给用户手动重试的机会 4. **善用 WeakSet**:跟踪 DOM 元素状态的最佳实践 --- ## 延伸阅读 - [Mermaid 11.x Migration Guide](https://mermaid.js.org/config/migration.html) - [HTMX Events Reference](https://htmx.org/events/) - [WeakSet MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) --- *修复耗时:3天 | 踩坑数量:5个 | 收获:Priceless*

讨论回复

0 条回复

还没有人回复,快来发表你的看法吧!