logo

WYSIWYG 에디터 선택하기

작성 2020년 9월 23일

업데이트 2020년 9월 23일

WYSIWYG은 텍스트 편집에 대한 다양한 기능을 제공하고 그 편집한 결과를 그대로 출력하여 보여주는 에디터이다. 이메일 제작을 위한 솔루션을 만들 때 WYSIWYG은 아주 중요하다. 이메일을 제작할 때 필요한 가장 기본적인 요소인 텍스트, 이미지, 버튼 중 아무래도 텍스트의 비중이 가장 높기 때문이다. 사용자 경험 중 많은 부분을 차지하고 있는 만큼 얼마나 기능이 다양한가보다도, 있는 기능이 얼마나 안정적으로 작동하는지가 중요하다. 이 명제를 깨닫고 실현할 수 있기까지 시행착오가 있었기에 정리해 보려고 한다.

Draft.js

가볍고 쉬운 인터페이스를 제공하지만 치명적인 한글 버그

처음 배리 에디터 제작을 시작하며 WYSIWYG 에디터를 선택했을 때는 공식 문서가 심플하고 사용하기 쉬워 보이는 것이 기준이었다. 그중에서도 오픈소스이며, 모르는 내용을 구글에 검색했을 때 나오는 답변이 많아야 했다. 나는 개발 경험이 없는 초심자였고 최대한 참고로 삼을 수 있는 소스가 필요했기 때문이다. 그렇게 Draft.js를 선택하게 되었다. React를 만든 페이스북에서 개발한 라이브러리여서 적용하기 쉬웠고 사용자도 많아서 StackOverflow에 질문을 검색하면 웬만한 답변은 다 나왔다. 그렇게 순조롭게 WYSIWYG 선택 및 적용을 마치는구나, 싶었을 때 맞닥뜨린 버그가 있는데 바로 한글 입력시 뷰어에 반영이 지연되는 버그였다.

교훈1: CJK 언어 사용자라면 IME를 기억하자

이때 IME(Input Method Editor)가 무엇인지 처음 알았다. (MDN 문서 참고) 로마자 기반 언어와 달리, CJK 언어는 문자가 조합되어 한 글자를 형성하는 특징이 있다. 따라서 자판을 입력할 때도 다르게 동작한다. 로마자는 한 글자 입력시마다 커서가 움직이는데 CJK는 여러 문자가 조합되어 한 글자가 완성되었을 때 커서가 움직인다. 그렇게 한 글자가 입력되는 과정을 composition 이라 한다. 다음 코드를 브라우저에서 실행해 보면 input 에 한글을 입력할 때마다 composition 이벤트가 진행되는 순서를 로그로 확인해 볼 수 있다.

const input = document.createElement('input');
const log = document.createElement('log');

document.body.appendChild(input);
document.body.appendChild(log);

const inputElement = document.querySelector('input');
const logElement = document.querySelector('pre');

const events = ['compositionstart', 'compositionupdate', 'compositionend', 'keydown'];

events.forEach((event) => {
  input.addEventListener(
    event,
    (e) => {
      logElement.textContent += `${event}: ${e.data || e.keyCode}\n`;
    },
    true,
  );
});
한글 입력시 composition 이벤트 로그

로그 확인시 한 글자를 입력하기 시작하면 compositionstart 이벤트가 호출되고, 초성 중성 종성이 입력되기까지는 compositionupdate 이벤트가 호출되며, 그리고 마지막으로 다음 글자가 입력되기 시작했을 때 기존 글자의 compositionend 이벤트와 다음 글자의 compositionstart 이벤트가 연달아 호출되는 과정을 볼 수 있다. composition 이 완료되기 전까지는 뷰어에 반영되지 않기 때문에, 지연 현상이 발생하고 사용자 경험의 불편을 초래하게 된다.

교훈2: 라이브러리 선택 전 공식 문서를 꼼꼼히 읽자

이러한 한글 관련 버그는 타겟 시장이 한국인 솔루션에서 치명적이다. 게다가 공식 문서의 Issues and Pitfalls 섹션에 한글 관련 버그가 이미 명시되어 있다는 것도 뒤늦게 알았다. 여러모로 배리 에디터에서 Draft.js를 계속 사용하기에는 문제가 있었기에 새로운 라이브러리를 찾기로 했다. 이 경험을 통해 라이브러리를 선택하기 전에는 공식 문서를 충분히 꼼꼼하게 읽고, 무엇보다 명시된 이슈들에는 무엇이 있는지 확인해 보아야 한다는 교훈을 얻었다.

CKEditor5

가장 안정적이고 오랜 기간 검증되어 온 텍스트 편집기

문제 해결을 위해 여러 가지 WYSIWYG 에디터를 검토해 보았다. 마찬가지로 오픈 소스이며 사용자가 많고 깃헙 스타 수도 많은 라이브러리 중에 SlateQuill 등이 있었다. 그러나 이들 라이브러리 역시 해외에서 개발되었기 때문에 CJK 언어의 IME를 완벽히 지원해주지 않았다. 반대로 한국에서 개발되어 한글 관련 버그가 없는 Summernote가 있었지만 jQuery 기반이라 React 프로젝트에 적용하기에는 조금 망설여졌다.

또 완벽한 WYSIWYG 에디터를 찾는 것이 힘든 이유는 그만큼 텍스트를 입력하고 편집하는 데 있어서 고려해야 할 이슈들이 많기 때문이다. 예를 들어 굵기와 이탤릭체 적용과 같은 기본적인 기능만 요한다면 선택의 폭이 넓어지겠지만, 배리 에디터의 텍스트 편집기는 이모티콘이나 링크 커스터마이징, 그리고 개인화된 필드 삽입 등 보다 복잡한 플러그인 기능들이 포함되어야 했다. 참고로 Draft.js를 사용할 때는 텍스트 문단의 정렬이 왼쪽 정렬로만 고정되어 있어서 정렬 기능을 구현할 때 직접 에디터 노드에 접근하여 해당 노드의 스타일 속성을 강제해야 하는 이슈도 있었다. 따라서 다음과 같은 사항을 고려하여 선택하는 것이 좋다.

  1. 필요한 기능을 리스트업한 후, 해당 기능이 API 또는 플러그인으로 이미 제공되어 있는지
  2. 만약 기능 추가의 가능성이 있다면 커스텀 플러그인 구현에 대한 확장성이 어느 정도인지
  3. 타겟 사용자에게 치명적인 버그가 있는지 (예: 한글 입력시 지연되는 버그)

이것들을 모두 고려하여 새롭게 선택한 라이브러리는 CKEditor5이다. CKEditor5는 우선 가장 중요한 한글 버그가 없었고, 오픈 소스이며, 오래된 만큼 안정적이고 검증된 라이브러리이다. 또한 필요한 기능에 대하여 이미 구현되어 있는 플러그인을 참고할 수 있는 것도 장점이었다. 커스텀 플러그인 구현에 대해서도 공식 문서에 기본 개념과 함께 여러 가지 케이스가 자세히 설명되어 있다.

좋았던 점: 편집 결과 내보내기

마지막으로 이메일 콘텐츠로서 편집된 텍스트를 내보내기 위해서는 HTML 형식으로 결과물을 변환하는 기능이 지원되어야 한다. Draft.js에서는 텍스트 편집 상태를 저장하는 모델을 ContentState 로 정의하고 있는데, 이는 immutable.js 기반으로 구현되어 있어서 플레인한 JS 형식으로 변환하려면 convertToRaw 메소드를, 반대의 경우에는 convertFromRaw 메소드를 사용해야 한다. 또한 HTML로부터 Draft.js 모델로 변환하는 convertFromHTML 메소드는 제공하는데, 반대로 Draft.js 모델에서 HTML으로 변환하기 위해서는 서드파티 라이브러리를 추가로 적용해야 했다. 반면 CKEditor5에서는 편집시 자체적으로 데이터 저장 모델을 업데이트하고, HTML 변환이 완료된 버전을 뷰어에 반환한다. 따라서 편집 결과를 내보내고 또 기존의 데이터를 불러들이는 데 있어서도 불필요한 형식 변환을 거칠 필요 없이 HTML 형식으로 관리할 수 있다는 점이 편리했다.

아쉬운 점: 스타일 커스터마이징이 유연하지 않음

CKEditor5를 적용하고 나서, 배리 에디터의 텍스트 편집기는 필요한 기능을 더욱 안정적으로 제공할 수 있게 되었다. 대체적으로 만족스러운 결과지만 한 가지 아쉬운 점이 있었다. 라이브러리 자체적으로 스타일을 어느 정도 강제하고 있다 보니, 스타일을 배리의 디자인 시스템에 맞추어 커스터마이징하는 데 있어서 유연한 대응이 조금 어려웠다. css를 덮어쓰기 위해 !important 를 남발하게 되고, 플러그인 소스를 포크떠서 아이콘 이미지만 수정하는 등 그리 좋지 않은 방법들을 사용해야 했다.

완벽한 WYSIWYG 에디터는 있을까?

사실 배리의 에디터 프로젝트를 시작하던 가장 처음 단계에서는 큰 포부와 함께 0에서부터 모든 기능을 만들어 보겠다는 (말도 안 되는) 시도를 하기도 했다. 하지만 그런 시도를 해봤기 때문에 라이브러리의 필요성도 알게 되고, 시행착오는 있었지만 CKEditor5가 최선의 선택이라는 것도 이해할 수 있게 됐다. 특히 대부분의 WYSIWYG 에디터는 HTML의 contenteditable 속성을 기반으로 하는데 이 속성 자체가 불완전하다. 다양한 입력 유형과 이에 따른 복잡한 selection 처리, 그리고 언어에 따른 고려 사항 등 WYSIWYG 에디터를 만들기 위해서는 대응해야 할 이슈의 폭이 굉장히 넓은데 contenteditable 속성이 이를 모두 커버하고 있지 않기 때문에 문제가 발생한다. CKEditor도 버전 4까지는 contenteditable 을 사용했었지만 결국 버전 5에서는 자체 데이터 모델을 구현함으로써 contenteditable 에 작별을 고했다고 한다. (참고 - CKEditor 5: A new era for rich text editing)

또한 리서치 결과 메일침프 등 이메일 에디터를 제공하는 주요 서비스들에서도 CKEditor를 사용하고 있다는 것을 알 수 있었다. 그만큼 WYSIWYG 에디터 분야에서는 CKEditor가 지금까지 가장 문제를 잘 해결한 솔루션이라는 반증이 아닐까 싶다. 아직 이슈들이 남아있다 하더라도 앞으로도 계속 문제를 개선해 나갈테니 WYSIWYG 에디터가 필요하다면 CKEditor5를 사용하는 것을 가장 추천할 수 있을 것 같다.