简介:面向 Drawio 桌面用户的 Mermaid(美人鱼)图表集成插件,基于 JavaScript 开发,能够将饼状图、顺序图、甘特图、状态图、流程图、类图等常见图表的绘制过程,简化成一行行简洁的标记语言脚本,双击形状编辑后保存即可自动重绘,解决了手动绘图耗时长、协作修改困难的问题。压缩包共 45 个文件,大小仅 1.98MB,内部包含 js 插件源码、mmd 脚本样例、png 与 gif 效果预览、drawio 工程文件、json 配置以及 md 说明文档等,不同类型文件各自承担代码、演示、配置和文档职责,便于按需查阅和二次开发。当前已有 1292 人学习下载。借助包内完整示例图、在线演示与测试工程,读者可以快速掌握双击形状编辑美人鱼脚本、离开编辑器后自动重绘的操作流程,并利用所有配置选项映射为 draw.io 形状属性的特性,自由定制图表样式与交互行为。无论是日常绘图还是深度集成到 Drawio 桌面的二次开发场景,都能获得直接可用的参考素材与可运行代码。 上周一个同事把一段Mermaid代码甩给我,说帮我把这张流程图画出来。我打开drawio,鼠标悬在半空,心里清楚得很:手动拖矩形、连线、调布局,一套下来半小时就没了,而且改个文字还要来回点选。Mermaid的优势恰恰在这——几行代码就能把节点和关系说清楚,但drawio又不认这个格式。于是就有了这个念头:给drawio桌面版做一个能识别Mermaid代码的插件,把代码粘贴进去,自动生成可编辑的图形。Mermaid,中文圈子里经常喊它美人鱼,其实和鱼没什么关系,就是用文本描述图表的一种语法。这篇文章就把我的实现思路、踩坑记录和完整流程都捋一遍,适合正在用drawio画各种架构图、流程图,又不想浪费太多时间在纯手动拖拽上的朋友。
1. 先弄清痛点:Drawio和Mermaid到底差在哪
1.1 Drawio图形化拖拽的效率瓶颈
drawio桌面版几乎是程序员和非技术同事之间沟通图形的一种通用语言。它支持各种图形模板,导出PNG、SVG、PDF都方便,还自带XML格式,也能直连各种网盘。但它的交互模式仍然是"鼠标拖拽为主",画一张稍微像样的图,要么靠对齐线小心翼翼地调整,要么靠选中多个节点然后挨个改坐标。我见过不少同事画一张二十个节点的架构图,光调格式就花了四十分钟。
它更别扭的一点是:图一旦复杂起来,改版成本很高。比如流程图里要加一条线、改一个判断框,牵一发动全身,手动调整的边际成本真的不低。久而久之,大家就会想找个更高效的方式来定义图形。
1.2 Mermaid的代码优势与生态局限
Mermaid是另一种画图路子:用文本定义图表。节点、连线、子图,全部写在纯文本里。它的语法学起来很快,核心概念不超过十个。在线编辑器mermaid.live或者VS Code的插件都能边写边预览,配合git做版本管理,改动一目了然。
但Mermaid的局限性也很明显:生成出来的图大多是"一次性"的。想继续在已有基础上手动微调?不行,你得回到代码里改,然后重新渲染。想和drawio里已有的其他元素混排、连接?也不行。它的生态相对孤立,大多停留在Markdown文档和各类在线预览器里。这就导致很多团队里出现一种尴尬局面:文档里用Mermaid画图很快,但需要交付一张"能二次编辑"的正式图时,又只能回到drawio里手动重画。
1.3 插件要解决的三个核心问题
这个插件的目标很明确:让drawio能读Mermaid代码,并把代码转成真正可编辑的drawio图形。我给自己列了三个必须解决的问题:
- 输入方式要足够简单:插件菜单里点一下,粘贴代码就跑。
- 生成的图形必须是原生可编辑的:不是贴一张图片进来,而是每个节点、每条连接线都能在drawio里继续改。
- 布局不能太难看:至少不能所有节点堆叠在左上角,得用drawio的自动布局算法做一个初步整理。
这三个需求看起来不复杂,但真正实现起来还是会踩不少坑,下面从头拆解。
2. 插件原理:Mermaid代码如何变成Drawio图形
2.1 Drawio桌面版的插件加载机制
drawio桌面版本身基于Electron架构,支持通过JavaScript插件扩展功能。插件本质上是一个JS文件,通过配置或启动参数让应用在启动时加载它,加载后会执行Draw.loadPlugin回调,并拿到一个ui对象。这个对象就是drawio主界面的核心句柄,通过它可以拿到当前的graph实例、注册菜单、添加按钮、弹窗等。
实际开发中,我是先把插件文件放到一个固定目录,再通过配置让应用启动时加载。不同版本的入口略有差别,我以Windows下当前稳定版为例:打开drawio后,在顶部菜单里找到 Extras -> Configuration,会打开一个JSON配置编辑器,在里面加上plugins数组,指向本地插件文件路径。如果没有这个入口,也可以用命令行参数--plugins=/path/to/plugin.js来加载。桌面版另外有一个好处——插件加载成功后会有明确的UI反馈,所以调试起来比纯命令行工具直观得多。
2.2 三条技术路线:本地渲染桥接、完整库内嵌、轻量解析
写插件的第一步是定技术路线。我搜了一圈,市面上常见的方案其实有三种,我最终权衡下来选了第三种。
方案A是调用Mermaid官方CLI:先装好Node.js和mermaid-cli,用命令行把Mermaid代码渲染成SVG文件,再让drawio导入SVG。好处是语法支持完整,坏处是drawio插件本身跑在Electron渲染进程里,直接调系统命令很别扭,还得依赖外部环境,用户换台电脑就废了。
方案B是内嵌完整Mermaid解析器:把Mermaid的JS库整个打包进插件,在drawio里实时解析渲染。语法支持最完整,但Mermaid库体积不小,而且和drawio的插件API整合起来非常费力,调试成本高,只是为了导入一张图就把整个渲染引擎搬进来,有点杀鸡用牛刀。
方案C是轻量解析器:只解析流程图(flowchart)中常用的节点和连线语法,转换成drawio原生图形。功能上肯定没有完整Mermaid广,但胜在实现简单、稳定、不依赖外部环境。对大部分日常画图场景(架构图、流程图、时序简图)完全够用。我最终选了它。
三条路线的对比大概是这样:
| 技术路线 | 语法支持 | 外部依赖 | 实现难度 | 我的推荐度 |
|---|---|---|---|---|
| 方案A:调用mermaid-cli | 完整 | Node.js + CLI,较多 | 中 | 低 |
| 方案B:内嵌完整Mermaid库 | 完整 | 需打包大体积JS库 | 高 | 低 |
| 方案C:轻量解析器+drawio原生图形 | 常用流程图语法 | 无 | 低 | 高 |
2.3 轻量方案的三个核心模块
整个插件拆成三块:
- 语法解析模块:接收Mermaid文本,输出节点列表和边列表。这里不需要完整的AST语法树,正则做词法拆解就够,关键是能识别常见的图形语法。
- 图形生成模块:把节点列表映射成drawio的cell,边映射成connection,并设置对应的形状样式。
- 布局计算模块:插入完所有节点和边之后,调用drawio内置的
mxHierarchicalLayout做一次分层布局,让图能看得过去。
这样模块化设计的好处是,以后想扩展支持子图、支持时序图,只需要在语法解析模块里加规则,其他模块基本不用动。
3. 实操:手写一个Mermaid导入插件
3.1 环境准备与确认当前版本
我这边实操用的环境是Windows 10加drawio桌面版最新稳定版。如果你用的是旧版本,可能配置入口和部分API有差异,但整体思路通用。装好drawio之后,先建一个专门放插件的目录,比如D:\drawio_plugins。然后在配置里加一个插件路径。如果配置编辑器里没有内容,可以粘贴下面这个最小JSON:
{ "plugins": [ { "url": "file:///D:/drawio_plugins/mermaid_plugin.js" } ] }注意路径要写绝对路径,Windows上路径分隔符和斜杠的转义容易踩坑,建议直接把JSON粘贴进配置编辑窗口保存,drawio会自动校验格式。配置完成后重启drawio,看到顶部菜单栏多出"Mermaid",说明插件加载成功。如果没多出来,按文末第4章的排查步骤走。
3.2 插件骨架:注册菜单与弹窗交互
插件的基本结构如下:
Draw.loadPlugin(function(ui) { var graph = ui.editor.graph; ui.menubar.addMenu('Mermaid', function(menu) { menu.addItem('从Mermaid代码导入', null, function() { openImportDialog(); }); }); function openImportDialog() { var dlg = new mxWindow('从Mermaid导入', null, 300, 200, 600, 400); var textarea = document.createElement('textarea'); textarea.style.width = '560px'; textarea.style.height = '340px'; textarea.placeholder = '粘贴 Mermaid flowchart 代码,例如:\ngraph TD\n A[开始] --> B{判断}'; dlg.content.appendChild(textarea); dlg.setClosable(true); var btn = document.createElement('button'); btn.textContent = '生成'; btn.onclick = function() { convertToGraph(textarea.value); dlg.close(); }; dlg.content.appendChild(btn); dlg.setVisible(true); } });这里用到了mxWindow做弹窗,简化起见我用一个textarea让用户粘贴代码,点击按钮触发转换。实际项目里如果你想让体验更好,也可以改成支持选择 .mmd 文件读取,但在插件里访问本地文件系统需要额外的桥接处理,粘贴文本是最稳的MVP方案。mxWindow的构造参数在不同版本里略有差异,如果发现窗口没显示,优先检查drawio的调试控制台报错。
3.3 解析Mermaid语法:正则解析节点和关系
这个功能一个标准Mermaid flowchart代码大概长这样:
graph TD A[开始] --> B{是否合法} B -->|是| C[通过] B -->|否| A这是整个插件里最容易翻车的地方。Mermaid的语法看着简单,但实际写出来风格很自由:有人喜欢一行一个节点定义,有人喜欢把边写在节点定义后面,还有人喜欢用中文标签不加引号。所以解析器不能写得太死。我采用了两步走方案:先扫出所有节点定义,再扫出所有边定义。
节点定义的常见形态包括:
A[开始]:矩形A(开始):圆角矩形A{判断}:菱形A[[子程序]]:双线矩形
对应正则大致长这样:
var nodePatterns = [ { regex: /([A-Za-z0-9_\u4e00-\u9fa5]+)\s*\[\[(.+?)\]\]/g, shape: 'rectangle;strokeWidth=2;' }, { regex: /([A-Za-z0-9_\u4e00-\u9fa5]+)\s*\{([^}]+)\}/g, shape: 'rhombus;perimeter=rhombusPerimeter;' }, { regex: /([A-Za-z0-9_\u4e00-\u9fa5]+)\s*\(([^()]+)\)/g, shape: 'rounded=1;' }, { regex: /([A-Za-z0-9_\u4e00-\u9fa5]+)\s*\[([^\[\]]+)\]/g, shape: 'rectangle;' } ];这里允许中文标签,所以ID的正则里加上了\u4e00-\u9fa5。但有个坑:A(开始)这种圆角矩形和箭头混在一起时,正则容易误匹配。所以我会先去除每一行里箭头符号后面的部分,再做节点扫描。边可以用另一组正则来收集:
var edgePattern = /([A-Za-z0-9_\u4e00-\u9fa5]+)\s*(?:-->|---|-.->|==>|--\s*[^>]+?-->)\s*(?:|\|([^|]+)\|)\s*([A-Za-z0-9_\u4e00-\u9fa5]+)/g;带标签的边,比如A -->|是| B,还需要把标签文本提取出来,后面作为边上显示的label。解析完之后,整理成两个数组返回给图形生成模块。
3.4 生成图形:插入节点、连线和自动布局
拿到节点和边之后,接下来就是往drawio图形模型里塞东西。所有插入操作必须放在beginUpdate和endUpdate之间,否则undo栈会乱掉。我先按临时坐标插入所有节点,再插入边,最后执行一次分层布局:
function convertToGraph(code) { var parsed = parseMermaid(code); if (!parsed || parsed.nodes.length === 0) { alert('没有解析到有效节点,请检查Mermaid语法'); return; } var parent = graph.getDefaultParent(); graph.getModel().beginUpdate(); try { var cellMap = {}; var i = 0; parsed.nodes.forEach(function(node) { var x = 40 + (i % 5) * 180; var y = 40 + Math.floor(i / 5) * 100; var style = 'whiteSpace=wrap;html=1;' + node.shape; var cell = graph.insertVertex(parent, null, node.label, x, y, 120, 60, style); cellMap[node.id] = cell; i++; }); parsed.edges.forEach(function(edge) { var source = cellMap[edge.from]; var target = cellMap[edge.to]; if (source && target) { graph.insertEdge(parent, null, edge.label || '', source, target, 'edgeStyle=orthogonalEdgeStyle;'); } }); var layout = new mxHierarchicalLayout(graph); layout.execute(parent); } finally { graph.getModel().endUpdate(); } }这里有几个细节值得说清楚。第一,mxHierarchicalLayout执行时要求所有节点和边都已经插入模型,所以顺序上必须是先节点、再边、最后布局。第二,临时坐标我用了简单的5列网格排布,布局算法执行后会完全重排坐标,这样用户看到的第一眼就是整理好的图,而不是一堆叠在一起需要手动拖开的乱麻。第三,边默认用正交拐角样式,和drawio里常用的流程图风格一致,视觉上更舒服。
3.5 完整代码结构与调用流程
整理一下完整的调用流程:启动时加载插件,注册"Mermaid"菜单;用户点击菜单项,弹窗收集Mermaid代码;转换函数里先解析语法,再插入图形数据,最后自动布局。如果你还想加一点实用性,可以在解析失败时输出具体的行列提示,帮用户定位Mermaid语法错误。
我建议把解析模块单独拆成一个文件,因为在写的过程中你会发现需要不断补充语法规则。比如后续想支持subgraph子图,只需要在解析器里识别到subgraph块后,把它视为一个分组容器节点,再在该容器内插入子节点即可。这样主入口代码一直保持简洁,扩展点都集中在解析器里。
4. 常见问题与排查实录
4.1 插件菜单不显示怎么办
这是最常遇到的问题。先分清是配置没生效还是插件文件报错了。配置没生效的话,重启前先打开配置编辑器,确认JSON能被drawio正确保存;如果保存后重启还是没有"Mermaid"菜单,检查插件文件路径是否包含中文字符或空格,路径里的特殊字符很容易导致加载失败,我建议插件目录和文件名全部用英文。
如果配置看起来没问题,就按Ctrl+Shift+I打开开发者工具,切到Console标签页,看有没有红色报错。常见问题是插件文件本身语法错误,或者插件代码里使用了某些drawio对象但调用时机不对。把Draw.loadPlugin回调外的全局代码都收进回调函数里,能避免不少奇怪的初始化问题。
注意:drawio的插件是启动时一次性加载的,修改插件文件后必须完全退出再重启,不是简单刷新页面就能生效。
4.2 Mermaid语法解析失败的场景
我刚开始写解析器的时候,老老实实按Mermaid官方的BNF语法来,结果发现用户写的代码千奇百怪:标签里有中文括号、有换行、有特殊字符,还有人写A-->B和A --> B混着来。后来我把解析策略放宽松了——先按行处理,行内先去掉注释(%%开头的内容),再做正则匹配;对无法解析的行直接跳过,只在最后统计里提示"有x行未识别"。
这样容错很高,代价是有时候误解析会悄悄吞掉你不想吞的节点。实际上可以通过给用户返回一个解析报告来解决,但MVP阶段我选择"尽量不打断创建流程"。如果你需要解析更复杂的语法,比如加入了子图或不同箭头样式,建议引入一个成熟的Mermaid解析库做词法分析,而不是持久战式地堆正则。正则适合快速验证,撑到大上百行代码时维护成本会明显上升。
4.3 布局混乱和性能卡顿的解决方案
布局混乱的根源大多是插入顺序和布局算法不匹配。比如有些节点还没插入完就执行了布局,或者边引用了不存在的节点ID。我在代码里用cellMap保存所有节点引用,布局前先做一轮存在性校验,从源头避免了"幽灵边"。
性能问题主要体现在大图上:一次性解析超过200个节点时,beginUpdate/endUpdate之间的批量插入完全没有问题,但mxHierarchicalLayout的计算量会明显增加。我的处理办法是给布局算法加一个节点数阈值,超过150个节点就跳过自动布局,改为按解析顺序生成网格坐标,让用户自己用drawio自带的布局按钮二次整理。这样在超大图场景下也不会卡死应用。
4.4 生成文件异常报错时先别慌
用插件生成图形的时候,偶尔会把drawio搞到报"非绘图文件"错误。这类报错多半是某个节点或边的XML属性不合法,比如style里混入了不可见的控制字符。排查时最有效的办法是把画布不断缩小,看是哪个区域图形异常;或者打开文件的XML源码直接搜索异常片段。插件生成的图用到的大部分属性都是drawio原生支持的,只要不手动往style里塞奇奇怪怪的键值,基本不会触发这个问题。另外提醒一句,drawio文件正常双击就能打开,如果遇到打不开的情况,优先备份原文件再操作。
这个插件折腾下来,我最大的体会是:工具链的打通不一定要靠"完整实现"去解决问题,能用最小代价解决80%的日常痛点,就值得先落地。Mermaid和drawio的定位本来就不完全一样,硬要做成完整替代Mermaid渲染器的插件,投入产出比反而不高。但如果你只是想让这两者之间的连接不再靠手动,这个轻量方案已经很能打了。
最后分享一个小技巧:我平时写Mermaid代码会用在线预览器或VS Code插件先把图画出来,确认无误后再粘贴进drawio生成最终的可编辑版本。这样流程兼顾了Mermaid的"快"和drawio的"可精细调整",是现在的日常画图习惯。未来如果还有精力,我会考虑给插件加上子图支持和导出Mermaid的逆向转换,那样整个工作流就彻底闭环了。
本文还有配套的精品资源,点击获取