首页
/ Mermaid 新增图表类型完全指南:从 JISON 文法到渲染管线(历史参考)

Mermaid 新增图表类型完全指南:从 JISON 文法到渲染管线(历史参考)

2026-09-06 15:41:47作者:昌雅子Ethen

本篇基于 mermaid 仓库的社区贡献文档 new-diagram-jison.md,系统讲解向 mermaid 添加一种新图表类型的完整流程:定义 JISON 文法、用 yy 对象存储解析数据、实现渲染器、注册类型检测以及接入主题与无障碍能力。需要特别注意的是,官方文档已明确 JISON 文法处于弃用(Deprecated)状态,基于 JISON 的新图表 PR 将不被接受;但仓库中仍有 18 个图表使用 .jison 文件(如 flow.jisonsequenceDiagram.jisonclassDiagram.jison),理解这套历史管线对于维护旧图表、排错以及理解 parser/renderer 分层架构仍然必要。读完后,你将掌握 mermaid 图表“文法解析 → 数据存储 → 类型检测 → 渲染触发”的完整调用链,以及无障碍(Accessibility)与主题(Theming)两套通用基础设施的接入方式。

背景:JISON 已弃用,新图表请走 Langium/Chevrotain

原文明档开头即给出警告:

JISON grammars are deprecated in mermaid. Please use Langium instead. See New Diagram for more information. New diagrams with JISON grammars will not be accepted.

即:JISON 文法已弃用,请使用 Langium 代替,参见新版指南 new-diagram.md。新图表的推荐做法是:

  • 使用 Chevrotain 编写文法,与图表自身一同放在 packages/mermaid/src/diagrams/<diagram>/parser/ 下,用例图(usecase)是参考实现:包含词法器、CstParser 与构建图表模型的 CST visitor,并共享 runChevrotainParse.ts 来运行 lexer/parser 组合并带源码位置地报告错误;
  • 另一批图表(architecture、gitGraph、info、packet、pie、radar、treemap 等)使用 Langium 文法,位于独立包 packages/parser
  • 旧 JISON 图表继续受支持——修 bug 就地在原处修改,而不是重写,但它不是新工作的目标。

因此,下文的 JISON 流程应作为维护既有图表的参考手册来读:它完整展示了 mermaid 图表模块的骨架,而这套骨架(parser + db + renderer + styles + injectUtils)在 Langium/Chevrotain 时代依然保留,只是文法编写语言发生了变化。

Step 1:文法与解析(Grammar & Parsing)

定义识别关键词

为新的图表类型定义 JISON 文法的第一步,是规定一段文本如何被识别为该类型图表——通常是一个起始关键词。原文给出的对照:

  • flowchart 以关键词 graph 开头;
  • sequence diagram 以关键词 sequenceDiagram 开头。

组织方式上,需要为每种图表在 diagrams 目录下新建一个文件夹,并在其中放一个 parser 子文件夹存放文法文件。仓库内现存 JISON 图表均遵循这一布局,例如 xychart.jisonblock.jison

官方示例包 mermaid-example-diagram 提供了最小完整的参照。其词法部分 exampleDiagram.jison 展示了典型结构:

%lex

%options case-insensitive

%%

"example-diagram"       return 'example-diagram'    ;
[\s\n\r]+              return 'NL'      ;
[\s]+                  return 'space';
"showInfo"            return 'showInfo';
<<EOF>>               return 'EOF'     ;
.                     return 'TXT' ;

/lex

%start start

%% /* language grammar */

start
	: example-diagram document 'EOF' { return yy; }
	;

document
	: /* empty */
	| document line
	;

line
	: statement { }
	| 'NL'
	;

statement
	:  showInfo   { yy.setInfo(true);  }
	;

可以看到:词法规则(%lex/lex 之间)负责把关键词 example-diagram 识别出来,语法规则中 %start start 声明起始符号,而 statement 规则里 yy.setInfo(true) 这一行,正是解析动作(action)把解析结果写入数据对象的典型写法。

在解析过程中存储数据

原文“Store data found during parsing”一节指出:解析器在解析过程中遇到的数据会被暂存,供后续渲染器使用。JISON 的机制是——你可以让解析器在解析期间调用由解析器的使用者注入的一个对象,该对象在解析过程中被调用来存储数据。原文给出的语法片段:

statement
	: 'participant' actor  { $$='actor'; }
	| signal               { $$='signal'; }
	| note_statement       { $$='note';  }
	| 'title' message      { yy.setTitle($2);  }
	;

在这段文法中,当解析器遇到 title 关键字时,会调用数据对象上的 setTitle 方法——$2 是第二个语法制约(即 title 后的 message),$$ 是该产生式的返回符号。示例包的 exampleDiagramDb.js 就是这样一个“数据对象”:它维护 messageinfo 两个内部状态,并导出 setMessage/getMessage/setInfo/getInfo/clear 五个函数供解析器调用和渲染器读取,最后以 default export 汇总成 db

定义 parseError

原文特别强调(Note):务必为解析器定义 parseError 函数并调用 mermaid.parseError,这样就能为最终用户提供统一的解析错误检测方式。yy 对象上应提供:

exports.parseError = function (err, hash) {
  mermaid.parseError(err, hash);
};

yy 对象在解析时是这样被初始化绑定的:

const parser = exampleParser.parser;
parser.yy = db;

即:先把图表的 db 模块挂到 parser.yy 上,文法中的动作代码(如 yy.setInfo(true))才能在解析期把数据写入该 db。

Step 2:渲染(Rendering)

写一个渲染器:输入是 Step 1 解析阶段收集到的数据,输出是渲染出的图表 SVG。原文明确建议:参考 sequenceRenderer.js 而不是 flowchart 的渲染器,因为 sequence 的渲染器是更通用(generic)的示例。渲染器应放在该图表自己的文件夹内。

示例包中的 exampleDiagramRenderer.js 展示了渲染器入口的典型形态:导出一个 draw(text, id, version) 函数,内部通过 getConfig() 读取配置、处理 securityLevel === 'sandbox' 的 iframe 场景(从 #i + id 的 iframe 的 contentDocument 中取 body)、用 d3 的 select('#' + id) 选中目标 SVG 并追加内容,最后调用 setupGraphViewbox 设置视框。

从源码结构看,图表最终是以一个聚合对象注册的——diagram-definition.ts 把五个部分捆绑在一起:

export const diagram = {
  db,          // 数据对象(Step 1 的存储目标)
  renderer,    // 渲染器(本步)
  parser,      // JISON 生成的解析器
  styles,     // 主题样式函数(见 Theming 一节)
  injectUtils,
};

这个五元组(db / renderer / parser / styles / injectUtils)就是 mermaid 图表模块的标准骨架,新图表无论用 JISON、Langium 还是 Chevrotain 实现文法,都遵循同样的聚合方式。

Step 3:新图表类型的检测(Detection)

原文指出:需要在 diagram-api/detectType.tsdetectType 中增加对新图表类型的检测能力,检测命中时应返回一个新图表类型的键(key)

关于 key 的命名,原文给出了一段与无障碍直接相关的要求:这个 key 会被用作 SVG 元素的 aria-roledescription,因此应当是一个能清晰描述图表类型的词。以 UML 部署图为例:

  • 好:"UMLDeploymentDiagram"(读屏软件会念成 "U-M-L Deployment diagram");
  • 好:"deploymentDiagram"(念成 "Deployment Diagram");
  • 差:"deployment"(不足以描述图表)。

同时原文注明:类型 key 不必与文法中的起始关键词相同,但两者保持一致会更方便。

对照当前仓库的 detectType.ts,实现上已演化为检测器注册表模式:模块维护一个 detectors: Record<string, DetectorRecord> 映射,detectType(text, config) 先剔除 front matter、%%init 指令和注释(frontMatterRegex/directiveRegex/anyCommentRegex),然后按注册顺序逐一调用各图表的 detector(text, config),第一个返回真值的 key 即为检测结果;全部未命中则抛出 UnknownDiagramError。注册通过 addDetector(key, detector, loader) 完成,其中 loader 支持按需懒加载(lazy load)图表模块——只有被检测命中的图表才会加载。registerLazyLoadedDiagrams 的文档注释还特别提示:检测器的顺序很重要,越具体的检测器越要靠前,因为第一个返回 true 的检测器决定最终加载的图表。

Step 4:最后一块拼图——触发渲染

原文说明:到前三步为止,mermaid 能把文本检测为新类型,但真正尝试渲染时却找不到匹配,渲染会失败。修复方式是在 main.js 的 switch 语句中为 init 增加一个新的 case,其值与 Step 3 检测返回的类型一致;该 case 中的代码应调用该图表的渲染器,并把解析器得到的数据作为参数传入。

结合 Step 3 中检测器注册表的现状可以推断:新版实现中“main.js 的 switch”已被 addDetector 时提供的 loader 所取代——检测命中 key 后,通过 loader 异步取回 diagram 对象(即上文五元组),再依次调用 parser、renderer 完成渲染。对贡献者而言,核心契约没有变:检测返回的 key、注册时提供的 loader、以及五元组内部的数据流必须一一对应

把解析器作为独立模块使用(Usage of the parser as a separate module)

原文给出了解析器脱离 mermaid 主流程独立使用的完整流程,分四段。

Setup(装配)

const graph = require('./graphDb');
const flow = require('./parser/flow');
flow.parser.yy = graph;

Parsing(解析)

flow.parser.parse(text);

Data extraction(数据提取)

graph.getDirection();
graph.getVertices();
graph.getEdges();

通过 mermaid API 获取解析器

const parser = mermaid.getParser();

原文再次强调:parse 需要一个 graph 对象来存储数据(flow.parser.yy = graph;),关于该对象的细节参见 graphDb.js。这也解释了 JISON 方案的核心数据流:解析器是“无状态动作代码”,所有状态都活在外部的 db/graph 对象里——这正是 Step 1 中 yy 机制的意义。

布局(Layout):用 dagre-wrapper 而非 dagre-d3

原文指出:如果你的新图表使用基于 dagre 的布局,请以 flowchart-v2 为模板。这样你就能用上 dagre-wrapper 而不是 dagreD3——官方正在从后者迁移出去。仓库中独立包 mermaid-layout-elkmermaid-layout-tidy-tree 的存在也印证了布局引擎正走向可插拔化:新图表若需要自动布局,优先考虑与现有 wrapper 层集成,而不是直接内嵌 dagre-d3。

图表的通用能力(Common parts of a diagram)

原文列出了各图表类型之间被标准化的四块通用能力,mermaid 力求让所有图表在最终用户视角下行为一致:

  • Directives(指令):一种在图代码内部修改图表配置的方式(如 %%{init: ...}%%);
  • Accessibility(无障碍):让作者为使用读屏软件的用户提供标题、描述等附加信息;
  • Themes(主题):修改图表样式的统一方式;
  • Comments(注释):应遵循 mermaid 的注释标准。

下面对其中两块给出原文的完整技术细节。

Accessibility:aria-roledescription 与 accTitle/accDescr

mermaid 会为图表 SVG 元素自动添加三类无障碍信息:aria-roledescription、可访问标题(accessible title)、可访问描述(accessible description)。

其中 aria-roledescription 会被自动设置为 Step 3 检测返回的图表类型 key 并插入 SVG 元素(定义参见 W3C 的 ARIA 标准,原文附 w3.org 链接,此处从略)。

可访问标题与描述的图内语法在 accessibility 文档 中有说明。作为设计目标,各图表之间的 JISON 无障碍语法应当保持一致。原文给出的标准文法片段(lexical 部分 + 语法部分):

* lexical grammar */
%lex
%x acc_title
%x acc_descr
%x acc_descr_multiline

%%
accTitle\s*":"\s*                                { this.begin("acc_title");return 'acc_title'; }
<acc_title>(?!\n|;|#)*[^\n]*                     { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s*                                { this.begin("acc_descr");return 'acc_descr'; }
<acc_descr>(?!\n|;|#)*[^\n]*                     { this.popState(); return "acc_descr_value"; }
accDescr\s*"{"\s*                                { this.begin("acc_descr_multiline");}
<acc_descr_multiline>[\}]                        { this.popState(); }
<acc_descr_multiline>[^\}]*                      return "acc_descr_multiline_value";

statement
    : acc_title acc_title_value  { $$=$2.trim();yy.setTitle($$); }
    | acc_descr acc_descr_value  { $$=$2.trim();yy.setAccDescription($$); }
    | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }

要点:词法器用 %x 声明排他状态(exclusive state),遇到 accTitle:this.begin("acc_title") 进入单行标题捕获态,捕获到行尾 this.popState() 返回;accDescr:{ 进入多行描述态,直到 } 为止。语法部分统一经 yy.setTitle / yy.setAccDescription 写入数据对象。

设置标题与描述的函数由公共模块提供。原文以 flowDb.js 的导入为例:

import {
  setAccTitle,
  getAccTitle,
  getAccDescription,
  setAccDescription,
  clear as commonClear,
} from '../../commonDb';

即各图表 db 不重复实现存取逻辑,而是从 commonDb 统一复用,并在自己的 clear 中调用 commonClear 做状态复位。最后,无障碍标题与描述会在 mermaidAPI 的 render 函数中被插入到 SVG 元素中——与 aria-roledescription 的注入点一致。

Theming:主题引擎接入点

mermaid 支持主题并内置主题引擎,用法参见 theming 文档。原文指出,给图表添加主题支持,代码上集中在几个关键位置:

  1. 样式引擎入口在 src/styles.js。其中的 getStyles 函数会在 mermaid 应用样式时被调用;
  2. 该入口会调用你的图表应提供的返回 CSS 的函数。图表侧的这个函数(通常也叫 getStyles)放在 src/diagrams/<你的图表>/ 文件夹下,文件名为 styles.js。它接收主题选项作为参数,原文示例:
const getStyles = (options) =>
  `
    .line {
      stroke-width: 1;
      stroke: ${options.lineColor};
      stroke-dasharray: 2;
    }
    // ...
    `;
  1. 需要把你的函数登记到 src/styles.jsthemes 对象中(原文以假想的 xyzDiagram 为例):
const themes = {
  flowchart,
  'flowchart-v2': flowchart,
  sequence,
  xyzDiagram,
  //...
};
  1. 颜色选项与数值的实际定义在 src/theme/theme-[xyz].js 中。只要你把图表所需的选项补充进现有主题文件,主题系统就能平滑工作而不出现意外

示例包的 styles.js 正好演示了第 2 点的真实形态:它按 options.THEME_COLOR_LIMIT 循环生成 .section-N 的选择器,从 options['cScale' + i]options['cScalePeer' + i]options['cScaleInv' + i]options['cScaleLabel' + i] 读取主题变量拼出 CSS,最后导出 getStyles。而 exampleDiagramRenderer.jsdraw 函数里读取的 getConfig().themeVariables.THEME_COLOR_LIMIT 与这里的 options.THEME_COLOR_LIMIT 相互印证:主题变量既经由 styles 函数影响 CSS,也经由 themeVariables 进入渲染逻辑本身(例如决定循环绘制多少段)。

关键文件索引

关注点 文件
本篇对应源文档 docs/community/new-diagram-jison.md
新版(Langium/Chevrotain)指南 docs/community/new-diagram.md
最小示例图表(五元组骨架) diagram-definition.ts
示例 JISON 文法 exampleDiagram.jison
示例数据对象 exampleDiagramDb.js
示例渲染器 exampleDiagramRenderer.js
示例主题样式 styles.js
类型检测与检测器注册表 detectType.ts
Chevrotain 公共解析入口 runChevrotainParse.ts
Langium 文法包 packages/parser

小结

  • 新增图表:JISON 已弃用且不接受新 PR,请遵循 new-diagram.md 采用 Chevrotain(与图表同目录)或 Langium(packages/parser);
  • 维护既有 JISON 图表:牢记三步数据流——parser.yy = db 装配、文法动作中 yy.setXxx(...) 写入、渲染器读取 db 后绘制;错误处理必须走 mermaid.parseError
  • 无论文法技术栈如何演进,图表模块的契约不变:五元组(db/renderer/parser/styles/injectUtils)+ 检测器 key(兼作 aria-roledescription,须语义完整)+ 公共无障碍函数(commonDb)+ 主题文件中的选项定义 + 指令、注释等通用能力保持一致。
登录后查看全文
热门项目推荐
相关项目推荐