최근 들어 특히 프론트엔드 개발자들 사이에서는 Vite가 매우 인기를 얻고 있다. 오죽하면 지인 개발자 분들은 Vite 신봉자라고 이들을 표현하기도 한다. 여담으로 Vite를 바이트라고 부르는 분들이 많은데, 실제 발음은 비트이다. (프랑스어 배운게 이럴 때 도움이 된다..) 하여튼간 이는 단순한 소문이 아니다.
State of JS 2024 통계 를 보면, vite가 현재 Next.js보다 많이 사용되면서 React보다 긍정적인 평가를 받고 있음을 볼 수 있다. (물론 해당 통계를 분석한 아티클 내에도 부메랑 효과를 이야기하며 신생 라이브러리는 비교적 타 라이브러리보다 초반엔 긍정적이고 많이 사용하는 경향이 있음을 명시한다.)
Next.js보다 많이 쓴다는 것은 솔직히 본인도 적잖은 충격이었는데, 어떻게 이렇게 단시간 안에 Vite가 성장하게 되었는지 지금부터 알아보도록 하자.
(잠깐, 위에서 React보다 긍정적이라는 것은 성능이 아니라 순전히 인기도 측면에서 React 만큼이나 긍정적인 평가를 받고 있다고 보는 것이 옳을 것이다. React는 javascript library이고, Vite는 빌드 툴이다.)
번들러의 출현
WEB1 시대의 자바스크립트는 기본적인 DOM 조작이나 이벤트 처리 등을 위해 사용되었으나, WEB2 시대로 넘어오며 인터넷이라는 플랫폼을 기반으로 사용자와 공급자 간의 상호작용 요소가 많아지고 비즈니스 모델을 구현하기 위해 더욱 복잡하고 많은 로직을 구현해야 했다.
그렇다보니 코드가 길어지면서 유지보수가 힘들어졌으며, 스크립트를 분리한다고 하더라도 변수명이 겹치는 등의 문제가 발생하며 개발자 경험을 극도로 저하시켰다. 또한 ECMA Script 표준이 정립되고 버전업이 되면서, 최신 ECMAScript 문법이 반영되지 않은 구형 브라우져에서는 동작하지 않아 브라우저 간의 호환성까지 신경써야 하는 사태가 발생했다.
이때 번들러라는 개념이 등장한다. bundle을 해주는 도구라는 뜻으로, 이때 bundle의 사전적 정의는 묶음이다. 즉 따로 노는 HTML, CSS, JS 파일들이 어디서나 잘 작동할 수 있도록 묶어주는 도구라고 생각하면 편하겠다.
목적과 용도에 맞게 여러개로 쪼개져 있는 파일들을 묶어서 HTTP Request 횟수를 줄이는 것은 물론 각 파일의 변수들이 겹치지 않도록 스코프를 유지하면서 IIFE 등의 함수로 충돌을 방지한다. 또한 트랜스컴파일링을 통해 최신 ECMAScript 문법이 다양한 브라우져에서 동작할 수 있도록 한다. 그리고 사용하지 않는 코드를 제거하는 트리 셰이킹 과정부터 코드 스플리팅 등의 최적화 과정까지 수행해주는 것으로 발전하게 되었다. 더 나아가 개발자들의 생산성을 높이기 위해 개발 서버에서 수정된 파일을 감지하고 변경된 부분만 반영하는 HMR(Hot Module Replacement) 기능까지 추가되었다.
개발 서버에 있어서의 번들러의 한계
번들러는 위 기능들을 통해 개발자들에게 무한한 편의를 제공했다. 번들링한 파일들은 배포 서버에서 의도된 기능들을 잘 수행해냈다. 하지만 서비스의 규모가 커지며 수천 개의 모듈이 만들어지면서 다시 한번 문제점이 도래했다.
첫째로 개발 서버 하나를 켜기 위해 번들링하는 과정에서 너무 오랜 시간이 걸리는 것이다. 콜드 스타트처럼 서버를 킬 때마다 번들링 과정을 처음부터 다시 해야 하는 경우가 보통이기 때문이다.
또한 HMR을 이용해 state를 유지한 상태에서 업데이트를 해준다고 하더라도, 변경된 부분을 다시 모두 번들링한 후 전달하기 때문에 선형적으로 시간이 늘어났다.
Vite의 등장
Vite는 이러한 문제점들을 해결하기 위해 등장했다. 어떻게 보면 당연하다고 느껴질 수 있는 부분이지만 Vite가 그걸 해낸 것이다.
Vite는 크게 모듈을 dependencies와 Source code로 범주를 나누는데, 이때 dependencies는 개발 시 내용이 잘 바뀌지 않는 plain한 js 파일이며 라이브러리를 떠올리면 될 것이고, source code는 개발 시 내용이 자주 바뀌는 파일이며 우리가 밥먹듯 수정하는 jsx 파일을 생각하면 된다.
이때 dependencies의 번들은 Go 언어로 작성된 Esbuild 기반의 도구를 통해 사전 번들링, 즉 빌드 타임에 미리 번들링을 해둔다. 보통 개발하는 폴더만 보아도 node_modules에 해당하는 그런 npm 모듈의 크기가 가장 큰 것을 알 수 있는데, 서버를 킬 때 이 모든 내용들을 매번 다시 번들링한다면 수많은 시간이 들 것이다. 따라서 이런 정적인 모듈들은 추가, 삭제, 변경되지 않는 이상 미리 번들링하여 개발 서버 시작 시간을 단축한다. 물론 사전 번들링을 하는 시간 조차도 low-level 언어인 Go로 작성되어 기존 Webpack이나 Parcel보다 10-100배 정도 빠르다.
그리고 Source Code에 해당하는 코드는 ESM을 이용해 브라우져가 이를 로드하고 교체하도록 의도한다. Webpack과 같은 전통적인 번들러의 경우 CJS를 사용하기 때문에 수정된 모듈 하나를 교체하는데 있어 전체 모듈을 다시 번들하는 불상사가 벌어지는데, ESM은 수정된 모듈 하나만 번들링하여 교체하기 때문에 이런 문제가 발생하지 않는다. 즉 HMR에 걸리는 시간에 영향을 거의 끼치지 않는다는 의미이다.
뿐만 아니라 dependencies의 번들된 결과물에 대해서는 Cache-Control: max-age=31536000,immutable
, source code 중 바뀌지 않는 파일에 대해서는 304 Not Modified
를 통해 캐싱을 이용하여 번들링 시간을 단축한다. 그렇게 되면 HTTP Request 횟수가 기하급수적으로 줄어들 것이다.
그러면 Webpack은 절대 쓰면 안되겠네?
사실 위에서 말한 기능들 중 대부분은 Webpack에서 복잡한 설정과 가이드라인을 통해 어느정도 달성할 수 있다. 초기 설정이 되어 있지 않을 뿐 추가가 가능하기 때문이다. 하지만 Vite는 이 과정이 모두 자동화되어 있으며, 특히 개발 서버에서의 Esbuild와 ESM을 통한 효율은 개발자의 DX(Developer Experience)를 크게 향상시켜줄 것이다. 또한 Next.js처럼 SSR 환경을 공식적으로 지원하고 있기 때문에 더욱 확장성이 높다고도 판단할 수 있겠다.
아직 ESbuild의 안정성 이슈로 배포 서버에서는 Rollup만을 활용하고 있으나, 추후 Rust 기반의 Rolldown으로 성능도 함께 잡을 수 있다면 더할 나위 없는 빌드 툴이 되지 않을까 싶다.