본문 바로가기
Front End

간이 에디터 만들기 + 라이브러리 배포

by 코딩파이 2024. 10. 20.

React Highlight Editor

 

0. 만들게 된 계기

이번 프로젝트를 진행하면서 특정 계산식을 만드는 UI가 필요했습니다. 

예를들면 휘발유 사용량 / 시간(H) 을 계산하여 시간당 휘발유 사용량을 구하는 것 처럼요.

최소한의 리소스로 구현하고자 여러 동료분들께 여쭤보기도 하고

Tiptap 같은 에디터 라이브러리의 Mention 등 기능도 보긴 했지만 제가 원하는 요구사항을 모두 충족하지 못할 것 같았습니다.

왜나면 적은 공식의 유효성 검사 및 가공 방식 자체가 명확하게 정의된 상황이 아니었거든요.

 

결국 최대한의 자유도를 보장하고자 실제로 에디터와 비슷한 무언가를 만들고자 결정했습니다.

외부 라이브러리에서 지원하지 않는 기능 떄문에 중간에 갈아 엎는 것이 더 많은 리소스를 소요할 것이라 생각했고,

에디터는 어떻게 만들어지는지 궁금하기도 했습니다.

 

 

 

1. 에디터, 어떻게 만들지?

사실 궁금증만 있을 뿐, 어떻게 구현하는지에 대해 하나도 감이 오질 않았습니다.

보통 사용하는 input과 textarea로는 에디터에서 지원하는 수많은 기능을 구현하는 것이 무리라는것만 알고 있었습니다.

탐색결과 HTML Element에 존재하는 contenteditable 속성을 이용하여 에디터를 만든 예제를 확인했습니다.

Element 자체를 수정할 수 있기 때문에 text-content 수정과 DOM에 접근하여 span태그나 스타일링을 준다면 원하는 기능을 가진 간이 에디터를 만들수 있겠다고 생각했습니다.

그러나 개발이 늘 그렇듯, 문제가 발생했습니다.

 

1-1. onInput 사용으로 직접 할당시 문제

contenteditable은 input과 다르게 onChange 이벤트가 아닌 onInput 이라는 이벤트를 사용합니다.

그래서 아래처럼 직접 DOM 요소에 콘텐츠를 주입하는 상황에서 Enter 키로 줄바꿈을 시도한다면 커서가 해당 Element의 맨 앞으로 가는 문제가 있었습니다.

 <div
      ref={ref}
      contentEditable
      onInput={e => ref.current.textContent = e.target.value ...(생략) }
      ...
  />

 

다행히 해당 이슈는 겪는 사람들이 많아서인지 Stack Overflow에서 쉽게 해결책을 찾을 수 있었습니다.

요소를 선택하고 커서의 위치를 가져와서 조정하는 Selection와 Range를 이용한다면 Enter를 친 시점에 요소의 가장 마지막으로 커서를 옮길 수 있었습니다.

그런데 이 방식으로 문제는 해결했지만, 문제는 자음/모음이 분리되는 현상이 있었습니다.

생각해보니 한글은 자음+모음이 합쳐져서 하나의 문자를 이루는데, 자음이든 모음이든 하나의 문자열이 입력된 순간 강제로 textContent에 강제로 할당하니 자 모음 요소가 조합되지 않고 나열되기만 했습니다.

결국 onInput 으로 강제 할당하는 방법을 포기하고 다른 방법을 시도했습니다.

 

1-2. 불완전한 태그 관리

입력 시점마다 입력값들을 제어하고 싶었던 가장 큰 원인은 불완전한 태그 요소를 즉각적으로 삭제하고 싶었기 떄문입니다.

예를들어 에너지사용량 이라는 태그가 있다고 했을때, 이 글자중 어떤 하나의 글자라도 제거되어 너지사용량, 에너지사용, 에너사용량 과 같은 불완전한 의미를 가진 문자열이 된다면 태그를 즉각 지우는 것이 옳다고 생각했습니다.

여러 방법을 고민 끝에,

앞서 언급한 Selection와 Range 을 사용하여 현재 커서가 위치한 요소가 불완전하다면 삭제하기로 조건을 설정하였고,

불완전한지 여부는 특정 태그를 추가할 때 Element의 textContent와 동일한 값을 id에 추가했습니다. 그리고 해당 요소의 text-content와 id 다르다면 불완전한 요소로 인식하는 것입니다.

<-- 정상 상태 -->
<span id='에너지사용량' style='...'>에너지사용량</span>

<-- 불완전 상태 -->
<span id='에너지사용량' style='...'>너지사용량</span>

 

 

 

물론 아직 완전히 해결못한 한 가지 버그가 남아있긴 하지만, 일단 해당 방법으로 원하는 로직은 모두 구현되었습니다.

 

 

 

2. 라이브러리 배포를 위한 확장성 고려

처음부터 라이브러리로 배포할 계획은 아니었습니다.

그러나 앞서 말했듯 다양한 미래의 변수에 대응하고자 많은 확장성을 지닌 채로 설계하였고,

그러다보니 좀 더 확장성을 고려하여 짜면 이거 충분히 라이브러리로 발행할 수 있지 않을까? 라는 생각이 들었습니다.

저와 비슷한 요구사항을 구현해야 하는 누군가가 있을 수 있으니까요.

 

2-1. 내부 환경 걷어내기

우선 회사에서 사용되는 내부 스타일 라이브러리나 다른 의존성을 최대한 걷어내는 것이 우선이었습니다.

다른 라이브러리 의존성을 지닌채로 발행이 가능하지만, 사실 그럴만한 로직이 많다고 판단하지 않았거든요.

아무튼 오랜만에 div태그를 쓰고, 스타일링은 CSS파일이 아닌 React의 CSSProperties를 사용하여 구현했습니다.

저는 개인적으로 JS in CSS 방식을 선호하기도 하고, 다른 에디터 라이브러리들이 대부분 ClassName과 CSS를 사용하여 제가 편한 방식으로 구현하고 싶었거든요.

 

2-2. 태그(하이라이트) 커스텀

CSS 파일을 걷어내고 태그를 커스텀하기 위해서는 커스텀 스타일 객체를 Hooks 선언할 때 전달하도록 설계했습니다.

그리고 추가 이벤트를 호출 할 때 스타일 객체의 key값을 넘겨 해당 스타일을 지정하여 사용하면 됩니다.

추가로 스타일 뿐 만 아니라 span태그 자체에 할당할 수 있는 여러 property를 주입할 수 있도록 열어뒀습니다.

 

 

 

3. 미구현과 추가 구현사항

위 고민을 토대로 구현 및 라이브러리 발행에 성공했지만 미처 잡지 못한 에러와 추가 구현사항 또한 존재합니다.

 

1. [Bug] 드래그로 여러 태그를 삭제시, 커서 끝에 걸친 항목은 즉시 삭제되지 않음

커서의 앞에 걸친다면 해당 요소를 부모, 형제, prev / next 요소 값으로 읽어올 수 있었지만 이상하게 커서 끝에 걸친 항목은 찾을 수 없었습니다.

다른 기능도 구현해야 하는 시간상, 이 부분만 미제 버그로 남겨두어야 했습니다.

 

2. [Todo] 키보드상에서 Mention 기능 추가

현재는 위 UI 예시처럼 에디터 외부에서만 태그 추가가 가능합니다.

이게 요구사항이라 이대로 구현하긴 했지만, 저는 꽤 불편하게 느꼈습니다.

떄문에 Slack이나 Notion, 카카오톡의 Mention 기능처럼 에디터 내부에서 태그를 추가하는 기능이 확장된다면 편리할 것 같다고 생각했습니다.

 

3. [Todo] 의존성 수정 등 라이브러리 최적화

아무래도 급하게 발행하고 구현이 우선이다보니 최적화된 라이브러리를 만들지 못했습니다.

불필요한 의존성이나 코드는 덜어내고 필요한 추가해야할 사항을 추가해서 좀 더 라이브러리 스럽게 발행하고자 시도해보고 싶습니다.

Readme도 좀 더 채우고요.

 

 

 

4. 후기와 배운점

총 약 이틀간의 구현부터 배포까지, 이 짧은 시간으로 contenteditable 속성이나 selection과 range를 활용하는 에디터 전문가가 되었다고 생각하진 않습니다. 전부 알고 구현한건 아니니까요.

그래도 확실한건 컴포넌트나 로직을 설계할 때 사용성과 확장성을 고려하는 시야가 조금은 더 넓어진 것 같습니다.

팀 내 동료들과 협업하는 환경에서는 위 두 요소가 조금 부족하더다고 제가 직접 부족한 부분을 설명하거나,

필요한 부분을 즉각적으로 수정하면 되기 때문에 시야가 조금 좁았다면

저와 일면식 없는 누군가가 해당 라이브러리를 사용한다면 제가 당장 답변하기도, 수정하기도 어려우니까요.

 

사용성과 확장성을 동시에 잡는 것은 아직 어려운 일이지만, 아무튼 좀 더 고려하고

앞으로 해당 라이브러리도 더 유지보수 할 수 있었으면 좋곘습니다.

 

감사합니다.