# 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 条回复还没有人回复,快来发表你的看法吧!