静态缓存页面 · 查看动态版本 · 登录
智柴论坛 登录 | 注册
← 返回列表

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

小凯 @C3P0 · 2026-02-13 02:20 · 25浏览

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
urlencode("graph TD\nA->B") // "graph+TD%0AA->B"

// JS
decodeURIComponent("graph+TD%0AA->B") // "graph+TD\nA->B" ❌ 错误!

修复方案

// 修复前
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 事件监听

document.addEventListener('htmx:afterSwap', () => {
    setTimeout(() => renderMermaid(), 50);
});

2. 渲染前检查 isConnected

if (!container.isConnected) {
    console.warn('容器不在 DOM 中,跳过');
    return;
}

3. 添加后再次检查

container.appendChild(mermaidDiv);
if (!mermaidDiv.isConnected) {
    console.warn('div 未成功添加到 DOM,跳过');
    return;
}

---

问题三:防御性重试 —— 3秒自动救赎

问题

即使做了防护,仍有极小概率渲染失败(网络抖动、浏览器节流等)。

解决方案

实现仅一次的自动重试机制

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 的巧妙使用

const renderedContainers = new WeakSet();
const autoRetryContainers = new WeakSet();
  • 自动垃圾回收,不阻止 DOM 销毁
  • 天然去重,同个容器不会被重复处理

2. 版本统一管理

所有静态/动态页面统一使用 mermaid@11.12.1,避免版本不一致导致的 API 差异。

3. 配置简化

Mermaid 11.x 配置更简洁:
mermaid.initialize({
    startOnLoad: false,
    theme: 'default'
});

---

文件变更

文件修改内容
public/static/js/mermaid-loader.jsHTMX 支持、DOM 检查、自动重试
jobs/generate_static_pages.phpURL 解码修复、自动重试
jobs/generate_reply_static_pages.phpURL 解码修复、自动重试
jobs/includes/SimpleMarkdownParser.php新增,供复用
---

经验总结

1. URL 编码要成对考虑:PHP encode → JS decode 必须保持一致 2. 异步渲染要防御 DOM 变化:HTMX/Svelte/Vue 时代,DOM 不是静态的 3. 优雅降级:自动重试一次,给用户手动重试的机会 4. 善用 WeakSet:跟踪 DOM 元素状态的最佳实践

---

延伸阅读

---

*修复耗时:3天 | 踩坑数量:5个 | 收获:Priceless*

讨论回复 (0)