同時編集を作るとき、難所はカーソルの描画だけではありません。複数人が同じ箇所を変えたときの収束、切断中の編集、再接続後の差分交換まで考える必要があります。Yjsは、その土台を担うJavaScript向けのCRDT実装です。完成済みのエディタや専用サーバーを渡すのではなく、競合なくマージできる共有データ型を中心に、UIや通信を別の部品として組み合わせます。(GitHub, 公式ドキュメント)
共有データを普通のMapやTextに近い感覚で扱う
YjsではY.Docの中にY.Map、Y.Array、Y.Textなどを置きます。各クライアントで生じた変更はバイナリのdocument updateとして表現され、受け取る順序が前後しても、同じ更新を重複して受け取っても適用できます。state vectorを交換すれば、相手に足りない差分だけを算出する構成も取れます。アプリ側で編集操作ごとの競合解決規則を一から組む代わりに、共有状態の操作へ落とし込めるわけです。(Document Updates)
import * as Y from 'yjs';
const doc = new Y.Doc();
const title = doc.getText('title');
title.insert(0, '共同編集する文章');
ただし、Y.Docを作っただけでは画面も通信も付きません。ここを分けて考えるのがYjsの設計をつかむ近道です。
エディタ、共有状態、通信を切り離す
公式チュートリアルでは、QuillとY.Textをy-quillのQuillBindingで結び、さらにy-websocketやy-webrtcなどのproviderを接続します。ProseMirror、Tiptap、Monaco、CodeMirrorなどにもbindingが用意されています。エディタが入力を受け、bindingが共有型との相互変換を担い、providerがupdateを運ぶ。三つの責務が薄く分かれています。(Collaborative Editor)
Yjs本体はnetwork agnosticです。WebSocketを使うなら、中央の接続先で認証・認可を扱いやすいy-websocketを選べます。ブラウザ同士をつなぐWebRTCや、既存の通信基盤に合わせた独自providerも候補です。通信方式を選べることと、本番運用が自動で片付くことは別です。y-websocketのREADMEも、付属する単純なインメモリbackendは容易にスケールできないと明記しています。(y-websocket)
文書と「いま誰がいるか」は保存先を分ける
共同編集画面には、本文とは寿命の違う情報があります。参加者名やカーソル位置、オンライン状態といったAwareness情報です。Yjsではこれを文書本体へ永続化せず、別のAwareness CRDTで配ります。利用者がオフラインになれば、その状態は削除されます。履歴として残す文書と、その瞬間だけ必要な気配を同じデータへ押し込まない設計です。(Awareness & Presence)
オフライン編集にはy-indexeddbを併用できます。文書の更新をブラウザのIndexedDBへ保存するため、次回はローカルの内容を先に読み、network providerとは不足分だけを同期できます。通信とローカル保存も別々のproviderなので、要件に応じて足せます。(y-indexeddb)
試すなら、まず公式demoで二つのタブを開き、入力、切断、再接続時の動きを見る。その後で、本文、Awareness、認証、永続化をどの層が持つかを図にするとよさそうです。Yjsが減らすのはCRDTとエディタ連携を再実装する負担であり、権限設計やbackend運用まで消すわけではありません。この境界を見誤らないことが、導入判断の要になります。