1. 从一张草图到一套系统:diagram-design 到底在解决什么问题
第一次看到 diagram-design 这个词,很多人会以为它只是“画图”的另一种说法。但真正在项目里被图表折磨过的人知道,问题从来不是“画不画得出来”,而是“画出来的东西能不能用、能不能改、能不能被别人接手”。我做过不少技术方案配图、流程梳理和架构示意,早期也用过各种拖拽式工具,结果是图越画越漂亮,维护成本却越来越高——改一个节点,整张图重排;换一个主题,颜色全乱;想复用某个模块,只能截图粘贴。
diagram-design 的核心,是把“图表”从一次性美术产物,变成可维护的设计系统。它关注的不是单张图的视觉效果,而是一整套规则:节点怎么定义、连线怎么走、间距怎么统一、颜色怎么分层、不同图种之间怎么共享同一套语义。换句话说,它更像是一套“图表的 CSS 框架”,而不是一个画图软件。
这套东西适合谁?如果你只是偶尔画一张流程图发群里,可能用不上。但如果你需要持续输出架构图、时序图、状态机、数据流图,并且这些图要跟着代码和文档一起迭代,那 diagram-design 的思路就非常值得参考。它解决的是三个具体问题:第一,图表风格不统一导致沟通成本高;第二,图表源文件难以版本管理和协作;第三,重复绘制相同结构的图浪费大量时间。
我后来把这套思路落地到自己的项目里,最直观的变化是:以前画一张部署架构图要四十分钟,现在改一个配置文件,十秒钟重新生成。这不是工具带来的魔法,而是设计系统带来的复利。
2. 核心设计思路拆解:为什么是“系统”而不是“模板”
2.1 图表即代码:把视觉元素抽象成可配置项
diagram-design 最底层的逻辑,是承认图表本质上是一种结构化数据。节点是对象,连线是关系,布局是算法,样式是规则。一旦接受这个前提,就可以用处理数据的方式来处理图表。
我最初尝试的是“模板法”:做几个漂亮的模板,每次复制一份改文字。这个方法在第三张图就崩了,因为模板里的样式是硬编码的,改一个圆角半径要手动调二十个节点。后来转向“配置法”:把颜色、字号、间距、圆角、连线样式全部抽成变量,节点只负责声明自己是什么类型。这样改一次配置,所有图同步更新。
具体来说,我会定义一套基础 token:
tokens: color: primary: "#2B6CB0" secondary: "#718096" accent: "#ED8936" background: "#FFFFFF" surface: "#F7FAFC" spacing: unit: 8 nodePadding: 16 layerGap: 48 typography: family: "Inter, sans-serif" size: node: 14 label: 12 title: 18 shape: radius: 6 strokeWidth: 1.5这些 token 不直接出现在图里,而是被语义层引用。比如“数据库节点”引用color.primary和shape.radius,“外部服务节点”引用color.secondary。这样做的最大好处是:当品牌色调整时,我只需要改一个十六进制值,所有图自动对齐。
注意:token 的命名要按“用途”而不是“外观”来定。叫
color.primary而不是color.blue,叫spacing.layerGap而不是spacing.48。否则换主题时你会陷入“蓝色变量指向红色值”的混乱。
2.2 语义分层:让图表自己解释自己
很多图表看起来乱,不是因为画得不好,而是因为缺少语义层次。diagram-design 强调把节点分成“层”:基础设施层、服务层、数据层、外部依赖层。每一层有自己的视觉权重,读者一眼就能看出哪些是核心,哪些是辅助。
我在实际项目中会把层定义成枚举:
enum DiagramLayer { Infrastructure = "infra", Service = "service", Data = "data", External = "external", Client = "client" }然后为每一层绑定样式规则。比如基础设施层用浅灰背景、细边框;服务层用主色填充、白色文字;数据层用深色背景、等宽字体;外部依赖层用虚线边框、降低透明度。这样即使节点数量很多,读者也能通过视觉层次快速建立认知地图。
这个思路的另一个好处是:它强迫你在画图之前先想清楚系统结构。以前我经常画到一半发现“这个组件到底算服务还是算基础设施”,现在必须在数据模型里先定义清楚。画图变成了建模,而不是拼图。
2.3 布局约束:用规则代替手动拖拽
手动拖拽最大的问题是不可复现。今天把节点放在这里,明天换个人打开,位置全变了。diagram-design 的做法是定义布局约束,让算法决定位置。
常见的约束包括:
- 同层节点水平对齐,垂直间距固定
- 跨层连线优先走直线,避免交叉
- 同一父节点的子节点聚拢在一起
- 连线标签靠近中点,避免遮挡
我用的是基于 dagre 的自动布局,配合自定义的层级排序。核心配置如下:
const layoutConfig = { rankdir: "TB", nodesep: 40, ranksep: 80, edgesep: 20, marginx: 24, marginy: 24, align: "UL", ranker: "network-simplex" };rankdir控制方向,nodesep控制同层节点间距,ranksep控制层间距。这些值不是拍脑袋定的,而是根据 token 里的spacing.unit推导出来的:nodesep = unit * 5,ranksep = unit * 10。这样布局和样式共享同一套节奏,整体看起来才协调。
实操心得:自动布局不是万能的。遇到环形依赖或复杂交叉时,我会手动指定几个关键节点的
rank,让算法在局部服从人工约束。完全放任算法,出来的图可能逻辑正确但阅读体验很差。
3. 核心细节解析与实操要点:从零搭一套可用的图表系统
3.1 节点定义:把业务概念翻译成图表元素
节点不是随便画的矩形。在 diagram-design 里,每个节点类型都对应一个业务概念,并且有明确的输入输出。我通常会先列一张表,把系统里所有出现的实体分类:
| 节点类型 | 语义 | 视觉特征 | 典型属性 |
|---|---|---|---|
| 客户端 | 用户入口 | 圆角矩形,浅色填充 | 平台、版本 |
| 网关 | 流量入口 | 六边形,主色边框 | 协议、限流 |
| 服务 | 业务逻辑 | 矩形,主色填充 | 语言、副本数 |
| 数据库 | 持久化 | 圆柱体,深色填充 | 引擎、容量 |
| 缓存 | 加速层 | 菱形,虚线边框 | 策略、过期 |
| 消息队列 | 异步通信 | 平行四边形,强调色 | 主题、分区 |
| 外部依赖 | 第三方 | 虚线矩形,低透明度 | 供应商、SLA |
这张表一旦定下来,画图就变成了“选类型、填属性”。我见过很多团队图表混乱,根本原因是每个人对“服务”和“组件”的理解不一样,画出来的形状和颜色自然五花八门。统一节点定义,等于统一了团队的心智模型。
在实现上,我会为每种节点写一个渲染函数:
function renderServiceNode(node) { return { shape: "rect", style: { fill: tokens.color.primary, stroke: tokens.color.primaryDark, strokeWidth: tokens.shape.strokeWidth, rx: tokens.shape.radius, ry: tokens.shape.radius }, label: `${node.name}\n${node.replicas} replicas`, font: { family: tokens.typography.family, size: tokens.typography.size.node, color: "#FFFFFF" } }; }注意标签里带了副本数,这是有意为之。图表不只是结构示意,还应该承载关键运维信息。读者看到“3 replicas”就知道这个服务是高可用的,看到“1 replica”就会多问一句。
3.2 连线规则:让关系表达更准确
连线比节点更容易被忽视。很多人画图时连线就是一条直线加箭头,但关系其实有很多种:同步调用、异步消息、数据流向、依赖关系、继承关系。如果全部用同一种线型,读者根本分不清。
diagram-design 建议至少区分四种连线:
- 实线箭头:同步调用,表示请求响应
- 虚线箭头:异步消息,表示事件通知
- 粗实线:数据流向,表示批量传输
- 无箭头实线:关联关系,表示结构包含
我在配置里这样定义:
edges: sync: style: solid arrow: triangle color: "#2B6CB0" width: 1.5 async: style: dashed arrow: open color: "#ED8936" width: 1.5 dashPattern: [6, 4] dataflow: style: solid arrow: triangle color: "#38A169" width: 3 association: style: solid arrow: none color: "#A0AEC0" width: 1连线标签的位置也很关键。默认放在中点容易和线重叠,我会根据线的角度动态调整偏移:
function labelPosition(edge) { const midX = (edge.source.x + edge.target.x) / 2; const midY = (edge.source.y + edge.target.y) / 2; const angle = Math.atan2(edge.target.y - edge.source.y, edge.target.x - edge.source.x); const offset = 12; return { x: midX + Math.sin(angle) * offset, y: midY - Math.cos(angle) * offset }; }这个偏移量来自 token 的spacing.unit * 1.5,保证和整体节奏一致。
注意:连线交叉是自动布局的常见问题。如果交叉太多,优先调整节点顺序,而不是手动拉线。手动拉线会破坏布局的可复现性,下次重新生成又乱了。
3.3 主题切换:一套数据多种输出
diagram-design 的另一个实用特性是主题切换。同一份图表数据,可以输出成浅色模式、深色模式、打印模式,甚至不同品牌的配色方案。这在做技术分享时特别有用:投屏用深色,文档用浅色,打印用高对比度。
实现方式是把所有颜色引用都指向 token,然后准备多套 token:
const themes = { light: { background: "#FFFFFF", surface: "#F7FAFC", text: "#1A202C", primary: "#2B6CB0" }, dark: { background: "#1A202C", surface: "#2D3748", text: "#F7FAFC", primary: "#63B3ED" }, print: { background: "#FFFFFF", surface: "#FFFFFF", text: "#000000", primary: "#000000" } };渲染时传入主题名,所有节点和连线自动换色。我实测下来,深色模式在投屏时对比度更好,但打印时必须换成高对比度方案,否则灰色文字根本看不清。
3.4 版本管理:图表也要进代码仓库
图表源文件用纯文本格式存储,这是 diagram-design 区别于拖拽工具的关键。纯文本意味着可以 diff、可以 review、可以回滚。我把图表定义放在diagrams/目录下,和代码一起提交。
diagrams/ architecture.diagram.yaml deployment.diagram.yaml sequence-payment.diagram.yaml tokens/ light.yaml dark.yaml每次修改图表,git diff 会显示具体改了哪个节点、哪条连线。Code review 时同事可以直接评论“这个服务为什么依赖外部缓存”,而不是对着一张图片说“这里好像不太对”。
实操心得:图表文件命名要带业务含义,不要用
diagram1、diagram2。我习惯用architecture-<系统名>.diagram.yaml,这样搜索起来很方便。另外,图表文件里不要写死颜色值,全部引用 token,否则主题切换会失效。
4. 实操过程与核心环节实现:从数据到成图的完整链路
4.1 第一步:定义图表数据模型
一切从数据开始。我不会先打开画图工具,而是先写 YAML。以一张典型的微服务架构图为例:
title: "订单系统架构" layers: - id: client name: "客户端层" - id: gateway name: "接入层" - id: service name: "服务层" - id: data name: "数据层" nodes: - id: web layer: client type: client label: "Web 前端" - id: mobile layer: client type: client label: "移动端" - id: gateway layer: gateway type: gateway label: "API 网关" - id: order layer: service type: service label: "订单服务" replicas: 3 - id: payment layer: service type: service label: "支付服务" replicas: 2 - id: inventory layer: service type: service label: "库存服务" replicas: 2 - id: orderDb layer: data type: database label: "订单库" - id: cache layer: data type: cache label: "Redis 缓存" edges: - from: web to: gateway type: sync label: "HTTPS" - from: mobile to: gateway type: sync label: "HTTPS" - from: gateway to: order type: sync label: "REST" - from: order to: payment type: sync label: "RPC" - from: order to: inventory type: sync label: "RPC" - from: order to: orderDb type: dataflow label: "读写" - from: order to: cache type: sync label: "缓存"这份 YAML 就是图表的“源代码”。它不包含任何视觉信息,只描述结构和关系。这样做的好处是,同一份数据可以渲染成架构图、部署图、甚至时序图,只要换一个渲染器。
4.2 第二步:编写渲染管线
渲染管线负责把数据变成图形。我用的是一条简单的流水线:
- 解析 YAML:读取节点、连线、层级
- 应用布局:根据层级和连线计算坐标
- 绑定样式:根据节点类型和主题绑定视觉属性
- 生成 SVG:输出矢量图形
- 导出 PNG:按需生成位图
核心代码结构如下:
async function renderDiagram(source, themeName) { const data = parseYaml(source); const theme = loadTheme(themeName); const tokens = mergeTokens(baseTokens, theme); const graph = buildGraph(data); const layout = computeLayout(graph, tokens); const styled = applyStyles(layout, tokens); const svg = generateSvg(styled); return svg; }computeLayout是最关键的一步。我用 dagre 做基础布局,然后根据层级做二次调整:
function computeLayout(graph, tokens) { const g = new dagre.graphlib.Graph(); g.setGraph({ rankdir: "TB", nodesep: tokens.spacing.unit * 5, ranksep: tokens.spacing.unit * 10, marginx: tokens.spacing.unit * 3, marginy: tokens.spacing.unit * 3 }); g.setDefaultEdgeLabel(() => ({})); graph.nodes.forEach(node => { g.setNode(node.id, { width: estimateWidth(node.label, tokens), height: tokens.spacing.unit * 6 }); }); graph.edges.forEach(edge => { g.setEdge(edge.from, edge.to); }); dagre.layout(g); return g; }estimateWidth根据标签长度和字号估算节点宽度,避免文字溢出。这个估算不需要很精确,但必须稳定,否则每次渲染宽度不一样,图会跳。
4.3 第三步:处理层级与分组
层级不是简单的上下排列。同一层内的节点应该有视觉分组,比如“订单服务”和“支付服务”都属于业务服务,应该用同一个背景框起来。diagram-design 用“容器节点”来实现分组。
containers: - id: businessServices label: "业务服务" layer: service members: [order, payment, inventory]渲染时,容器先画背景,再画成员节点。容器背景用surface色,边框用虚线,标签放在左上角。这样读者一眼就能看出哪些服务属于同一个业务域。
注意:容器不要嵌套太深。超过两层的嵌套会让图变得难以阅读。如果确实需要多层分组,考虑拆成多张图,而不是在一张图里塞下所有细节。
4.4 第四步:导出与嵌入
生成的 SVG 可以直接嵌入文档,也可以导出 PNG 用于演示。我通常同时输出两种格式:
diagram-design render architecture.diagram.yaml \ --theme light \ --output dist/architecture-light.svg diagram-design render architecture.diagram.yaml \ --theme dark \ --output dist/architecture-dark.svg \ --format png \ --scale 2--scale 2用于生成高清位图,投屏时不会模糊。SVG 用于网页嵌入,可以无限缩放。
嵌入文档时,我会在 SVG 里保留title和desc标签,方便屏幕阅读器识别。这是很多人忽略的无障碍细节,但在团队协作中很重要。
5. 常见问题与排查技巧实录
5.1 布局错乱:节点重叠、连线交叉
这是最常见的问题。原因通常有三个:节点尺寸估算不准、层级顺序不合理、连线太多导致算法无法优化。
排查步骤:
- 检查
estimateWidth是否返回了合理值。如果标签很长但宽度估算太小,节点会重叠。 - 检查
rankdir是否适合当前图。从上到下适合层级图,从左到右适合流程图。 - 检查是否有环形依赖。dagre 对环形依赖处理不好,需要手动打断。
我的处理方式是:先看日志里有没有“cycle detected”,如果有,就在数据里把某条边标记为constraint: false,让布局算法忽略它。
edges: - from: serviceA to: serviceB type: sync constraint: falseconstraint: false表示这条边不参与层级计算,只负责渲染。这样可以打破环,同时保留视觉上的连线。
5.2 文字溢出:标签太长被截断
节点宽度是估算的,但文字实际渲染宽度受字体影响。如果用了非等宽字体,估算值和实际值可能差很多。
解决方案有两个:一是用等宽字体做估算,渲染时也用等宽字体;二是给节点设置最大宽度,超出部分自动换行。
function wrapLabel(label, maxWidth, fontSize) { const charsPerLine = Math.floor(maxWidth / (fontSize * 0.6)); const words = label.split(/\s+/); const lines = []; let current = ""; words.forEach(word => { if ((current + " " + word).length > charsPerLine) { lines.push(current.trim()); current = word; } else { current += " " + word; } }); if (current) lines.push(current.trim()); return lines.join("\n"); }0.6是经验系数,不同字体略有差异。我实测下来,Inter 字体用 0.58,系统默认无衬线字体用 0.62。
5.3 主题切换后颜色对比度不足
深色模式下,如果主色没有调整,文字可能看不清。比如浅色模式用#2B6CB0配白字没问题,深色模式还用这个色配深色背景,对比度就不够了。
解决方案是给每个主题单独定义语义色,而不是简单反转。深色模式的主色应该更亮:
const themes = { light: { primary: "#2B6CB0", onPrimary: "#FFFFFF" }, dark: { primary: "#63B3ED", onPrimary: "#1A202C" } };onPrimary表示“在 primary 背景上的文字颜色”。这样切换主题时,文字和背景的对比度始终有保障。
5.4 渲染速度慢:大图卡顿
节点超过 200 个时,dagre 布局会明显变慢。我的优化经验是:
- 关闭不必要的动画和过渡
- 缓存布局结果,相同数据不重复计算
- 分片渲染,先渲染可见区域
const layoutCache = new Map(); function computeLayoutCached(graph, tokens) { const key = JSON.stringify({ graph, tokens }); if (layoutCache.has(key)) { return layoutCache.get(key); } const result = computeLayout(graph, tokens); layoutCache.set(key, result); return result; }缓存 key 用完整数据序列化,确保数据变了缓存失效。这个优化在交互式编辑场景下效果很明显,拖动节点时不需要重新计算全局布局。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 节点重叠 | 宽度估算不准 | 打印节点实际宽度 | 调整估算系数或换等宽字体 |
| 连线交叉严重 | 层级顺序不合理 | 查看 rank 分配 | 调整节点顺序或关闭部分约束 |
| 文字被截断 | 标签过长 | 检查 wrap 逻辑 | 增加最大宽度或缩小字号 |
| 深色模式看不清 | 对比度不足 | 用对比度检查工具 | 为深色主题单独定义语义色 |
| 渲染卡顿 | 节点过多 | 统计节点数量 | 启用缓存或分片渲染 |
| 主题切换失效 | 硬编码颜色 | 搜索十六进制值 | 全部改为 token 引用 |
| 导出模糊 | 分辨率不足 | 检查导出参数 | 增加 scale 或改用 SVG |
实操心得:遇到布局问题时,先把图简化到最小可复现案例。删掉一半节点,看问题是否还在。大部分布局问题在节点少于 20 个时都能定位到具体原因。
6. 工具选型与生态搭配:不重复造轮子
6.1 渲染引擎:dagre 还是 elk
dagre 是最成熟的 JavaScript 布局库,适合大多数层级图。elk 的布局质量更高,但配置复杂,体积也大。我的选择是:日常用 dagre,遇到复杂图再上 elk。
| 对比项 | dagre | elk |
|---|---|---|
| 体积 | 小 | 大 |
| 配置复杂度 | 低 | 高 |
| 布局质量 | 良好 | 优秀 |
| 环形依赖处理 | 一般 | 好 |
| 适用场景 | 常规架构图 | 复杂网络图 |
如果团队里没有人专门维护图表系统,dagre 是更稳妥的选择。elk 的学习曲线比较陡,配置项太多,容易调出奇怪的结果。
6.2 输出格式:SVG 优先,PNG 补充
SVG 是矢量格式,无限缩放,适合网页和文档。PNG 适合演示和聊天工具分享。我的做法是默认输出 SVG,需要时再转 PNG。
转换用 sharp 或 resvg,两者都很稳定。sharp 速度快,resvg 对 SVG 特性支持更完整。如果图里用了滤镜或渐变,建议用 resvg。
const sharp = require("sharp"); async function svgToPng(svgBuffer, scale = 2) { return sharp(svgBuffer) .resize({ width: undefined, height: undefined, kernel: "lanczos3" }) .png({ quality: 100 }) .toBuffer(); }scale参数控制输出分辨率。投屏用 2 倍,打印用 3 倍,网页用 1 倍。
6.3 编辑器集成:VS Code 预览
我习惯在 VS Code 里直接预览图表。装一个 SVG 预览插件,保存 YAML 后自动刷新。这样改一行数据就能看到效果,反馈循环很短。
配置方式是在项目里加一个.vscode/settings.json:
{ "svg.preview.background": "transparent", "files.associations": { "*.diagram.yaml": "yaml" } }svg.preview.background设为透明,方便看深色和浅色两种主题的效果。
注意:预览插件只负责显示,不负责渲染。YAML 到 SVG 的转换还是靠自己的渲染管线。不要指望插件能理解你的图表语义。
7. 从个人项目到团队规范:diagram-design 的落地经验
7.1 先统一 token,再统一图表
团队落地最大的障碍不是技术,而是习惯。每个人都有自己的配色偏好和画图方式。如果一上来就要求所有人用同一套系统,阻力会很大。
我的做法是分两步:第一步,先统一 token。把颜色、字号、间距定下来,但不强制大家用。第二步,等大家发现统一 token 确实省事之后,再推广图表系统。
token 的制定要让大家参与。我会拉一个共享文档,让每个人提名自己常用的颜色和字号,然后投票选出最终方案。参与感越强,执行阻力越小。
7.2 图表即文档:和代码一起维护
图表文件放在代码仓库里,和代码一起 review。每次架构变更,必须同时更新图表。这条规则听起来简单,执行起来需要纪律。
我的经验是:把图表更新写进 Definition of Done。没有更新图表的 PR 不允许合并。一开始大家会抱怨,但坚持两个月后,图表和代码的同步率会明显提升。
7.3 定期重构图表:删掉不再使用的节点
图表和代码一样,会积累技术债。不再使用的服务、已经下线的依赖、过时的流程,都应该从图里删掉。我每个季度会做一次图表清理,把半年内没有引用的节点和连线删掉。
清理时要注意:先确认没有文档引用这张图,再删除。有些老文档可能还在用旧图,删之前要通知相关同事。
7.4 培训与分享:让新人快速上手
新人入职时,我会花半小时讲 diagram-design 的基本用法:怎么定义节点、怎么引用 token、怎么渲染。然后让他们画一张自己负责模块的架构图。画完 review 一次,基本就掌握了。
培训材料不需要很复杂,一个 YAML 示例加一个渲染命令就够了。关键是让新人感受到:画图不是负担,而是帮助自己理解系统的方式。
8. 扩展方向:diagram-design 还能怎么用
8.1 自动生成:从代码注释提取图表
如果代码里已经有结构化的注释,可以写一个脚本自动提取节点和连线,生成图表数据。这样架构图永远和代码同步,不需要手动维护。
function extractDiagramFromCode(sourceFiles) { const nodes = []; const edges = []; sourceFiles.forEach(file => { const annotations = parseAnnotations(file); annotations.forEach(ann => { if (ann.type === "service") { nodes.push({ id: ann.name, type: "service", label: ann.label }); } if (ann.type === "depends") { edges.push({ from: ann.from, to: ann.to, type: "sync" }); } }); }); return { nodes, edges }; }这个思路适合微服务架构,每个服务在自己的代码里声明依赖,汇总后自动生成全局图。
8.2 交互式图表:点击节点查看详情
SVG 支持交互。可以在节点上绑定点击事件,弹出详情面板,显示服务的负责人、SLA、监控链接等信息。这样图表就不只是静态图片,而是运维入口。
实现方式是在 SVG 里嵌入>svg.querySelectorAll("[data-node-id]").forEach(node => { node.addEventListener("click", () => { const id = node.getAttribute("data-node-id"); showDetailPanel(id); }); });
showDetailPanel可以从 API 拉取实时数据,显示服务健康状态。
8.3 多图联动:从架构图跳到时序图
同一份数据可以渲染成不同图种。点击架构图里的某个服务,可以跳转到对应的时序图,展示该服务的调用链路。这种联动在排查问题时特别有用。
实现方式是给节点加link属性,指向另一张图的 ID。渲染时生成超链接,点击后打开新图。
nodes: - id: order type: service label: "订单服务" link: "sequence-order.diagram.yaml"这个功能不需要很复杂的实现,但能显著提升图表的实用性。
9. 我踩过的坑与最终建议
第一个坑是过度设计。一开始我想把所有东西都做成可配置的,结果配置文件比图还复杂。后来我给自己定了一条规则:只有出现三次以上的重复,才抽成配置。这条规则帮我省了很多时间。
第二个坑是忽视字体。不同系统默认字体不一样,导致同一份数据在不同电脑上渲染结果不同。后来我强制使用 Web Font,并在渲染前等待字体加载完成。这个细节很不起眼,但直接影响图表的可复现性。
第三个坑是不做版本管理。早期图表文件没有进仓库,改乱了只能重画。现在所有图表都在 git 里,每次修改都有记录,回滚只需要一条命令。
如果你准备开始用 diagram-design 的思路管理图表,我的建议是:先从一张图开始,定义好 token 和节点类型,跑通渲染管线。不要一上来就搞大而全的系统。等第一张图稳定运行一个月,再推广到其他图。图表系统的价值在于长期维护,而不是一次性产出。
最后分享一个实用技巧:给图表文件加一个lastUpdated字段,每次渲染时自动更新。这样一眼就能看出哪张图很久没维护了,提醒自己及时更新。