Skip to content

๐Ÿฅ› ๋™์‹œํŽธ์ง‘ ๋งˆํฌ๋‹ค์šด ์—๋””ํ„ฐ ๊ตฌํ˜„๊ธฐ

Byeongju Park edited this page Dec 3, 2024 · 1 revision

๋ฌธ์ œ ์ •์˜: ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์—๋””ํ„ฐ

  1. ๋งˆํฌ๋‹ค์šด์„ ์ง€์›ํ•˜๋Š” ์—๋””ํ„ฐ

๋งˆํฌ๋‹ค์šด์€ ๊ฐ„๊ฒฐํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ๊ธ€์˜ ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ตœ๊ทผ ๋งŽ์€ ์—๋””ํ„ฐ์—์„œ ์ง€์›ํ•˜๊ณ  ์žˆ๊ณ  ์„ ํ˜ธ๋„๊ฐ€ ๋†’์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์—๋””ํ„ฐ ์š”๊ตฌ์‚ฌํ•ญ์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  1. ํ˜‘์—…์ด ๊ฐ€๋Šฅํ•œ ์—๋””ํ„ฐ

ํ—ˆ๋‹ˆํ”Œ๋กœ์šฐ ์„œ๋น„์Šค ๋ชฉํ‘œ์— โ€˜๋™์‹œ ํŽธ์ง‘โ€™์ด ํฌํ•จ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ˜‘์—… ํ™˜๊ฒฝ์„ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์œผ๋กœ Notion ๊ณผ ๊ฐ™์€ ์‹ค์‹œ๊ฐ„ ๋™์‹œํŽธ์ง‘ ๊ธฐ๋Šฅ์„ ๋ชฉํ‘œ๋กœ ๊ตฌํ˜„์„ ์‹œ์ž‘ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Milkdown์œผ๋กœ ๋งŒ๋“œ๋Š” ์œ ์—ฐํ•œ ๋งˆํฌ๋‹ค์šด ์—๋””ํ„ฐ

๋งˆํฌ๋‹ค์šด ์—๋””ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด Milkdown ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

Milkdown์€ ํ…์ŠคํŠธ ํŽธ์ง‘ ์—”์ง„์ธ ProseMirror ์Šคํƒ ์œ„์—์„œ ๊ตฌํ˜„๋˜์—ˆ์œผ๋ฉฐ, ํ”Œ๋Ÿฌ๊ทธ์ธ ํ˜•ํƒœ๋กœ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ํ™•์žฅ์„ฑ๊ณผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ๋งค์šฐ ๋›ฐ์–ด๋‚ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ, headless ์ปดํฌ๋„ŒํŠธ๋กœ ์„ค๊ณ„๋˜์–ด ์žˆ์–ด ์Šคํƒ€์ผ์„ ์ž์œ ๋กญ๊ฒŒ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Milkdown์€ Y.js์™€์˜ ๊ฒฐํ•ฉ์„ฑ์ด ๋›ฐ์–ด๋‚˜ ์‹ค์‹œ๊ฐ„ ํ˜‘์—… ๊ธฐ๋Šฅ ๊ตฌํ˜„์— ์ ํ•ฉํ•˜๋ฉฐ, Vue์™€ React์™€์˜ ํ†ตํ•ฉ๋„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค๋ฅธ ์–ด๋–ค ์žฅ์ ๋ณด๋‹ค, Yjs ์™€ ๊ฒฐํ•ฉ์ด ๋›ฐ์–ด๋‚˜๋‹ค๋Š” ์ ์ด ๊ฐ€์žฅ ์ปธ์Šต๋‹ˆ๋‹ค. ๋™์‹œ ํŽธ์ง‘ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ด๋ฏธ Yjs๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์„ค๊ณ„๊ฐ€ ์ด๋ฃจ์–ด์ง€๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Block ๋‹จ์œ„๋กœ ํŽธ์ง‘ํ•˜๊ธฐ

Milkdown์˜ BlockProvider์„ ์‚ฌ์šฉํ•˜์—ฌ, ๋ฌธ๋‹จ ์˜†์— ๋“œ๋ž˜๊ทธ ๊ฐ€๋Šฅํ•œ Block View๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์ด UI๋ฅผ ์กฐ์ž‘ํ•˜๋ฉด ์—๋””ํ„ฐ์˜ ์ปจํ…์ŠคํŠธ์—๋„ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๋ฐ˜์˜๋˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„๋ฉ๋‹ˆ๋‹ค.

Block View๋ž€, ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์—๋””ํ„ฐ ๋ทฐ์™€ ์ƒํ˜ธ ์ž‘์šฉํ•˜๊ฑฐ๋‚˜ DOM์—์„œ ๋ฌด์–ธ๊ฐ€๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ์ƒํƒœ๊ฐ€ ์—๋””ํ„ฐ ๋ทฐ์™€ ์—ฐ๊ฒฐ๋  ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

m2

ํ˜‘์—… ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ

Yjs๋ฅผ ํ†ตํ•œ ํ˜‘์—… ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ collab ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด์ ์ธ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. Y.Doc ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฌธ์„œ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  2. y-websocket์„ ์‚ฌ์šฉํ•˜์—ฌ ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ์„ ๊ตฌ์„ฑํ•˜๊ณ , ์‹ค์‹œ๊ฐ„ ํ˜‘์—…์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
  3. Milkdown์˜ ํ˜‘์—… ์„œ๋น„์Šค(CollabService)๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ณ , Y.doc ์ธ์Šคํ„ด์Šค์™€ ์—๋””ํ„ฐ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค.
  4. ์ดˆ๊ธฐ ๋™๊ธฐํ™” ๊ณผ์ •์ด ์™„๋ฃŒ๋˜๋ฉด ๋ฐœ์ƒํ•˜๋Š” synced ์ด๋ฒคํŠธ๊ฐ€ ๊ฐ์ง€๋˜๋ฉด, ๊ธฐ๋ณธ ํ…œํ”Œ๋ฆฟ์„ ๋ฌธ์„œ์— ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  5. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋  ๋•Œ WebSocket ์—ฐ๊ฒฐ์„ ํ•ด์ œํ•˜๊ณ , ๊ด€๋ จ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

m1

์„œ๋ฒ„ ๊ด€์ ์—์„œ ๋ฐ”๋ผ๋ณด๊ธฐ

์„œ๋ฒ„์—์„œ๋Š” ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์‹œ ๊ณต์œ ๋˜๊ณ  ์žˆ๋Š” Y.doc ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ถ€์—์„œ ์ œ๊ณต๋˜๋Š” ๋ฉ”์„œ๋“œ์ธ Y.encodeStateAsUpdate ๋ฅผ ํ†ตํ•ด์„œ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๊ฐ€์ ธ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ DB์— ์ €์žฅํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ, ์ด๋ฏธ ์ €์žฅ๋˜์–ด์žˆ๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ Y.doc ๋ฐ์ดํ„ฐ์— ์ ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ๋Š”, Y.applyUpdate๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  ๊ณต์œ ๋˜๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ์— ์—…๋ฐ์ดํŠธ๋ฅผ ์ ์šฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

flushSync๊ฐ€ ๋ผ์ดํ”„์‚ฌ์ดํด ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉ๋˜๋ฉด ์•ˆ๋จ

Pasted image 20241120161744

  • react warns: "flushSync was called from inside a lifecycle method. React cannot flush when React is already rendering. Consider moving this call to a scheduler task or micro task."
  • flushSync()๊ฐ€ ๋ Œ๋”๋ง ์ค‘์— ํ˜ธ์ถœ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.ย ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ Œ๋”๋ง์„ ๊ฐ•์ œํ•˜๋Š” flushSync๋ฅผ ๋ Œ๋”๋ง์ด ์ง„ํ–‰ ์ค‘์ผ ๋•Œ ํ˜ธ์ถœํ•˜๋ฉด React์˜ ๋‚ด๋ถ€ ์ฒ˜๋ฆฌ ํ๋ฆ„์„ ๋ฐฉํ•ดํ•  ์ˆ˜ ์žˆ์–ด ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • ํ™•์ธ ๊ฒฐ๊ณผ, ์—๋””ํ„ฐ์™€ ํ˜‘์—… ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๊ฒฐํ•ฉํ•˜๋Š” ๋กœ์ง์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ flushSync๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์ด useEffect ์•ˆ์—์„œ, ์ฆ‰ ๋ผ์ดํ”„์‚ฌ์ดํด ๋ฉ”์„œ๋“œ ๋‚ด์—์„œ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋˜์—ˆ๋˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

  • ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋ฉดย flushSync()ย ํ˜ธ์ถœ์„ย setTimeout๊ณผ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋งคํฌ๋กœ ํƒœ์Šคํฌ๋กœ ์ทจ๊ธ‰๋˜๊ฒŒ ํ•˜์—ฌ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—๋Ÿฌ ๋ฌธ๊ตฌ์—์„œ๋Š” ์Šค์ผ€์ฅด๋Ÿฌ ํƒœ์Šคํฌ ํ˜น์€ ๋งˆ์ดํฌ๋กœ ํƒœ์Šคํฌ๋กœ ์ด๋™ํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ์ง€๋งŒ, ๋งˆ์ดํฌ๋กœ ํƒœ์Šคํฌ๋กœ ์˜ฎ๊ธฐ๊ฒŒ ๋  ์‹œ ๋ Œ๋”๋ง ์‚ฌ์ดํด ๋˜ํ•œ ๋งˆ์ดํฌ๋กœ ํƒœ์Šคํฌ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋˜‘๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ๋‹ค์‹œ ๋งˆ์ฃผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋งคํฌ๋กœ ํƒœ์Šคํฌ๋กœ ์ด๋™์‹œํ‚ค๋ฉด React๊ฐ€ ๋ Œ๋”๋ง ์‚ฌ์ดํด์„ ์™„๋ฃŒํ•œ ํ›„์— ์ฒ˜๋ฆฌํ•  ๊ฒƒ์„ ํ™•์‹คํžˆ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
useEffect(() => {
  setTimeout(() => {
    flushSync(() => {
      // ๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ฝ”๋“œ
    });
  }, 0);
}, []);

์•ž์œผ๋กœ์˜ ๊ฐœ์„ ์ง€์ 

  • ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๋„์ž…: Notion๊ณผ ๊ฐ™์ด ํˆดํŒ์ด๋‚˜ ์Šฌ๋ž˜์‹œ๋ฅผ ํ†ตํ•ด ์„œ์‹์„ ์ถ”๊ฐ€/๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ ๋„์ž…์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ํ†ต์ผ๋œ UI: ์‚ฌ์šฉ์ค‘์ธ shadcn๊ฐ€ UI๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋„๋ก ๊ฒฐํ•ฉ ๊ณผ์ •์ด ์ถ”๊ฐ€์ ์œผ๋กœ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๋™์‹œํŽธ์ง‘ ํ…Œ์ŠคํŠธ: ๋‹ค์–‘ํ•œ ๋™์‹œ ํŽธ์ง‘ ์ƒํ™ฉ์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ์ ์„ ํ…Œ์ŠคํŠธ๋ฅผ ํ†ตํ•ด ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

References

[Collaborative Editing | Milkdown](https://milkdown.dev/docs/guide/collaborative-editing)

[Total Studio โ€“ Serverless CRDT-Based Markdown Editor | Computer Science Blog @ HdM Stuttgart](https://blog.mi.hdm-stuttgart.de/index.php/2024/08/31/total-studio-serverless-crdt-based-markdown-editor/)

[Using Milkdown Kit | Milkdown](https://milkdown.dev/docs/guide/using-milkdown-kit)

https://docs.yjs.dev/api/document-updates

TEAM

DOCS

DEV-NOTES

PRESENTATION

Clone this wiki locally