CMP Mermaid:不用 WebView 的跨平台原生 Mermaid 渲染

442 阅读7分钟

🚀 先看结论

  • 🌍 跨平台:支持 Android、iOS、Desktop 和 Web(Kotlin/Wasm)。
  • 📊 完整范围:Mermaid.js 12.0.0 的 33 个官方图表家族已全部实现,33/33 达到 Stable。
  • ✅ 质量证据:完成 8,448 组 Native/Official 同源码视觉对拍、16,896 张矩阵截图、528 页 contact sheet;另有 441 组独立生产场景和 787 个 JVM 测试。
  • 📦 已经发布:KMP/CMP 与 Kotlin 1.7.21 Android 制品已进入 Maven Central,iOS 制品已发布到 CocoaPods。
  • 🧪 立即体验:打开 Web Playground

Mermaid 很适合把流程图、时序图、状态图直接写进 Markdown。

但在 Android、iOS 或 Compose Multiplatform 应用里展示 Mermaid,常见做法通常是启动 WebView、加载 Mermaid.js,再显示 JavaScript 生成的 SVG。只展示一张图时很直接;一个页面同时出现十几张甚至几十张图时,WebView 的内存、启动开销、生命周期、滚动体验和多实例管理就会成为实际成本。

所以我做了开源项目 CMP Mermaid:

把 Mermaid.js 12.0.0 的解析、状态、布局和渲染语义忠实翻译到 Kotlin Multiplatform,最终交给 Compose Canvas 绘制。生产链路不使用 WebView,也不加载 Mermaid.js。

CMP Mermaid Web Playground

这不是把 Mermaid 放进 WebView

CMP Mermaid 的正式渲染流程是:

Mermaid source
    -> Kotlin preprocessor and translated parser
    -> translated diagram database and layout preparation
    -> platform-independent MermaidScene
    -> Compose Canvas

其中:

  • mermaid-core 负责解析、Diagram DB、布局准备、主题和平台无关 SceneGraph;
  • mermaid-compose 负责 Compose Canvas 绘制、文本测量、交互与缩放;
  • Official 渲染与 Playground 位于隔离的调试工具中,不进入生产依赖。

业务模块只需依赖原生渲染制品。Mermaid.js 只用于生成官方参考图和视觉对拍证据,不会进入正式渲染链路。

下面是两组同源码结果。

Flowchart:CMP Native / Compose Canvas

Flowchart rendered by CMP Native

Flowchart:Official / Mermaid.js 12.0.0

Flowchart rendered by Mermaid.js

XY Chart:CMP Native / Compose Canvas

XY Chart rendered by CMP Native

XY Chart:Official / Mermaid.js 12.0.0

XY Chart rendered by Mermaid.js

Mermaid 12.0.0 的 33 个图表家族

当前版本已经完成 Mermaid.js 12.0.0 全部 33 个用户可见图表家族:

类别图表家族
流程与结构Flowchart、Sequence、Class、State、Entity Relationship、Requirement、Use Case、C4、Architecture、Block、TreeView、Railroad、Packet
数据与规划Gantt、Timeline、Kanban、User Journey、Git Graph、XY Chart、Pie、Radar、Sankey、Treemap、Quadrant、Venn
思维与专业建模Mindmap、Ishikawa、Cynefin、Event Modeling、Agentflow、Swimlanes、Wardley Map、ZenUML

33 个家族都经过各自的解析、状态、布局、图形、主题、文本和视觉门禁。项目不再是“只支持几个热门图表”的早期版本。

同时内置 Mermaid 12.0.0 的 11 个主题:

default, dark, forest, neutral, base,
neo, neo-dark, redux, redux-color,
redux-dark, redux-dark-color

业务侧也可以从预设主题通过 Kotlin copy 派生品牌主题,或者传入 Mermaid 兼容的 themeVariables。

为什么选择“翻译源码”,而不是重新发明一套

如果目标只是画几个 Demo,自己写一个简化 parser 和自动布局并不难。真正困难的是长期维护:

  • Mermaid 新版本增加语法后如何跟进;
  • shape、marker 或 edge routing 改动后如何定位差异;
  • parser 能识别,但 Diagram DB 状态语义不一致时如何修复;
  • 浏览器 SVG 与 Compose Canvas 文本度量不同时如何保持行为一致。

如果实现与上游没有映射关系,每次升级都只能靠截图找差异,再一点点猜问题在哪里。短期看起来快,长期维护成本很高。

CMP Mermaid 采用源码映射的忠实翻译路线:

  1. 锁定 Mermaid、Jison、D3、Marked、Dagre、ELK 等版本;
  2. Kotlin parser、DB、layout、shape、theme、renderer 标注对应的上游文件和函数;
  3. 可生成的 parser table、rule、entity、fixture 和 worker 从锁定输入生成;
  4. Mermaid 升级时对照上游 diff,只翻译发生变化的部分;
  5. 每次变更重新运行 Native/Official 对拍与跨平台门禁。

这不是逐字符照搬 JavaScript,而是要求每段核心行为都能回答:

它对应 Mermaid 的哪段源码?

为什么 Compose 端需要这个适配?

这种映射让项目可以持续跟进 Mermaid 上游,而不是维护一套只能靠视觉猜测差异的自研实现。

“Stable”不是标签,而是公开证据

“能解析”“能画出来”和“可以用于生产”之间还有很长距离。当前 Stable 结论由几层独立证据共同支撑:

1. 441 组独立生产场景

这些是手写的复杂结构,不来自 Demo Gallery,用于能力覆盖、确定性 SceneGraph 重放、人工视觉审查、性能压力和常规 Quality Gate。

  • 独立生产场景:441 组 Native/Official 对拍;
  • 已声明能力覆盖:738/738;
  • 独立语料截图:882 张;
  • 确定性 SceneGraph 重放:441 个通过,0 个不一致。

2. 8,448 组 Native/Official 视觉矩阵

33 类图每类 256 个唯一 Mermaid source,总计:

  • 8,448 个 CMP Native 截图;
  • 8,448 个 Mermaid.js 12.0.0 Official 截图;
  • 16,896 张矩阵截图;
  • 528 页 contact sheet,每页展示 16 组同源码结果;
  • 8,448/8,448 通过几何门禁和细节审查;
  • 其中 7,458 组自动通过,990 组经人工复核通过;
  • 0 个未解决案例。

Flowchart Native/Official visual parity

自动门禁检查空白图、严重裁切、内容边界、前景密度和几何异常;它不会被包装成“像素级完全一致”,因此全部 528 页 contact sheet 仍然经过人工审阅。

3. 单元、主题、压力和跨平台门禁

JVM tests:              787 passed, 0 failed
Theme matrix:           363 / 363
Native stress inputs:   7,936 passed
Platform builds:        Android / iOS / Desktop / Web passed

完整报告和所有对比图都公开在仓库中:

0.1.6 已发布:可以直接接入

目前生产制品已经发布到 Maven Central 和 CocoaPods。

使用场景制品
Kotlin Multiplatform 核心能力io.github.swithun-liu:mermaid-core:0.1.6
Compose Multiplatform 渲染io.github.swithun-liu:mermaid-compose:0.1.6
Kotlin 1.7.21 Android 核心能力io.github.swithun-liu:mermaid-core-android-kotlin17:0.1.6
Kotlin 1.7.21 Android Compose 渲染io.github.swithun-liu:mermaid-compose-android-kotlin17:0.1.6
iOS 二进制CocoaPod CMPMermaid 0.1.6

KMP / CMP

dependencies {
    implementation("io.github.swithun-liu:mermaid-compose:0.1.6")
}

Kotlin 1.7.21 Android

dependencies {
    implementation(
        "io.github.swithun-liu:mermaid-compose-android-kotlin17:0.1.6",
    )
}

iOS / CocoaPods

pod 'CMPMermaid', '0.1.6'

制品页面:

Compose 基本用法:

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.swithun.cmpmermaid.compose.MermaidDiagram
import com.swithun.cmpmermaid.core.MermaidTheme
import com.swithun.cmpmermaid.core.MermaidThemePreset

@Composable
fun Diagram(source: String) {
    MermaidDiagram(
        source = source,
        modifier = Modifier.fillMaxWidth(),
        theme = MermaidTheme.preset(MermaidThemePreset.Default),
        contentDescription = "Mermaid diagram",
    )
}

跨平台情况

Parser、Diagram DB、布局准备、主题和 SceneGraph 主要位于 commonMain,构建和运行验证覆盖:

  • Android;
  • iOS;
  • Desktop;
  • Web(Kotlin/Wasm)。

在线 Demo 本身也是 Compose Multiplatform Web 版本,可以直接体验 33 类图表、11 个主题、源码实时编辑和 Native/Official 对比:

swithun-liu.github.io/cmp-mermaid…

仍然有哪些边界

Stable 的准确含义是:

在 Mermaid.js 12.0.0 基线和项目公开声明的 738 个能力点范围内,33 个图表家族已经通过生产场景、视觉矩阵、单元测试、压力和跨平台门禁。

仍需明确的边界:

  • 任意浏览器 themeCSS 依赖 DOM/CSS 语义,Native 端不做静默近似;
  • Compose 与浏览器的字体度量不同,不追求逐像素完全一致;
  • 输入内容异常与运行时异常会返回结构化错误,由宿主决定回退 UI;
  • Mermaid.js 只存在于隔离的 Official/Debug 工具中,不进入生产模块。

这些边界全部公开记录。项目不会把未支持能力静默降级成一张“看起来差不多”的图。

最后

这个项目最初只想解决一个具体问题:

一个页面需要展示很多 Mermaid 图时,能不能不要创建很多 WebView?

现在,CMP Mermaid 已经完成 Mermaid 12.0.0 的 33/33 图表家族,生产制品也已进入 Maven Central 与 CocoaPods,并建立了完整、公开、可复现的视觉和跨平台证据。

如果你正在做 Kotlin Multiplatform、Compose Multiplatform、Markdown/文档工具,或者也遇到 Native Mermaid 渲染问题,可以直接体验或查看实现:

觉得项目有价值,欢迎 Star、提交 Issue,或者带真实 Mermaid case 来验证。