개발 회고록 - 켈시어 & 켈시어 웹 인터프리터

베이시스·2022년 6월 23일
0

이야기

목록 보기
2/2

🔗 젤다.. 아니 링크

켈시어 Github
켈시어 웹 인터프리터 Github Web

🌁 배경

여느 토이 프로젝트가 그렇듯 처음부터 거창한 목표를 갖고 시작한 건 아니었다. 한 게임에서 등장하는 인물의 말투가 너무 웃겨서, 최근에 본 엄랭의 구조에서 착안해 그 인물의 어투를 모사한 언어를 만들겠다고 마음먹었다.


개발이라... 그런건가. 내 말투를 말이지.

❓ 켈시어 구현체 개발

개발 자체는 노베이스가 아니었던만큼 신속하게 진행됐다. 이렇게 빨라도 괜찮은거야? 라는 말이 나올 만큼. 마침 Node.js를 가장 많이 다루어 보기도 했고.

문자열을 정규표현식으로 처리하는 과정에서 한 번만 찾는 것이 아닌 전역 검색하는 방법을 습득한 건 덤이다.

  • g 플래그는 문자열 전역에서(한번 찾는다고 중단하지 않음)
  • i 플래그는 대소문자 구분 없이
export const checkOnlyDots = (statement) => 
	statement.replace(/\./gi, "").length > 0;

다만 한 가지 방법만으로는 원하는 형태로 문자열을 가공하기 힘들어 여러 함수를 순차적으로 돌렸는데, 덕분에 처리된 문자열이 올바른 형태인지 검증하는 로직이 덕지덕지 붙어 가독성을 떨어트렸다.

타입 체크만 좀 잘 되었어도... 하는 아쉬움이 남는다.

하지만 우리에게는 또 다른 문제가 남아 있었으니, 바로 배포였다.

배포

npm에 프로젝트를 게시해 보기는 처음이었다. 어쩌저찌 구글링 해가며 배포를 위한 준비를 다 마치고 npm link 로 로컬에서 실행해 보는데 자꾸 모듈이 없다고 한다.

분명 프로젝트 경로에 잘 들어있고 import/export 구문도 문제없는데 말이다.
이 문제는 정말 황당하고도 어이없게 해결되었는데, package.json의 files에 모듈이 있던 경로를 지정하지 않아서 발생한 문제였다.

{
  (...)
  "files": [
    "cli",
    "dist"
  ],
  (...)
}

이렇게 경로를 지정하고 나니 언제 그랬냐는 듯이 실행이 잘 되었고, 역사적인(?) 첫 npm 패키지 배포를 할 수 있게 되었다.

❗️ 켈시어 웹 인터프리터 개발

특정 인물의 어투를 모티브로 한 만큼 대화창 형태로 UI를 구성했다.

반응형 UI는 미디어 쿼리와 grid 레이아웃으로 금방 구현했지만 문제는 대화창이었다.

코드를 입력한 뒤 결과가 대화식으로 보여지는 만큼 스크롤을 맨 아래로 내려줄 필요가 있었는데, 분명 useRef로 DOM을 지정해준 뒤 해당 위치로 스크롤을 내렸는데도 어중간하게 내려오는 문제가 생겼다.

state가 변경되면 DOM 요소가 리렌더링되는데, 리렌더링이 완전히 되지 않은 상태에서 스크롤을 내렸기에 렌더링이 완료되지 않은 시점의 끝까지 스크롤링이 된 것이라는 추측을 할 수 있었다.

그래서 리렌더링이 끝난 뒤에 스크롤링이 이루어질 수 있도록 약간의 sleep을 주었다.

{
  (...)
  setLog(...);
  await sleep(100);
  conversationLog.current.scrollTop = conversationLog.current.scrollHeight;
}

React 차원에서 렌더링이 끝났는지 알 수 있는 방법이 분명 있을 것 같았지만 빠른 구현이 우선이었기에 이와 같은 임시방편을 사용했다. 언젠가는 고쳐야 할 기술 부채인 셈이다.

+ 추가

위에서 적용한 sleep 핵을 간단하게 해결했다.
렌더링이 끝난 후에 스크롤을 해야 한다는 점에 착안, useEffect() Hook을 사용하였다.

React의 생명주기 메소드에 대한 이해가 부족해 잘못된 코드를 작성했던 것.

// Scroll on re-render
useEffect(() => {
  conversationLog.current.scrollTop =
    conversationLog.current.scrollHeight;
}, [log]);

더 높은 Lighthouse 점수를 향해서

크롬 브라우저에 내장된 Lighthouse는 개발자 도구에 내장된 퍼포먼스 측정 도구로, 여기서 측정된 점수는 사용자 경험(UX)을 판단하는 지표가 된다.
점수를 올려보고자 아래와 같은 방법을 적용했다.

  1. 네트워크 부하가 큰 웹 폰트에 대해 font-display: swap 적용

    폰트의 용량이 클 경우 font-display 속성을 기본값으로 두면 로딩이 완료될 때까지 해당 폰트를 사용하는 엘리먼트의 글자가 보이지 않게 된다. (정확히는 브라우저마다 기본값이 다르며, Chrome, Edge 기준이다.) 이 때 swap 옵션을 적용하면 로딩 전에는 현재 사용할 수 있는 내장 폰트를 사용하다가 로딩이 완료되면 해당 폰트로 바꾸게 된다.
    로딩 전까진 필자가 기대한 레이아웃이 아니고 로딩 완료 시 Layout shift가 조금 생길 수는 있으나 사용자가 페이지와 더 일찍 상호작용할 수 있게 된다는 장점이 더 크다고 보았다.

  2. 사용하지 않는 모듈 삭제 및 직접 구현

    • 마크다운 구현
      초기 버전에서는 사용 설명의 마크다운을 표시하기 위해 @uiw/react-markdown-preview 라이브러리를 사용했다. 마크다운을 필자의 의도와 동일하게 보여주었지만 패키지가 꽤나 무거웠다.
      그래서 해당 패키지는 보내주고 필요한 마크다운 요소만 직접 styled-components 로 구현하였다. 아래 코드는 구현한 마크다운 요소의 일부이다.

    • react-icons@react-icons/all-files 으로 대체
      분명히 번들 사이즈를 줄일 만큼 줄였다고 생각했는데 이상할 정도로 번들 크기가 컸다. 최적화 전 번들 크기는 1.36MB로 규모에 비해 말도 안 되게 큰 수치다.
      번들 사이즈를 어떻게 줄일 수 있을지 고민하던 중, react-icons 가 사용하지 않는 아이콘까지 아이콘 그룹 전체를 import한다는 점에 착안해 필요한 것만 하나씩 import하는 방법을 찾았고 @react-icons/all-files 으로 모든 아이콘을 대체했다.

위 최적화 방법을 모두 적용하고 난 뒤 번들 크기는 370KB로 여전히 작지는 않지만 납득할 수 있는 수치로 내려왔다.

마지막으로 호스팅 중인 현 시점의 Lighthouse 점수를 공개한다.

간단한 웹 앱이긴 하지만 프론트엔드 개발을 시작한 후로 이런 점수는 처음 받아본다. 조금은 감격스럽다.

📚 마치며

다른 사이드 프로젝트를 진행하던 중 갑자기 꽂혀서 진행하게 된 토이 프로젝트였는데 흥미가 확 당겨 시작한 프로젝트였던 만큼 즐겁게 개발할 수 있었다.

한편 장기적으로는 이 웹 앱을 PWA로 구현할 생각이 있고 관련 리소스도 모두 준비해 두었지만 Service worker 구현에 애로사항이 꽃피어 다음 구현 목표로 조정했다. 진행하던 사이드 프로젝트가 마무리되는 대로 PWA 구현에 집중하지 않을까 싶다.

어제보다 더 나은 프론트엔드 개발자가 되기 위해 오늘도 달린다.

profile
사진찍는 주니어 프론트엔드 개발자

2개의 댓글

comment-user-thumbnail
2022년 7월 7일

잘 봤습니다 !

1개의 답글