Yjsは、複数人が同時に同じ文書を編集しても衝突しない仕組み——CRDT(Conflict-free Replicated Data Type)——をJavaScriptで実装したライブラリだ。GitHubで★22,565を集め、Evernote・GitBook・Proton Docs・AFFiNEといった製品の共同編集機能を支えている。
日本語の「Yjs入門」記事はすでに何本もある。そこで本記事は、入門の先にあるいま実際に何をインストールすることになるのかを実測した。というのも、GitHubの main ブランチを開いて読んでいるコードは、すでに yjs ではなく @y/y という別名のパッケージになっているからだ。
@beta なら動く(2026-08-21 実測。npm 11.1.0 / Node.js 22.13.1)この記事はCRDTライブラリ「Yjs」を解説します。開発を自動化するツール全体の地図はAI自動化ツール|ノーコードからコードまで2026年版の比較と選び方をご覧ください。
30秒でわかる Yjs
・Y.Doc の中に Y.Text / Y.Map / Y.Array といった共有データ型を作ると、その変更が自動で他の参加者へ配られ、衝突なくマージされる。サーバー側に調停ロジックを書く必要がない
・通信方式を問わない(network agnostic)。WebSocket・WebRTC・BroadcastChannel・独自のトランスポートを「プロバイダ」として差し替えられる
・オフライン編集・Undo/Redo・バージョンスナップショット・他人のカーソル表示(awareness)を標準で持つ
・MIT。2014年開始、作者は Kevin Jahns 氏とドイツ RWTH Aachen 大学の研究室。★22,565(2026-08-21時点)
この記事で実測したポイント
・npm i yjs は今も正解。13.6.32 は2026-08-04公開の現役安定版で、放置されているわけではない
・v14 は「別名の別パッケージ」として並走している。GitHub の main は @y/[email protected]。同じリポジトリを読んでいるのに、npmで入るものとは別物になる
・壊れるのはAPIであってデータではない。v13↔v14 の update は双方向で適用できた。移行コストはJavaScript側の書き換えに集中する
・v14を素直に入れると起動しない。npm i @y/y @y/websocket は成功するのに import で落ちる。原因はスコープ付きパッケージの latest タグが改名前を指していること
Yjsとは——CRDTで「衝突しない共同編集」を実現するJavaScriptライブラリ
共同編集を自前で実装しようとすると、必ず「二人が同じ位置を同時に編集したらどうするか」という問題にぶつかる。ユーザーAが3文字目に「あ」を挿入し、同じ瞬間にユーザーBが3文字目を削除したら、サーバーはどちらを勝たせるべきか。
従来この問題は OT(Operational Transformation) で解かれてきた。Google Docs が採用している方式で、操作を受け取ったサーバーが「Bの削除位置はAの挿入によって1つずれるはずだ」と座標を変換していく。強力だが、変換関数の網羅が難しく、中央サーバーが必ず必要になる。
CRDTは別のアプローチをとる。「どの順番でマージしても同じ結果になる」ようにデータ構造そのものを設計しておけば、変換も調停も要らない。Yjsはこの発想を、開発者から見て普通のデータ型に見える形で提供する。
(y-websocket等) participant B as クライアントB A->>A: ytext.insert(0, 'AAA') B->>B: ytext.insert(0, 'BBB') Note over A,B: 互いを知らないまま
ローカルで即座に反映 A->>P: encodeStateAsUpdate() のバイナリ B->>P: encodeStateAsUpdate() のバイナリ P->>B: Aのupdate P->>A: Bのupdate A->>A: applyUpdate() B->>B: applyUpdate() Note over A,B: 双方とも "BBBAAA"
順序に依存せず必ず一致
上の図の収束は実際に測った。Y.Doc を2つ作り、互いを知らない状態で片方に AAA、もう片方に BBB を挿入してから相互に update を適用すると、両方とも BBBAAA で一致した。どちらのupdateを先に適用しても結果は変わらない。これがCRDTの「衝突しない」の中身だ。
開発者から見えるのは「特殊能力つきの普通のデータ型」
Yjsが優れているのは、この理論を shared types(共有データ型) という平凡な見た目のAPIに包んでいる点にある。Y.Map は set / get を持つただのマップに見えるし、Y.Array は insert / delete を持つただの配列に見える。違うのは、変更が自動的に他の参加者へ伝播し、マージされることだけだ。
・Y.Text — 文字列。リッチテキストの書式属性も持てる
・Y.Map — キー・バリュー
・Y.Array — 順序つきリスト
・Y.XmlFragment / Y.XmlElement — ProseMirror などのツリー構造エディタ向け
何を代替できるのか
Yjsが省略してくれるのは、次の3つをまとめた「共同編集バックエンド」だ。
・サーバー側の調停ロジック — 誰の編集を優先するかの判断が不要になる。サーバーはバイナリを右から左へ流すだけの中継役に降格できる
・オフライン対応の差分同期 — 切断中の編集をローカルに溜め、再接続時にまとめて送っても正しくマージされる
・Undo/Redoの実装 — Y.UndoManager が「自分の変更だけを取り消し、他人の変更は巻き戻さない」という共同編集特有の挙動を担当する
実際の採用例もこの範囲に集中している。READMEの “Who is using Yjs” には Evernote、GitBook、Proton Docs、AFFiNE、Huly、ToolJet、Cargo、Sana などが並ぶ。ノート・ドキュメント・社内ツールビルダーといった、「複数人が同じ画面を同時に触る」製品が主な顧客層だ。同じ領域を扱うHulyとは|Linear・Jira・Slack・Notionを1つに束ねる自己ホストOSSワークスペースもYjsをこの用途で使っている。
npm i yjs で実際に入るもの——v13安定版とv14(@y/y)が並走する現状
ここからが本記事の主題だ。Yjsのリポジトリを開くと、いま少し奇妙なことが起きている。
GitHubの main ブランチにある package.json を読むと、こう書いてある。
・name: @y/y
・version: 14.0.0-rc.24
一方、npm i yjs で入るのは [email protected] だ。つまりリポジトリのデフォルトブランチと、npmで配布されている安定版は、パッケージ名からして別物になっている。v13の系列は v13 ブランチに残されている。
実測したバージョンの地図
2026-08-21時点で、npmの各タグが実際に何を指しているかを確認した結果が次の表だ。
| パッケージ | latest |
beta |
next |
備考 |
|---|---|---|---|---|
yjs(v13系) |
13.6.32(2026-08-04) | — | 14.0.0-8 | latest が現役の安定版 |
@y/y(v14系) |
14.0.0-rc.7 | 14.0.0-rc.24(2026-07-15) | — | latest が古いRCを指す |
y-websocket |
3.1.0 | — | 2.0.5-0 | v13用プロバイダ |
@y/websocket |
4.0.0-0 | 4.0.0-rc.2 | 4.0.0-1 | v14用プロバイダ |
y-protocols |
1.0.7 | — | — | v13用 |
@y/protocols |
1.0.6-0 | 1.0.6-rc.1 | 1.0.6-1 | v14用 |
y-indexeddb |
9.0.12 | — | 9.0.0-6 | v13用永続化 |
@y/indexeddb |
— | — | — | npmに存在しない(404) |
main とnpmの安定版はパッケージ名から違う(2026-08-21 実測)この表で押さえるべき点は2つある。
1つ目。[email protected] は2026-08-04リリースで、この記事の執筆時点からわずか17日前だ。 v14が進行中だからといってv13が放棄されているわけではない。「安定版が古いまま放置されている」という状況ではない、という点は誤解しやすいので先に書いておく。
2つ目。スコープ付き(@y/)パッケージでは latest が最新のRCを指していない。 @y/y の latest は 14.0.0-rc.7 で、最新の rc.24 は beta の側にある。同じことが @y/websocket(latest=4.0.0-0 / beta=4.0.0-rc.2)と @y/protocols にも起きている。
ただしdist-tag はパッケージの性質ではなく、公開側がいつでも動かせる可変の設定だ。本記事の表は2026-08-21時点の実測値であり、メンテナが latest を貼り直せばこの状況は解消する。読者が自分で現在値を確認するにはこれを実行する。
# dist-tag の現在値を確認する(この記事の表と食い違っていれば、すでに更新されている)
npm view @y/y dist-tags
npm view @y/websocket dist-tags
実測:素直に入れると起動しない
この2つ目の点が実害になる。v14を試そうとして最も自然な npm i @y/y @y/websocket を実行すると、次のようになった。
# v14 を素直に入れてみる(実測:npm 11.1.0 / Node.js 22.13.1)
npm i @y/y @y/websocket
# → added 8 packages, and audited 9 packages in 522ms
# → 警告なし、exit 0
node -e "import('@y/websocket')"
# → Error: Cannot find package 'y-protocols'
インストールは警告ひとつ出さずに成功する。npm ls の出力もきれいに見える。それでも最初の import で落ちる。
原因はツリーを展開すると分かる。
npm ls --all
# [email protected]
# +-- @y/[email protected]
# | +-- @y/[email protected]
# | +-- [email protected]
# | `-- [email protected] ← 改名前の旧プレリリースが peer として同居
# `-- @y/[email protected]
# `-- [email protected]
@y/[email protected] は改名途中のバージョンだった。ソースコードは旧名の y-protocols/sync や yjs を import しているのに、package.json の依存には新名の @y/protocols しか書かれていない。宣言と実際のimportがずれているため、モジュール解決に失敗する。おまけに未充足のpeer依存として [email protected](放棄された非スコープのv14プレリリース)まで自動で入り、1つのツリーにYjs実装が2つ、lib0が2バージョン同居する状態になる。
@beta を付けると解決する。
# v14 を試すならこちら(実測:正常に import できた)
npm i @y/y@beta @y/websocket@beta
# → added 4 packages, and audited 5 packages in 442ms
node -e "import('@y/websocket').then(m => console.log(Object.keys(m)[0]))"
# → WebsocketProvider
@y/[email protected] のソースは @y/protocols/* と @y/y を正しく import しており、peerDependencies も {"@y/y": "*"} と改名後に揃っている。依存ツリーに残るlib0も 1.0.0-rc.25 の1つだけになった。
インストールが成功することは、動くことを意味しない
npm i @y/y @y/websocket は exit 0 で終わり、EBADENGINE も ERESOLVE も peer警告も出なかった(インストールログは全7行)。パッケージマネージャの出力だけを見ていると問題に気づけない。v14系を触るときは、インストール直後に node -e "import('@y/websocket')" のような最小のimport確認を1回挟むのが確実だ。
インストールと最小実装——v13で共同編集を動かす手順
実務でいま作るなら、選択肢はv13一択だ。ここでは最短で動くところまでの手順を書く。
1. インストール
# 本体と、WebSocket経由で同期するプロバイダ
npm i yjs y-websocket
# 入ったバージョンを確認(13.x であること)
npm ls yjs y-websocket
npm ls の確認を入れているのは、前章のような「入っているつもりのものと違う」を避けるためだ。ここで [email protected] と [email protected] が出れば正しい。
2. 同期サーバーを起動する
y-websocket にはそのまま使えるサーバーが同梱されている。認証も永続化もない開発用だが、まず動かすには十分だ。
PORT=1234 node ./node_modules/y-websocket/bin/server.cjs
3. 最小のクライアントコード
import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
const doc = new Y.Doc()
// 'my-room' が同じクライアント同士が同期する
new WebsocketProvider('ws://localhost:1234', 'my-room', doc)
const ytext = doc.getText('shared-text')
ytext.observe(() => {
console.log('現在の内容:', ytext.toString())
})
// 誰が編集しても、全員の observe が発火する
ytext.insert(0, 'Hello ')
このコードで押さえるべきは doc.getText('shared-text') の一行だ。ドキュメント内の名前('shared-text')が一致していれば、それが同じ共有データ型として全員に共有される。 ここから先はエディタのバインディング(ProseMirror なら y-prosemirror、CodeMirror なら y-codemirror.next)を挟めば、実際の編集UIにつながる。
なお、Yjs本体はエディタを持たない。エディタ側の選定についてはOvertype完全ガイド|~111KB Markdownエディタを Tiptap/Lexical 等8種と比較が、軽量エディタから Tiptap・Lexical までの比較を扱っている。
4. 削除したデータは本当に消えるのか
CRDTを検討するとき必ず出るのが「削除しても墓標(tombstone)が残ってデータが際限なく膨らむのでは」という懸念だ。これは実際に測れる。
const doc = new Y.Doc()
const t = doc.getText('t')
t.insert(0, 'x'.repeat(5000))
Y.encodeStateAsUpdate(doc).length // → 5015(バイト)
t.delete(0, 5000)
Y.encodeStateAsUpdate(doc).length // → 24(バイト)
5,000文字を入れたときの update は5,015バイト、その全文字を削除した後は24バイトまで縮んだ。Yjsは既定でガベージコレクションを行い、削除済みの内容そのものを保持し続けない。「CRDTは容量が増え続ける」という一般論は、少なくともYjsのデフォルト設定には当てはまらなかった。
v14(@y/y)で何が変わるか——APIは非互換、データは互換
v14を実際にインストールして、v13と何が違うかを測った。結論から書くと、JavaScript APIは大きく壊れているが、保存済みデータは壊れない。
実測:doc.getText() が存在しない
v14で最初に驚くのはここだ。Yjsのあらゆるチュートリアルの1行目に出てくるメソッドが無い。
| 呼び出し | [email protected] | @y/[email protected] |
|---|---|---|
doc.getText |
function |
undefined |
doc.getMap |
function |
undefined |
doc.getArray |
function |
undefined |
doc.getXmlFragment |
function |
undefined |
doc.get |
function |
function |
doc.zzzGarbageControl(対照群) |
undefined |
undefined |
最下行は対照群だ。存在しないはずの名前も undefined を返すことを確認したうえで、上の4つが同じ undefined だと言っている。v14では型ごとの取得メソッドが doc.get() 1つに統合された。
// v13
const t = doc.getText('t')
// v14 (@y/y)
const t = doc.get('t') // → YType インスタンス
型クラスの側も同じ整理を受けている。トップレベルのエクスポートを数えると、v13は103個で、その中に Text Map Array XmlElement XmlFragment XmlHook XmlText AbstractType が並ぶ。v14 rc.24 は150個にエクスポートが増えているのに、これら7つの型クラスは1つも含まれていない。代わりに単一の Type(実体は YType)と、スキーマ用の $ytype / $ytypeAny が入っている。
実測:黙って無視される改名
もう1つ、見つけにくい変更がある。Y.UndoManager のオプション名だ。
| オプション | [email protected] | @y/[email protected] |
|---|---|---|
ignoreRemoteMapChanges |
保持される(true) |
undefined |
ignoreRemoteAttributeChanges |
undefined |
保持される(true) |
zzzGarbageOption(対照群) |
undefined |
undefined |
v14で旧名を渡しても例外は投げられない。デタラメなオプション名を渡したときと同じく、静かに無視されるだけだ。つまりv13のコードをv14へ持っていくと、Undo/Redoの挙動が警告なしに変わる可能性がある。移行時に静的な検索で潰しておくべき箇所だ。
同様に Y.XmlFragment の toDOM() は v14 で undefined になっていた(toString と toJSON は関数のまま残っているので、プロパティ取得自体が壊れているわけではない)。リッチテキストまわりでは toDelta() / applyDelta() の引数が Quill Delta から lib0 の delta 形式へ変わり、依存する lib0 も ^0.2.99 から ^1.0.0-rc.21 へメジャーが上がっている。
実測:データの互換性は保たれている
ここが移行判断で一番重要な点だ。同じプロジェクトに [email protected] と @y/[email protected] を同居させて、updateを相互に適用した。
| 方向 | 結果 |
|---|---|
| v13 が生成した update → v14 のドキュメント | 適用成功(文字列が正しく復元された) |
| v14 が生成した update → v13 のドキュメント | 適用成功(同上) |
バイナリのwire formatは双方向で互換だった。同じ内容のupdateサイズはv13が22バイト、v14が25バイトとわずかに違うが、相互運用そのものは成立している。
つまり移行コストは「保存済みデータの変換」ではなく「アプリケーションコードの書き換え」に集中する。 データベースに溜めたYjsのバイナリを心配する必要はない。
READMEはパッケージの中身と一致していない
@y/[email protected] の tarball に同梱されている README を開くと、Getting Started には npm i yjs y-websocket と書かれ、サンプルコードは doc.getArray('my-array') を使っている。しかしこのREADMEが同梱されているパッケージ自身に getArray は存在しない。GitHubの main にあるREADMEも同じ内容だ。v14のドキュメントはまだ追いついていないので、リポジトリのREADMEをv14の手引きとして読まないほうがいい。実際のAPIは同梱の tests/ ディレクトリのほうが正確で、そこでは y.get('array') という新しい書き方が使われている。
v14のRC進行状況
タグの日付も見ておく。v14.0.0-rc.0 は2026-02-25、最新の rc.24 は2026-07-15公開だ。約5か月かけて24本のRCを重ね、そこから記事執筆時点まで約5週間新しいRCが出ていない。一方でv13側は2026-08-04に13.6.32が出ている。開発の主戦場はv14だが、出荷の主戦場は依然としてv13という状態だ。
Automerge・Loroとの比較——CRDTライブラリの選び方
CRDTの実装はYjsだけではない。実際に検討対象になる3つを、npmの実データで並べる。
| Yjs | Automerge | Loro | |
|---|---|---|---|
| npmパッケージ | yjs 13.6.32 |
@automerge/automerge 3.4.1 |
loro-crdt 1.14.1 |
| 実装言語 | 純粋なJavaScript | Rust → WASM | Rust → WASM |
| npm展開後サイズ | 2,253 KB(135ファイル) | 45,455 KB(56ファイル) | 18,876 KB(37ファイル) |
| ブラウザ向けESM単体 | 293 KB(dist/yjs.mjs) |
WASM同梱 | WASM同梱 |
| GitHub ★ | 22,565 | 6,518 | 6,053 |
| ライセンス | MIT | MIT | MIT |
| 最終push | 2026-08-06 | 2026-08-19 | 2026-08-11 |
| エディタ連携 | README の Bindings は18件、うちエディタは12件(ProseMirror / Quill / CodeMirror / Monaco / Ace / Slate / Lexical / Tiptap / BlockNote / BlockSuite / Milkdown / Superdoc) | READMEに記載なし | READMEに記載なし |
| 他言語実装 | Rust(y-crdt)・Python(pycrdt)・.NET・Kotlin・Elixir・Go | Rust本体 + 各言語binding | Rust本体 + 各言語binding |
選び方の目安はこうなる。
・ブラウザで動くエディタを作るならYjs。 純粋なJavaScript実装で配布物が最も軽く、既存エディタへのバインディングが揃っている(AutomergeとLoroのREADMEにはエディタ連携の記載が無かった)。WASMのロードを挟まないぶん初期化も素直だ
・サーバー側やネイティブアプリが主戦場ならAutomerge / Loro。 Rust実装が本体なので、JS以外の言語から一級市民として使える。ただしWASMバイナリを同梱するぶん配布サイズは1桁大きい
・要素の「移動」を正しく扱いたいならLoro。 READMEが機能として掲げているのが Rich Text CRDT・Moveable Tree・Moveable List で、リストやツリーのノードを移動する操作を型として持つ。Yjsで同じことをすると「削除+挿入」になり、同時編集時に要素が複製されうる
ベンチマークの数値には注意
YjsのREADMEは性能比較として dmonad/crdt-benchmarks をリンクしているが、このリポジトリの最終pushは2024-04-29で2年以上更新されていない。Automerge 3.x も Loro 1.x もその後にメジャーバージョンが進んでいるため、掲載されている数値を現在の性能差として引用するのは避けたほうがいい。本記事の比較表に速度の列を置いていないのはこのためだ。
プロバイダとエコシステム——同期・永続化・サーバーの選択肢
Yjs本体は通信を持たない。ドキュメントの状態をどう運ぶかは「プロバイダ」が担当する。ここがYjsを実サービスに載せるときの実質的な設計ポイントになる。
公式プロバイダの現況
| プロバイダ | 最新版 | 最終push | 用途 |
|---|---|---|---|
y-websocket |
3.1.0 | 2026-08-06 | WebSocketでの同期。開発用サーバー同梱 |
y-webrtc |
10.3.0 | — | P2P同期。サーバーを持たない構成 |
y-indexeddb |
9.0.12 | 2025-02-12 | ブラウザのIndexedDBへの永続化 |
y-protocols |
1.0.7 | — | 同期・awareness のプロトコル実装 |
y-indexeddb の最終pushが2025-02-12(1年半前)である点は把握しておきたい。前掲のとおり v14向けの @y/indexeddb はnpmに存在しない。
実際に @y/y@beta と y-indexeddb を同居させて確かめたところ、y-indexeddb の peerDependencies が {"yjs": "^13.0.0"} であるため、npmが別途 [email protected] をツリーに追加した。この2つの Doc クラスは相互に instanceof が false になるまったく別のクラスで(対照として同一バージョン同士では true になることも確認した)、@y/y のドキュメントを y-indexeddb で永続化することはできない。オフライン対応が必要なアプリはv14へ移行できない、というのが現時点の結論になる。
運用向けサーバーの選択肢
同梱の開発用サーバーは認証も永続化も持たないため、本番では別の実装を選ぶことになる。READMEに挙がっている主なものは次の通りだ。
・Hocuspocus(★2,543、MIT、2026-08-10 push) — SQLite永続化・Webhook・認証を持つ拡張可能なスタンドアロンサーバー。Tiptap を作る ueberdosis 製
・y-sweet — S3またはファイルシステムへ永続化するサーバー。マネージドのクラウド版もある
・Liveblocks(@liveblocks/yjs) — WebSocket基盤と永続化をフルマネージドで提供。設定も運用も不要
・Velt — マネージドのYjsバックエンド。オフライン対応・バージョン履歴・E2E暗号化を持つ
・teleportal — 任意のストレージ・任意のJSランタイム・任意のトランスポートで自前の同期サーバーを組むためのフレームワーク
自前でサーバーを持たずに共同編集を実装する構成については、Figmaクローンの作り方解説|Next.js×Liveblocks×Fabric.jsのリアルタイム協調編集がマネージドサービスを使った実装例を扱っている。
JavaScript以外から使う
Yjsのバイナリ形式は言語非依存なので、サーバー側を別言語で書くこともできる。READMEには y-crdt(Rust実装)を基盤とした各言語バインディングが並ぶ。
・pycrdt — Python(ypy は非推奨となり、こちらへ移行)
・ydotnet — .NET / C#
・ykt — Kotlin
・y_ex — Elixir
・yr — R
・ygo — Go(複数の実装がREADMEに掲載されている)
よくある落とし穴と、いま移行すべきかの判断
実測を通じて見えた注意点を整理する。
1. GitHubのREADMEをv14の手引きとして読まない
前述のとおり、main のREADMEは npm i yjs と doc.getArray() を案内しながら、そのブランチのコードには getArray が無い。リポジトリを見て「これがv14の書き方だ」と判断すると確実にずれる。 v14のAPIを知りたい場合は、パッケージ同梱の tests/ や型定義(dist/src/index.d.ts)を見るのが正確だ。
2. スコープ付きパッケージでは latest を信用しない
@y/y @y/websocket @y/protocols の3つとも、latest が改名前後の古いプレリリースを指している。v14に触るときは常に @beta を明示する。 そして、インストールが成功しただけで安心せず、node -e "import('...')" を一度通す。
3. ignoreRemoteMapChanges は静かに死ぬ
v14への移行でエラーにならずに挙動だけ変わる、最も見つけにくい変更だ。移行前にコードベース全体を ignoreRemoteMapChanges で検索し、ignoreRemoteAttributeChanges へ置き換えておく。
4. オフライン永続化が要るならv14は選択肢に入らない
@y/indexeddb が存在しない以上、ブラウザのオフライン編集を持つアプリはv14へ進めない。ここは公開待ちだ。
判断のまとめ
2026-08-21時点の推奨
・新規開発 → npm i yjs(13.6.32)。安定版として現役で、プロバイダもエディタ連携もすべて揃っている
・既存プロジェクト → 移行しない。データ互換は確認できたのでいつでも移行できるが、いま急ぐ理由がない
・v14を評価したい → npm i @y/y@beta @y/websocket@beta。ただし永続化プロバイダが無いこと、READMEが追いついていないことを前提に
・v14の正式リリースを待つ判断材料 → @y/indexeddb がnpmに公開されるかどうか。エコシステムが揃った合図になる
Yjsそのものの評価は変わらない。★22,565、MIT、12年の実績、Evernote や Proton Docs のような実サービスでの採用、そして純粋なJavaScriptで2MB強という配布サイズ。共同編集を実装するうえでの第一候補であり続けている。今回測ったのは「ライブラリの良し悪し」ではなく「いま何をインストールすると何が起きるか」だ。 そしてその答えは、2026年8月時点では拍子抜けするほど単純だった——npm i yjs でいい。
参照ソース
・yjs/yjs — GitHub リポジトリ(README・package.json・リリースタグ。2026-08-21 参照)
・Yjs 公式ドキュメント(docs.yjs.dev)
・npm: yjs / npm: @y/y(dist-tags・公開日・展開後サイズ)
・yjs/y-websocket — GitHub
・automerge/automerge — GitHub / loro-dev/loro — GitHub(比較対象の実データ)
・dmonad/crdt-benchmarks — GitHub(最終更新2024-04-29。数値は現行版と一致しない点に注意)