2024年9月、ブロックエディタを三週間かけて作っていました。発想はいつも通りです。段落がひとつのブロックで、ドラッグして並べ替えられて、ブロックごとに書き直し用のAIボタンが付いている。目新しさはありません。Notion が何年も前からやっていることです。
ドラッグには dnd-kit を使い、ブロックごとにエディタのインスタンスを持たせました。Block コンポーネントは id、index、content、onDelete、onContentChange、引数が五つある regenerateContent、コンテキスト用のブロック一覧全体、そして生成を途中で止めるための ref に入れた AbortController を受け取っていました。動いてはいました。その週のコミットのひとつは、そのまま "never give up!!" という名前です。記録に残っているのはそれです。
六週間後、その全部が消えて、代わりに拡張機能を積んだエディタがひとつだけ残っていました。
ブロックを自分で束ねると何が壊れるか
問題はドラッグではありませんでした。ドラッグはうまくいきました。問題はそれ以外の全部で、次の順番で判明していきました。
- 内側への内容の同期。 各ブロックには
content !== editor.getHTML()を比べてsetContentを呼ぶuseEffectがありました。状態が変わったかどうかを HTML の文字列比較で判断するのは、壊れるまでは動いているように見える類の代表例です。 - ブロックをまたぐ選択。 できません。ブロックごとに別のエディタなら、ある段落の途中から次の段落の途中まで選択することはできません。部分的な直し方はありません。文書がひとつあるか、N 個あるかのどちらかです。
- 元に戻す。 インスタンスごとに履歴スタックが別々です。Ctrl+Z はカーソルのあるブロックで戻るだけで、直前にやった操作は戻りません。誰もそんな挙動は期待していません。
- 貼り付け。 段落を三つ貼ると、三つともひとつのブロックの中に入ります。それを分割するのは、文書スキーマがすでに知っていることを手で作り直すことです。
この四つはどれも、始めた時点では頭にありませんでした。四つとも、ここに書いた順番どおりに現れました。
書き直しはプロトタイプより短かった
10月半ばには、block/ フォルダの隣に block2/ フォルダができていて、中身はエディタとツールバーとフック、三ファイルだけでした。フック全体で四十行。StarterKit、TextStyle、FontFamily、TextAlign、Highlight、Placeholder。配列に宣言した六つの拡張だけで、統一された履歴も、連続した選択も、スキーマに沿った貼り付けも付いてきました。11月にはタスクリスト、コードブロック、URL からの画像が入りました。それぞれ import 一行と配列の一項目です。
フォルダ名がすべてを物語っています。block2 はリファクタリングではありません。隣にあるものはもう救えないと認めたということです。
このサイトの CSS も同じでした。自前のクラスを詰めた千六百八十一行のファイルがありました。コンポーネントに移したとき、生き残ったのは色のトークンだけ。globals.css の百十三行です。残りは作り直しでした。
ライブラリが必ず勝つところ
「ライブラリを使え」という話ではありません。外から見えるよりも問題がずっと大きい領域が具体的にあって、それはいつも同じ顔ぶれだという話です。
- リッチテキストエディタ。 選択、履歴、貼り付け、スキーマはひとつのシステムです。四つのうち三つだけ取ることはできません。
- 認証とセッション。 自分で書いたリフレッシュトークンに感謝してくれる人はいません。
- 日付とタイムゾーン。 変な事例は自分のマシンではなく本番で来ます。
- スタイル。 自前のクラス体系は、最初はきれいでも、最後にはドキュメントの貧弱なフレームワークになります。
共通のサインはこれです。難しいのが最初の画面ではなく、始めた時点では見えない部品どうしの相互作用なら、ライブラリが勝ちます。難しいのが自分の業務ロジックなら、そもそもライブラリはありません。
いまはどう決めているか
順番を変えました。いまはライブラリから始めて、詰まったときだけそこから出ます。
去年の夏に必要だった共同編集エディタはこう片付きました。PlateJS のテンプレートから始めて、上に Yjs を載せる。最初のコミットから三日後には、他のユーザーのカーソルが画面に出ていました。数か月前の自分なら、props が五つある Block から始めていたはずです。去年作った Markdown のメモツールでは、エディタを自作する選択肢すら考えませんでした。CodeMirror を入れて、CSS に highlight.js を import して、あとは本題に集中しました。
もっと速く片が付く例もあります。2025年4月、ツール付きのエージェントを書きました。コンテキスト用の tools を持つ agent.ts です。翌日には、それを削除してプロバイダのストリーミングを直接使うコミットがあります。作ってから消すまで二十四時間未満。これは良い結果のほうです。
本当のコスト
自前で作ることは時間の無駄ではありません。エディタで貼り付けが何をするのかを私が知っているのは、下手に作り直したことがあるからです。その知識はドキュメントを読むだけでは手に入りません。
コストは、それにいつ気づくかにあります。手作りのブロックに三週間、block/ と block2/ の同居にさらに六週間で、合わせて二か月です。3D マップの最初のページは十日もったあと、map-v2 というブランチにまるごと置き換えられました。そこでのコストは二か月ではなく十日です。早く切ったからです。
その差は、始めた時点でどれだけ知っていたかではありません。もう十分に分かっていると認めるまでにどれだけかかったか、です。
自前で作るのは、高くつくドキュメントの読み方です。その代金を払う価値がある場合もあります。価値がないのは、同じことに二度払うことです。
