이 글에서 다루는 것
하이브리드 블록 에디터를 다듬으면서 네 가지 층이 겹쳤다: 프로덕션 번들이 모든 페이지에서 비슷하게 무거운 이유, 여러 블록에 걸친 텍스트 선택·삭제, 선택 툴바가 스크롤 후 어긋나는 문제, 목차처럼 본문 전체를 읽는 UI가 매 타이핑마다 리렌더되는 문제. 각 작업이 어떤 상황에서 무엇을 목표로 어떻게 풀렸고 무엇을 배웠는지를 작업별로 정리한다.
하루 요약
| 작업 | 무엇을 하고 싶었나 | 한 일 | 결과 |
|---|---|---|---|
| 번들 | 라우트별로 무거운 청크 분리 — shared vendor가 모든 페이지 안 끌게 | splitChunks catch-all에서 chunks:"all" + 고정 name 제거, chunks:"initial"·maxSize 조정, static→dynamic import | 무거운 에디터·차트 라이브러리가 첫 페이지에서 빠짐 |
| 크로스블록 | 범위를 state에 안 두고 연산 시점에 Selection에서 파생 | 단일블록은 guest, 크로스만 document handler — 경계 split 한곳에 모음 | delete·mark가 경계 split을 재사용, owning PM 없이 안전 |
| 플로팅 UI | 열려 있는 동안 scroll·resize마다 anchor 재측정 — DnD 후 stuck 방지 | selectionchange로 UI 트리거, scroll(capture) + resize에서 좌표 갱신 | 스크롤·리사이즈 후에도 툴바가 선택과 안 어긋남 |
| Context | 자주 바뀌는 document tree를 넓은 runtime Context에서 분리 | TOC·파생 UI용 전용 Context로 구독 범위 축소 | 한 글자 타이핑에 목차·통계까지 리렌더되던 것 차단 |
1. Webpack shared vendor 함정
배경지식 (개념)
- 코드 스플리팅: 라우트마다 필요한 JS만 나눠 로드하는 것.
dynamic import로 무거운 라이브러리를 늦게 불러올 수 있다.
어떤 상황이었나
프로덕션 빌드 후 여러 라우트의 First Load JS가 비슷하게 크다. 코드 스플리팅을 했는데도 무거운 에디터·차트 라이브러리가 첫 페이지에 같이 올라온다.
핵심 작업
splitChunks catch-all vendor에 chunks: "all"과 고정 name이 있으면 코드 스플릿이 무력화될 수 있다. chunks: "initial"·maxSize·고정 name 제거와 함께 static import 체인을 dynamic import로 바꿔야 무거운 라이브러리가 빠진다. 결과적으로 무거운 에디터·차트 라이브러리가 첫 페이지에서 빠졌다.
교훈
- 모든 라우트가 같이 무겁다면 shared vendor 청크를 먼저 의심한다.
chunks: "all"+ 고정 name은 dynamic import를 무력화할 수 있다.
→ Webpack splitChunks vendor shared-chunk trap
2. 크로스블록 텍스트 연산
배경지식 (개념)
- Guest 에디터: 포커스된 블록 하나만 리치텍스트로 편집하는 패턴. (focused-guest-editor 참고)
어떤 상황이었나
블록마다 guest 에디터가 따로 있어, 한 블록 끝에서 다음 블록 시작까지 드래그 선택이 native Selection으로만 존재한다. “어느 PM이 주인인가”가 없다.
핵심 작업
범위를 state에 두지 않고 연산 시점에 Selection에서 파생하고, 블록 경계 split을 한곳에 모아 delete·mark가 재사용하게 했다. 단일블록은 guest, 크로스만 document handler로 가른다. 결과적으로 owning PM 없이도 delete·mark가 경계 split을 재사용하며 안전하게 동작했다.
교훈
- 크로스블록 범위를 React state에 두지 말 것 — Selection API에서 파생하고, 블록 경계 처리는 한곳에 모은다.
→ Cross-block text range operations
3. 플로팅 UI 좌표
배경지식 (개념)
- 플로팅 UI:
position: fixed로 선택 영역 근처에 뜨는 툴바·팝오버.
어떤 상황이었나
텍스트를 고른 뒤 툴바 좌표를 한 번만 잡으면, 스크롤·리사이즈 후 툴바가 선택과 어긋난다.
핵심 작업
position: fixed 1회 좌표 캡처만으로는 부족하다. UI가 열려 있는 동안 scroll capture와 resize에서 anchor를 재측정한다. 선택 UI 트리거는 mousedown/mouseup보다 selectionchange가 DnD 후 stuck을 피하기 쉽다. 결과적으로 스크롤·리사이즈 후에도 툴바가 선택과 어긋나지 않았다.
교훈
- fixed 좌표 1회 캡처만으로는 부족하다. 스크롤 컨테이너가 움직이면 anchor를 다시 잰다.
mousedown/mouseup보다selectionchange가 DnD 후 선택 UI에 유리한 경우가 많다.
→ Fixed-position floating UI and scroll tracking · selectionchange for floating selection UI
4. Context 구독 분리
배경지식 (개념)
- 파생 UI: TOC·통계처럼 본문 트리 전체를 읽어 만드는 UI. document가 한 글자 바뀔 때마다 같이 리렌더되기 쉽다.
어떤 상황이었나
본문 트리가 한 글자 바뀔 때마다 목차·통계까지 같이 리렌더된다.
핵심 작업
TOC처럼 본문 전체를 읽는 파생 UI는 자주 바뀌는 document를 넓은 runtime Context에 넣지 않고 전용 Context로 분리해 memo·render count를 지켰다. 결과적으로 한 글자 타이핑에 목차·통계까지 리렌더되던 것을 차단했다.
교훈
- 본문 전체를 읽는 파생 UI는 넓은 Context에 document tree를 넣지 않는다.
→ React Context render granularity