개요
contenteditable 에디터 디버깅은 브라우저 차이, 이벤트 타이밍, DOM 복잡성으로 인해 어렵습니다. 이 가이드는 효과적인 디버깅 기법을 다룹니다.
디버깅 도구
contenteditable 에디터 디버깅을 위한 필수 도구입니다.
브라우저 DevTools
브라우저 DevTools를 효과적으로 사용:
// Chrome DevTools: 포커스된 페이지 에뮬레이션
// 요소 검사 시 blur 이벤트 방지
// Rendering 패널 → "Emulate a focused page"
// DOM 변형에 중단점 설정
// Elements 패널 → 우클릭 → Break on → Subtree modifications
// 이벤트 모니터링
monitorEvents(element, ['input', 'beforeinput', 'compositionstart']);
// 이벤트 리스너 가져오기
getEventListeners(element);팁: Chrome DevTools에서 "Emulate a focused page"를 사용하면 DevTools를 클릭할 때 요소가 사라지는 것을 방지할 수 있습니다.
이벤트 로깅
class EventLogger {
#logs: EventLog[] = [];
log(event: Event) {
this.#logs.push({
type: event.type,
timestamp: Date.now(),
target: event.target,
data: this.#extractEventData(event)
});
}
#extractEventData(event: Event) {
if (event instanceof InputEvent) {
return {
inputType: event.inputType,
data: event.data,
isComposing: event.isComposing
};
}
if (event instanceof CompositionEvent) {
return {
data: event.data
};
}
return {};
}
getLogs() {
return this.#logs;
}
clear() {
this.#logs = [];
}
}
// 사용법
const logger = new EventLogger();
editor.element.addEventListener('*', (e) => logger.log(e));일반적인 문제
일반적인 디버깅 시나리오 및 해결책입니다.
선택 영역 문제
// 선택 영역 문제 디버깅
function debugSelection() {
const selection = window.getSelection();
console.log('선택 영역:', {
rangeCount: selection.rangeCount,
isCollapsed: selection.isCollapsed,
anchorNode: selection.anchorNode,
anchorOffset: selection.anchorOffset,
focusNode: selection.focusNode,
focusOffset: selection.focusOffset
});
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
console.log('범위:', {
startContainer: range.startContainer,
startOffset: range.startOffset,
endContainer: range.endContainer,
endOffset: range.endOffset,
collapsed: range.collapsed
});
}
}
// 선택 영역 변경 모니터링
document.addEventListener('selectionchange', () => {
debugSelection();
});DOM 동기화 문제
// 모델과 DOM 비교
function compareModelAndDOM(editor) {
const modelContent = editor.getModelContent();
const domContent = editor.element.textContent;
if (modelContent !== domContent) {
console.error('불일치:', {
model: modelContent,
dom: domContent
});
}
}
// DOM 변형 모니터링
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
console.log('DOM 변형:', {
type: mutation.type,
target: mutation.target,
addedNodes: Array.from(mutation.addedNodes),
removedNodes: Array.from(mutation.removedNodes)
});
});
});
observer.observe(editor.element, {
childList: true,
subtree: true,
characterData: true
});IME 문제
// IME 조합 디버깅
function debugIME() {
const element = editor.element;
element.addEventListener('compositionstart', (e) => {
console.log('조합 시작:', e.data);
});
element.addEventListener('compositionupdate', (e) => {
console.log('조합 업데이트:', e.data);
});
element.addEventListener('compositionend', (e) => {
console.log('조합 종료:', e.data);
});
element.addEventListener('beforeinput', (e) => {
console.log('입력 전:', {
inputType: e.inputType,
data: e.data,
isComposing: e.isComposing
});
});
}
// 조합이 활성화되어 있는지 확인
function isComposing() {
return document.activeElement === editor.element &&
(editor.element as any).isComposing;
}디버깅 기법
효과적인 디버깅 기법입니다.
중단점
// 조건부 중단점
editor.on('operation', (operation) => {
if (operation.type === 'insert' && operation.data === 'problem') {
debugger; // 여기서 중단
}
});
// 특정 조건에서 중단
function breakOnCondition(condition: () => boolean) {
if (condition()) {
debugger;
}
}
// 선택 영역 변경 시 중단
document.addEventListener('selectionchange', () => {
const selection = window.getSelection();
if (selection?.rangeCount === 0) {
debugger; // 선택 영역이 손실될 때 중단
}
});상태 검사
// 에디터 상태 검사
function inspectEditor(editor) {
return {
model: editor.getModel(),
selection: editor.getSelection(),
history: editor.getHistory(),
isComposing: editor.isComposing,
dom: {
content: editor.element.innerHTML,
textContent: editor.element.textContent
}
};
}
// 콘솔에서 에디터 상태 사용 가능하게 만들기
(window as any).editor = editor;
(window as any).inspectEditor = () => console.log(inspectEditor(editor));성능 디버깅
성능 문제 디버깅입니다.
// 작업 성능 측정
function measurePerformance(fn: () => void) {
const start = performance.now();
fn();
const end = performance.now();
console.log(`작업이 ${end - start}ms 걸렸습니다`);
}
// 렌더링 프로파일링
function profileRender(editor: Editor) {
console.profile('render');
editor.render();
console.profileEnd('render');
}
// 메모리 사용량 모니터링
function checkMemory() {
if ('memory' in performance) {
console.log('메모리:', (performance as any).memory);
}
}모범 사례
- 이벤트 로깅을 사용하여 이벤트 시퀀스 추적
- 동기화 문제 디버깅 시 모델과 DOM 상태 비교
- 특정 시나리오에 집중하기 위해 조건부 중단점 사용
- 선택 영역 문제 디버깅을 위해 선택 영역 변경 모니터링
- 브라우저별 문제 식별을 위해 여러 브라우저에서 테스트
- 병목 현상 식별을 위해 성능 프로파일링 사용
- 디버깅 코드를 프로덕션 코드와 분리