에디팅 방식

contenteditable과 Rust/WASM 문서 코어를 함께 쓸 때 IME·선택·모델이 어긋나지 않게 하려면 정책과 루프를 명시해야 합니다.

개요

WASM “뇌”를 쓰는 웹 에디터도 대개 브라우저 편집 표면을 씁니다: 루트 contenteditable(또는 얇은 래퍼). Rust는 검증·변환·협업·무거운 파싱을 맡고, DOM은 캐럿·IME·네이티브 선택을 맡습니다.

이 페이지는 그 분업을 건강하게 유지하는 정책·루프를 다룹니다. 일반적인 모델/뷰 이론은 에디터 → 아키텍처, IME·클립보드 세부는 연결된 Wasm 가이드를 보세요.

contenteditable + WASM 하이브리드

하이브리드에서는 JavaScript/TypeScript가 사용자가 입력하는 DOM 트리에 리스너를 달고, 필요 시 Rust에서 돌아온 패치를 적용합니다. WASM은 실행 취소·협업·내보내기에 믿을 수 있는 정본 문서(또는 연산 로그)를 가집니다.

  • 장점: IME·맞춤법·접근성 트리·플랫폼 선택 동작을 브라우저가 제공합니다.
  • 단점: 브라우저마다 HTML 조금씩 다르게 만듭니다. 지속적으로 맞춰야 하며, 시나리오에 실제 차이가 정리돼 있습니다.

Rust는 여기서 편집 표면 전체를 대체하지 않고, 한 언어로 두고 싶은 깨지기 쉽거나 비싼 문서 로직을 옮기는 쪽입니다.

진실의 원천

한 시점에 하나의 권위를 정하세요:

  • 모델 우선: 안정된 편집마다(예: IME compositionend 후) WASM에 연산 적용 → JS가 DOM을 모델에 맞춤. 조합 중에 DOM과 싸우면 위험합니다.
  • 입력 중 DOM 우선: 타이핑 동안은 DOM이 정본이고, 주기적으로 또는 커밋 시점에 Rust 모델로 파싱. 파싱이 손실이 있으면 스키마·테스트로 틈을 막아야 합니다.

실용적 절충: IME 조합 세션 동안은 DOM을 우선하고, 확정 후 한 번에 Rust에 반영하는 패턴이 많습니다. IME·조합 입력.

DOM 전체 문자열과 Rust 트리를 매 키마다 서로 독립적으로 갱신하면 레이스·중복 문자가 납니다. 넘기는 지점을 설계하세요.

입력 이벤트·순서

컨트롤러 코드 안에 명시적 파이프라인을 두세요(핸들러만 흩뿌리지 않기):

  1. beforeinput — 가능한 편집 가로채기/취소, getTargetRanges() 활용.
  2. input — DOM 변경 후. 조합 밖에서 DOM 스냅샷 동기화를 둘 때 디바운스.
  3. composition* — Rust에 “미리보기”를 줄지, 확정 텍스트만 줄지.
  4. selectionchange — 모델 좌표로 매핑; 비동기 WASM 갱신이 Range를 깨뜨리지 않게. 선택·오프셋.

이벤트 의미: 에디터 → 입력 처리.

DOM ↔ 모델 루프

흔한 루프 예:

  • 커밋 시 파싱: 서브트리 직렬화 또는 DOM diff → 구조화 연산을 WASM에 → 모델이 거부·정규화하면 DOM에 반영.
  • JS에서 연산 스트림: beforeinput 유형을 작은 연산으로 바꿔 WASM에 적용 → 최소 DOM 패치 지시를 반환.
  • 투영: WASM이 권위; 트랜잭션마다 직렬화 뷰로 DOM 전체 또는 부분 교체(제어는 크고 diff 비용도 큼).

매 이벤트마다 HTML 전체를 WASM으로 넘기지 말고 프로파일로 검증하세요. JS ↔ WASM 경계.

이론: 에디터 → 모델–DOM 동기화.

DOM 형태·스키마

브라우저는 <br>, 빈 블록, 중첩 span을 제각각 넣습니다. Rust 스키마로 허용 형태를 정하고, Enter·붙여넣기 뒤에 정규화합니다(JS에서 빠른 DOM 수술 또는 WASM에서 네이티브와 규칙 공유).

스키마·HTML: 에디터 → 모델·스키마, HTML 매핑.

비편집 아일랜드

contenteditable=false·멘션·수식 블록·위젯을 본문에 섞으면 경계에서 선택이 접히거나 점프합니다. Android와 데스크톱 차이도 큽니다.

WASM이 해결해 주지 않습니다. 뷰 레이어에서 클릭 대상·키보드 포커스·Rust 모델로의 매핑(위젯 건너뛰기)을 정해야 합니다.

접근성, 비편집 블록 관련 케이스는 사이트 시나리오를 참고하세요.

WASM 호출 시점

실무용 가이드:

  • 호출: 스키마 검증, 변환 적용, 붙여넣기 HTML 파싱, CRDT 병합, 내보내기/가져오기, 무거운 diff.
  • 피하기: 모델 미리보기가 꼭 필요하지 않다면 매 compositionupdate마다 호출하지 않기.
  • 배치: 빠른 입력을 프레임 단위 등으로 묶되, IME 커밋 순서를 뒤바꾸지 않기.

캔버스·자체 렌더러

본문을 Canvas/WebGL로 그리는 제품도 숨긴 contenteditable로 IME를 받는 경우가 있습니다. 완전 커스텀이면 IME·접근성 부담이 큽니다.

인쇄 정밀 레이아웃·게임 UI 같은 명시적 이유가 없으면 DOM 호스트를 유지하는 편이 낫습니다.

Rust가 맡는 일

  • 문서 모델 + 연산(변환 적용, 스키마 강제).
  • 붙여넣기·가져오기 HTML 파싱, 마크다운 왕복.
  • CRDT·동기화(예: Yrs)를 WASM으로 두고 JS 호스트와 연동. 협업·CRDT.
  • 본문 줄바꿈을 브라우저에 안 맡기는 고급 레이아웃(선택).

선택·다음 단계

기본 권장: contenteditable 표면 + WASM 코어, 진실 원천 규칙 명시, 디버거에서 추적 가능한 동기화 루프. 렌더 최적화 전에 경계 트래픽을 측정하세요.

다음 읽을 것: IME·조합 입력 · JS ↔ WASM 경계 · 실행 취소·다시 실행 모델