はじめに
Google DocsやFigma、Notionを使っていて「この共同編集機能、自分のアプリにも欲しいな」と思ったことはないですか。
正直なところ、リアルタイム共同編集って実装のハードルが高いイメージがありました。コンフリクト処理とか、オフライン対応とか、考えることが多すぎて。でもYjsを使ってみたら、想像以上にシンプルに実装できて驚きました。
Yjsは現在GitHubで20,700スター以上を獲得していて、113人のコントリビューターが開発に参加。最終コミットも数日前という活発なプロジェクトです。バンドルサイズは26.6kB(minified + gzipped)で、この手のライブラリとしてはかなり軽量な部類。
30代になって思うのは、「難しそう」で避けてきた技術も、実際に触ってみると案外いけるということ。Yjsはまさにそのパターンでした。
Yjsとは
YjsはCRDT(Conflict-free Replicated Data Type)を実装したJavaScriptライブラリです。
CRDTって何かというと、簡単に言えば「複数の人が同時に編集しても、自動的にいい感じにマージしてくれるデータ構造」のこと。分散システムで一貫性を保つための仕組みで、学術的な背景がしっかりしています。
Yjsは「業界最速のCRDT実装」を謳っていて、実際にベンチマークでも優秀な成績を残しています。MITライセンスなので商用利用も問題なし。
特徴・メリット
1. ネットワーク非依存
これ、意外と重要なポイントなんですよ。Yjsは特定のネットワーク技術に依存していません。
WebSocket、WebRTC、独自プロトコル、何でも使える。つまり、P2Pでもサーバー経由でも、好きな構成で構築できる。インフラの自由度が高いのはありがたい。
2. オフライン編集対応
個人的には、この機能が一番刺さりました。
ネットワークが切れても編集を続けられて、再接続時に自動でマージ。出先でWi-Fiが不安定な時とか、地下鉄で作業してる時とか、オフライン対応があると安心感が違う。
3. 豊富なエディタ統合
ProseMirror、Quill、CodeMirror、Monaco、Slate、Tiptapなど、主要なリッチテキストエディタとの統合がすでに用意されています。
「車輪の再発明」をしなくていいのは時短になる。既存のエディタライブラリと組み合わせて、サクッと共同編集機能を追加できます。
4. 共有カーソル対応
複数人で編集してると「今誰がどこを編集してるか」が見えると便利ですよね。Yjsはカーソル位置の共有もサポートしていて、各エディタバインディングでリアルタイムにカーソルを表示できます。
5. Undo/Redo機能
Y.UndoManagerを使えば、Undo/Redo機能も簡単に実装できます。共同編集でも「自分の変更だけを戻す」みたいな制御が可能。
インストール方法
基本パッケージ
npm install yjs
プロバイダーの追加
通信方法に応じてプロバイダーを追加します。WebSocketを使う場合:
npm install y-websocket
WebRTCでP2P通信したい場合:
npm install y-webrtc
ブラウザのIndexedDBに永続化したい場合:
npm install y-indexeddb
エディタバインディング
使いたいエディタに応じて追加。例えばQuillの場合:
npm install y-quill quill
ProseMirrorやTiptapの場合:
npm install y-prosemirror
基本的な使い方
ドキュメントの作成
まずは基本中の基本、Yドキュメントを作成してデータを操作するところから。
import * as Y from 'yjs'
// Yドキュメントを作成
const ydoc = new Y.Doc()
// 共有Map型を取得
const ymap = ydoc.getMap('shared-data')
ymap.set('name', '田中')
ymap.set('age', 35)
console.log(ymap.get('name')) // '田中'
getMap、getArray、getTextなどで共有データ型を取得できます。同じ名前で取得すれば、異なるクライアント間で同期されるデータ構造になります。
変更の監視
データの変更を監視したい場合はobserveを使います。
const yarray = ydoc.getArray('my-list')
yarray.observe(event => {
console.log('配列が変更されました')
event.changes.delta.forEach(change => {
if (change.insert) {
console.log('追加:', change.insert)
}
if (change.delete) {
console.log('削除:', change.delete, '件')
}
})
})
// これで上のobserveが発火する
yarray.insert(0, ['りんご', 'バナナ'])
WebSocketで同期
実際にリアルタイム同期を実現するには、プロバイダーを接続します。
import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
const ydoc = new Y.Doc()
// WebSocketサーバーに接続
const provider = new WebsocketProvider(
'wss://your-server.com',
'room-name',
ydoc
)
// 接続状態の監視
provider.on('status', event => {
console.log('接続状態:', event.status) // 'connected' or 'disconnected'
})
// あとは通常通りドキュメントを操作するだけ
const ytext = ydoc.getText('editor')
ytext.insert(0, 'Hello, World!')
これだけで、同じroom-nameに接続している全クライアント間でデータが同期されます。
Quillエディタとの統合
リッチテキストエディタと組み合わせる例です。
import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import { QuillBinding } from 'y-quill'
import Quill from 'quill'
const ydoc = new Y.Doc()
const provider = new WebsocketProvider('wss://your-server.com', 'doc-room', ydoc)
const ytext = ydoc.getText('quill-content')
const quill = new Quill('#editor', {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ list: 'ordered' }, { list: 'bullet' }]
]
}
})
// バインディングを作成するだけで同期が有効に
const binding = new QuillBinding(ytext, quill, provider.awareness)
provider.awarenessを渡すことで、他のユーザーのカーソル位置も表示されるようになります。
Undo/Redo機能の追加
import * as Y from 'yjs'
const ydoc = new Y.Doc()
const ytext = ydoc.getText('editor')
// UndoManagerを作成
const undoManager = new Y.UndoManager(ytext)
// 操作
ytext.insert(0, 'Hello')
ytext.insert(5, ' World')
// Undo
undoManager.undo() // 'Hello' に戻る
// Redo
undoManager.redo() // 'Hello World' に戻る
実践的なユースケース
1. ドキュメント共同編集アプリ
Google Docsライクなアプリを作るなら、YjsとTiptapの組み合わせが王道。Tiptapはy-prosemirrorバインディングを使えばすぐに共同編集対応できます。
2. リアルタイムホワイトボード
FigmaやMiroのようなツール。座標データをY.Mapで管理して、図形の追加・移動・削除を同期させる構成。
const shapes = ydoc.getMap('shapes')
// 図形を追加
shapes.set('shape-1', {
type: 'rectangle',
x: 100,
y: 200,
width: 50,
height: 50,
color: '#ff0000'
})
3. コードエディタ
CodeMirrorやMonacoとの統合で、VSCode Live Shareのような機能を実装可能。y-codemirrorやy-monacoを使えば、シンタックスハイライト付きの共同コーディング環境が作れます。
4. タスク管理アプリ
Y.Arrayでタスクリストを管理、Y.Mapでタスクの詳細を保持。チームで同時にタスクを追加・編集しても競合しない。
5. プレゼンテーションツール
スライドデータをYjsで管理して、発表者と視聴者でリアルタイム同期。「現在のページ」もawarenessで共有すれば、参加者全員が同じページを見られる。
プロバイダーの選択
用途に応じてプロバイダーを使い分けると良いです。
| プロバイダー | 用途 | 特徴 |
|---|---|---|
| y-websocket | 一般的なWebアプリ | サーバー経由、安定性重視 |
| y-webrtc | P2Pアプリ | サーバー不要、低レイテンシ |
| y-indexeddb | オフライン対応 | ブラウザに永続化 |
| Hocuspocus | 本格運用 | 認証・永続化・スケーリング対応 |
| Liveblocks | マネージドサービス | インフラ管理不要 |
| PartyKit | エッジコンピューティング | グローバル分散 |
個人的には、プロトタイプにはy-websocket + y-indexeddb、本番環境ではHocuspocusかLiveblocksをおすすめします。
まとめ
Yjsを使えば、リアルタイム共同編集機能を思ったより簡単に実装できます。
正直なところ、CRDTという概念を聞いた時は「難しそう」と身構えていたんですよ。でも実際にYjsを触ってみると、抽象化がしっかりしていて、内部の複雑さを意識せずに使える。これ、ライブラリとしてはかなり重要なことだと思います。
コスパ的に見ても、26.6kBのバンドルサイズでこれだけの機能が使えるのは優秀。主要なエディタとの統合も揃っているので、「とりあえず共同編集を試したい」という段階から、本格的なプロダクション環境まで対応できます。
Google DocsやFigmaのような体験を自分のアプリに組み込みたいなら、Yjs一択ですね。
興味がある方は、まずは公式ドキュメント(https://docs.yjs.dev)を眺めてみてください。Getting Startedがよくできていて、動くものがすぐ作れます。
