☰
零依赖离线Mermaid本地编辑器:构建管线与渲染沙箱拆解
2026/9/29 2:30:30 网站建设 项目流程

零依赖离线Mermaid本地编辑器:构建管线与渲染沙箱拆解

【免费下载链接】mermaidGeneration of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown项目地址: https://gitcode.com/GitHub_Trending/me/mermaid

一个完整的所见即所得图编辑器,零后端、零 CDN、运行时只加载两个本地脚本,它就藏在 Mermaid 官方仓库的private子包里。它就是 packages/mermaid-local-editor——一个从dist/产物直接离线运行的 Mermaid 本地编辑器。

一键构建命令:pnpm build:mermaid:full

在仓库根目录只需执行一条命令,即可完成构建并拉起本地静态服务:

pnpm build:mermaid:full

执行后浏览器打开 http://localhost:8081,你会看到左侧是 Mermaid 源码文本域、右侧是流程图预览区的双栏布局,顶部工具栏提供多图切换、视图重置与 SVG 导出;断网状态下继续输入、缩放、切换图表均不受影响。

数据流鸟瞰:从一次键入到可恢复的视图状态

你在左侧文本域敲下的字符,会依次经过四个处理节点,每个节点只接收上一节点的产物:

  1. 渲染:mermaid.render()把源码编译成 SVG 字符串,此刻内容还是"未净化的";
  2. 净化:DOMPurify按白名单剥离危险标签与属性,仅放行 Mermaid 渲染必需的foreignObject标签和xmlns属性;
  3. 沙箱展示:净化后的 SVG 写入新建的sandbox iframe,成为预览区唯一的内容来源;
  4. 持久化:源码与缩放平移得到的{ scale, panX, panY }视图状态一起写入localStorage,下次启动时直接恢复到上次的视角。

因为流程是单向的,模块之间几乎没有直接耦合,唯一的交汇点是state这个视图状态对象。源码布局与这条流水线一一对应:

static/ ├── index.html # 工具栏与双栏骨架 ├── app.js # 装配入口,持有 render/load/applyTransform 主流程 ├── styles.css └── js/ ├── config.js # mermaid.initialize 与全局视图状态 ├── renderer.js # 渲染 → 净化 → 沙箱 ├── storage.js # localStorage 多图管理 ├── ui.js # 工具栏事件与防抖自动保存 └── navigation.js # 键盘节点导航

核心机制深挖

为什么用"拷贝式"构建链而非把编辑器打进主包

如果把编辑器当作主包的一部分参与 bundle,它会拖着全部图类型的解析器一起膨胀,且编辑器改一行代码就要触发 Mermaid 运行时的完整重构建。构建脚本因此把编辑器当纯静态站处理:只取已构建好的mermaid.min.js和依赖目录里的purify.min.js,用cpy平铺进vendor/子目录。

根 package.json 中该命令的真实定义是一条五段链,每一段只做一件事:

pnpm clean && pnpm build:mermaid && pnpm copy:editor \ && pnpm copy:mermaid && pnpm copy:dompurify && pnpm serve:dist

copy:editor用**/*原样复制static/下所有文件,保证index.html里的./vendor/...、./app.js相对引用在产物目录中结构不变;copy:dompurify则直接从 pnpm 扁平化存储的dompurify@*/版本化目录里取purify.min.js,--flat让产物永远是干净的vendor/两个文件。代价是:编辑器持有的是 Mermaid 运行时的"构建时快照",dist/根目录与vendor/下的两份mermaid.min.js没有自动同步机制,升级 Mermaid 必须重跑整条链。

sandbox iframe 如何保证用户输入碰不到主页面

若渲染出的 SVG 直接插进主页面 DOM,任何残余的事件处理器或脚本载荷都会与工具栏、存储代码共享同一 origin 和权限,一条onload就足以读走localStorage里的全部图表。所以 renderer.js 的常规分支坚持"永不直接插入主页面":

const iframe = document.createElement('iframe'); // 只给同源权限、不放开 allow-scripts:预览与主页面形成硬边界 iframe.sandbox = 'allow-same-origin'; preview.appendChild(iframe); state.iframeRef = iframe; // 主页面只负责传数据,节点本身交给 iframe 文档,避免跨文档直接操作 const svgEl = new DOMParser() .parseFromString(cleanSvg, 'image/svg+xml') .documentElement; svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); iframe.contentDocument.body.appendChild( iframe.contentDocument.importNode(svgEl, true) );

代价是隔离带来的操作成本:applyTransform()的缩放平移只能经state.iframeRef.contentDocument跨文档取到svg元素,每处访问都要判空;E2E 分支(IS_E2E为真时净化后直插 DOM)正是为绕过这一成本、让自动化截图可直接断言而存在的。

视图状态如何保证"缩放位置"刷新后不变

若缩放平移只改 CSS,用户排好的视角会在切换图表或刷新后丢失,多图工具的使用成本会高到不可接受。所以编辑器把视图状态当作与源码同级的一等数据,逐帧写回存储:

function applyTransform() { const svg = state.iframeRef.contentDocument?.querySelector('svg'); // 钳制平移上限,防止极端拖拽下 transform 数值漂移到无穷 state.panX = Math.max(-20000, Math.min(20000, state.panX)); svg.style.transform = `translate(${state.panX}px, ${state.panY}px) scale(${state.scale})`; // 视图随图表一起落盘,切换图表与刷新后才能恢复上次视角 storage.updateCurrent({ view: { scale: state.scale, panX: state.panX, panY: state.panY }, }); }

因为localStorage里单张图的结构是{ [名称]: { src, view } }(见 storage.js),"源码 + 视角"成对存取;300ms 防抖自动保存(ui.js)也写同一结构,忘记点 Save 也不会丢源码。代价是applyTransform在每次滚轮与拖拽事件里都触发一次localStorage写入,高频滚动时序列化开销随图表数量线性放大。

防御与容错:用户输入要过的四道闸

威胁/故障向量防御层对应代码位置失效后果
图源码中的点击跳转与链接注入securityLevel: 'strict'解析层抑制config.js的initMermaid点击节点触发外部跳转
SVG 中注入<script>、危险属性DOMPurify 白名单净化,豁免foreignObject/xmlnsrenderer.js的renderDiagram危险内容进入 DOM
残余on*内联事件二次遍历删除所有on前缀属性renderer.js的renderDiagram节点插入时触发onload等事件
残余载荷在预览区执行sandbox="allow-same-origin"(无allow-scripts)renderer.js的 iframe 创建预览逃逸沙箱、触碰主页面与存储
Mermaid 语法错误try/catch后以红色<pre>展示e.messagerenderer.js的catch分支白屏,用户无法定位错误
极端拖拽导致视图状态溢出panX/panY钳制在±20000app.js的applyTransformtransform 漂移、画布不可寻

这是解析、净化、属性、沙箱四层纵深防御的堆叠,任何单层被绕过都不会走到脚本执行;表格下半部分属于容错层,只保证"出错仍可用",不承担安全职责。

动手改造清单

🛠如果你要换默认主题或首次访问内容:入口是static/js/config.js的theme字段与static/js/storage.js中默认main图的src,影响范围是编辑器内两个文件、不触碰构建脚本;注意构建链是原样拷贝static/,改完必须重跑pnpm build:mermaid:full才能反映到dist/产物。

🎛如果你要调缩放范围或平移手感:滚轮系数-0.0015与0.2/4的缩放钳制写死在renderer.js的doc.onwheel分支,±20000平移钳制在app.js,影响范围是这两个文件;由于只改静态源、不动运行时构建,重跑一次拷贝步骤即可验证。

收束

离线能力不等于"没有依赖",而是两段闭环:构建期把vendor/收拢,运行期把沙箱收拢。"构建期收拢 + 运行期隔离"这套模式,可以直接移植到你自己的私有化部署工具里。

【免费下载链接】mermaidGeneration of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown项目地址: https://gitcode.com/GitHub_Trending/me/mermaid

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询