Mermaid 工程化渲染、站点接入与安全治理手册
编辑器里好好的图,为什么发布后只剩语法错误
一次接口拆分评审结束后,开发者在编辑器预览里看到完整的调用图,提交后站点构建却失败;另一位同事把错误“修好”后,节点链接可以点击,但安全扫描又发现图源码能够插入 HTML。问题并不只是少一个括号。编辑器插件、静态站点插件、浏览器脚本和 CI 中的 CLI 可能加载不同 Mermaid 版本、主题、字体与安全配置,同一段源码因此会得到不同结果。
Mermaid 是“文本 -> 解析器 -> 布局 -> SVG/PNG/PDF”的渲染链。真正应进入版本库的是图源码、渲染配置、依赖锁和验证命令;导出图片只是特定版本和字体环境下的构建产物。只检查编辑器截图,会漏掉四类常见故障:语法兼容漂移、站点配置被图内配置覆盖、无头浏览器缺字体,以及远程渲染把内部架构文本送出信任边界。
下面使用 Mermaid 与 @mermaid-js/mermaid-cli 的 11.16.0 版本线建立可复现基线。版本会继续演进,升级前应查看 Mermaid releases 和 mermaid-cli releases,并以项目锁文件和 npm ls 的实际输出为准。
先把渲染器装进项目,而不是装进某个人的机器
Mermaid 库负责浏览器或应用内解析渲染;mermaid-cli 提供 mmdc 命令,并使用无头浏览器生成文件。官方 CLI 支持本地 npm 包、全局安装和容器入口,具体参数见 mermaid-cli 官方仓库。团队项目优先安装开发依赖并提交锁文件:
npm install --save-dev mermaid@11.16.0 @mermaid-js/mermaid-cli@11.16.0
npm ls mermaid @mermaid-js/mermaid-cli
npx mmdc --helpnpm ls 同时证明直接依赖和实际解析版本;只有 package.json 没有锁文件,干净机器仍可能得到另一棵依赖树。CLI 会带入 Puppeteer/Chromium 一类较大的运行依赖,第一次安装和 CI 冷缓存都要计入下载时间、磁盘和供应链扫描成本。
不希望在开发机安装 Node 依赖时,可以使用官方 GitHub Container Registry 镜像。镜像标签还应进一步解析为 digest,并由镜像准入流程扫描:
docker pull ghcr.io/mermaid-js/mermaid-cli/mermaid-cli:11.16.0
docker image inspect ghcr.io/mermaid-js/mermaid-cli/mermaid-cli:11.16.0 \
--format '{{index .RepoDigests 0}}'容器运行时只挂载图目录,使用非 root 用户,并且不要把整个仓库、SSH 目录或云凭证目录挂进去。Linux/Podman 的用户映射和 SELinux 挂载示例可在 CLI 官方仓库的 alternative installations 段落中核对。
第一张图要同时证明语法、语义与可访问性
创建 docs/diagrams/checkout-flow.mmd:
这里没有罗列图类型,而是表达一条可评审的运行链。节点名称描述责任,边描述协议或数据,数据库与事件总线是不同性质的依赖。accTitle 和 accDescr 会进入 SVG 的可访问标题和描述;Mermaid 的 可访问性说明 给出了生成的 title、desc 与 ARIA 关系。
生成 SVG:
mkdir -p build/diagrams
npx mmdc \
--input docs/diagrams/checkout-flow.mmd \
--output build/diagrams/checkout-flow.svg \
--backgroundColor transparent健康结果包括:命令退出码为 0,输出文件非空,SVG 中存在标题与中文节点文本:
test -s build/diagrams/checkout-flow.svg
grep -E '<title|<desc|结算服务' build/diagrams/checkout-flow.svgSVG 适合站点和代码评审,因为可缩放且通常体积较小;PNG 适合聊天工具、工单和不接受 SVG 的系统;PDF 适合固定页面的评审包。格式不是装饰选择:
npx mmdc -i docs/diagrams/checkout-flow.mmd -o build/diagrams/checkout-flow.png -w 1920 -s 2
npx mmdc -i docs/diagrams/checkout-flow.mmd -o build/diagrams/checkout-flow.pdfPNG 的 width 与 scale 会直接放大像素、文件体积、渲染内存和 CI 时间。PDF 与 PNG 还要实际打开检查分页、透明背景和字体;文件存在不等于可读。
用反向实验留下能定位的失败证据
把边标签的右竖线删掉,形成 docs/diagrams/checkout-flow-bad.mmd:
flowchart LR
Browser[浏览器] -->|HTTPS Gateway[API 网关]运行:
npx mmdc \
-i docs/diagrams/checkout-flow-bad.mmd \
-o build/diagrams/checkout-flow-bad.svg
echo "exit=$?"预期得到 parser error、源码附近的行列提示和非零退出码。CI 应让这个退出码阻断合并,而不是提交一张旧 SVG 继续发布。若错误只在站点出现,依次比对:
npm ls mermaid @mermaid-js/mermaid-cli
node --version
npx mmdc --help
git diff -- package.json package-lock.json pnpm-lock.yaml yarn.lock另一个反向实验用于识别字体漂移。把 fontFamily 改成一款只存在于某位设计师电脑上的字体,在干净容器中重新导出,再对比 SVG 中实际样式和 PNG 截图。常见证据是中文回退字体不同、文字宽度变化、节点变高或标签被截断。修复不是继续调节点宽度,而是把获准字体安装进渲染镜像,或使用各构建环境都具备的字体栈。
配置不是美化文件,它决定运行边界
项目级 mermaid.config.json 可以给 CLI 和站点共用一组基线:
{
"theme": "base",
"securityLevel": "strict",
"fontFamily": "Noto Sans SC, Microsoft YaHei, Arial, sans-serif",
"maxTextSize": 30000,
"maxEdges": 300,
"suppressErrorRendering": true,
"flowchart": {
"htmlLabels": false,
"curve": "linear"
},
"themeVariables": {
"primaryColor": "#dcecff",
"primaryTextColor": "#16202a",
"primaryBorderColor": "#3f6f96",
"lineColor": "#52616f",
"background": "#ffffff"
}
}npx mmdc -c mermaid.config.json \
-i docs/diagrams/checkout-flow.mmd \
-o build/diagrams/checkout-flow.svg字段会改变不同层面的行为:
| 字段 | 改变什么 | 配错后的证据 |
|---|---|---|
securityLevel | HTML、链接和脚本交互的信任级别 | loose 让不可信图源码拥有更大的页面行为面 |
maxTextSize | 单份图源码可解析的文本上限 | 过小拒绝合法大图,过大增加内存与拒绝服务风险 |
maxEdges | 图中边的数量上限 | 依赖图突然爆炸时渲染时间和布局内存上升 |
suppressErrorRendering | 语法错误时是否向 DOM 插入错误图 | 关闭后站点可能把内部源码片段显示给读者 |
fontFamily | 排版宽度、中文覆盖和品牌一致性 | CLI 与浏览器输出尺寸不同、文字回退或方框 |
htmlLabels | 标签是否使用 HTML/foreignObject | 导出器、CSP、邮件和图片处理链兼容性不同 |
themeVariables | 色彩、对比度和派生颜色 | 暗色页不可读、打印灰阶丢失、升级后视觉漂移 |
Mermaid 的 配置 schema 把 securityLevel、maxTextSize、maxEdges 等列为安全相关配置;站点应通过 secure 列表阻止图作者覆盖它们。图内旧式 %%{init: ...}%% directive 已被官方标记为弃用,新图应使用 frontmatter config,详见 配置来源与覆盖顺序 和 directive 弃用说明。
主题定制只能以 base 为可修改基底。颜色用十六进制值,不要假设 CSS 颜色名会被主题引擎处理;完整变量与派生关系见 主题配置。品牌色通过对比度与灰阶打印检查后才能成为团队模板,不能把“视觉统一”建立在低对比文本上。
接进站点时,把解析与渲染分成两道门
Markdown 平台原生支持 Mermaid 时,先确认它实际使用的 Mermaid 版本、允许的 frontmatter、CSP 与安全级别。官方 集成目录 列出了 GitHub、GitLab、VuePress、VitePress 等入口,但社区插件的维护、版本和安全策略仍由各插件负责,不能因为目录中有名字就免除准入检查。
自建站点可直接使用 Mermaid API。初始化只做一次,图源码变化时先解析,再渲染:
import mermaid from "mermaid";
mermaid.initialize({
startOnLoad: false,
securityLevel: "strict",
secure: [
"secure",
"securityLevel",
"startOnLoad",
"maxTextSize",
"maxEdges",
"suppressErrorRendering"
],
maxTextSize: 30000,
maxEdges: 300,
suppressErrorRendering: true,
theme: "base"
});
export async function renderDiagram(element, source, id) {
await mermaid.parse(source);
const { svg, bindFunctions } = await mermaid.render(id, source);
element.innerHTML = svg;
bindFunctions?.(element);
}mermaid.parse 只验证语法,不证明字体、布局、链接和容器尺寸正确;render 才会生成 SVG。两步拆开后,编辑器可以在不污染页面的情况下展示解析错误,CI 仍需用 CLI 生成最终产物。API 行为与错误处理见 Mermaid usage。
不可信用户能够提交图源码时,strict 是起点,不是全部防线。还要限制请求体大小、边数量、渲染并发、超时和输出大小;渲染 worker 使用只读文件系统、无云凭证身份和受限网络;生成的 SVG 经过组织认可的清洗与 CSP 策略后再内联。若只用 <img src> 展示静态 SVG,交互能力会减少,攻击面也更容易界定。
静态站点更稳妥的链路是构建期渲染:
*.mmd -> 语法检查 -> 固定版本 CLI -> SVG -> 站点资源目录 -> 链接检查客户端渲染把解析和布局成本转移给每个访问者,并会受浏览器、字体、CSP 和禁用 JavaScript 影响;构建期渲染增加 CI 时间和产物存储,却换来稳定快照、缓存与发布前失败。图少且需要交互时可客户端渲染,公开知识库和审计文档通常更适合构建期 SVG。
不要把内部图源码交给未知渲染端点
本地 CLI 不需要账号或服务端凭证。浏览器直接渲染也不需要把源码发送给 Mermaid 官方服务。任何“把源码 POST 到 URL、返回图片”的远程渲染方案都会获得图中的服务名、信任边界、数据流、故障策略和可能出现的内部地址。
采用远程服务前至少确认:
数据是否用于日志、缓存、训练或故障追踪,留存多久,能否删除。传输和静态加密、处理区域、子处理方与访问审计是否满足组织要求。URL 是否直接编码完整图源码;即使服务声称不存储,代理、浏览器历史、访问日志和工单仍可能记录 URL。
超时、并发、最大图尺寸、失败重试和费用由谁承担。服务故障时能否切回锁定版本的本地 CLI。
Mermaid 开源库和 CLI 使用 MIT 许可,不要求购买席位;Mermaid Chart 是独立的托管协作产品,团队、SSO、配额和商业价格会变化,采购时应从 Mermaid Chart 官方价格页 逐项核对,而不是把托管产品能力写成开源库能力。
从一次渲染追到底层机制
解析器先把文本转换为图模型,布局引擎根据节点、边、方向与约束计算坐标,渲染器再生成 SVG。复杂度不只取决于源码字节数:完全连接的节点会快速增加边数,长标签会扩大布局空间,HTML label 会带入浏览器排版,外部字体加载会改变文字度量。CLI 又把这一链路放进 Chromium,因此 CPU、内存、进程数和字体缓存都会影响构建。
容量治理应记录趋势而不是照搬一个固定秒数:
每份图的源码字节、节点数、边数与输出字节。冷启动和热缓存渲染耗时分布。同时渲染时的峰值内存、超时与失败类型。
依赖下载和浏览器缓存体积。站点发布后的 SVG 数量、总大小与缓存命中。
当单图持续逼近边数或文本上限时,优先按读者任务拆成上下文图、关键调用图和故障路径图。继续提高上限会让图同时失去可读性和可运维性。需要精确坐标、复杂手工排版或大型模型查询时,文本式 Mermaid 也许已不是合适的主工具。
团队模板要能阻止图与系统一起漂移
推荐在仓库中固定这些资产:
docs/diagrams/*.mmd
docs/diagrams/README.md
mermaid.config.json
scripts/render-diagrams.mjs
package.json
package-lock.json
build/diagrams/ # 是否提交由发布策略决定每张架构图都要能追到 owner、服务目录或接口契约、相关 ADR 和最后一次验证它的变更。不要在图里写“最新架构”;用稳定文件名承载当前状态,历史交给 Git。涉及接口删除、服务拆分、消息主题、数据库所有权或信任边界的代码变更,应把对应 .mmd 纳入同一个 PR。
CI 至少执行:
npm ci
npm ls mermaid @mermaid-js/mermaid-cli
rm -rf build/diagrams
npx mmdc -c mermaid.config.json -i docs/diagrams -o build/diagrams
git diff --exit-code -- docs/diagrams mermaid.config.json实际的批量输入参数应以锁定 CLI 的 --help 为准。若团队提交生成 SVG,再增加“重新生成后 git diff --exit-code”;若不提交生成物,则由发布流水线保存带提交 SHA 的制品并做链接检查。视觉回归只对关键图使用,避免布局引擎的小幅坐标变化制造大面积噪声。
供应链升级按三层处理:先在分支更新 Mermaid、CLI 与锁文件;再渲染全部图,检查语法错误、输出差异、字体、链接与可访问标题;最后更新渲染镜像 digest 和回滚版本。不要只升级站点插件而保留旧 CLI,也不要让全局 mmdc 越过项目锁文件。
故障证据怎样收敛到修复动作
| 现象 | 第一组证据 | 常见原因 | 修复与复测 |
|---|---|---|---|
| 编辑器成功、CI parser error | 两端 Mermaid 版本与锁文件 | 插件和 CLI 语法线不同 | 对齐版本,CLI 重渲染全部图 |
| CLI 成功、站点空白 | 浏览器控制台、CSP、容器尺寸 | 脚本未加载、CSP 阻断、隐藏容器宽度为零 | 修复加载时机与 CSP,真实页面复测 |
| 中文方框或节点变形 | 渲染环境字体列表、SVG 样式、PNG 截图 | 无头浏览器没有目标字体 | 安装获准字体并锁镜像,干净环境重渲染 |
| 大图超时或内存激增 | 文本、节点、边、输出大小趋势 | 图模型过密、并发无界 | 拆图,限制 maxEdges、并发和超时 |
| 图内链接突然可点击 | securityLevel 与覆盖来源 | 图内配置或插件把安全级别降为 loose | 站点锁定 secure 配置,重新发布 |
| 导出与页面主题不一致 | CLI config、站点 initialize、图 frontmatter | 三处主题来源不同 | 确立站点基线与允许覆盖字段 |
清理个人实验时删除临时输出和浏览器缓存,不删除项目锁文件:
rm -rf build/diagrams
npm cache verify
docker image rm ghcr.io/mermaid-js/mermaid-cli/mermaid-cli:11.16.0卸载项目依赖应通过包管理器并提交锁文件变化:
npm uninstall --save-dev @mermaid-js/mermaid-cli mermaid回滚升级时恢复 package.json、锁文件、配置文件和渲染镜像 digest,再从干净依赖执行全量渲染。只恢复旧 SVG 会留下“源码、渲染器和产物互相不对应”的隐患。
合并前最后看一遍运行事实
图源码、配置、锁文件和渲染命令都在版本控制中。正向图生成 SVG,反向语法实验返回非零退出码。编辑器、站点和 CI 的 Mermaid 主版本与安全配置一致。
securityLevel、maxTextSize、maxEdges 不能被不可信图源码覆盖。中文字体在干净渲染环境中存在,SVG、PNG 和站点实页没有截断。图包含可访问标题与描述,颜色经过对比度和灰阶检查。
远程渲染的数据、日志、费用、身份和退出路径已经评审。图的 owner、相关服务、接口、ADR 和变更触发条件可追踪。依赖、CLI、Chromium 和容器 digest 纳入漏洞扫描与升级窗口。
大图的节点、边、输出大小、耗时和峰值内存有趋势记录。
Mermaid 的效率不来自“几行文字就有一张图”,而来自源码、实现和评审证据能一起变化。把渲染版本、安全配置、字体、失败证据和 owner 固定下来,图才会从一次性插图变成可维护的架构资产。
