ADR-0040: 記事に埋め込むキャンバスの図は、元データ入りの SVG を content/figures/ に置いて画像の記法で指し、ビルドのときにインラインにし、キャンバスには図の名前を渡す¶
Status¶
承認
Context¶
運営者がキャンバスで描いた図を、記事に埋め込む。
図をリポジトリに保存する形、記事への置き方、訪問者が記事の図をキャンバスで開く方法、図に入る第三者のアイコンの扱いを決める必要がある。
運営者がキャンバスから書き出した SVG を置くディレクトリ、記事の Markdown から図を指す書き方、キャンバスが図を読む URL、キャンバスに渡す識別子の形も決める必要がある。
- 埋め込むコンテンツは、種別にも形式にもしない。本文に置く機能として追加する。
- ADR-0030: 種別を記事の 1 つにし、構成を記事の形式で、シリーズをディレクトリで表す
- Mermaid の図は、ビルドのときに、ブラウザを使わずに SVG にする。ページに、図を描くための JavaScript を配らない。
- ADR-0016: mermaid の図を、ビルドでも島でも Pele で SVG にする
- キャンバスは、draw.io の Embed Mode と Excalidraw の 2 つのツールで、別のパスに置く。
- draw.io は、図の XML を入れた SVG(
xmlsvg)を書き出す。Excalidraw は、scene を入れた SVG を書き出す。どちらのエディタも、書き出した SVG をファイルとして開ける。 - サイトは、OS の設定でライトとダークを切り替える。サイトに、テーマを切り替える操作を置かない。
data-themeの属性は、Storybook だけが使う。- 出典: トークンの文書
- プレイグラウンドでは、他人が用意したコードを、訪問者の操作なしに動かさない。運営者が用意した初期ファイルは、この条件の対象にしない。他人が用意したものを読み込む機能を追加するときは、決め直す。
- リポジトリは公開で、コードと記事の本文を MIT License で公開している。
- Excalidraw が書き出す SVG には、使った文字の書体が入る。書体の Excalifont と Xiaolai は、OFL-1.1 である。
- 出典: Excalifont
- 出典: lxgw/kose-font
- AWS は、アイコンを構成図を作るために使うことを認めている。AWS のアイコンの再配布の条件と、Google Cloud のアイコンの利用条件は、読んだページに無かった。
- 「We allow customers and partners to use these toolkits and assets to create architecture diagrams.」
- 出典: AWS Architecture Icons
- draw.io の README は、図として書き出したものを、アイコンの条項の対象の外と書いている。
- 「This restriction does not apply to end-user diagram output (such as exported images or documents) …」
- 出典: jgraph/drawio の README
- キャンバスのツールは、名前を付けた複数の図を IndexedDB に保存する。
- 記事の原稿は
apps/web/src/content/articles/に置き、ディレクトリの入れ子は 1 階層までにする。ADR-0030 の Decision 2 の URL の表に、図の URL は無い。- ADR-0030: 種別を記事の 1 つにし、構成を記事の形式で、シリーズをディレクトリで表す
- 公開するスライドの Markdown は、
apps/web/src/content/slides/に置く。 - 実行環境のプロジェクトは、記事のディレクトリの下に置く。記事の iframe の URL には、埋め込みの ID だけを書き、ツールのページが JSON の URL を組み立てる。
- draw.io が書き出す元データ入りの SVG は、ルートの
contentの属性に図の XML を持つ。Excalidraw が書き出す SVG は、payload-type:application/vnd.excalidraw+jsonのコメントと、scene を持つ。
Decision¶
1. リポジトリに置く形¶
運営者は、キャンバスから書き出した元データ入りの SVG を、リポジトリに置く。
図のファイルと SVG を別に持たず、SVG の 1 枚だけを置く。
- ビルドにブラウザが要らない。Mermaid の図と同じ理由である。
- 運営者は、置いた SVG をキャンバスで開いて、描き直せる。
2. 図を置くディレクトリ¶
運営者は、キャンバスから書き出した SVG を、apps/web/src/content/figures/ に置く。
- ファイルの名前は、
<名前>.drawio.svgか<名前>.excalidraw.svgにする。拡張子で、描いたツールを表す。 <名前>は、小文字の英数字とハイフンだけにする(^[a-z0-9][a-z0-9-]*$)。ディレクトリの入れ子は作らない。- ビルドは、SVG の中の元データが拡張子のツールと合うかを確認し、合わないときは失敗する。
content/ の直下の 1 つのディレクトリにする理由は、MIT License の対象から外す記述が 1 つのパスで済むことと、1 つの図を複数の記事で使えることである。
3. 記事の Markdown から図を指す書き方¶
運営者は、Markdown の画像の記法で、図のファイルを相対パスで指す。

- ビルドは、
content/figures/のファイルを指す画像を、インラインの SVG に置き換える。そのほかの画像は変えない。 - 代替テキストは必須にする。空のときと、指したファイルが無いときは、ビルドが失敗する。
- 画像の記法のタイトル(引用符の中の文)は、図のキャプションにする。
- 全ての図の下に、「キャンバスで開く」のリンクを出す。
画像の記法にする理由は、.md のまま書けること、エディタのプレビューと GitHub で図が見えること、代替テキストとキャプションを書く場所が記法にあることである。
4. 記事への置き方¶
ビルドのときに、SVG を記事の HTML にインラインで入れる。
ページに、図を描くための JavaScript を配らない。
- draw.io の SVG は、ルートの
color-schemeの指定を外し、背景と線の色の変数(--ge-dark-color、--ge-light-color)にトークンを渡す。 - Excalidraw の SVG は、線と文字のデフォルトの色(
#1e1e1e)を、トークンに置き換える。 - 運営者が選んだ色は、置き換えない。
インラインにする理由は、図の背景と線の色が、ページのトークンと揃うことである。
5. 図の URL¶
apps/web のビルドは、置いた SVG を、変えずに https://fukuemon.dev/figures/<名前>.<ツール>.svg に出力する。
fukuemon.dev は、/figures/* に、play.fukuemon.dev を許す Access-Control-Allow-Origin を返す。
変えずに出力する理由は、キャンバスのツールが、運営者の書き出した元データを、そのまま読めるためである。
サイトの URL に、次の 1 つを足す。ADR-0030 の Decision 2 のほかの URL は変えない。
| URL | 中身 |
|---|---|
/figures/<名前>.<ツール>.svg |
記事の図の、元データ入りの SVG。play.fukuemon.dev のキャンバスのツールが読む |
6. 訪問者が記事の図をキャンバスで開く方法¶
記事の図に「キャンバスで開く」のリンクを置き、キャンバスに図の名前だけを渡す。
https://play.fukuemon.dev/drawio/?figure=vpc
https://play.fukuemon.dev/excalidraw/?figure=vpc
- キャンバスのツールが、名前から図の URL(
https://fukuemon.dev/figures/<名前>.<自分のツール>.svg)を組み立てて、fetchする。 - キャンバスは、fukuemon.dev の図のパスだけを読み込む。任意の URL は読み込まない。
- キャンバスのツールは、名前が Decision 2 の規則に合わないときは、
fetchしない。 - キャンバスのツールは、リダイレクトを辿らずに
fetchする。 - 運営者が置いた図だけを開けるので、ADR-0031 の Decision 4 の「運営者が用意した初期ファイル」と同じ扱いにする。Decision 4 は決め直さない。
名前だけを渡す理由は、URL の値で、/figures/ のほかのパスと、ほかの origin を指せなくなるためである。
ツールは URL のパスで決まるので、図を、描いたツールと別のツールに渡すリンクは作れない。
7. 図の名前の規則と URL の形を置く package¶
図の名前の規則と、ファイルの名前と URL のパスの形は、新しい package(packages/figures、@fukuemon/figures)に置く。
- package は、次の 6 つを持つ。
- 図を描いたツールの型(
drawio、excalidraw) - 名前が Decision 2 の規則に合うかを返す関数
- 名前とツールから、ファイルの名前(
<名前>.<ツール>.svg)と、URL のパス(/figures/<名前>.<ツール>.svg)を組み立てる関数 - ファイルの名前を、名前とツールに分ける関数
- 名前とツールから、「キャンバスで開く」のリンクのパスとクエリ(
/<ツール>/?figure=<名前>)を組み立てる関数 - クエリから名前を読む関数。クエリのキー(
figure)は、値としても公開する
- 図を描いたツールの型(
- package は、SVG を読む処理、
fetch、2 つのアプリの origin の値を持たない。 apps/webのビルドは、この package で、ファイルの名前を検証し、出力するパスと、「キャンバスで開く」のリンクを組み立てる。apps/playgroundは、同じ package で、クエリからfigureの値を読んで検証し、fetchする URL のパスを組み立てる。- package は、workspace のほかの package にも、React にも依存しない。
package にする理由は、図を出力する側(apps/web)と、読む側(apps/playground)の 2 つが、同じ規則と同じパスの形を使うためである。
8. 第三者のアイコンと書体が入った図¶
図のファイルを置くディレクトリは、Decision 2 の apps/web/src/content/figures/ だけである。
LICENSE か README に、そのディレクトリの図に入っている第三者のアイコンを、MIT License の対象から外すと書く。
アイコンの利用条件の原文は、運営者が確認する。
Excalidraw の SVG に入る書体(Excalifont、Xiaolai。OFL-1.1)も、同じ記述で、MIT License の対象から外す。
Verification¶
端末、ブラウザ、バージョンは、ADR-0039 の Verification と同じである。
エディタから書き出した SVG を、記事の形のページに 7 通りの置き方で並べ、JavaScript を無効にして、3 つのブラウザで見た。
| 置き方 | ライト | OS がダーク | data-theme="dark" |
|---|---|---|---|
draw.io を <img> で置く |
読める | 読める | 読める |
| draw.io をインラインで置く | 読める | 読める | 読めない |
draw.io をインラインで置き、color-scheme の指定を外して、トークンを渡す |
読める | 読める | 読める |
Excalidraw を <img> で置く(ライトの 1 枚) |
読める | 読めない | 読めない |
Excalidraw を <picture> で置く(ライトとダークの 2 枚) |
読める | 読める | 読めない |
| Excalidraw をインラインで置き、線と文字のデフォルトの色をトークンに置き換える | 読める | 読める | 読める |
Excalidraw をインラインで置き、ダークで色を反転する(CSS の filter) |
読める | 読める | 読める |
- draw.io を
<img>で置いた行のdata-theme="dark"は、Chromium と Firefox で見た。WebKit の画面は見ていない。 - draw.io の SVG は、色を
light-dark()で持ち、明暗の両方の色を持つ。大きさは 15.6 KB(図形 4 つ、線 3 本)で、アイコンはパスで入る。 - Excalidraw の SVG は、ライトの色だけを持つ。大きさは 26.2 KB(図形 3 つ、矢印 2 本、文字 1 つ)で、使った文字の書体が data URI で入る。
- 記事のページのリクエストは、ページと図の 3〜4 件だけで、キャンバスのコードは読み込まれなかった。
- キャンバスのページに
?src=<URL>を渡し、別の origin に置いた元データ入りの SVG をfetchで読んで開いた。draw.io と Excalidraw の両方で、図が開いた(Chromium)。図を返す側に、Access-Control-Allow-Originのヘッダが要った。 - draw.io で、
?src=で開いただけでは、IndexedDB に保存してあった図は変わらなかった。開いた図を編集すると、保存してあった図が、開いた図に上書きされた(Chromium)。試作は、図を 1 つだけ保存する作りである。 - Excalidraw の scene から、Node だけで SVG を作る形は、1 回試して失敗した(
ERR_IMPORT_ATTRIBUTE_MISSING)。動く条件は未確認である。
Considered Options¶
| 選択肢 | 強み | 弱み |
|---|---|---|
| 採用: 元データ入りの SVG を置き、ビルドのときにインラインにして、色をトークンに渡す | ビルドにブラウザが要らない。JavaScript なしで読める。図の背景と線の色が、ページのトークンと揃う | ビルドに、SVG の文字列を置き換える処理が要る。Excalidraw の、運営者が選んだ色は、ダークで変わらない |
| インラインにし、Excalidraw はダークで色を反転する | Excalidraw の、運営者が選んだ色も、ダークで読める色になる | 反転した色は、運営者が選んだ色と違う色になる |
<img> で置く。Excalidraw は <picture> で 2 枚置く |
ビルドの処理が要らない。HTML が小さい | 図の背景の色が、ページのトークンと揃わない。Excalidraw は、運営者がダークの SVG をもう 1 枚書き出す |
| 図のファイルを置き、ビルドのときに SVG にする | リポジトリに置くファイルが、図の元データだけになる | draw.io の図を、ブラウザを使わずに SVG にする手段を確認していない。Excalidraw は、Node だけで SVG を作る形が動かなかった |
| キャンバスが、任意の URL の図を読み込む | fukuemon.dev のほかに置いた図も開ける | 他人が用意した図を、URL を開かせるだけで訪問者のキャンバスに読み込める。ADR-0031 の Decision 4 を決め直すことになる |
| 訪問者が記事の図を開く機能を、最初の公開に入れない | 作るものが少ない | 訪問者が、記事の図を描き変えて試せない |
| 第三者のアイコンが入った図を、何も書かずに置く | 文書を直さない | アイコンが、MIT License の対象に見える |
| クラウドのアイコンを使う図を、記事に埋め込まない | リポジトリに、第三者のアイコンが入らない | 構成図を記事で見せられない |
図を置くディレクトリの選択肢は次のとおり。
| 選択肢 | 強み | 弱み |
|---|---|---|
採用: apps/web/src/content/figures/ に置く |
スライドの Markdown と同じ並びになる。MIT License の対象から外す記述が 1 つのパスで済む。図を複数の記事で使える | 図と記事が、別のディレクトリになる。図の名前を、ツールごとに、サイトの全体で重ならないようにする |
apps/web/public/figures/ に置く |
ビルドが、図を出力する処理を持たずに済む | 運営者が書く原稿が、content/ と public/ に分かれる |
記事のディレクトリの下(articles/<記事>/figures/) |
図が、記事と同じ場所にまとまる。実行環境のプロジェクトと同じ並びになる | 図を置くディレクトリを 1 つにする決定(Decision 2)と合わない |
Markdown の書き方の選択肢は次のとおり。
| 選択肢 | 強み | 弱み |
|---|---|---|
| 採用: 画像の記法で、図のファイルを相対パスで指す | .md のまま書ける。プレビューと GitHub で図が見える。代替テキストとキャプションを書ける |
シリーズの中の記事は、../../figures/ と書く。ビルドに、図を指す画像を置き換える処理が要る |
コードフェンス(figure)に、図の名前を書く |
Mermaid の図と同じ形になる | 代替テキストとキャプションを書く場所を、別に決める。プレビューで図が見えない |
ディレクティブ(::figure{name="…" alt="…"})で書く |
属性を足しやすい | ディレクティブのプラグインが要る。プレビューで図が見えない |
MDX のコンポーネント(<Figure name="…" alt="…" />)で書く |
属性を型で検証できる | .mdx の記事でしか使えない |
キャンバスに渡す識別子の選択肢は次のとおり。
| 選択肢 | 強み | 弱み |
|---|---|---|
採用: 図の名前だけを渡す(?figure=vpc) |
埋め込みの ID(ADR-0049)と同じ形になる。ほかのパスと、ほかの origin を指せない。拡張子と、ツールのページが合わないリンクを作れない | キャンバスのツールのコードが、fukuemon.dev の図のパスの形に依存する。パスの形を、2 つのアプリが使う package に置く(Decision 7) |
図のパスを渡す(?figure=/figures/vpc.drawio.svg) |
キャンバスのツールが、パスの組み立てを持たずに済む | キャンバスのツールが、パスの先頭、..、拡張子とツールの一致を検証する。検証の抜けが、ほかのパスの読み込みになる |
図の名前の規則と URL の形を置く場所の選択肢は次のとおり。
| 選択肢 | 強み | 弱み |
|---|---|---|
| 採用: 依存を持たない新しい package に置く | 規則とパスの形が 1 か所になる。プロジェクトの JSON の形を @fukuemon/playground-project に置いた ADR-0049 と、同じ分け方になる |
package が 1 つ増える |
apps/web と apps/playground の 2 か所に、同じ形を書く |
package を増やさずに済む | 片方だけを直すと、ビルドが通ったまま、「キャンバスで開く」のリンクの先で、図を読めなくなる |
Consequences¶
Positive¶
- 運営者は、キャンバスで描いた図を、SVG の 1 枚を置くだけで記事に埋め込める。
- 訪問者は、JavaScript を無効にしても、記事の図を読める。図は、ライトとダークのどちらでも読める。
- キャンバスの読み込みが、記事の初回の表示を遅らせない。
- 訪問者は、記事の図をキャンバスで開いて、描き変えられる。
- 運営者は、SVG の 1 枚を
content/figures/に置き、画像の記法の 1 行を書くだけで、図を記事に埋め込める。 - 運営者は、ファイルの名前で、図を描いたツールを見分けられる。
- 他人が、自分の用意した図を読み込ませる URL を作れない。
Negative¶
- インラインにした SVG の分だけ、記事の HTML が大きくなる。
- ビルドの置き換えは、draw.io と Excalidraw が書き出す SVG の形に依る。エディタのバージョンが上がったときに、置き換えを確認し直す。
- Excalidraw の図で、運営者が選んだ色は、ダークでもライトの色のままである。運営者は、両方の背景で読める色を選ぶ。
- Excalidraw の SVG には、OFL-1.1 の書体の一部が入る。
- 第三者のアイコンの利用条件を、運営者が確認するまで、アイコンの入った図を公開のリポジトリに置けるかが決まらない。
- サイトの URL と、
Access-Control-Allow-Originを返すパスが、1 つずつ増える。 - 図の名前を変えると、その図を指す記事の Markdown と、訪問者が保存した「キャンバスで開く」のリンクが、古い名前を指す。
- workspace の package が、1 つ増える。
- 図を描いたツールの型(package)と、保存する図のツールの種類(
apps/playground)が、同じ文字列を別々に持つ。ツールのページが、同じ文字列を両方に渡す。 - SVG の中の元データが、どのツールのものかを見分ける処理は、
apps/webのビルドと、apps/playgroundのツールのページの 2 か所が持つ。2 か所が別々に持つ理由は、ビルドは見分けるだけで、ツールのページは元データを取り出すまで行うためである。 - ビルドに、画像の記法を置き換える処理と、拡張子と元データの一致を確認する処理が増える。
- 1 つの記事に複数の図をインラインで入れたときに、SVG の中の
idが重なるかは、確認していない。