有段时间我需要在完全隔离的内网环境里维护一份技术文档,图形偏偏多得很。团队一直用Mermaid写流程图和时序图,问题在于,在线编辑器用不了,平时那套mermaid-cli也没跑通,最后只能在开发机渲染好再拷图,来回折腾。等我把整套离线链路整明白,发现从安装Node到导出PNG、SVG,里头的坑比想象中多不少。这篇就按实操顺序捋一遍,给同样被隔离环境卡住的人少走几步弯路。
1. 离线使用场景和核心需求拆解
1.1 先分清“离线”到底指什么
很多人一上来就在找“Mermaid离线包”,其实需求往往不一样。我归纳下来基本是四种:第一种,人在内网,想要一个能渲染Mermaid的Web页面,适合在浏览器里画图;第二种,文档里嵌了Mermaid代码块,希望在本地Markdown工具或静态站点里正常出图;第三种,图表要作为图片插入Word、PPT或者提交到代码仓库,必须渲染成PNG或SVG文件;第四种,开发阶段就要把Mermaid能力集成到内部系统里,本地没网也能跑。
这四种场景对应的方案完全不同。第一种可以用Live Editor的离线打包版本,第二种靠支持Mermaid的本地编辑器,第三种和第四种则几乎绕不开mermaid-cli。从热搜词里也能看出来,“mermaid live editor”和“mermaid在线渲染”这种关注度一直很高,但真正难倒人的是第三种——把Mermaid代码变成PNG和SVG文件,而这个问题恰恰很多教程一笔带过。
1.2 为什么会卡在PNG/SVG导出上
W为什么偏偏倒出就卡住了?根源在Mermaid的运行方式上。Mermaid本质是一段JavaScript库,要在浏览器内核里渲染成图形。浏览器有,但命令行没有,所以要导图就得有一个“无头浏览器”参与绘制。mermaid-cli底层调用的正是Puppeteer,而Puppeteer又要下载对应版本的Chromium。这一条链在联网机器上很顺畅,在内网却每一步都可能断掉。
这解释了为什么很多人明明装了mermaid-cli,一执行mmdc就报各种错误,最终都指向浏览器下载失败或启动不了。理解了这条原理,后面的所有操作就有了方向:离线环境下要解决Node安装、npm包离线导入、Chromium二进制准备三个问题,三者缺一不可。这也是本文想重点展开的核心环节。
2. 方案选型:离线渲染Mermaid的几种可行路线
2.1 从编辑器到命令行的完整路径对比
要把Mermaid离线用起来,方案不止一条。我实际摸过几种,列个表对比一下,方便按自己的场景选。
| 方案 | 适合场景 | 导出PNG | 导出SVG | 离线友好度 | 备注 |
|---|---|---|---|---|---|
| 官方Live Editor离线版 | 偶尔画一张,不想写命令行 | 手动截图 | 支持直接下载 | 中 | 需从官网或已联网机器获取打包文件 |
| VS Code插件+本地浏览器预览 | 日常编辑,文档协作 | 不支持直接导出 | 可复制代码 | 中 | Markdown Preview Mermaid Support 类插件 |
| Typora | 个人笔记,本地写文档 | 复制图片 | 复制SVG | 高 | 图随文档走,改代码要手动同步 |
| drawio嵌入Mermaid | 需要在线编辑已有图 | 方便 | 方便 | 高 | 本质是导到drawio再改,跨工具折损 |
| mermaid-cli | 批处理、自动化、高精度输出 | 支持 | 支持 | 低→可配置 | 推荐用于正式输出图片的场景 |
我自己最终留下的是mermaid-cli,原因很实在:能自动化、参数可控、出图质量稳定。其他方案适合轻量场景,一旦图多了、要求统一风格、要跑在流水线里,回到命令行几乎是必然选择。
2.2 为什么大多数情况绕不开mermaid-cli
假设你的文档里有20张Mermaid图,手动一张张开页面渲染再截图,精度和效率都很差。就算能下载SVG,PNG的分辨率、背景色、缩放比例每个都不同,完全没法统一。mermaid-cli则支持一条命令读入所有指定的.mmd文件,批量出图,还能通过参数统一图片尺寸、背景、缩放倍数,对文档工程化是刚需。
mermaid-cli导出的PNG背后其实是Chromium截屏,不是矢量重绘,所以它能保证所见即所得。SVG则是直接生成的矢量文件,做印刷或者后续二次编辑都方便。这两类格式对应不同下游需求,通常都需要,所以命令行工具反而是最不容易被替代的路线。
2.3 顺带评估drawio和Live Editor离线路线
那是不是只用mermaid-cli就够了?也不是。如果团队用了drawio,本身支持通过Mermaid 插件让用户在图编辑器和文本语法之间切换,这种交互体验是纯命令行给不了的。不过drawio的离线安装和Mermaid插件在隔离环境里部署,又是一套依赖管理,配置成本不一定低。
官方Live Editor离线版则适合“临时用但不想装任何东西”的场景。把整个前端页面打包下载,内网打开就能编辑,渲染完成之后用浏览器自带功能存SVG,或者用截图工具拿PNG。这种做法胜在零依赖,不足是没有批量能力、截图像素不好控制。所以我的建议很直接:只想画一两张,用离线编辑器;要正经产出图片,直接学mmdc,一次配置长期受益。
3. 实操准备:内网搭建Mermaid渲染工具链
3.1 先解决运行时:离线安装Node.js
mermaid-cli运行在Node环境里,所以第一步是装Node。内网装Node和普通软件不太一样,没法用nvm拉远程包,好在官方提供了Windows、Linux、macOS的全平台二进制包。在能联网的机器上,从Node官网下对应系统版本的tar.gz或.msi,拷进内网解压即可。
以Linux服务器为例,例如拿到的包是node-v18.20.4-linux-x64.tar.xz,放到/opt目录下,解压后配置环境变量:
tar -xf node-v18.20.4-linux-x64.tar.xz -C /opt/ ln -s /opt/node-v18.20.4-linux-x64/bin/node /usr/local/bin/node ln -s /opt/node-v18.20.4-linux-x64/bin/npm /usr/local/bin/npm node -v npm -v解压版Node有个小坑:npm会去找全局目录,如果没有配置权限,后面装包可能报EACCES。稳妥做法是给npm设置一个用户级目录,或者直接用root执行、提前把~/.npm-global配置好。我第二次在内网部署时就是这样先踩了权限的坑。另外,如果系统里有老版本Node,建议选择Node 18或20这些LTS版本,mermaid-cli的新版本对Node版本有要求,太老跑不起来。
3.2 在联网机器上准备完整的离线npm包
离线安装npm包的核心思路是:让npm把mermaid-cli及其全部依赖下载到一个缓存目录,再把这个目录整个拷到内网。推荐用npm pack或者npm cache配合npm install --offline来做。个人更建议用npm cache方式,因为依赖较多时更省心。
在联网机器上执行:
mkdir /tmp/mmdc-offline cd /tmp/mmdc-offline npm init -y npm install @mermaid-js/mermaid-cli此时node_modules已经生成,npm缓存里也有了对应包。要精确控制版本,可以指定版本号,例如@mermaid-js/mermaid-cli@10.9.1,避免联机和内网版本不一致导致找不到对应Puppeteer的坑。接下来把整个/tmp/mmdc-offline目录打包传到内网,体积可能有几百MB,因为里面有Chromium。这个体积正常,不要觉得异常。
另一种可行方法是先在有网机器上跑一次npm cache add 包名,然后拷贝整个~/.npm/_cacache目录到内网对应位置,再用npm install --offline安装。但实测下来,直接拷贝带node_modules的项目目录更省事,尤其对隔离环境更友好,因为不用管缓存结构是否匹配。
3.3 Chromium二进制缺失的终极处理方案
把带node_modules的目录拷到内网后,如果Puppeteer的默认浏览器路径没配好,运行时仍会报“Could not find Chromium”。原因在于Puppeteer在安装时调用了@puppeteer/browsers脚本去下载Chromium,如果下载失败或路径被跳过,缓存里没有可用浏览器。
解决方案有两种。第一种简单粗暴:在安装时设置环境变量PUPPETEER_SKIP_DOWNLOAD=true跳过下载,然后单独找一台已经安装Chrome/Chromium的Windows或Linux机器,把目录复制过去,运行时通过--puppeteer-config指定路径。第二种更可控:在联网机器上单独跑一次下载脚本缓存Chromium:
// download-chrome.js const { install } = require('@puppeteer/browsers'); (async () => { await install({ browser: 'chrome', buildId: 'stable', cacheDir: '/tmp/chrome-cache', }); })();执行后把/tmp/chrome-cache整个带到内网,运行时让Puppeteer读这个缓存目录。需要说明的是,这种方式在国内网络环境下,能否稳定下载依赖对象本身的连通性,这里不展开,但如果你在隔离程度高的环境,最好提前验证一次是否有可用的外部下载条件,若没有就把“拷目录”方案作为首选项。
我在实际部署中用的是备用方案:找内网一台已经装过Chrome的机器,把chrome.exe所在路径记录下来,用配置文件的方式让mermaid-cli直接走系统浏览器。这样省下了Chromium的下载和缓存问题,稳定性也不错。
3.4 用puppeteer-config文件管理浏览器路径
mermaid-cli支持通过一份JSON配置文件指定Puppeteer的选项,这是离线环境的标配操作。在项目目录里新建puppeteer-config.json,内容如下:
{ "executablePath": "/opt/chrome/chrome", "args": ["--no-sandbox", "--disable-setuid-sandbox"] }其中--no-sandbox主要在Linux服务器上跑时有帮助。平时我不建议root用户跑无头浏览器时不加这个参数,实际碰到过默认沙箱权限不足导致白屏的案例。这一配置需要在执行mmdc时用-p参数指向:
./node_modules/.bin/mmdc -p puppeteer-config.json -i input.mmd -o output.png配置好这一步,才算把离线的最后一个堵点打通了。之后所有导图操作都能正常跑通,体验和联网环境差别不大。
4. 从Mermaid代码到PNG、SVG的完整导出实操
4.1 准备一份可用的测试图例和基础命令
先用一个最简单可复现的例子入门。新建文件test.mmd,内容如下:
graph TD A[需求收集] --> B[方案设计] B --> C{评审通过?} C -->|否| B C -->|是| D[开发实现] D --> E[测试验证] E --> F[上线发布]这是最基础的代码,实际上扩展成任意类型的flowchart、sequenceDiagram或gantt都适用,不过第一次测试最好用简单图,便于排错。此时执行导出命令:
./node_modules/.bin/mmdc -i test.mmd -o test.png ./node_modules/.bin/mmdc -i test.mmd -o test.svg如果工具链配置正常,目录下会生成两个文件。PNG默认是96dpi左右,对于屏幕显示足够,要用于印刷或者PPT全屏展示,建议提高输出分辨率。
4.2 导出一张高质量PNG的关键参数
PNG导出的核心是控制分辨率、缩放、背景、宽度。我第一次接触时以为图片尺寸由-w直接决定,其实-w控制的是最终输出的图片宽度,和真实可用尺寸有关。还有-s这个缩放因子,mermaid-cli官方定义为缩放系数,3表示300%,即放大三倍渲染,但不改变逻辑尺寸。
实践中最常用的组合是这样:
./node_modules/.bin/mmdc -i test.mmd -o test.png -s 3 -b white -w 1200其中-b设置背景色,默认是white,但很多场景要透明背景就设为transparent,比如把图贴到深色PPT或网页里。-w会把生成的图片按宽度约束重新缩放,搭配-s用的时候注意,两者不要同时控制同一维度,容易产生期望外的分辨率。通常做法是只设-s 2或-s 3,不做-w限制,让图片保持原始宽高比,兼容性更好。有次我为了把图片压到800px宽,直接加了-w 800,结果导出后文字也被拉伸模糊,后来改为靠缩放因子控制,才得到清晰且尺寸合理的图。
其实可以这样理解:mermaid渲染时先在内存里按96dpi生成位图,-s控制内存位图的放大倍数,相当于超采样抗锯齿,数值越高文字边缘越平滑,同时PNG文件自然变大。建议在2到4之间调试,太高的倍数对复杂图收益不大。
4.3 SVG导出的细节与后续处理方法
导出SVG则相对简单:
./node_modules/.bin/mmdc -i test.mmd -o test.svgSVG是矢量文件,不需要设置分辨率。它的问题更多在后期使用环节。Mermaid生成的SVG里会带一些引用的CSS类和字体信息,直接用浏览器打开一般正常,但如果用Illustrator等工具打开,可能出现文字偏移或字体缺失。我在导入Visio时就遇到过一次,后面通过调整SVG中引用的字体族为通用字体族解决。
另外,SVG文件中如果存在中文,要确保渲染环境里有中文字体。否则Chromium会按字体回退逻辑用一种没注册的字体代替,最终导出的SVG在自己机器上看着正常,换到没有对应字体的机器就乱。处理方式是在配置文件中指定字体路径,或者统一用系统常用中文字体如Microsoft YaHei、Noto Sans CJK SC。如果团队文档是跨平台全阅读,尽可能在流程图里少用特殊字符,常规汉字一般没事。
4.4 批量导出几十张图的自动化脚本
当图数量多起来,手敲命令不合适。我会把图放在./diagrams目录里,写一个脚本遍历处理:
#!/bin/bash set -e for mmd_file in diagrams/*.mmd; do base_name=$(basename "$mmd_file" .mmd) echo "processing $base_name ..." ./node_modules/.bin/mmdc -p puppeteer-config.json -i "$mmd_file" -o "output/${base_name}.png" -s 3 -b transparent ./node_modules/.bin/mmdc -p puppeteer-config.json -i "$mmd_file" -o "output/${base_name}.svg" done在Windows下可以写等价的PowerShell脚本,思路一致。执行前保证output目录存在,否则导出会失败。这里有个实用经验:脚本里加上--failOnError,如果某张图语法有误,命令会直接返回非0状态,在CI里能够中断构建,避免产出半成品没人发现。
5. 高频问题排查与避坑实录
5.1 经典错误:找不到Chromium或沙箱报错
这是离线环境遇到最多的一个问题。报错信息格式多为“Could not find Chrome”、“Failed to launch the browser process”或“SUID sandbox helper not found”。处理方式按三步排查:第一步,确认为mermaid-cli提供的Puppeteer配置文件路径被正确传递;第二步,确认配置文件中的executablePath指向的内置浏览器可执行文件真实存在且有执行权限;第三步,在Linux上确保添加了--no-sandbox参数。
从我个人经验看,第一次跑不起来,八成是executablePath写错了或路径下没有可执行文件。可以先手动执行一次ls -l /opt/chrome/chrome验证一下,而不是盲目改参数。
5.2 导出的图片中文变成方块或缺失
中文字体缺失是最影响观感的问题,尤其在服务器端导出。Linux服务器默认通常没有Windows下的宋体或微软雅黑,渲染时只能fallback,结果往往是方块。最直接的解决办法是在服务器上安装中文字体:
apt-get install fonts-noto-cjk或者将Windows系统的msyh.ttc复制到Linux的/usr/share/fonts目录,执行fc-cache -f刷新字体缓存。如果你的容器是精简版,可能没有fontconfig命令,需要一并安装。装好后重新跑一次导出,中文基本就正常了。
5.3 Mermaid语法对HTML标签的支持差异
Mermaid图里经常用<br/>换行或加粗标签,这些在在线编辑器里没问题,但mermaid-cli不同版本对HTML标签处理有差异。特别是flowchart节点里用<b>标签时,部分版本会解析成实体文本而不是标签,导致显示错乱。规避办法是优先使用Mermaid自身的换行语法,例如在节点文本中使用<br/>已经是官方示例,但更稳妥的办法是少在经典语法里依赖HTML渲染能力,该用sequenceDiagram中的<br/>时留意不同版本的表现。
如果只是给节点文本换行,用<br/>倒是通用,但如果要加颜色、背景这类富文本样式,推荐改用其他标记方式,或者干脆在SVG输出后用编辑器工具二次加样式,因为mermaid的富文本能力真的不是设计来承接复杂HTML的。
5.4 同一套图在不同环境渲染结果不同
这个问题发生频率不低,我在交接项目时碰到过几次。原因是mermaid版本不同、主题变量不同、字体不同。为了保持结果稳定,建议在项目里固定mermaid版本,做法是在package.json中锁定@mermaid-js/mermaid-cli的具体版本号,并统一使用某种主题,例如-t default、-t dark、-t forest,避免使用默认主题但依赖了某次版本更新带来的样式变化。
如果对内网出口受限,注意命令行工具可能每个季度升一次级,不要频繁更新,更新一次就要重新做一遍完整的离线同步。我自己通常锁一个版本,批量生成图时完全不改动环境,确保可重复性。
5.5 问题排查速查表
把上面几个高频坑整理成一张表,方便现场排查:
| 现象 | 可能原因 | 快速处理 |
|---|---|---|
| 执行mmdc报找不到浏览器 | Puppeteer配置路径错误或没有Chromium | 检查puppeteer-config.json的可执行路径 |
| 图渲染出来全空白 | 沙箱权限问题 | 加--no-sandbox参数 |
| PNG图片文字模糊 | 缩放因子过低 | 将-s设为2或3 |
| 中文变方块 | 缺少中文字体 | 安装fonts-noto-cjk |
| 与在线编辑器渲染效果不同 | Mermaid版本或主题不一致 | 固定版本和主题 |
| 批量命令中途停止 | 单张图语法错误 | 排查对应.mmd文件语法 |
6. 基于个人实践的经验沉淀
把这套链路完整跑通之后,我在多个项目里都采用了同样的模式:给文档库配置好mermaid-cli环境,所有图形文件都以.mmd文本形式维护,然后通过脚本统一导出PNG和SVG。这比手动改图、重新截图高效很多,也方便做版本管理,代码评审时可以直接看.mmd文件的差异。
最后提一个实用习惯:在源文件头部统一写清楚图的类型和主题。例如:
%%{init: {'theme':'base', 'themeVariables': {'primaryColor': '#d9e8fb'}}}%% graph LR A[模块A] --> B[模块B]这一段init配置能让团队作图风格统一,也能让导出环境更可控。我之前踩过几次版本不同导致主题色不对的坑,固定init之后省了很多麻烦。Mermaid离线这件事,说难是真繁琐,理顺后又觉得是固定套路,先装Node和依赖,再配好Chromium路径,导出就是一行命令的事。