【今のところ】CloudflareWorkers で Lexical を同期している YDoc を HTML に変換する【できない】
YDoc を CloudflareWorkers DurableObjects 内部に持っているという前提。
その条件下でどうにかして YDoc から Lexical の同期状態を取り出して worker 上で HTML として返せるようにしたいというのがゴール。
YDoc の serialize
変更状態しか取れなさそう。ここから HTML にするのは多分無理なので Lexical が必要ぽい。
function serializeYDoc(yDoc: Y.Doc) {
const documentState = Y.encodeStateAsUpdate(yDoc)
const base64Encoded = fromUint8Array(documentState)
return base64Encoded
}
function deserializeYDoc(base64YDoc: string) {
const binaryEncoded = toUint8Array(base64YDoc)
const deserializedYDoc = new Y.Doc()
Y.applyUpdate(deserializedYDoc, binaryEncoded)
return deserializedYDoc
}
Lexical から HTML を出力する
これはできそう。ただこれが CloudflareWorkers 上で動くかどうか
import {$generateHtmlFromNodes} from '@lexical/html';
const htmlString = $generateHtmlFromNodes(editor, selection | null);
worker 上では editor がないので @lexical/headless
から editor をつくるっぽい。
おおよそやりたいこと発見。
const { createHeadlessEditor } = require('@lexical/headless');
const { $convertToMarkdownString, TRANSFORMERS } = require('@lexical/markdown');
app.get('article/:id/markdown', await (req, res) => {
const editor = createHeadlessEditor({
nodes: [],
onError: () => {},
});
const articleEditorStateJSON = await loadArticleBody(req.query.id);
editor.setEditorState(editor.parseEditorState(articleEditorStateJSON));
editor.update(() => {
const markdown = $convertToMarkdownString(TRANSFORMERS);
res.send(markdown);
});
});
YDoc を headless editor で読み込んで html で吐き出す discussions があった。
気になるコードを発見.... worker だと絶対動かなそう
// $generateHTmlFromNodes can only be used on the server-side when JSDOM is globally set up right now
// see https://github.com/facebook/lexical/issues/3097
const restoreDOM = globallyInstallJSDOM();
必要っぽい.....
JSDOM を全く時間が足りないらしい
See the benchmarks, they go over the 5ms of the free plan - you’d have to use a larger plan that gives you 20ms to be able to do this.
そもそも node.js まみれなので実行不可能
HTML にするのは不可能なので markdown で進めてみる。
とりあえず真似して作る
const editor = createHeadlessEditor({
namespace: "headless",
nodes,
onError: (error) => {
console.error(error);
},
});
const id = "1";
const provider = {
awareness: {
getLocalState: () => null,
getStates: () => [],
},
} as unknown as Provider;
const targetDoc = new Doc();
const binding = createBinding(
editor,
provider,
id,
targetDoc,
new Map([[id, targetDoc]]),
);
binding.root
.getSharedType()
.observeDeep((events: YEvent<YText>[], transaction: unknown) => {
syncYjsChangesToLexical(binding, provider, events, false);
});
const state = encodeStateAsUpdate(originDoc);
applyUpdate(targetDoc, state);
editor.update(
() => {
/** */
},
{ discrete: true },
);
await new Promise<void>((resolve) => {
const state = editor.getEditorState();
state.read(() => resolve());
});
createBinding を入れるとこれが出るようになる。
@lexical/yjs
が cjs なため Yjs was already imported. This breaks constructor checks and will lead to issues! - [https://github.com/yjs/yjs/issues/438](https://github.com/yjs/yjs/issues/438)
@lexical/yjs
は cjs で yjs
は esm で読んでるからっぽい....react の multiple instance 再来の感覚.....
yjs
@lexical/yjs
進展が全くない....
error 出るものの動きはするのでいったん無視する(どうせ直せないので)
$convertToMarkdownString(TRANSFORMERS);
を実行して markdown を得るだけなのだが、これを improt した瞬間に Prism is not defined
で worker が落ちるようになった。
原因はこいつ。Syntax highlight のために prismjs を入れているっぽい。(えー依存に入れるの?って思った外入れしてほしい気持ち)
prismjs 入れればいいのかと思い入れたらめちゃくちゃ node 依存が入ってきた。
ここが原因。worker の self は WorkerGlobalScope を拡張した ServiceWorkerGlobalScope だが、instanceof で評価したときに別物になる。当然 window もないので node.js モードになり、すべてが入ってくる。終わり
2024/02/09 時点での結論
ほぼほぼ不可能。解散!