이번 포스팅에서는...
실무에서 Vite migration을 진행하면서 알아본 내용을 간략히 정리합니다.
개발팀에서는 CRA 기반 프로젝트를 vite로 마이그레이션하기로 결정했었는데요,
그 이유는 다음과 같았습니다.
프론트엔드 개발을 하게 되면 개발 서버를 실행하고, 소스코드를 갱신하면서 브라우저로 테스트를 하게 됩니다. 이 서버 실행 속도와 소스코드를 갱신하는 HMR의 속도가 느려지면 그만큼 작업 속도도 느려지고 개발 생산성이 저하됩니다.
CRA 기반인 만큼 webpack을 사용하는 기존 프로젝트는 규모가 커져 이미 이러한 단점을 갖고 있었고, 마이그레이션에 드는 리소스에 비해 향상시킬 수 있는 개발생산성의 이점이 크다고 판단했습니다.
그리고 추가적으로 다음과 같은 부분도 고려했습니다.
모노레포 툴들도 대부분 Next.js와 vite를 기반으로 가이드를 제공하고 있었습니다. 모노레포 툴 뿐 아니라 다른 모든 도구들도 리액트에서 공식적으로 CRA를 권장 및 지원하지 않는 만큼 이러한 추세를 따라가고 있었기 때문에 장기적인 관점에서도 좋은 선택이 되어줄 것이라 생각했습니다.
우선 vite가 무엇인지 부터 살펴보겠습니다.
“기존 쓰던 툴을 더 간결하고 사용하기 편하게 만들자”
Vite는 Vue.js를 개발한 에반 유가 Snowpack의 컨셉은 가져가되, 개선해서 만든 도구입니다.
Snowpack의 컨셉과 다른 번들러의 기능을 모은 프론트엔드 번들 도구라고 볼 수 있습니다.
esbuild와 브라우저 모듈을 이용한 개발모드, 개발 서버, 프록시 서버, 번들 툴, 코드 스플리팅, HMR… 등등 통합적인 기능을 지원합니다.
그 외에도 단순한 개발 세팅 경험을 제공하여 2021년 가장 만족도가 높은 번들툴로 선정되기도 했습니다.
기존 번들러들의 컨셉을 모두 흡수하면서도 간결한 사용방식과 안정성, 훌륭한 문서!
조금 더 알아볼까요?
vite를 이해하기 위해서는 기존 번들러 도구와, 번들링이 어떻게 발전해왔는지, vite는 어떻게 동작하는지를 알아야 합니다.
태초의 자바스크립트는 웹 애플리케이션을 만들기 위한 목적으로 만들어지지 않았기 때문에 파일을 여러 개로 분리해 개발할 수조차 없었습니다.
그 말은 즉, 대규모 프로젝트를 진행할 수 없다는 의미이기도 합니다. 하나의 파일에서 동시에 여러 개발자가 여러 기능을 위한 코드를 추가하며 깃으로 협업할 수는 없으니까요..!
node가 탄생하면서 서버에서 자바스크립트를 사용할 수 있게 되었죠. 이 떄 commonJS방식의 'module'이 만들어집니다.
또한 npm의 등장으로 만들어진 모듈을 모두가 공유할 수 있었고, 이 때부터 자바스크립트에서 본격적으로 모듈이 활용되기 시작합니다.
서버 프로그래밍은 코드를 미리 만들고, 파일에서 코드를 동적으로 불러오면 용량의 제한과 방식의 제한이 없습니다.
반면 브라우저는 js파일을 저장할 수 없고 불러와야 하므로 로딩/비동기 처리를 고려해야합니다.
여기서 발생하는 문제는, 스크립트는 순서대로 실행해야 하는데 비동기 처리된 로딩 순서가 보장되지 않는다는 것입니다.
CommonJS와 달리 비동기가 가능한 AMD라는 방식으로 브라우저에서 돌아가는 모듈을 만들어 이 한계를 보완하고 이에 따라 브라우저에서 실행할 수 있는 프로그램의 규모도 점차 커지게 되었습니다.
여전히 여러개의 파일을 비동기로 로드함에 있어 속도가 느리다던지 하는 이슈들이 존재했기 때문에 번들링의 개념이 등장했습니다.
개발할 때는 여러 파일로 나누어 개발하지만 로드할 때는 파일을 하나로 합쳐 전달할 수 있도록 한 것이죠.
그러나 이러한 번들러에도 여전히 단점은 있었으니... 빌드속도가 느리다는 것이었습니다.
기존 모듈방식의 문제점은 해결했지만 그로 인해 모든 파일을 하나로 만드는 단계가 추가되었기 때문입니다.
esbuild라는 기존 빌드 속도보다 100배나 빠른 도구가 탄생하게 됩니다!
esbuild는 기존 번들러와 달리 go언어로 작성되어 속도 측면에서 강점을 가집니다.
스크립트 기반의 느린 js가 아니라 native의 기능을 최대한 이용해 더 빠른 빌드 작성이 가능해진 것이죠.
그러나 esbuild가 압도적으로 속도가 빠름에도 웹팩을 쓰는것이 여전히 보편적이었습니다.
웹팩은 단순한 번들러라기 보다도 개발 통합툴에 가깝습니다. 빌드 + 트랜스파일링, 트리셰이킹, asset 지원, 코드 스플리팅 등 많은 것을 지원하니까요.
그에 비해 esbuild는 빌드 도구에 지나지 않는 측면이 있었습니다.
즉, 바닐라 자바스크립트 형태의 라이브러리 등만 esbuild를 사용해 개발이 가능했고, 프레임 워크를 기반으로 하는 웹 개발은 기본 번들러를 사용해야만 했습니다.
결국 최종 결과물을 만들기 위해서는 기존 툴 웹팩등이 필요했습니다.
빠른 빌드는 가능했으나 모든 기능을 하나로 만들어 통합하지는 못했다는 것이 esbuild의 한계였습니다.
그래서 Snowpack이 등장했습니다. Snowpack은
이 방식으로 HMR (Hot module replacement)기능을 강력하게 제공할 수 있게 되었는데, HMR이란 파일 수정 시 새로고침을 하지 않고 수정된 파일의 내용만 반영할 수 있는 기능을 말합니다.
그러나 등장한 이후 안정화되기까지는 다소 시간이 필요했고, 그 사이 치고 올라온 존재가 바로,
Webpack, Rollup, Parcel과 같은 도구를 사용해 번들링을 하면서 프론트엔드 개발자들의 개발 경험이 크게 향상되었습니다.
그러나 점점 높은 수준의 앱을 개발하기 시작하면서 처리하는 Javascript의 양이 기하급수적으로 증가하게 되었고, 대규모 프로젝트에서 수 천개의 모듈을 담게 되면서 Javascript기반 도구에 대한 성능에 대한 병목현상이 발생하게 됩니다. 개발 서버를 시작 하는데에도 많은 시간이 소요되었습니다.
콜드 스타트 방식으로 서버를 실행했을 때,
기존 번들러 기반 도구들은 애플리케이션 내 모든 소스 코드에 대해 빌드 작업을 마쳐야 페이지를 제공할 수 있었습니다.
콜드-스타트 방식: 서버가 최초 실행되어 이전에 캐싱한 데이터가 없는 경우
그러나 vite는 애플리케이션 모듈을 dependencies와 source code 두 가지 카테고리로 나누어 개발 서버 시간을 줄일 수 있습니다.
Vite는 번들을 생성하는 과정이 필요하지 않아 서버의 시작 속도가 매우 빠릅니다. 개발자 역시 번들 없이 모듈화된 컴포넌트의 수정사항을 브라우저로 확인 가능합니다.
ES build를 사용해 종속성을 미리 묶어 사전 번들링하기 때문입니다.
Es Build는 GO로 작성되었으며, Javascript 기반의 번들러보다 10~100배 더 빠른 속도로 종속성을 사전 번들링할 수 있습니다.
esbuild는 dependencies와 Source code를 분리해 사전 번들링을 합니다.
한번 빌드한 결과는 캐싱을 해두어 다음 개발 빌드에 바로 적용
‘번개’ 같은 HMR(Hot Module Replacement) 제공
기존 번들러 기반으로 개발을 진행할 때, 소스 코드를 업데이트하게 되면 번들링 과정을 다시 거쳐야 했습니다. 따라서 서비스가 커질수록 소스 코드 갱신 시간 또한 선형적으로 증가하는 단점이 있었죠. 모든 파일을 번들링하고 페이지에서 불러와야 하니까요.
이 대안으로 HMR(Hot Module Replacement)을 냈지만, 여전히 완벽한 대안이 되어주지는 못했습니다.
HMR(Hot Module Replacement): 브라우저를 새로고침하지 않아도 웹팩으로 빌드한 결과물이 웹 애플리케이션에 실시간으로 반영될 수 있게 도와주는 설정
어떤 모듈이 수정되면 vite는 수정된 모듈과 관련된 부분을 교체하고, 브라우저에서 해당 모듈을 요청했을 때 교체된 모듈을 전달합니다. 이 때 전 과정에서 ESM을 사용해 앱 사이즈가 커져도 HMR을 포함한 갱신 시간에는 영향을 끼치지 않습니다.
ESM (EcmaScript Modules)이란 ES6에서 도입되었으며, import/export를 사용하여 모듈을 동적으로 로드할 수 있는 모듈 시스템
Webpack과 같은 기존의 번들 기반 방식에서는 모든 소스코드가 빌드되어서 한번에 번들링된 형태로 서비스를 제공했다면, Native ESM 기반 방식의 Vite에서는 그럴 필요가 없습니다. 번들링이 필요가 없고 브라우저에서 필요한 모듈의 소스코드를 import할때 이것을 전달만 하면 되기 때문입니다.
이것은 결국 현대 대부분의 브라우저에서 ESM일 지원하기에 가능한 것인데요, ESM이 나오기 전에는 자바스크립트 언어레벨에서 지원하는 모듈시스템이 없었기 때문에 번들링이 필요했던 것이고, 지금은 자바스크립트 언어레벨에서 모듈시스템이 들어가 있고 거의 모든 브라우저에서 이것을 지원을 하기 때문에 Vite에서는 ESM을 기반으로 만들 수 있게 된 것이죠.
esbuild를 이용한 사전번들링과 Native ESM 기반의 Dev Server로 인해 서버 구동속도와 HMR 속도를 증가시켰다고 볼 수 있습니다.
Cache-Control 캐시제어 헤더를 사용해 캐싱해서 요청 횟수를 최소화해 페이지 로딩 속도를 향상 시키는 것이죠.
소스코드는304 Not Modified
로, 디펜던시는 Cache-Control: max-age=31536000, immutable
을 이용해 캐시합니다.
확실히 소스코드 갱신 시간이 약 5배 정도 빨라진 것을 확인할 수 있었습니다.
이에 비해 마이그레이션을 위해 필요한 작업은 생각보다 복잡하지 않았습니다.
물론 고려해야 할 것들은 있습니다. 예를 들어, 설정 이후 실행하려 하면 환경변수 관련 에러가 발생하는 것을 볼 수 있습니다. process.env로 사용하던 환경변수에서 process를 찾을 수 없다고 뜨는데요, import.env.VITE_*
와 같은 형태로 환경변수를 사용해야 하기 때문에 유의해야 합니다. 다만 모든 환경변수들은 한 곳에서 상수로 관리하고 있었기 때문에 변경하는 데에 별다른 문제는 없었습니다.
다만, 전체적인 변경사항과 설정이 있는 만큼 신중해야 했던 것도 사실입니다.
이를 위해 팀원들과 충분히 함께 스터디하고, 테스트용
vite는 배포 시 번들링 과정이 필요한데, 이 번들링 단계에서는 Esbuild를 사용하지 않습니다.
생태계 및 안정성 등을 고려해 RollUp을 번들러로 사용하고 있고, 추후 개발과 빌드 모두 esbuild를 사용할 수 있도록 하는 것을 고려 중입니다.
프로덕션에서 번들되지 않은 ESM을 가져오면 import가 중첩되어 추가 네트워크 통신으로 인해 비효율성이 발생한다고 합니다. 개발 서버와 프로덕션 빌드 간 최적의 출력과 동작 일관성을 보장하는 것은 쉽지 않기 때문에 vite는 미리 설정된 빌드 커맨드를 이용하고 빌드 퍼포먼스 최적화를 진행합니다.
추후에 개발과 빌드 모두 esbuild를 사용할 수 있도록 하는 것을 계획하고 있는 것으로 보이네요!
esbuild가 등장한 이후 Native방식의 빌드 도구들의 필요성이 부각되고, Nextjs는 Rust를 이용한 swc컴파일러를 채택하는 등의 시도도 생겨나고 있는 것 같습니다. 앞으로의 번들러/빌드 도구에 대한 방향성도 꾸준히 지켜보면 좋을 것 같습니다!