Bun WebView でブログに Mermaid レンダリングエンジンを構築した
Bun の組み込み WebView API で Playwright を置き換え、tldraw で Mermaid をレンダリングするツールを構築した。Sunil Pai の手法を出発点に、追加のブラウザ依存不要の道を通した。
ブログを書く人なら誰もが共通して経験する。記事にアーキテクチャ図を入れたいとき、Figma を開いて三十分ドラッグするか、スクリーンショットを貼って見苦しくなるか。
Mermaid は「テキスト即ち図表」の問題を解決する。数行の DSL を書くと、図にレンダリングしてくれる。だが一つ厄介な点がある。実行時レンダリングには Mermaid の JS ライブラリを読み込む必要があり、ビルド時レンダリングにはブラウザ環境が必要だ。
Sunil Pai が美しい答えを出した。彼のブログnever waste a tokenの記事で、ツールを一つ添付していた。@tldraw/mermaid で Mermaid のコードブロックを tldraw のシェイプに変換し、Playwright でビルド時に SVG をエクスポートする。結果はあの手描き風のフローチャートで、ライト/ダークモード両方が自動生成される。
読んだ翌日、自分でも一式構築することにした。だが Chromium はインストールしたくなかった。
Bun には WebView API が組み込まれている。macOS ではシステム標準の WKWebView を使い、Linux と Windows では Chrome や Edge を駆動する。肝は追加でブラウザをインストールする必要がないことだ。システム標準のもので足りる。
第一版はシンプルだった。React コンポーネントを書き、tldraw editor と @tldraw/mermaid を読み込み、Harness スクリプトで Bun.WebView を使ってウィンドウを作成、ページを読み込み、Mermaid ソースコードを注入、createMermaidDiagram でレンダリング、getSvgString で SVG をエクスポートする。10分で動作した。
だがツールとプロトタイプの間には二つの「ん?」があった。
第一の問題はフォントだ。tldraw には手描きフォント tldraw_draw があり、SVG に base64 woff2 で埋め込むと 200KB 近く占める。ブログ用の画像では手描き風は必須ではない。@font-face ブロックを削除し、フォントをシステムの sans-serif に fallback させれば、中国語も正常に表示できる。
第二の問題は SVG が大きすぎることだ。各 foreignObject 内の div には、すべての CSS プロパティを含む style 属性が付いていた。これらはブラウザのデフォルト値で、SVG foreignObject では何の意味もない。style 文字列を解析し、font-family、color、text-align など必要な属性だけを残し、残りはすべて捨てる。
この二段階で、313KB の SVG が 24KB になった。
第三の問題はレイアウトだ。@tldraw/mermaid は TD 方向の flowchart をレンダリングするとき、ノードを本当に縦に並べない。graph TD、flowchart TD、さらには direction TB を手動指定しても、tldraw のレンダラーは無視した。ノードは常に左から右へ並び、幅は簡単に 2000px を超える。
最終的な選択は、LR レイアウトを受け入れ、ノードのテキストを簡略化し、24KB の SVG をブログの max-width: 640px のカラム幅に適応して縮小させることだった。実用上十分な効果だ。
第四の問題は地味だが言及する価値がある。SecurityError だ。canvas toBlob で SVG を WebP に変換しようとしたところ、SVG 内の foreignObject が canvas を汚染し、ブラウザが直接例外を投げた。WebView screenshot に切り替えると、macOS の WKWebView が webp 形式をサポートしていないことが分かった。最終的に SVG 出力を残した。
ツールが完成した。三つの入力方式をサポートする。
bun run mermaid arch.mermaid # ファイルから
cat arch.mermaid | bun run mermaid # パイプから
bun run mermaid -- 'flowchart TD\nA --> B' # 引数から
SVG のペアを出力する。ライトとダークそれぞれ一つ、ブログ記事に直接埋め込める。
振り返ると、このツールの技術スタックは興味深い。Bun.WebView でヘッドレスレンダリング、bun build でフロントエンドバンドルをビルド、tldraw でグラフィックエンジン、ブラウザのインストールが必要な npm パッケージは一つもない。全体のフローで使ったランタイムは一つだけだ。下図がこのツールの完全なワークフローだ。
Sunil の原版は Playwright と Vite と Chromium を使っていた。Bun 版はこの四つを一つに簡略化した。
もちろん代償もある。macOS では WebView がウィンドウをポップアップして閉じる。真のヘッドレスができるのは Linux だけだ。さらに tldraw 自体がフル機能のキャンバスエディタで、Mermaid のレンダリングのためだけに 10MB のバンドルをバンドルするのは確かに重い。だがバンドルはキャッシュでき、tldraw をアップグレードしなければ一度バンドルするだけで済む。
最後に一つ言っておきたい。「この機能は小さすぎてツールを書く価値がない」という理由で手作業で図を描くべきではない。自動化ツールの利点はあの数分を省くことではない。継続的にスタイルの一致した図を産出でき、毎回同じ規範を使えることだ。
一度ツールを書けば、後続のすべての記事が恩恵を受ける。
同じ WebView 技術はドット絵生成ツールにも使われている。出力が SVG からピクセル風画像に変わっただけだ。
関連ツール:
- ドット絵生成 — AI 生成ドット絵
関連記事: