번들 사이즈의 25%를 차지하는 highlight.js 개선하기

Youngjin
Dong-gle
Published in
6 min readOct 18, 2023

해당 글은 우아한테크코스 5기 프로젝트 “동글”의 번들 사이즈를 최적화하면서 겪은 문제와 그 해결과정을 담은 글입니다.

1. highlight.js란?

프로젝트 동글은 글에 존재하는 코드블럭을 이쁘게 스타일링 해주어야 했다.

highlight.js 라이브러리를 통해 그 목적을 달성할 수 있다.

2. 개선 이유

동글의 production 번들의 패키지를 bundleAnalyzerPlugin로 분석한 결과 highlight.js가 번들 사이즈의 약 25%(286KB/1180KB)를 차지하고 있었다.

동글은 개발자가 타겟 유저층이므로 코드블럭을 꾸미는 것은 필수 기능에 해당한다.

하지만 너무 큰 사이즈로 인해 로딩이 길어져 사용자 경험에 좋지 않다.

3. 문제 상황 인식 및 해결과정

문제 상황

문제는 highlight.js를 사용하는 방식에 있다.

현재 highlight.js의 모든 코드를 한번에 불러오기 때문에,

유저는 gzip 기준 약 286KB에 달하는 리소스를 고스란히 요청하고있다(!)

node_modules에 있는 highlight.js 폴더에 들어가면 그 이유를 알 수 있다.

index.js에서 모든 언어를 등록해서 export 하고 있기 때문이다.

따라서 code spliting을 통해 필요한 언어에 대한 코드(js파일)만 들고오면 될 것이다.

요청하는 글에 있는 코드블럭의 언어만을 들고와야 하는데,

번들 과정에서는 요청하는 글이 어떤 코드블럭 언어를 이용하는지 알 수가 없기 때문에

글을 요청하는 런타임에서 동적 import를 이용해 필요한 언어에 해당하는 코드만 가져와보자.

해결 과정1: highlight.js에 동적 import 도입

highlight.js의 core만 들고온 뒤

필요한 언어만 동적으로 등록해주었다.

하지만 런타임에선 파일을 찾을 수 없다는 에러가 터졌다.

node_modules에 들어가서 highlight.js의 package.json을 까보니 exports에는 딱히 문제가 없는 것 같았다.

그래서 원인 파악을 계속 하다가..

webpack exports의 버그인 것 같았다. (관련 PR)

highlight.js와 webpack 레포지토리 둘 다 관련 이슈들이 많이 올라왔지만, 딱히 해결한 사람은 없었다.

따라서 대체 라이브러리로 `prismjs`를 찾아내었다.

해결 과정2: `prismjs` 도입

npmtrends에서 `prismjs`를 발견했다.

notion이 쓰고있고, 이전에 개발 블로그를 만들었을 때 도입한 기억이 있다.

`prismjs`는 `highlight.js`에 비해 매우 가벼운 번들 사이즈를 자랑한다.

기본적으로 `prismjs`는 필요한 언어의 모듈을 일일히 가져와서 하이라이팅 하도록 설계되었다.

각 언어 모듈은 즉시실행함수로 되어있었고, 동적 import를 통해 모듈을 가져올 수 있다.

해결 과정3: html string 파싱과 동적 import

동글의 글은 서버에서 string형식의 html 코드를 받아와서 바로 렌더링하고 있다.

각 코드 블럭은 `<code className=”language-javascript”>` 와 같은 형태로 감싸져있기 때문에,

`language-*`에서 *부분을 가져와야했다.

따라서 regex에 맞는 언어들을 뽑아낸 다음 해당 언어에 해당하는 prism 모듈을 동적 import 하였다.

4. 결과

요청 리소스 최적화

highlight.js (변경 전)

prism.js + dynamic import (변경 후)

요청 리소스가 2.0MB -> 1.6MB 로 약 400KB 감소하여 약 20%의 리소스를 절약하였다!

스타일 변경

highlight.js와 prismjs

코드블럭 스타일의 변경도 있다.

참고로 오른쪽 `prismjs`는 노션과 같은 스타일이다.

동글은 노션 연동을 제공하기 때문에, 노션을 익숙한 유저들의 사용자 경험을 그대로 이어준다는 부분에서도 의미가 있다!

5. 최적화 후기

간단할 줄 알았지만,, 생각보다 삽질을 많이했다.

사실 위에서 나열한 해결과정 1, 2, 3은 시간순서가 아니다.

실은 2, 3, 1 순으로 해결했고, highlight.js가 code spliting을 제공하지 않는다고 생각해서 `prismjs`를 바로 도입했었다.

즉 prismjs로 최적화 후 highlight.js를 이용하면서도 동적 import를 도입할 수 있을 것 같아 라이브러리를 까보면서 시도해본 흔적이다🙂

물론 webpack 설정 exports 필드의 버그때문에 목적달성은 못했지만, 단순 docs나 블로그들에 적혀있는 사용방법 외에 주도적으로 라이브러리 이용방법을 구상해봤다는 점에서 한단계 성장했다고 생각한다😎

--

--