Vite
비트바이트 아니다는 간결한 모던 웹 프로젝트 개발 경험에 초점을 맞춰 탄생한 빌드 도구이다. 프랑스어로 "빠르다"를 의미하는데, 이름대로 Vite를 처음 실행해보면 CRA와 비교했을 때 엄청 빠른 것을 확인할 수 있다. 왜이렇게 vite의 실행속도가 빠른지 궁금하여 찾아본 내용을 정리해보았다.
번들링
개발을 하다보면, 컴포넌트를 생성하고 함수를 분리하는 등 기능 단위로 파일들을 쪼개며 모듈화하게 된다. 그런데, 브라우저에서 ESM(ES Module)을 지원하기 전(CommonJS, AMD, UMD..)까지는 JavaScript 모듈화를 네이티브 레벨에서 진행할 수가 없었다. JavaScript 프로그램을 필요에 따라 가져올 수 있는, 별도의 모듈로 분할하는 매커니즘을 브라우저가 지원하지 않았다는 뜻이다. 그래서 소스 모듈을 브라우저에서 실행할 수 있는 파일로 크롤링, 처리 및 연결하는 "번들링(Bundling)"이라는 해결 방법을 사용해야 했다.
기존의 문제점
개발 서버의 시작 시간
애플리케이션이 발달함에 따라, JavaScript 모듈의 개수가 증가하게 됐다. 심하면 수천 개까지! 이에 개발 서버를 가동하는 데 오랜 시간을 기다려야 했는데, 번들러 기반의 도구의 경우 애플리케이션 내 모든 소스코드에 대해 크롤링 및 빌드 작업을 마쳐야지만 실제 페이지를 제공할 수 있었기 때문이다. 또한 HMR*을 사용하더라도, 변경된 파일이 적용될 때까지 수 초 이상 소요됐다. (HMR* - Hot Module Replacement. 모든 종류의 모듈을 새로고침 하지 않고 수정된 파일(module)만 교체(replace)하는 것)
Vite는 어떻게 개발 서버 시작 시간을 개선했는가
vite는 애플리케이션의 모듈을 dependencies와 source code 두 가지 카테고리로 나누어 개발 서버 시작 시간을 개선했다.
dependencies
개발 시 그 내용이 바뀌지 않을 일반적인 JavaScript 소스 코드이다. 기존 번들러로는 컴포넌트 라이브러리와 같이 몇 백 개의 JavaScript 모듈을 갖고 있는 매우 큰 디펜던시에 대한 번들링 과정이 매우 비효율적이었고 많은 시간을 필요로 했다.
Vite는 개발 시 내용이 바뀌지 않을 코드들을, 사전번들링을 이용하여 시간을 단축했다. 사전 번들링에는 Esbuild를 사용했는데, 이 번들러 도구는 Go언어로 작성되었기 때문에 JavaScript로 작성된 기존 번들러(Webpack, Parcel) 대비 10-100배 빠른 속도를 제공할 수 있다.
Esbuild 공식홈페이지에 따르면, esbuild가 JavaScript 파일을 파싱하는 시간에, node는 번들러의 JavaScript를 파싱해야하기 때문이라고 한다(While esbuild is busy parsing your JavaScript, node is busy parsing your bundler's JavaScript. By the time node has finished parsing your bundler's code, esbuild might have already exited and your bundler hasn't even started bundling yet). 그밖에 병렬적 처리, 메모리의 효율적 사용 등등이 있다.
source code
컴파일링이 필요하고, 수정이 매우 잦은 TS, JSX 등의 Non-plain JavaScript는 어떻게 처할까? Native ESM을 이용하여, 브라우저가 직접 import문을 해석하고 요청하는 모듈에 대해서만 소스 코드를 변환하고 제공한다. 이는 전체 애플리케이션을 미리 번들링하는 것보다 훨씬 빠르다. Vite의 이러한 접근 방식은 "No-Bundle", "Unbundled" 라고도 불린다.
기존의 번들러 기반으로 개발을 하면, 소스 코드를 업데이트할 때마다 번들링 과정을 다시 거쳐야했다. 물론 일부 번들러는 메모리에서 작업을 수행하여 실제로 갱신에 영향을 받는 파일들만 새롭게 번들링하도록 했지만, 콜드 스타트*시에는 결국 모든 파일에 대한 번들링을 수행해야 했다. (콜드 스타트* - 최초르 실행되어 이전에 캐싱한 데이터가 없는 경우)
vite는 ESM을 이용하여 어떤 모듈이 수정되면 그저 수정된 모듈과 관련된 부분만을 교체하고, 브라우저에서 해당 모듈을 요청하면 교체된 모듈을 전달한다. 전 과정에서 완벽하게 ESM을 사용한다.
배포 시 번들링 과정이 필요한 이유
source code는 번들링이 아닌, Native ESM 방식을 이용하여 개발 속도를 빠르게 하였다. 하지만 배포 시에는 번들링을 해야한다...!
프로덕션에서 번들되지 않은 ESM을 가져오는 것은 중첩 import 문제가 발생하게 되는데, 각 import는 새로운 네트워크 요청을 발생시킨다. 그런데 import 체인이 깊이 중첩돼 있다면, 여러 번의 순차적인 네트워크 요청을 야기하여 로딩 시간을 크게 증가시킬 수 있다. 예를 들어 A.js 가 B.js를 import하고, B.js가 C.js를 import한다면, 브라우저는 A, B, C 모듈을 순차적으로 로드해야 한다.
따라서 프로덕션 환경에서도 최적의 로딩 성능을 얻으려면 코드 스플리팅, 지연 로딩 및 트리 쉐이킹 등을 이용해 번들링 하는 것이 좋다. vite에서는 번들 도구로 Rollup을 이용한다.