「描く」から「生成する」へ:AIとMermaidの強力な親和性
これまで、フロー図やシーケンス図を作成するには、Draw.ioや各種ドローツールを開き、
マウスを使って時間をかけてボックスや矢印を配置するのが一般的だったように感じます。
しかし、生成AIが日常的なツールとなった今、この「手作業で描く」スタイルは大きな転換点を迎えています。
LLM(大規模言語モデル)は構造化されたテキストの出力が非常に得意です。
そのため、GUIツールで図を作成するよりも、「AIにMermaid記法のコードを吐かせ、それをそのまま貼り付ける」方が圧倒的に速く
、かつ正確に図解を行えます。
「AIが図の構造を考え、エンジニアはそれをMarkdownに貼り付けるだけ」。
このワークフローと最も相性が良いのが、テキストベースで図を定義するMermaidです。
本記事では、RailsアプリケーションのブログにMermaid.jsを組み込み、
AIとの親和性を最大限に活かしながら、Markdownを書くだけでメンテナンス性の高い図を自動生成するアーキテクチャ
について解説します。
アーキテクチャ全体像
単にライブラリを読み込むだけでなく、「サーバー側でのマークアップ」と「クライアント側での描画」の役割を明確に分離しました。
1. サーバー側:Markdownレンダラーの拡張
Markdownを解析する際、Mermaidのコードブロックを「ただのコード」としてではなく、「図の種」として出力します。
-
Gem選定:
redcarpetを採用。高速かつ、レンダリング処理を自由にオーバーライドできるためです。 -
カスタムレンダラー:
block_codeメソッドを上書きし、言語指定がmermaidの場合のみ<div class="mermaid">を出力するように変更。これにより、既存のシンタックスハイライト(Prism.js等)との干渉を防ぎます。 -
セキュリティ:
html_escapeを通すことで、図の定義内に悪意のあるスクリプトが混入するリスク(XSS)を排除しています。
2. クライアント側:ライフサイクルに合わせた描画制御
Rails特有の「画面遷移(Turbo/Turbolinks)」と「描画パフォーマンス」の両立がキモです。
-
遅延ロード: 常にMermaidを読み込むのではなく、ページ内に
.mermaidクラスが存在する時だけ CDN から動的にロード。 -
二重描画の防止:
data-rendered属性を活用。Turboによるページ復元時や、動的なコンテンツ追加時に、「まだ図解されていないノードだけ」を狙ってmermaid.run()を実行します。
3. プレゼンテーション層:レスポンシブ対応
MermaidのSVGは、放っておくと画面幅を突き抜けることがあります。
CSSで「枠」と「余白」を制御し、スマホ閲覧時は横スクロールを許容する設計にしました。
実装のコア・スニペット
サーバーサイド(Ruby)
パーサーを拡張し、特定のフェンス(```mermaid)を検知するロジックです。
# app/helpers/custom_markdown_renderer.rb
class CustomMarkdownRenderer < Redcarpet::Render::HTML
def block_code(code, language)
return '' if code.nil?
lang = language.to_s.strip.downcase
if lang == 'mermaid'
# Mermaid.jsが解釈できる形式に変換
%(<div class="mermaid">#{ERB::Util.html_escape(code)}</div>)
else
# 通常のコードハイライト用
%(<pre><code class="language-#{ERB::Util.html_escape(lang)}">#{ERB::Util.html_escape(code)}</code></pre>)
end
end
end
クライアントサイド(JavaScript)
Turbo環境下でも安定して動作させるためのハンドリング例です。
// Mermaidの初期化と実行
const initMermaid = async () => {
const targets = document.querySelectorAll('.mermaid:not([data-rendered])');
if (targets.length === 0) return;
// 動的インポート(必要な時だけロード)
const { default: mermaid } = await import('[https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs](https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs)');
mermaid.initialize({ startOnLoad: false, theme: 'neutral' });
await mermaid.run({ nodes: targets });
// 描画済みフラグを立てる
targets.forEach(n => n.dataset.rendered = 'true');
};
// Turboの遷移イベントに合わせて発火
document.addEventListener("turbo:load", initMermaid);
お披露目:シーケンス図サンプル
ブラウザ上では、中央寄せのSVGとして表示されます。コードブロックのコピー機能や他のハイライト表示は従来どおり維持できました。
まとめ
ちょうどブログにも図を使った説明を使っていきたいと思っていたところだったので、意外に簡単に導入できてよかったです!
ぜひ皆さんも表現の幅を広げるために導入してみてはいかがでしょうか?