프로젝트

VanillaJS 노션 클로닝

너리 Neori 2022. 11. 18. 16:11

11월 8일부터 16일까지 프로그래머스 데브코스에서 VanillaJS로 노션(Notion)을 클로닝하는 개인 프로젝트를 진행했습니다.

 

프로젝트 배포: https://yuri-notion.vercel.app

 

매일같이 쓰는 툴이라서 익숙하지만 한편으론 편리한 만큼 구현해야 할 기능이 많아서 걱정됐습니다. 그래서 강의를 다시 듣고 이해하는 데 집중하느라 초반엔 빠른 속도로 진행하지 못했습니다. 더 이상 지체되면 안 될 것 같아서 구현 사항을 참고하여 설계부터 조금씩 시작했습니다.

 

 


 

최소 구현 사항

- 글 단위를 Document라고 합니다. Document는 Document 여러 개를 포함할 수 있습니다.
- 화면 좌측에 Root Documents를 불러오는 API를 통해 루트 Documents를 렌더링합니다.
  - Root Document를 클릭하면 오른쪽 편집기 영역에 해당 Document의 Content를 렌더링합니다.
  - 해당 Root Document에 하위 Document가 있는 경우, 해당 Document 아래에 트리 형태로 렌더링 합니다.
  - Document Tree에서 각 Document 우측에는 + 버튼이 있습니다. 해당 버튼을 클릭하면, 클릭한 Document의 하위 Document로 새 Document를 생성하고 편집 화면으로 넘깁니다.
- 편집기에는 기본적으로 저장 버튼이 없습니다. Document Save API를 이용해 지속적으로 서버에 저장되도록 합니다.
- History API를 이용해 SPA 형태로 만듭니다.
  - 루트 URL 접속 시엔 별다른 편집기 선택이 안 된 상태입니다.
  - /documents/{documentId}로 접속 시, 해당 Document의 content를 불러와 편집기에 로딩합니다.

본 글에서 '페이지'와 '문서'는 같은 의미로 쓰이는 점 참고 바랍니다.

(글 단위를 노션에서는 '페이지', 요구사항에서는 Document로 명명합니다)

 

 

설계

노션을 잘 안다고 생각했지만 막상 하나하나 뜯어보니 낯설게 느껴졌습니다. 일단 최소 구현 사항과 관련된 유저 시나리오를 노트에 적어보고 어떤 컴포넌트로 구성할지 틀을 잡아봤습니다. 노션에서는 사이드바에서 페이지 추가 버튼을 클릭하면 새 페이지 모달 창을 띄우는데, 저는 데브코스에서 배운 자동 저장 편집기를 최대한 활용하기 위해 우측에 바로 페이지를 보여주기로 결정했습니다.

개발 과정에서 추가로 필요하다고 느낀 컴포넌트를 구현하고 유틸 함수를 추가하는 등 계속해서 설계를 수정해나갔습니다.

 

 

구현

먼저 사이드바를 구현했습니다. HTML의 <details>와 <summary> 태그를 사용하면 손쉽게 토글 리스트를 구현할 수 있지만 이벤트나 스타일 등 커스텀하기 어려워 <ul>과 <li>로 만들었습니다. 루트 문서의 하위 문서들은 재귀 방식으로 렌더링합니다.

그리고 문서 편집 영역을 구현하기 위해 <input>과 <textarea>를 배치했습니다.

참고로 편집기에서 노션처럼 제목에서 줄바꿈 처리가 되도록 하려면 input 대신 textarea나 div+contentEditable 조합으로 만들어야 합니다..(추후 구현 예정)

아~ 배고픈 사람

history API로 url 라우팅 처리를 하여 문서의 id에 따라 해당 문서의 제목과 내용을 편집기에 로딩하고 수정합니다. API 요청은 fetch API로 처리했습니다. 문서 제목이 수정될 때마다 해당 탭의 제목(Document.title)도 함께 바뀌게 했습니다.

 

이때부터 성급한 마음에😩 레이아웃, 폰트, 색 등 스타일을 적용하기 시작했습니다. reset.css로 기본 스타일을 초기화했습니다. 구글 확장 프로그램인 CSSViewerColorZilla를 사용해 노션의 레이아웃이나 색상 정보를 쉽게 구할 수 있었습니다.

(하지만 스타일 다 적용한 후 commit하는 과정에서 stash해놓고 실수로 지워버렸고....처음부터 다시 스타일 적용했던...네..)

급발진

 

문서를 추가할 때 루트 문서이면 null, 그 외에는 상위 문서의 id를 localStorage에 임시로 저장하여 API 요청의 옵션으로 전달했습니다. 그리고 새로고침되더라도 토글 리스트의 열림/닫힘 상태가 유지되도록 배열 형태로 문서의 id를 저장해 렌더링할 때 활용합니다.

특히 localStorage 접근과 API 요청 과정에서 주고받는 데이터의 형식을 일관되게 처리하는 점이 쉽지 않았습니다. 에러를 해결할 때 시간을 많이 쓰게 되어 type validation의 중요성을 느꼈습니다.

 

편집기에서 글의 제목을 수정하거나 삭제 버튼을 클릭했을 때 사이드바에 바로 반영이 되어야 하는데 해당 이벤트 핸들러를 어디에 어떻게 구현할지 가장 고민되었습니다. 일단 App 컴포넌트에서 구현하여 하향식으로 전달하게 했는데, 반복되는 코드가 많고 App 자체가 복잡해져 어떻게 개선하면 좋을지 계속 생각해보고 있습니다.

 

 

사이드바에서 제목이 긴 경우 노션에서는 ...으로 생략(ellipsis)하고 있어서 CSS로 적용하고 마우스 커서를 올리면 하이라이트되고 삭제/추가 버튼이 나타나도록 처리했습니다. 현재 선택된 페이지도 하이라이트되게 했습니다.

 

긴 글인 경우를 고려하여 textarea의 크기를 적절히 지정하고 하단에는 하위 문서의 링크를 구현했습니다(추가 구현사항 중 하나). 아직 Rich Editor로 구현하지 못해서 임의로 편집기 하단에 버튼 느낌의 UI 형태로 하위 문서 링크를 추가했습니다.

 

 

결과

화면 및 컴포넌트 구조는 다음과 같습니다.

 

본격 시연 영상에서 하소연하기

 

더 다양한 시연은 README.md에서 확인할 수 있습니다. :)

 

 

 

회고

회고 방법론 중 KPT 회고 방식으로 마무리하려고 합니다.

Keep

집중하기 좋은 환경

프로젝트 기간에 집중하기 위해 코어 타임에 디스코드에서 팀원들과 캠을 켜고 진행했습니다. 매일 서로 응원하고 고민을 나눈 우리 팀원 분들께 감사하다는 말씀을 꼭 전하고 싶습니다. 🌱

프로젝트 일지 작성

여기에는 미처 모두 담지 못했지만 고민한 흔적을 계속 기록한 덕분에 진행 과정을 생생하게 남기고 앞으로 개선할 점을 잘 파악할 수 있게 된 것 같습니다. 예전에는 집중이 되지 않을 때 생각만 하다가 시간을 보낸 적이 많았는데 의식의 흐름을 글로 표현해보면서 이슈를 명확히 파악할 수 있었습니다. 

(부족하고 엉성하지만) 설계 후 구현

코드 레벨에서 고민을 많이 하고 설계하는 훈련을 한 지 얼마 되지 않아서 어색하고 부족했습니다. 하지만 UI 배치 정도만 생각하고 구현을 시작했던 프로젝트보다 구현 속도나 이해도가 훨씬 나아졌고 유지보수에 대한 의욕도 큰 것 같습니다. 

 

Problem

UI에 치중한 구현 과정

눈에 보이는 UI 구현에 집중하다 보니 기존에 세워둔 설계가 바뀌기도 하고, CSS 파일을 자주 수정하여 commit으로는 스타일 적용 과정을 추적하기 어려워졌습니다. 마크업과 스크립트를 잘 분리하여 코드의 가독성을 높이고 싶었는데 반복되는 코드가 많고 단번에 이해하기에는 어려운 것 같습니다. 또한, XSS 공격에 취약한 innerHTML 메서드 대신 innerText 등을 사용해보고 싶었는데 기한 내 구현하다 보니 미처 시도하지 못해 아쉬움이 남습니다. 

일관적이지 않은 타입 유효성 검사(Type Validation)

new 키워드 없이 객체를 생성할 경우 에러를 던지도록 메서드를 구현하여 일관되게 대처했으나, 데이터 타입에 대한 유효성 검사는 일관적으로 하지 않았습니다. 유효성 검사로 사용자의 입력에 대한 안정성을 확보하고 개발 과정에서 발생할 수 있는 오류를 방지할 수 있습니다. JSDoc이나 TypeScript가 대안이 될 수 있는데 리팩토링 때 JSDoc을 알차게 활용해보고 싶습니다.

 

Try

나에게 맞는 환경을 찾고 유지하기

이번 프로젝트 이후 팀이 바뀌는데 새로운 팀원분들께도 집중할 수 있는 환경을 제안해볼 수 있을 것 같습니다. 코어타임 동안 컨디션에 맞게 머무를 장소를 잘 선택하는 것도 중요할 것 같습니다. 프로젝트 일지도 꾸준히 꼼꼼하게 작성하겠습니다.

공통 컴포넌트와 메서드를 분리하고 잘 활용하기

코드 레벨의 설계 과정에서는 각 파일과 컴포넌트의 역할과 공통적으로 쓰이는 기능을 작은 단위로 파악할 수 있어야 합니다. 메서드나 객체로 묶어 코드의 가독성을 높이는 방향을 충분히 고민해보겠습니다. JSDoc과 다양한 메서드도 적절히 사용해 생산성을 기르고 싶습니다.

추가 기능 구현

Rich Editor(div와 contentEditable로 구현)와 다른 페이지 멘션 시 링크를 거는 기능 등 과제에서 제안하는 추가 기능까지 마저 구현해보면서 구조를 개선해보고 싶습니다. 확장성 있는 코드인지 스스로 점검해볼 수 있는 기회가 될 수 있을 것 같습니다.

 

 

 

 


 

 

마무리

첫 웹 개발 프로젝트에서 VanillaJS로 개발할 당시에는 컴포넌트화나 상태 관리에 대한 고민 없이 기능 구현에만 집중했었습니다. 아쉬움이 많이 남았었는데 데브코스에서 이벤트 위임과 디바운싱, 컴포넌트 방식으로 생각하기, 비동기 처리, localStorage, History API 등 이론과 실습을 거쳐 단기간에 구현해보면서 할 수 있다는 자신감이 생겼습니다. 비록 아직 부족한 점이 정말 많지만 장기 프로젝트처럼 차근차근 보완해볼 예정입니다! 

 

 

전체 소스 코드는 다음 Repository에서 확인할 수 있습니다.

 

GitHub - glassk/FEDC3-4_Project_Notion_VanillaJS: VanillaJS 노션 클로닝

VanillaJS 노션 클로닝. Contribute to glassk/FEDC3-4_Project_Notion_VanillaJS development by creating an account on GitHub.

github.com

 

 

 

글 읽어주셔서 감사합니다 :D