フューチャー技術ブログ

Mermaid.jsをReact上でレンダリングする

テキストで書いた情報をもとにダイアグラムを作成するツールがいくつかあります。有名どころだとGraphviz、Mermaid.jsや、PlantUMLがあります。このうち、GitHubでも対応して、テキストで書いても絵が見えるので今後が利用が大きく伸びそうなのがMermaid.jsです。

Mermaid.jsをReactで表示しようとしたものの、Reactラッパーとしてnpmに公開されているものがどれも古くてメンテナンスされていなそうという問題がありました。この手のアダプター系のライブラリはどうしてもメンテナンスがされなくて放置されるのが早くなりがちです。脆弱性が残り続けたりします。また、もう1つ、Reactのバージョンポリシーが変わって、0.1, 0.2, 0.3…0.14だったのが、突然15, 16, 17, 18となったのも問題を加速しています。メンテされないライブラリがReactのバージョンを"react": "^16.12.0"みたいに指定すると、17、18とマッチしないので、Next.jsとかを使っていると、Reactのバージョンがコンフリクトしてインストールできなくなったりします。

そこで自前でやったところ、思ったよりも簡単だったのでそのやり方を紹介します。きっと、Vue.jsとかSvelteとかでもやりたい方は多いと良いと思いますし、React版ライブラリを公開しました、よりも内部の構造の紹介の方が有益でしょう。

MermaidのAPI

Mermaid.jsのAPIの端々にjQuery時代のドキュメントを見ると、いくつかやり方が書かれています。

  • mermaid.initialize()で、HTMLの中の特定のクラスを持つタグなどを一括して処理してSVG画像を生成して挿入する
  • mermaid.mermaidAPI.render()の機能を使ってSVGの要素を取得して表示

それ以外にdeprecataedなAPIとして、mermaid.init()を使って指定のタグ(initialize()よりも細かく指定可能)の表示を行う方法もあります。こちらはクラス名、jQueryのセレクターの結果オブジェクト、実DOMオブジェクトを処理対象として明示します。

mermaid.initialize()は実行したタイミングで全DOMを検索して実行しますが、いまどきのSPAとは相性がよくないと言えます。まず、指定のコンポーネントの中だけを処理対象にしたくても全DOMツリーが対象になってしまいますし、非同期でサーバーからソースを取得してきて実行する場合など、ソースが最初からそろっていないケースが多いでしょう。このmermaid.initialize()はサーバー側でレンダリングしていてDOM内のテキストノードに最初からコードがあり、document.onLoadだかで読み込めばOKというMPA前提のAPIです。

ReactなりなんなりがDOMのライフサイクルを管理していて、そのmermaid.jsを扱うタグもそれに従属していて、寿命管理はReactが担っているという関係の場合、処理対象が厳密に絞れる方がうれしいです。なのでdeprecatedなmermaid.init()を使うしかありません。

deprecatedでも問題ないのか、と言っても、コラボレーター自らこのAPIしか使わんと言っているので問題ないでしょう。

実装

次のコードがそのMermaid.jsを表示するコンポーネントです。src propsとしてMermaid.jsのソーステキストを受け取る前提となっています。まあコードに落としてみるとあっという間ですね。ドキュメントを理解するまでの時間の方が長かった。

import mermaid from "mermaid";
import { useRef, useEffect } from "react";

type Props = {
src: string;
className: string;
}

export function Mermaid({src, className}: Props) {
const ref = useRef<HTMLDivElement>(null);

useEffect(() => {
if (src) {
mermaid.init({}, ref.current);
}
}, [ref.current, src])

return (
src ?
<div className={className} ref={ref} key={src}>
{src}
</div>
: <div className={className} key={src} />
);
}

肝としては、このコードがやっていることをピックアップすると、

  • テキストノードとしてソースを書く({src}のところ)
  • mermaid.init()はソースが書かれたタグに限定(useRefを活用)
  • ソースは空だとやはり変換エラーになってしまうので、ソースが揃うまでは実行しない
  • キーを設定し、ソーステキストの修正ごとにDOMが再作成されるようにした

ぐらいです。

一番最後ですが、一度ビルドすると、ソースコードが削除されたタグが動的に書き換えられてしまい、2度目の変更が行われないようになります。変更を起こすにはソースが書き換わったときに、タグごと作り直す方が手っ取り早いです。key属性をタグに付与することでタグを作り直すことを指定しています。これをやらないと、ソースコードが編集されると、絵ではなく、そのソースコードが画面に出てしまいます。絵の更新をしてほしいですよね?

これを表示すると以下のようになります。


const src=`erDiagram

User {
INTEGER id PK
TEXT name
TEXT email
INTEGER test
}

Job {
INTEGER id PK
TEXT name
}

User }o--o{ Job : jobs`

return (
<Mermaid src={src}/>
)

めでたしめでたし。SVG形式で内部では生成されます。

まとめ

これで Mermaid.jsが表示できるようになりました。古い型式のAPIの良質ライブラリを現代のフレームワークで活用するのは頭のパズルっぽくていいですね。

PlantUMLについては長くなったので別エントリーにします。