Mermaid 是一个基于 JavaScript 的图表生成库,允许通过简单的文本语法创建多种类型的可视化图表。其核心特点是 “Diagrams as Code”(代码即图表),开发者可以用纯文本的方式描述图表结构,由引擎自动渲染为可交互的矢量图形。
特性 | 说明 | 示例场景 |
---|---|---|
文本驱动 | 用类 Markdown 语法定义图表 | 版本控制友好的文档 |
多图表支持 | 支持流程图、时序图、甘特图等 30+ 类型 | 技术文档、系统设计 |
跨平台 | 浏览器、Node.js、VS Code 等均可使用 | 博客嵌入、CI/CD 文档生成 |
可定制化 | 主题、样式、布局均可配置 | 企业品牌风格适配 |
自动化集成 | 可与 Puppeteer 等工具结合 | 自动化报告生成 |
```mermaid
graph TD
A[开始] --> B{条件判断}
B -->|是| C[执行操作]
B -->|否| D[结束]
```
```mermaid
sequenceDiagram
participant 用户
participant 系统
用户->>系统: 登录请求
系统-->>用户: 返回令牌
```
```mermaid
classDiagram
class Animal {
+String name
+void eat()
}
class Dog {
+void bark()
}
Animal <|-- Dog
```
Mermaid | Visio | Draw.io | |
---|---|---|---|
协作效率 | Git 友好 | 文件冲突 | 需手动同步 |
维护成本 | 改文本自动更新 | 需重新调整图形 | 需重新调整图形 |
自动化能力 | 支持 CI/CD 集成 | 无 | 有限 |
学习曲线 | 30 分钟掌握基础 | 2小时+ | 1小时+ |
### 1.1 诞生背景
Mermaid 的诞生源于以下技术需求:
1. **文档即代码**趋势的兴起
2. 传统绘图工具的局限性:
- Visio:商业软件,协作困难
- Draw.io:需要手动调整布局
- PlantUML:需要服务端支持
3. 开发者对可视化方案的核心诉求:
- 版本控制友好
- 可读性优先
- 自动化流程集成
- 跨平台渲染
interface MermaidAPI {
parse(text: string): void;
render(
id: string,
text: string,
cb?: (svgCode: string) => void
): Promise<string>;
initialize(config: MermaidConfig): void;
// ...其他方法
}
DOCTYPE html>
<html>
<body>
<div class="mermaid">
graph TD A[开始] --> B(处理流程) B --> C{判断条件} C -->|是| D[执行操作] C
-->|否| E[结束]
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js">script>
<script>
mermaid.initialize({ startOnLoad: true });
script>
body>
html>
import { create } from "mermaid-render";
const mermaid = create({
securityLevel: "loose",
theme: "forest",
});
const svg = await mermaid.render(
"diagram-1",
`
sequenceDiagram
用户->>+服务器: 请求数据
服务器-->>-用户: 返回响应
`
);
/* 自定义主题示例 */
.mermaid {
font-family: "Roboto Mono";
background-color: #f8f9fa;
path {
stroke: #2c3e50;
}
.node rect {
fill: #3498db;
stroke-width: 2px;
}
}
```mermaid
graph TD
A[开始] --> B{条件判断}
B -->|是| C[执行操作]
B -->|否| D[结束]
语法要素:
graph
方向:TD
(上下)/LR
(左右)[]
矩形 / ()
圆角 / {}
菱形-->
实线 / -.->
虚线 / ==>
粗线```mermaid
graph TD
A[开始] --> B{条件判断}
B -->|是| C[执行操作]
B -->|否| D[结束]
```
特殊语法:
alt
/else
条件分支par
并行操作critical
关键区域```mermaid
classDiagram
class Animal {
+String name
+void eat()
}
class Dog {
+void bark()
}
Animal <|-- Dog
Dog "1" *-- "0..n" Bone : 拥有
关系表示:
<|--
*--
o--
mermaid.initialize({
startOnLoad: true,
theme: "dark",
flowchart: {
curve: "basis",
},
securityLevel: "loose",
});
关键配置项:
theme
: 主题(default/forest/dark/neutral)fontFamily
: 全局字体logLevel
: 调试日志级别const render = async (elementId, code) => {
const { svg } = await mermaid.render(elementId, code);
document.getElementById("output").innerHTML = svg;
};
// 示例调用
render("demo", "graph TD\nA-->B");
// 预编译图表
// build-diagrams.js
const fs = require('fs');
const { create } = require('mermaid-render');
async function build() {
const mermaid = create();
const diagrams = fs.readdirSync('./src/diagrams');
for (const file of diagrams) {
const code = fs.readFileSync(`./src/diagrams/${file}`, 'utf8');
const { svg } = await mermaid.render(file, code);
// 输出到 public 目录
fs.writeFileSync(`./public/diagrams/${file.replace('.mmd','.svg')}`, svg);
}
}
build();
// 使用 Web Worker
**主线程代码**:
```javascript
// 主线程
const worker = new Worker('mermaid-worker.js');
// 发送渲染请求
worker.postMessage({
type: 'render',
config: {
theme: 'dark',
fontSize: 16
},
code: 'graph TD\nA-->B'
});
// 接收结果
worker.onmessage = (e) => {
if (e.data.type === 'svg') {
document.getElementById('output').innerHTML = e.data.svg;
}
};
Worker 线程代码 (mermaid-worker.js
):
// worker.js
importScripts('https://cdn.jsdelivr.net/npm/[email protected]/dist/mermaid.min.js');
self.onmessage = async (e) => {
if (e.data.type === 'render') {
// 初始化配置
mermaid.initialize({
...e.data.config,
startOnLoad: false
});
try {
// 服务端渲染模式
const { svg } = await mermaid.render('worker-diagram', e.data.code);
self.postMessage({ type: 'svg', svg });
} catch (err) {
self.postMessage({ type: 'error', message: err.message });
}
}
};
问题1:Worker 中报错 “window is undefined”
解决:使用 mermaid-render
替代浏览器版:
import { create } from 'mermaid-render';
const mermaid = create();
// ...其余代码不变
问题2:预编译 SVG 样式丢失
解决:内联 CSS 样式:
const { svg } = await mermaid.render('diagram', code);
const styledSVG = svg.replace('
问题3:图表更新后缓存未失效
解决:添加版本哈希:
function getCacheKey(code) {
return `${sha1(code)}-${mermaid.version}`;
}
问题4:中文显示异常
解决:指定中文字体
<style>
.mermaid {
font-family: "Microsoft YaHei", sans-serif;
}
style>
/* 自定义主题 */
.mermaid {
--bg-color: #2d2d2d;
--text-color: #f8f8f2;
--node-color: #3498db;
}
/* 动态切换主题 */
document.querySelector('#dark-mode').addEventListener('click', () => {
mermaid.initialize({ theme: 'dark' });
mermaid.reload();
});
// 开启调试模式
mermaid.initialize({ logLevel: 1 });
// 捕获解析错误
mermaid.parseError = (err, hash) => {
console.error("Mermaid Error:", err.message);
showErrorToast(err.message);
};
优化策略:
will-change: transform
CSS 属性.mermaid svg {
transform: translateZ(0);
}
技术栈:
// 使用 Yjs 实现协同编辑
const ydoc = new Y.Doc();
const ytext = ydoc.getText("mermaid");
ytext.observe((event) => {
renderDiagram(ytext.toString());
});
// 集成 Mermaid
const renderDiagram = debounce(async (code) => {
const { svg } = await mermaid.render("collab-diagram", code);
document.getElementById("canvas").innerHTML = svg;
}, 300);
以下是第二部分,重点解析服务端渲染与无头浏览器集成方案:
1. 环境差异:
- 缺少浏览器DOM API
- 字体渲染差异
- 异步加载资源限制
2. 性能要求:
- 高并发处理
- 内存泄漏预防
- 渲染缓存策略
3. 功能完整性:
- CSS样式继承
- 外部资源加载
- 交互功能模拟
方案 | 优点 | 缺点 |
---|---|---|
Puppeteer | 完整浏览器环境 | 资源消耗大 |
JSDOM | 轻量快速 | 缺少布局计算 |
Mermaid-cli | 官方工具链 | 定制能力有限 |
Canvas 模拟 | 高性能 | 样式兼容性问题 |
const puppeteer = require("puppeteer");
async function renderMermaid(mermaidCode) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(`
${mermaidCode}
`);
await page.evaluate(() => {
mermaid.initialize({ startOnLoad: true });
});
// 等待渲染完成
await page.waitForSelector(".mermaid svg");
const svg = await page.$eval(".mermaid", (el) => el.innerHTML);
await browser.close();
return svg;
}
// 共享浏览器实例
let _browser;
async function getBrowser() {
if (!_browser) {
_browser = await puppeteer.launch({
headless: "new",
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
}
return _browser;
}
// 缓存页面池
const pagePool = [];
async function getPage() {
if (pagePool.length > 0) return pagePool.pop();
const browser = await getBrowser();
const page = await browser.newPage();
await page.setViewport({ width: 1200, height: 800 });
return page;
}
// 释放资源
function releasePage(page) {
pagePool.push(page);
}
现象:部分图形元素缺失或错位
诊断步骤:
// 调试代码
await page.screenshot({ path: "debug.png" });
const logs = await page.evaluate(() => {
return Array.from(document.querySelectorAll(".mermaid")).map((el) => {
return {
width: el.offsetWidth,
height: el.offsetHeight,
computedStyle: window.getComputedStyle(el),
};
});
});
console.log("Layout metrics:", logs);
解决方案:
await page.waitForFunction(
() => {
return (
document.querySelectorAll('.mermaid[data-processed="true"]').length > 0
);
},
{ timeout: 5000 }
);
处理方案:
// 预加载字体
await page.addStyleTag({
content: `
@font-face {
font-family: 'MermaidFont';
src: url('data:font/ttf;base64,${fontBase64}');
}
`,
});
// 强制字体设置
await page.evaluate(() => {
const style = document.createElement("style");
style.textContent = `
.mermaid * {
font-family: 'MermaidFont' !important;
}
`;
document.head.appendChild(style);
});
以下是第三部分,重点解析插件开发与安全防护
// 交通信号灯图形插件
const trafficLightDiagram = {
id: "trafficLight",
parser: {
parse: (text) => {
const states = text.split("->");
return { transitions: states };
},
},
renderer: (diagram, api) => {
const circles = ["red", "yellow", "green"]
.map(
(color, i) =>
`${30 + i * 40} " r="15" fill="${color}"/>`
)
.join("");
return ` ${circles}`;
},
};
// 注册插件
mermaid.registerDiagrams([trafficLightDiagram]);
// 安全渲染配置
mermaid.initialize({
securityLevel: "strict", // 可选 'loose'|'strict'|'antiscript'
secure: true,
flowchart: {
htmlLabels: false, // 禁用HTML标签
},
});
// 输入过滤函数
function sanitizeMermaidCode(input) {
return input.replace(/[<>"&]/g, (m) => {
return {
"<": "<",
">": ">",
"&": "&",
'"': """,
}[m];
});
}
const { VM } = require("vm2");
async function safeRender(code) {
const vm = new VM({
timeout: 1000,
sandbox: {},
eval: false,
});
return vm.run(`
const mermaid = require('mermaid');
mermaid.initialize({ securityLevel: 'strict' });
mermaid.render('graph', \`${code}\`);
`);
}
const { expect } = require("chai");
const pixelMatch = require("pixelmatch");
describe("Mermaid渲染测试", () => {
it("流程图应正确渲染", async () => {
const svg1 = await renderMermaid("graph TD;A-->B");
const svg2 = await loadBaseline("flowchart.png");
const diff = pixelMatch(svg1, svg2, null, 800, 600);
expect(diff).to.be.below(100); // 允许100个像素差异
});
});
const testCases = [
{
input: "graph TD\nA-->B",
should: "生成2个节点和1个连线",
assert: (svg) => {
const nodes = svg.match(/]+class="node" /g);
expect(nodes).to.have.lengthOf(2);
},
},
// 更多测试用例...
];
// src/diagrams/flowchart/parser.ts
class FlowchartParser {
parse(text: string) {
const tokens = this.lexer.tokenize(text);
this.parser.input = tokens;
const ast = this.parser.flowchart();
this.validate(ast);
return ast;
}
private validate(ast: AST) {
if (!ast.nodes.length) {
throw new Error("流程图必须包含至少一个节点");
}
}
}
// src/diagrams/flowchart/renderer.js
function layout(ast) {
const ranks = {};
// 层级计算
ast.edges.forEach((edge) => {
const fromRank = ranks[edge.from] || 0;
ranks[edge.to] = Math.max(ranks[edge.to] || 0, fromRank + 1);
});
// 节点位置计算
const positions = {};
Object.keys(ranks).forEach((node) => {
positions[node] = {
x: ranks[node] * 200,
y: Math.random() * 100, // 模拟简单布局
};
});
return positions;
}
1. 智能化:
- AI辅助图表生成
- 自动布局优化
2. 三维化:
- WebGL集成
- AR/VR支持
3. 协同化:
- 实时协作编辑
- 版本对比工具
项目优化建议: