境内博客想用 Mermaid 画流程图,官方文档会让你引 jsdelivr CDN——实测不可靠。这篇是这个博客真实的落地路径:自托管 + 按需加载 + gzip,每一步都有验证命令和实测数字。

1. 拿到 mermaid.min.js(不走 CDN)

jsdelivr/GitHub 直连都不稳,npmmirror 的文件直链稳定可用:

# 先查最新版本
curl -s https://registry.npmmirror.com/mermaid/latest | \
  python3 -c "import sys,json;print(json.load(sys.stdin)['version'])"
# → 12.2.0

# 直链下载(注意 /files/ 路径形式)
mkdir -p static/js
curl -o static/js/mermaid.min.js \
  "https://registry.npmmirror.com/mermaid/12.2.0/files/dist/mermaid.min.js"

# 验证:文件尾部应有全局暴露语句
tail -c 200 static/js/mermaid.min.js
# → globalThis["mermaid"] = ...   ✓ 确认 UMD 全局可用

5,494,801 字节。直接上线太重?看第 4 步。

2. 渲染钩子:```mermaid 代码块变成图

站点级新建 layouts/_markup/render-codeblock-mermaid.html(Hugo render hook,两行):

<pre class="mermaid">
  {{ .Inner | htmlEscape | safeHTML }}
</pre>
{{ .Page.Store.Set "hasMermaid" true }}

文章里写普通代码块、语言标 mermaid 即可。关键在 Store.Set——它给模板间传了个标记。

3. 按需加载:只有含图的页面才背 5.5MB

覆写 layouts/_partials/extend_head.html(主题的 head 钩子):

{{- if .Store.Get "hasMermaid" }}
<script src="/js/mermaid.min.js" defer></script>
<script>
  window.addEventListener('DOMContentLoaded', function () {
    mermaid.initialize({
      startOnLoad: true,
      theme: 'dark',
      themeVariables: {
        background: '#16161e',
        primaryColor: '#1f2430',
        primaryTextColor: '#e8eaed',
        primaryBorderColor: '#6366f1',
        lineColor: '#8b8fa3',
        clusterBkg: '#12141c'
      },
      flowchart: { curve: 'basis', padding: 10 }
    });
  });
</script>
{{- end }}

两个决策说明:固定 dark 主题而不是跟随站点主题——图表容器与代码块统一深色,避免了浅色主题下图表配色割裂,也省掉主题切换时的重绘逻辑;themeVariables 逐项定制让节点底色、连线色与站点设计系统一致。

验证按需生效:

grep -l "mermaid.min.js" public/posts/tech -r | wc -l   # 只有含图页面
grep -c "mermaid.min.js" public/posts/tech/prompt-engineering/patterns/core-patterns/index.html
# → 0  无图页面不加载 ✓

4. gzip:5.5MB → 1.5MB

nginx server block 里加(CDN 没有的,自己给):

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 6;
gzip_types application/javascript text/css image/svg+xml application/json;

注意 gzip_types 必须显式列出 js/svg——nginx 默认只压 text/html,不配就是裸传。实测:

curl -s -o /dev/null -w "%{size_download}B\n" \
  -H "Accept-Encoding: gzip" https://blog.jocab.xyz/js/mermaid.min.js
# → 1572825B ≈ 1.5MB(原 5.5MB,压缩率 71%)

配合浏览器缓存(location /js/ { expires 30d; }),用户只有第一次访问含图页面时付这 1.5MB。

5. 全链路验收清单

# ① 文章里 mermaid 块正确渲染
curl -s https://site/some-post/ | grep -c 'class="mermaid"'
# ② 含图页面加载脚本、无图页面不加载(见第 3 步)
# ③ 脚本线上可取且已压缩(见第 4 步)
# ④ 视觉:无头 Chrome 截图确认图形真的画出来了
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --headless=new --virtual-time-budget=12000 \
  --screenshot=/tmp/mermaid-check.png https://site/some-post/

第 ④ 步别省:virtual-time-budget 给 JS 留渲染时间,截图能抓到"脚本加载了但图画失败"这类静默错误。


成本总结:一个 5.5MB 的静态文件、一段 15 行的钩子、六行 nginx 配置——换来全站任意文章零门槛画流程图/时序图,且无图页面零开销、境内访问零依赖。