最近在帮团队梳理技术文档体系,我发现一个很有意思的现象:大家宁愿写一大段文字来描述模块间的调用关系,也不愿意画一张图。问了一圈,理由出奇一致——“画图太麻烦了,调整对齐就要半天”。但文档里的架构描述一旦超过三句话,阅读的人就开始眉头紧锁。
后来我在一个内部项目里把图表全部改成了代码化设计,统一用文本描述来生成图。这个思路就是项目名里的diagram-design:把画图从“拖动鼠标微调”变成“写代码生成”,让图表像代码一样可维护、可评审、可版本管理。
这篇内容会完整拆解我在这个项目里的设计思路、工具选型、实操过程,以及从混乱到规范化过程中踩过的坑。如果你也在为文档里的图表维护头疼,或者想给团队搭一套图形资产体系,这篇应该能给你一个可直接落地的参考。
1. 核心思路:为什么图表要用“代码”来设计
1.1 从“画图”到“写图”:一次思维转变
传统的画图工具,比如 Visio、ProcessOn、draw.io,核心交互逻辑是“拖拽 + 连线”。你从左侧图元库拖一个矩形出来,双击打字,再拉一条线到另一个矩形。图好看不好看,取决于你手动对齐的耐心,以及同事之后维护时有没有同样的耐心。
diagram-design 的思路正好相反:图表的一切信息都由文本定义,包括节点、连线、分组、样式。你面对的不是一块画布,而是一段结构化的描述文本。听起来好像把简单事情搞复杂了,但实际用下来收益非常大:图表可以和代码一起提交到 Git 仓库,评审时直接看 diff,知道这周架构图里到底是哪个模块改了、哪条链路动了。这在传统画图工具里几乎做不到。
传统方式下,架构图的维护成本是“每一次变更都等于重画一遍”。而代码化设计之后,维护成本被压缩到“改一行文本”。这个差异在项目前期不明显,一旦图表数量超过十张、需要跟随架构持续演进的时候,就是两种完全不同的体验。
1.2 图表资产化:把图当作代码来管理
我在项目里定义了一个原则:图表是软件资产,不是一次性文档配图。既然它是资产,就必须有版本、有评审、有变更记录。代码化设计天然满足这些要求。
具体来说,我把所有图表的源文件统一放在项目仓库的diagrams/目录下,每个图表的源文件负责生成一张业务图。源文件是普通文本格式,无法直接预览,所以我配置了一个本地渲染环境,改完源文件立即生成新图预览。整个过程和写代码完全同构:新增图表就是新增文件,修改图表就是修改文本,废除图表就是删除文件。
这个方式让图表真正进入了软件工程的生命周期。团队里不只一个人能维护图表,不再是“这张图是谁画的就只能找谁改”。任何开发人员拿到源文件就能改,改完通过同一个渲染流程产出新图。这种资产化能力,是传统画图工具给不了的。
1.3 diagram-design 要解决的核心问题
这个项目的目标很明确:解决软件开发团队在文档协作中,图表维护成本过高的问题。
具体拆解来看,有三层诉求。第一层是“改得动”:架构演进时,图表能快速跟随调整,而不是成为一篇永远过期的文档。第二层是“查得到”:历史版本的图表能回溯,知道某次架构调整是在什么时候、由谁、为了什么而改。第三层是“接得上”:图表能融入现有的 CI/CD 流程,和技术文档、API 文档一起构建出完整的项目知识库。
这三层诉求落到技术选型上,就会自然导向代码化方案。因为只有文本形态的源文件,才能同时满足“可 Diff”“可版本控制”“可自动渲染”这三个特性。
2. 工具选型:四款主流代码化图表工具对比
2.1 统一建模语言方案与轻量级文本方案的取舍
选型之前,需要先明确“代码化设计”不是一个单一工具,而是一个工具家族。业内成熟的方案有好几类:UML 工具链、Mermaid、PlantUML、Graphviz、D2 等。它们都能用文本生成图,但设计哲学和应用场景差别很大。
UML 工具链更严谨,适合软件工程设计阶段;Mermaid 与 PlantUML 主打文档内嵌,Markdown 生态适配优秀;Graphviz 的强项是复杂拓扑图;D2 是新生力量,语法设计更现代。
我当时在 Mermaid 和 PlantUML 之间犹豫最久。两者都能满足代码化设计的需求,但对应的使用者体验差异明显。为了不让团队成员产生“我用哪个都行,为什么要学新工具”的抵触心理,我最后用一张对比表把差异摆出来,拉着团队一起做决策。
| 对比项 | Mermaid | PlantUML | Graphviz | D2 |
|---|---|---|---|---|
| 语法难度 | 低,接近自然语言 | 中等,需要记住关键字 | 较高,node 与 edge 概念抽象 | 低,声明式语法清晰 |
| Markdown 原生支持 | 优秀,GitHub 直接渲染 | 一般,需插件 | 不支持,需额外配置 | 尚可,有社区插件 |
| 复杂流程图 | 一般 | 较好 | 强大 | 良好 |
| 时序图 | 优秀 | 优秀 | 不支持 | 支持 |
| 类图 / 架构图 | 支持 | 强项 | 一般 | 良好 |
| 布局算法 | 固定,难以手动调整 | 可调,但精细控制有限 | 高度可控 | 动画式布局较现代 |
| 学习曲线 | 最平缓 | 中 | 陡峭 | 平缓 |
2.2 最终选型:为什么是 Mermaid 而不是 PlantUML
团队最后选了 Mermaid,核心原因有三个。
第一是语法直觉化。Mermaid 的语法非常贴近英文自然语言,比如A --> B就表示从 A 指向 B 的箭头,没有多余的关键字。团队成员第一次看到示例代码,不需要查阅文档就能猜到大概含义,上手成本极低。
第二是生态嵌入强。我们团队的技术文档平台原生支持 Mermaid 渲染,这就意味着源文件和渲染结果可以共存于同一个 Markdown 文件里。代码评审时,评审方看到的既可以是源码 diff,也可以是渲染后的效果,信息传递闭环在一个平台内完成。
第三是渲染生态成熟。Mermaid 在 GitHub、Notion、Obsidian 等主流平台均内置支持,本地只需要一个轻量客户端就能预览。
选择 Mermaid 也付出了一些代价。最明显的是布局自动化的不可控性,复杂图中节点偶尔会重叠。不过这个代价在团队场景里可以接受——我们做的是技术文档配图,不是出版级制图。
2.3 保留 PlantUML 的适用场景:类图补充
Mermaid 拿到图表设计的绝大部分工作,但有一个场景它确实弱:复杂类图的可读性。Mermaid 的 classDiagram 虽然能用,但多继承关系、泛化实现的展示效果不如 PlantUML 清晰。
所以我在方案里保留了一条补充路径:类图、包图这些 UML 强相关的图形,允许使用 PlantUML 绘制。对这个特例,我在仓库里适配了一个 PlantUML 渲染环境,和 Mermaid 主环境并存。
这条“默认 Mermaid + 特殊场景 PlantUML”的双轨方案,既保证了 90% 场景下的统一性,又不至于在 10% 的场景里硬凑。毕竟 diagram-design 的目标是让图表工作流顺畅,而不是让团队为工具的教条买单。
3. 实操过程:从零到一落地 diagram-design
3.1 准备基础环境
这一步我直接基于 Node.js 生态来做,因为团队本身就在 Node.js 技术栈上,不需要额外引入异构环境。
Mermaid 的本地渲染方式有两种:CLI 命令行工具和 Puppeteer 渲染方案。我选择的是@mermaid-js/mermaid-cli,原因是它的核心逻辑是“用无头浏览器渲染 SVG/PNG”,能最大程度保证本地渲染结果和线上 Markdown 渲染结果一致。
安装过程很简单,全局安装 CLI 工具即可:
npm install -g @mermaid-js/mermaid-cli安装完成后还需要一个可视化编辑器辅助预览。这一步有不少选择,我用的是 VS Code 的 Markdown Preview Mermaid Support 插件。它能在写 Markdown 的时候直接预览图表,支持常见流程图、时序图、状态图等,当团队协作时大家都能用同一套工具本地检查图表效果。
3.2 设计源文件目录规范
环境准备好之后,先别急着画图,而是定目录规范。这是整个项目里最容易被忽视、又最重要的一步。
我在项目仓库里建立了如下目录结构:
docs/ diagrams/ src/ system-overview.md user-login-flow.md order-process.md images/ system-overview.svg user-login-flow.svg order-process.svgsrc/目录存放 Mermaid 源文件,后缀名用.md,方便 Markdown 编辑器直接预览;images/目录存放渲染后的 SVG 图片文件。SVG 是矢量格式,在文档系统里放大缩小不会模糊,这是 PNG 做不到的。
每个源文件的头部我都写清楚这张图的元信息:项目名称、维护人、最后更新日期。这不仅方便追溯历史,也让后来接手的人第一眼就知道这张图的归属范围。
3.3 定义图表的统一风格
团队协作里最常见的灾难是:同一个项目的架构图,五个人画出来有六种风格。有人喜欢蓝色系,有人偏好圆角矩形,有人非要把线做成虚线。所以 diagram-design 上线第一天,我就定义了一份图表风格规范,并且把样式参数固化到每一个源文件里。
Mermaid 支持通过%%{init}%%指令进行主题定制。下面是我项目中常用的一段初始配置:
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#4F81BD', 'primaryTextColor': '#fff', 'primaryBorderColor': '#2E5387', 'lineColor': '#333333', 'fontSize': '16px' }}}%% graph TD A[用户请求] --> B[网关层] B --> C[业务服务] C --> D[数据持久层]这段配置的作用是统一色板和字体大小。团队里所有图表都从同一份基础配置开始,视觉呈现天然一致。这个细节看似简单,但对文档整体的专业感提升非常明显。
3.4 三层架构图完整示例
作为实战案例,我用 Mermaid 定义了一张典型的三层架构图。这张图满足大多数后端项目的架构描述需求,可直接复制套用:
graph TB subgraph 展示层 A1[PC 前端] A2[移动端] end subgraph 接入层 B1[统一接入网关] B2[鉴权服务] B3[限流服务] end subgraph 业务层 C1[用户中心] C2[订单中心] C3[商品中心] end subgraph 数据层 D1[(MySQL 主库)] D2[(Redis 集群)] D3[(对象存储)] end A1 --> B1 A2 --> B1 B1 --> B2 B1 --> B3 B1 --> C1 B1 --> C2 B1 --> C3 C1 --> D1 C2 --> D1 C2 --> D2 C3 --> D1 C3 --> D3这个示例里包含一个关键设计:subgraph分组的命名是有讲究的。展示层、接入层、业务层、数据层是按照请求流转顺序自上而下排列的,没有使用默认的随机布局,而是通过代码顺序控制渲染位置。这让整个架构图读起来有明确的方向感,比一张扁平的节点图好理解得多。
3.5 渲染输出与验证流程
源文件写完之后,需要统一渲染成文档系统可用的图片格式。我用 CLI 工具将.md中的 Mermaid 块提取渲染为 SVG:
mmdc -i docs/diagrams/src/system-overview.md -o docs/diagrams/images/system-overview.svg这条命令会解析指定文件中的 Mermaid 代码块,并输出 SVG 文件。如果执行过程中遇到语法错误,CLI 会明确指出第几行有问题,方便定位修改。
为了确保每次修改后的图表都不破坏整体效果,我写了一个简单的校验脚本,循环处理src/目录下所有文件。如果渲染无报错,脚本继续;有报错则立即停止,并提示文件路径和错误行号,这样一来图表变更就有了自动化的质量关卡。
4. 核心细节解析:从语法到布局的进阶技巧
4.1 卡住无数新手的节点文本空格问题
使用 Mermaid 时,最先遇到的一个细节问题是:节点文本里如果有特殊字符,渲染就会报错或者显示异常。
比如想定义一个文本为“用户登录成功”的节点,直接写A[用户登录成功]没问题,但文本里带上括号时,比如“用户信息(缓存中)”,直接写就会报错。原因是括号在 Mermaid 语法里是特殊字符,被解释器当作语法的一部分解析了。
解决办法是使用引号包裹节点文本:
graph LR A["用户信息(缓存中)"] --> B["查询结果(正常)"]这个看似很小的细节,在真正投入使用时经常遇到。文本里只要出现括号、引号、百分号等特殊字符,都优先考虑用引号包裹,避免语法歧义。
4.2 方向控制:为什么我的图总是横着长
Mermaid 的图方向是通过首行代码控制的:graph TB表示从上到下(Top-to-Bottom),graph LR表示从左到右(Left-to-Right),graph RL和graph BT则相反。团队里不少新成员开始时不注意方向定义,画出来的图总是横着铺开,和文档结构不协调。
我的建议是绝大部分架构图、流程图统一用TB,即从上到下;只有时序、操作步骤类图才考虑LR。这是因为文档阅读习惯是自上而下的,图的方向和文档结构保持一致,阅读体验最顺畅。
4.3 布局干预:subgraph 的正确用法
Mermaid 的自动布局在节点数量少时表现良好,但节点一旦超过 10 个,自动布局就容易出现连线交叉、节点重叠。这时最有效的干预手段就是subgraph分组。
不仅是视觉上把相关节点放在一个框里,更重要的是它引导布局算法——同一组内部的节点会被优先放置在一起。这就相当于告诉布局引擎:“这些节点是一伙的,别把它们拆散。”
我在上一节的架构图示例中已经展示了 subgraph 的用法:四个分层各一个分组,内部节点只和所属层相关。如果没有这四个 subgraph,12 个节点相互连线大概率会出现混乱的交叉。
4.4 样式定制:让重要节点在图中“会说话”
一张架构图如果所有节点都长一个样,阅读者很难凭视觉快速抓到重点。我在方案里总结了一套简单的样式优先级规则:核心服务、关键路径用高亮色;工具类和辅助类用浅色;数据存储用特殊形状。
Mermaid 支持在节点定义时直接附加样式类:
graph TD A[用户请求] --> B[鉴权服务] B --> C[核心订单服务] C --> D[(MySQL)] class B critical; class C highlight;然后通过样式定义让特定类显示为指定颜色:
%% 在 Mermaid 中通过 classDef 定义样式 classDef critical fill:#FF6B6B,stroke:#C92A2A,color:#fff; classDef highlight fill:#FFD43B,stroke:#E67700,color:#333;这样一来,关键服务在图中一眼可辨,评审人员扫一眼图就能找到链路里最重要、最脆弱的环节,沟通效率大幅提升。
4.5 注释与多人协作的“潜规则”
代码化设计的一个巨大优势是可以在源文件里写注释。Mermaid 支持%%注释语法,注释内容不会被渲染到图中,但会保留在源文件里。
我要求团队在每个节点的定义旁写清楚“这个节点是什么”以及“为什么存在”。这不是为了啰嗦,而是为了降低后续维护者的理解成本。半年后回来改图的人,你的注释就是最好的一手资料。
一个符合团队规范的源文件,头部通常长这样:
%% 系统总览图 %% 维护人: 张三 %% 变更记录: 2025.01.10 新增限流服务模块 graph TB A[客户端] --> B[网关]实践下来,这个习惯带来的价值被严重低估了。它让图表源文件不仅是生成图片的脚本,更成为一份轻量级的架构决策记录。
5. 实际问题排查与解决方案速查
5.1 渲染中文乱码的三种排查方向
用 Mermaid 渲染中文,最常遇到的坑是输出图片里中文变成方框或乱码。这个问题的根源通常不在 Mermaid 本身,而在于渲染环境缺少中文字体。
排查顺序建议从这三个方向走:第一检查系统是否安装了中文字体,Linux 服务器上尤其容易缺;第二检查渲染容器的字体配置,用 puppeteer 渲染时容器里有没有中文字体直接影响结果;第三检查输出格式,SVG 在浏览器里打开正常但图片格式乱码,多半是字体解析链路问题。
我的一劳永逸方案是在渲染环境里安装 Noto Sans CJK 字体。安装之后,中文字体渲染基本不会再出问题。
5.2 布局溢出:图片被截断的解决办法
Mermaid 在内容较多时,生成的 SVG 尺寸会比预期大。直接插入文档时会出现图片被截断或显示不全的情况。
这种情况的最直接解法是在渲染时设置更宽的画面尺寸。mmdc 命令支持-w和-H参数:
mmdc -i input.md -o output.svg -w 1200 -H 900如果你用的是 Markdown 原生渲染,也可以在前面定义的初始化配置里加入:
%%{init: {'theme': 'base', 'themeVariables': {...}, 'flowchart': {'useMaxWidth': true}}}%%useMaxWidth: true会让渲染出的 SVG 宽度自适应容器。这个配置在文档站里非常实用,避免大图撑破页面布局。
5.3 节点文本包含特殊字符时的转义策略
前面提过用双引号包裹文本,但这还不够彻底。当文本里同时出现引号和括号时,双引号方案也会失效。
这时候可以把普通引号换成 html 标签转义方式,或者使用 Mermaid 支持的最直接方案:直接使用 html 字符实体。比如文本里有<或>,就用<和>代替。
我通常的做法是:能不用特殊字符就不用,实在要用,优先引号包裹,其次 html 实体。这个策略覆盖了 99% 的业务场景。
5.4 缓存问题:改了代码图没变
Mermaid 的 Markdown 预览插件有缓存机制。不知道的朋友会以为代码没生效,反复改代码,结果图始终是旧版。
遇到这种情况,最简单的解决方法是强制刷新预览窗口。在大多数编辑器里,关闭再重新打开预览文件即可。CLI 方式不存在这个问题,每次渲染都是新进程,但本地预览插件确实需要手动处理。
所以我的团队约定:写图阶段用插件实时预览,确认无误后一律用 CLI 命令生成正式图片文件。最终发布以 CLI 输出的文件为准,插件预览只作为过程辅助。这个流程避免了“预览时好好的、发布后发现不一样”的尴尬情况。
5.5 语法报错的定位技巧
当 Mermaid 代码有语法错误时,错误信息通常比较简短,有时很难一眼看出问题。我的习惯是:把报错段落拆出来,单独放到一个最小测试文件里渲染。这样能快速缩小问题范围。
最常见的错误源有几个:括号不匹配、箭头符号写了一半、节点 ID 重复定义。其中节点 ID 重复的问题比较隐蔽——它不是传统意义的语法错误,但会让渲染结果出乎意料,因为同一个 ID 会被后续定义覆盖。
我给团队的建议很简单:每个节点的 ID 用语义化的英文或拼音命名,不要用 a、b、c 这种无意义字符。比如A[用户请求]可以命名为UserRequest[用户请求]。如果图足够复杂,这样做的好处非常明显——至少你能在报错信息里看懂是哪个节点出了问题。
6. 影响范围与项目延伸的思考
6.1 从架构图到全文档体系的图形资产
diagram-design 最初只用来做架构图,但落地后发现它的价值完全可以复制到所有文档场景。需求文档里的流程图、测试报告里的状态流转图、运维手册里的部署架构图,全部可以用同一套规范来管理。
我把这套逻辑沉淀成了团队的一组模板,并把它扩展成了文档规范的一部分。当所有图形资产都变得可追溯、可评审、可复用之后,团队技术文档的维护效率提升非常明显。过去半年,我没再听到有人抱怨“文档里的图过期了没人管”。
对于正在考虑引入这套方案的朋友,我的建议是三步走:先选一个高频使用的图表场景试点;跑通团队里最多人用的那个场景之后,再逐步扩展适用范围;稳定后再考虑把渲染集成进 Apps 的自动化流程里,实现文档即代码的完整闭环。
6.2 当前方案的边界与未来的扩展空间
方案就完全没有问题吗?不是的。它有几个明显的边界:非常复杂的类图仍然建议用专业 UML 工具;对像素级视觉有要求的对外宣传图也不适合用代码化方案;还有就是初期强制团队学习语法总会付出一些效率成本。
但我们换来的是长期主义:图表从没人维护的“一次性用品”变成了能被持续迭代的“资产”。业务流程变化的快节奏之下,这一点尤为重要。
未来这个方案还可以演进的方向包括:接入可持续集成的流水线,让每次提交自动生成最新的文档架构快照;沉淀一支团队级“图表设计系统”,让新加入的成员也能上手产出风格统一的专业图;更进一步,可以尝试把图形源文件当作接口数据进行其他维度分析。整体来看,diagram-design 这套思路的空间比想象中要大得多。
最后再分享一点我个人在这个项目里的体会:代码化设计图表的真正价值,不在于让绘图过程变快,而在于让图表重新进入工程化的管理轨道。当一张图能够被 diff、被回溯、被评审的时候,它就不再是文档里一张会过期的图片,而变成跟随项目一起演进的活资产。如果你也在为团队文档的图表维护头疼,不妨从一张用 Mermaid 定义的核心架构图开始,把画图这件事从“设计工具”迁移到“写代码”的轨道上来。