コンテンツにスキップ

ADR-0049: 記事の埋め込みの初期ファイルと設定を、apps/web のビルドが JSON にし、ツールのページが埋め込みの ID で読む

Status

承認

Context

記事から、プレイグラウンドのツールへ、初期ファイルと設定を渡す方法を決める必要がある。

Decision

1. 埋め込みの ID とプロジェクト

  • 1 つの埋め込みが持つファイルの一式と設定を、プロジェクトと呼ぶ。
  • 埋め込みの ID は、記事の contentId と、記事の中の埋め込みの名前の組にする(<contentId>/<埋め込みの名前>)。記事の中で、埋め込みの名前は重ならない。

2. 運営者が書くファイル

運営者は、プロジェクトを、記事の Markdown があるディレクトリの下の playground/<埋め込みの名前>/ に置く。
シリーズの記事は articles/<series>/playground/、シリーズに入れない記事は articles/playground/ である。

apps/web/src/content/articles/playground/<埋め込みの名前>/
apps/web/src/content/articles/<series>/playground/<埋め込みの名前>/
  playground.json     プロジェクトの設定
  ...                 初期ファイル

設定のファイルは、次の値を持つ。

キー 中身 使うツール
tool ツールの種類。node、sql、go のどれか 全て
open 最初に開くファイルのパス 全て
start 起動のコマンドの一覧 node
database true のときに、実行環境が DB のプロセスを起動する node
initialSql 初期データの SQL のファイルのパス。JSON には、ファイルの中身を入れる node、sql
  • start は、コマンド、引数の一覧、プロセスの名前を持つ値の配列で書く。ツールのページが、コマンドの文字列を引数に分ける処理を持たないためである。
  • 設定のファイルは、訪問者のファイルのツリーに出さない。
  • playground/ のディレクトリは、シリーズとして扱わず、ディレクトリの入れ子の数に数えない。

3. 渡す方法

  • apps/web のビルドが、プロジェクトごとに、初期ファイルと設定を 1 つの JSON にして出力する。
  • fukuemon.dev は、JSON のパスに、play.fukuemon.dev を許す Access-Control-Allow-Origin を返す。
  • 記事の iframe の URL には、埋め込みの ID だけを書く(https://play.fukuemon.dev/node/?project=<埋め込みの ID>)。
  • ツールのページが、埋め込みの ID から JSON の URL を組み立てて、fetch する。iframe の URL のクエリには、JSON の URL を書けない。
  • project のパラメータが無いときは、ツールのページが、運営者が用意したデフォルトのプロジェクトを開く。
  • JSON を読めないときと、埋め込みの ID に当たるプロジェクトが無いときは、ツールのページに、エラーと、fukuemon.dev/playground へのリンクを出す。デフォルトのプロジェクトは開かない。
  • apps/web のビルドは、設定の tool が node のプロジェクトを埋め込む記事の URL を集め、_headers に書く。

URL に書けるのを埋め込みの ID だけにする理由は、他人が、自分の用意したファイルを読み込ませる URL を作れないようにするためである。

4. JSON の形の置き場

JSON と、設定のファイルの型と schema は、新しい package(packages/playground-project、@fukuemon/playground-project)に置く。

  • package は、型と schema だけを持つ。JSON を出力する処理と、読み込む処理は持たない。
  • apps/web のビルドは、この schema で、設定のファイルを検証する。apps/playground は、読んだ JSON を、同じ schema で検証する。schema に合わないときは、JSON を読めないときと同じ表示にする。
  • package は、workspace のほかの package に依存しない。

package にする理由は、JSON を書く側(apps/web)と、読む側(apps/playground)の 2 つが、同じ形を使うためである。

Considered Options

渡す方法の選択肢は次のとおり。

選択肢 強み 弱み
採用: apps/web のビルドが JSON を出力し、URL には埋め込みの ID を書く トップレベルで開いたページも、同じ URL で同じ初期ファイルを読める。記事を直したときに、apps/web だけをデプロイする fukuemon.dev が、JSON のパスに CORS のヘッダを返す
apps/playground のビルドが、記事の初期ファイルを読んで JSON を出力する 同じ origin なので、CORS のヘッダが要らない apps/playground のビルドが、apps/web の記事に依存する。記事を直すたびに、プレイグラウンドもデプロイする
記事のページから postMessage で渡す 追加のリクエストが無い トップレベルで開いたページは、初期ファイルを受け取れない
iframe の URL に、JSON の URL を書く ツールのページが、URL の組み立てを知らずに済む 他人が、自分の用意したファイルを読み込ませる URL を作れる
初期ファイルを圧縮して、URL のフラグメントに書く リクエストが無い URL が長くなる。他人が用意したファイルを読み込ませる URL を作れる

設定を書く場所の選択肢は次のとおり。

選択肢 強み 弱み
採用: プロジェクトのディレクトリの、設定のファイルに書く 設定が、初期ファイルと同じ場所にまとまる。1 つの記事に、複数の埋め込みを置ける 設定のファイルの schema を作る
記事の frontmatter に書く 記事の設定が、1 か所にまとまる 複数の埋め込みを置くときに、埋め込みごとに設定を分ける書き方を決めることになる
記事の本文で、埋め込みのコンポーネントに渡す属性に書く 埋め込む場所で、設定が読める 起動のコマンドのような長い値を、属性に書く

JSON の形の置き場の選択肢は次のとおり。

選択肢 強み 弱み
採用: 型と schema だけを持つ package を作る 書く側と読む側が、同じ形を使う。形を変えると、typecheck が失敗する package が 1 つ増える
apps/playground が、@fukuemon/content-model に依存する package が増えない apps/playground が、記事とシリーズの schema にも依存する
2 つのアプリが、形を別々に持ち、ビルドのときに 2 つの形を比べる package の依存が増えない 同じ形を 2 か所に書く。比べる処理を作る

Consequences

Positive

  • 記事の iframe と、トップレベルで開いたページが、同じ URL で同じプロジェクトを開く。
  • 他人が用意したファイルを、URL で読み込ませられない。
  • 1 本の記事に、複数の埋め込みを置ける。

Negative

  • fukuemon.dev が、プロジェクトの JSON のパスに、CORS のヘッダを返す。
  • ツールのページが、fukuemon.dev の JSON のパスの形を知っている。パスを変えるときは、2 つのアプリを直す。
  • 設定のファイルの schema と、ビルドのときの検証を作る。
  • apps/playground が、ブラウザで schema を検証するので、ツールのページのコードに、検証のコードが入る。
  • package が 1 つ増える。