【VS Code】巨大なMermaid図が見づらい!を解決するズーム&パン機能付き拡張機能を作って公開した話
はじめに
VS Code Marketplace に「mermaid-pan-zoom」を公開しました!
この拡張は、Markdown に書いた Mermaid 記法の図を、拡大・縮小・パン操作しながらプレビューできる VS Code 拡張機能です。
この記事では、開発の過程でやったこと、つまずいたポイントとその解決策をまとめました。
VS Code の拡張機能を自作してみたい方や、Mermaid × React × WebView に興味がある方の参考になれば嬉しいです!
開発の動機
弊社開発チームではドキュメント管理に Markdown をよく使っています。
GitHub では Mermaid を使ってシーケンス図やフローチャート、ER 図などを描画できてとても便利なのですが、ER 図のようにテーブル数が 100 個近くあるケースだと、1 つ 1 つのテーブルが小さくなりすぎて見づらいという問題がありました。
GitHub のビューアーにも拡大機能はあるものの、正直あまり使いやすくありません…。
VS Code の拡張機能も探してみましたが、「Mermaid をプレビューできるもの」はあっても、パン(移動)やズームができるものが見当たらなかった(見落としていただけかもしれません)ので、自分で作ってみることにしました。
もともと VS Code の拡張を自作して公開してみたいと思っていたので、楽しみながら作れました!
開発の流れ
1. 拡張機能の雛形を作成
まずは公式ドキュメントのチュートリアルを参考に、雛形を作成しました。
-
yo code
で VS Code 拡張の基本プロジェクトを生成 - UI 表示に
webview
を利用するテンプレートを選択 - エントリーポイントは
extension.ts
、WebView 側の描画はpanel.ts
に分離
2. WebView 側で React を使えるようにする
WebView 内の UI は React + TypeScript で構築しました。
npm create vite@latest
npm i react react-dom mermaid react-svg-pan-zoom
npm i -D esbuild typescript eslint prettier
正直、今回のケースだけでいえば React を使わず Vanilla JS で書いた方が早く終わったと思います(1 日かからなかったかも…)。
ただ、今後もっとインタラクティブな UI を持つ拡張機能を作りたいときに React が使えると強いので、今回は勉強も兼ねて導入しました。
3. Mermaid × Pan/Zoom の組み合わせ
- Mermaid:
mermaid.render()
で SVG を生成 - Pan/Zoom:
react-svg-pan-zoom
を使って操作可能に
やっていることは単純で、Mermaid で SVG を作って、react-svg-pan-zoom で操作できるようにするだけです。
Mermaid 記法から SVG を生成する処理はカスタムフックに切り出していて、VS Code との通信(メッセージ送受信)は公式の WebView APIを参考に実装しました。
// webview-ui/src/hooks/useMermaidText.ts
import { useEffect, useState } from "react";
import { vscode } from "../utils";
export function useMermaidText(): string {
const [mermaidText, setMermaidText] = useState<string>("");
const handleMessage = (event: MessageEvent) => {
const message = event.data;
if (message.type === "send-mermaid") {
setMermaidText(message.payload);
}
};
useEffect(() => {
vscode.postMessage({ type: "get-mermaid" }); // 初期メッセージを送信して取得
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
return mermaidText;
}
つまずいたポイントと解決策
Mermaid の ID 問題
Mermaid が内部で生成する SVG の id
が 数字で始まると React 側でエラーになる問題がありました。
✅ 解決策: 正規表現で置換し、必ず mermaid-
プレフィックスを付けるようにしました。
const replacedSvg = svg.replace(new RegExp(uuid, "g"), `mermaid-${uuid}`);
ReactSVGPanZoom
が更新されない
value
が null
のままだとビューが動かず、ズームなどが反応しません。
✅ 解決策: onChangeValue
で状態を管理するようにしました。
<ReactSVGPanZoom
value={value}
onChangeValue={setValue}
>
WebView で画像が読み込めない
public
配下のファイルは読めるのに、assets
内の画像が参照できない問題がありました。
✅ 解決策:
-
import img from "../public/xxx.svg";
のようにしてバンドル対象に含める - もしくは
webview.asWebviewUri
を使って安全な URI に変換する
CSP (Content Security Policy) 対応
WebView ではセキュリティ上、スクリプトに nonce
を付与する必要があります。
✅ 解決策:
index.html
を読み込んだあとで nonce
を発行・付与する処理を入れて対応しました。
処理の流れ(ざっくり)
1. Mermaid 記法から SVG 文字列を生成
// webview-ui/src/components/organizations/Mermaid.tsx
const initialize = async (id: string, mermaidText: string) => {
const svg = await generateMermaidSvg({ id, mermaidText, mermaid });
if (!svg) return console.error("Failed to generate Mermaid SVG");
const matched = svg.match(/<svg[^>]*?id="(.+?)"/);
if (!matched) return console.error("Failed to find SVG ID");
const uuid = matched[1];
const replacedSvg = svg.replace(new RegExp(uuid, "g"), `mermaid-${uuid}`);
setSvgString(replacedSvg);
};
2. SVG を表示し、パン・ズーム機能を適用
return (
<>
{svgString && (
<ReactSvgPanZoomLoader
svgXML={svgString}
render={(content) => (
<ReactSVGPanZoom
className="mermaid"
ref={panZoomRef}
width={windowSize.width}
height={windowSize.height}
value={value}
onChangeValue={setValue}
tool={currentTool}
background="#fff"
modifierKeys={["Alt", "Shift", "Control"]}
onChangeTool={setCurrentTool}
customMiniature={() => <></>}
customToolbar={() => (
<Toolbar
tool={currentTool}
onChangeTool={setCurrentTool}
value={value}
onChangeValue={setValue}
/>
)}
>
<svg>{content}</svg>
</ReactSVGPanZoom>
)}
/>
)}
</>
);
Marketplace 公開までの流れ
1. package.json の設定
最低限の情報を記載します。
{
"name": "mermaid-pan-zoom",
"displayName": "mermaid-pan-zoom",
"description": "Zoom & Pan enabled Mermaid preview for VS Code",
"version": "0.0.1",
"engines": {
"vscode": "^1.102.0"
},
"categories": ["Other"],
"activationEvents": ["onLanguage:markdown"],
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "mermaidPanZoom.open",
"title": "mermaidPanZoom プレビューを開く"
}
]
}
}
2. 公開コマンド
npm install -g @vscode/vsce
vsce package
vsce publish
使い方
- Markdown に Mermaid のコードブロックを書く
- コマンドパレットで
mermaidPanZoom プレビューを開く
を実行 - ズームイン・ズームアウト・ドラッグで快適に操作!
まとめ
- React × Mermaid × WebView を使って VS Code 拡張を構築
- ID 問題・画像読み込み・CSP 対応などでハマった
- 最終的に Marketplace に公開できた 🎉
- React を使った WebView の作り方がわかったので、次はもっとリッチな拡張機能にも挑戦してみたい
拡張はこちらからインストールできます 👇
👉 mermaid-pan-zoom
次回は、もう少し具体的なサンプルを交えて「VS Code 拡張の作り方」を掘り下げて紹介する予定です。
Discussion