🐡

y-durableobjects で共同編集エディタを作る

2024/02/20に公開

はじめに

https://zenn.dev/naporin24690/articles/d230c2ba678ec6

の紹介記事です。Lexical と yjs の cloudflare workers 実装である、y-durableobjects を組み合わせて共同編集エディタを作ります。

使用するライブラリ

https://github.com/yjs/yjs
https://github.com/facebook/lexical
https://github.com/honojs/hono
https://github.com/napolab/y-durableobjects

共同編集エディタを作る

デモサイト。こういうのが作れます。

Yjs on Cloudflare Workers with Durable Objects Demo Movie

https://yjs.napochaan.dev/

こちらのリポジトリにサンプルコードがあります。

https://github.com/napolab/y-durableobjects/tree/main/example

Lexical で共同編集可能なエディタを作る

Lexical の公式ドキュメントには、共同編集可能なエディタを作るためのサンプルコードがあるのでそれに従って実装します。

ws://localhost:8787 になっている部分は wrangler dev によって開かれる port に合わせて適宜変更してください。

https://lexical.dev/docs/collaboration/react

import { LexicalComposer } from "@lexical/react/LexicalComposer";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { CollaborationPlugin } from "@lexical/react/LexicalCollaborationPlugin";

import { Doc } from "Yjs";
import { WebsocketProvider } from "y-websocket";

import type { FC, ComponentProps } from "react";
import type { Provider } from "@lexical/Yjs";
import type { InitialConfigType } from "@lexical/react/LexicalComposer";

type ProviderFactory = ComponentProps<typeof CollaborationPlugin>["providerFactory"];

const providerFactory: ProviderFactory = (id, map) => {
  const doc = new Doc();
  map.set(id, doc);

  const provider = new WebsocketProvider(new URL("/editor", "ws://localhost:8787").href, id, doc);

  // 公式通りやると型エラーになるのでキャスト
  return provider as unknown as Provider;
};

const initialConfig: InitialConfigType = {
  editorState: null,
  namespace: "Demo",
  nodes: [],
  onError: (error: Error) => {
    throw error;
  },
};

type Props = {
  id: string;
};

const Editor: FC<Props> = ({ id }) => {
  return (
    <LexicalComposer initialConfig={initialConfig}>
      <PlainTextPlugin
        contentEditable={<ContentEditable className="editor" />}
        placeholder={<div>Enter some text...</div>}
      />
      <CollaborationPlugin id={id} username={crypto.randomUUID()} providerFactory={providerFactory} shouldBootstrap />
    </LexicalComposer>
  );
};

export default Editor;

共同編集サーバー

共同編集サーバーは、y-durableobjects を使って実装します。y-durableobjectshono と組み合わせて使うことを想定しているため、hono と組み合わせて使います。

import { Hono } from "hono";
import { cors } from "hono/cors";
import { YDurableObjects, yRoute } from "y-durableobjects";

const app = new Hono();
app.use("*", cors());

const route = app.route(
  "/editor",
  yRoute((env) => env.Y_DURABLE_OBJECTS),
);

export default route;
export { YDurableObjects };

DurableObjects を使用するので wrangler.toml に以下のように設定します。

[durable_objects]
bindings = [
  { name = "Y_DURABLE_OBJECTS", class_name = "YDurableObjects" }
]

Hono の Bindings に Y_DURABLE_OBJECTS を追加しておきましょう。

type Bindings = {
  Y_DURABLE_OBJECTS: DurableObjectNamespace;
};

declare module "hono" {
  interface Env {
    Bindings: Bindings;
  }
}

あとは vite devwrangler dev を実行して、vite のサーバーにアクセスすると共同編集エディタが使えるようになります。

おわりに

CloudflareWorkers で簡単に共同編集エディタを作ることができました。Lexical 以外のエディタでも Yjs が使用できれば応用できると思います。ぜひ試してみてください。

Discussion