본문 바로가기

DevOps

[Bundler] The meaning of migration to Vite

반응형
목차
1️⃣ Module Bundler의 이야기
2️⃣ Vite의 등장
3️⃣ Vite으로의 마이그레이션 과정
4️⃣ 글을 줄이며

 

CRA(Create React App)는 Webpack 기반의 React가 제공하는 명령어이다. (사실 이제는 공식문서에 모습을 보이지 않는다.)

이를 이용해 구축한 🔗 프로젝트에서 팀원과의 개발 속도가 늦어짐을 느꼈고,

고민 끝에 그 이유를 번들러의 긴 빌드시간이라고 생각하였다.

 

결국 Vite으로의 마이그레이션을 결정하였고, 그 과정을 기록해보고자 한다.

 


1️⃣ Module Bundler의 이야기

번들러를 알기 위해 옛날 JavaScript 이야기를 정리하고자 한다.

 

태초에 JavaScript는 module 개념이 없었다.

웹에서 간단한 동작만 하기 위해 만들어졌기에, 파일을 분리할 필요성이 없었던 것이다.

 

이를 해결하기 위해 (브라우저 외부에서 JS를 실행시키는 V8 엔진을 활용한) 동기적으로 모듈을 호출하는 CommonJS 방식의 모듈방식이 만들어지고, node가 구현된다. 이를 통해 npm도 등장하며 JS의 module을 동작시킬 수 있게 된다.

하지만 브라우저에 있어서는, 비동기 로드가 문제점이 되었다.

 

이후에, 브라우저에서 모듈 방식을 동작시키기 위한 Browserify나,

비동기를 위한 AMD(Asynchronous Module Definition) & Require.js 방식,

그리고 CommoJS와 AMD 방식을 모두 호환하는 UMD(Universal Module Definition) 패턴이 생겨난다.

그리고 JS 언어 자체에서 모듈 시스템을 지원하기 위한 ES6(ES Module)이 명세되었는데, 이는 실제 객체/함수를 바인딩하여 순환 참조 관리가 편하고, 정적 분석이 가능하여 트리 쉐이킹이 쉽게 가능해졌다. (ES6 모듈 시스템은 "import"와 "export" 구문을 사용하여 모듈 간의 의존성을 명시적으로 선언하는 것이다. 번들러가 모듈을 분석할 때, 모듈 파일을 정적으로 해석하며 의존성을 파악할 수 있음을 의미한다. 이를 통해 사용되지 않는 코드를 식별하고 제거할 수 있다.)

이에 그치지 않고, 구형 브라우저의 호환을 위한 트랜스파일러 Babel, JS의 슈퍼셋 컴파일 언어 TypeScript 등이 모습을 보이기도 한다.

 

여러개 파일을 비동기로 로딩을 하는게 문제라면, 그냥 처음부터 파일을 하나로 합쳐서  전달하는 건 어떨까?

 

이로부터 "프론트엔드의 번들러" 개념이 탄생한다. 우리가 잘 아는 대표 3인방을 알아보자.

 

1. Webpack

단일 JS 파일로의 번들링 이외에, 서브파티 라이브러리 관리나, CSS나 이미지 같은 asset들을 JS로 변환하고 이를 분석하는 등 여러 최적화 작업을 수행한다.

이뿐만 아니라 개발환경에서 Live reloading(변경 시 자동 새로고침) / 새로고침 없이 런타임에 모듈을 업데이트 해주는 HMR(Hot Module Replacement) 등의 기능을 제공한다.

 

뛰어난 안정성 이면에,

트리 쉐이킹이 지원되기는 하지만 CommonJS 방식으로 모듈을 로드한 부분을 ES6 문법으로 교체해야 하고, UglifyJSPlugin, Terser같은 별도의 플러그인을 추가하는 등의 작업을 요해서 번거롭다거나,

코드 스플리팅에 있어서 다른 번들러보다 느린 속도를 보이는 등 단점들이 대두되고 있다.

2. Rollup

롤업은 기본적으로 ES6 모듈을 기본으로 따른다. 이로 인해 코드 스플리팅에 뛰어나다.

특히 entry point가 여러 개 있을 때, 중복해서 번들될 수 있는 부분을 알아내고, 공통되는 독립된 모듈로 분리해내는 데에 강점을 보인다.

3. Parcel

파셀의 가장 큰 장점은 Zero config. 즉, 별도의 설정 파일 없이 빌드 명령어를 통해 바로 사용이 가능하다. 이는 웹팩과 달리(entry point를 지정하지 않고), HTML 파일 자체를 읽으며 JS, CSS 이미지 에셋 등을 직접 참조하기 때문이다.

이 외에도 ES6 및 CommonJS 모듈 모두에 대해 트리 쉐이킹을 지원하고, 모든 모듈에서 Babel을 사용하여 최신 JS를 브라우저에서 지원하는 형식으로 컴파일한다.

 

web.dev의 tooling.report

 

2️⃣ Vite의 등장

프로젝트가 커질수록 번들러들의 빌드속도는 점점 느려져갔고,

특히 수정된 내용을 확인하기 위해서 계속 모든 파일들을 Bundle 해야했기에 DX가 현저하게 떨어진다.

이 때, esbuild가 등장한다.

 

- esbuild

기존 번들러와 달리, JS가 아닌 go언어로 작성이 되어 native의 기능을 최대한 이용해 100배 빠른 빌드 속도를 자랑한다.

하지만 이는 웹팩과 달리 “번들러의 역할”만 하였기에, 다른 기능들의 제공에는 미약한 모습을 보이는 감도 없지않아 있었다.

 

이를 적극 활용하기 위해 esbuild로 빌드하여 브라우저의 표준방식으로 개발하고, production은 기존 번들 툴로 만들어내는 “Snowpack”이 등장한다.

이는 파일을 하나 수정할 때마다 전체를 빌드해서 결과를 만들어내는 방식이 아닌, 각각의 모듈을 별도로 빌드를 하고, 수정이 발생하면 해당 파일만 다시 빌드를 하여 업데이트한다는 개념이다.

 

Snowpack의 빠른 수정반영

- Vite

Vite

 

Snowpack의 컨셉에 착안하여 Vite이 탄생한다.

Vite은 esbuild와 브라우저 모듈을 이용한 개발모드, 개발서버, 프록시 서버(→ SSR), 코드 스플리팅 등 지금까지 나왔던 Snowpack의 컨셉과 다른 번들 툴에서 제공하는 기능을 한데 모은 번들 도구가 된다.

 

Vite은 개발 환경에서 번들러가 아닌 Native ESM을 이용해 HMR을 지원한다. 즉, 브라우저가 곧 번들러가 되어 특정 모듈에 대한 소스 코드를 요청하면 Vite은 이를 전달할 뿐이다. 수정된 모듈과 관련된 부분만을 교체하기 때문에 앱 사이즈가 커져도 HMR을 포함한 갱신 시간에는 영향을 끼치지 않는다.

그리고 (Snopack과 같이) production에서는 중첩된 import로 인한 추가 네트워크 통신으로 인해, esbuild를 사용하는 것은 비효율적이기에, 기존 번들러 툴인 Rollup을 이용하여 번들링 하여 가져온다. Rollup의 유연한 플로그인 API 및 인프라에 의존하는 모습을 보인다.

리액트 서버 컴포넌트(RSC)와 같은 SSR 지원에는 약하다는 단점도 있지만 스타터로써 큰 힘을 발휘할 수 있다.

결국 Vite은 간결한 사용방식과 안정성, 접근성 높은 문서를 제공함으로써 높은 만족도로 평가 받게 된다.

 

-

Webpack에 비해 Vite의 장점이 뚜렷하다는 것은 이제 익히 알 것 같다. 

 

CRA는 Webpack 기반으로, css, sass, eslint, url, babel, file 등의 써드파티 로더를 제공한다.

번들링 과정으로 개발 환경에서 느린 속도를 보인다는 단점 외에도, 전담하는 maintainer가 부재하는 등 React와 Webpack의 피쳐들이 이슈를 야기하기도 한다.

이제는 CRA를 명령이 아닌 launcher로 전환하고자 하는 🔗 움직임도 보인다.

 

이 때문에 커뮤니티가 점차 안정화되는 Vite으로의 마이그레이션을 결정하였고,

다음 절차를 거쳐, 🔗 Migration을 진행하였다.

 


3️⃣ Vite으로의 마이그레이션 과정

  1. 의존성 모듈을 설치한다. vite  @vitejs/plugin-react  vite-plugin-svgr 
  2. index.html을 최상위 폴더로 이동한다. (🔗 공식문서) vite 서버에서, index.html이 엔트리 포인트로 동작한다.
    엔트리 포인트를 추가하기 위해, JS 소스코드를 <script type=”module” src=”~”> 로써 resolve한다.
    그리고 index html 파일에서는 자동으로 URL을 rebase하기 때문에 %PUBLIC_URL% 을 사용할 필요가 없다. 바꿔준다.
  3. tsconfig.json 파일을 알맞게 작성한다.
  4. package.json을 업데이트한다.
    1. 웹팩 기반의 번들러인 react-scripts 라이브러리를 삭제한다.
    2. vite 기반으로 재구성한다.
  5. process.env.REACT_APP_ 로 이용하던 🔗 환경변수를, import.meta.env.VITE_ 로써 사용한다.
    또한 import.meta.env.DEV(PROD/SSR) 를 통해 node 환경을 체크할 수 있다.
    이는, src/env.d.ts 파일에 interface를 정의함으로써 타입을 지정할 수 있다.
  6. test는 Vitest를 이용하고, ESLink & Prettier는 기존 설정을 유지한다.
  7. 이외에, vite.config.ts 파일에서 build 파일명을 변경할 수도, 서버 시작 시 자동으로 앱을 열 수도, 포트번호를 변경할 수도 있다.

 

그 결과, Production build 시간을 31s → 11s로 약 3배 가량 단축할 수 있었고,

개발 서버를 여는 시간은 10배 이상을 단축하며 DX에 크게 기여할 수 있었다.

 

before

Server startup time :: 35s

Production build time :: 31.14s.

File sizes after gzip:

190.94 kB build/static/js/main.99f3ab1a.js

5.05 kB build/static/css/main.5e8f4481.css

After

Server startup time :: 3285ms

Production build time :: 11.05s

dist/assets/index-481e8e6c.css 13.62 kB │ gzip: 3.89 kB

dist/assets/index-1b9e4386.js 602.43 kB │ gzip: 194.92 kB

 


4️⃣ 글을 줄이며

Vite으로 마이그레이션 한 것에 대해 크게 만족한다.

속도면에서 DX가 굉장히 향상하였고, 이 이외에도 안정되고 성장하는 커뮤니티를 통해 React의 피쳐들을 좀 더 쉽게 대응할 수 있을 것이다.

CRA로 모든 것을 할 수 있지만, Vite으로 더 나은 구현을 할 수 있다.

 

Next는 swc, rust로 만든 ES 컴파일러를 쓴다고 한다. 이 친구도 찾아보고자 한다.

[ 23/04/18 추가 ]
swc는 Rust로 작성된 번들러이기 때문에, 높은 메모리 안정성과 성능을 보장하여 빠른 빌드 시간을 제공한다.
번들 크기를 줄이는 데에 최적화 되어있고, ES9까지의 모든 기능 및 TS와 같은 다른 언어 확장도 지원한다.
하지만, 커뮤니티의 규모가 상대적으로 작고 미지원하는 기능이 몇 가지 있다. 대표적으로 getStaticProps/getServerSideProps와 같은 SSR 관련 기능을 지원하지 않아 Babel과 함께 컴파일한다.

 


Reference

🔗 Vite 이야기 (feat. Svelte) - TEO

🔗 (번역) ‘Create React App 권장을 Vite로 대체’ PR 대한 Dan Abramov의 답변

🔗 JavaScript 번들러로 본 조선시대 붕당의 이해 - 재그지그

🔗 Vite 공식문서

🔗 Migrating from Create React App (CRA) to Vite

🔗 초보 웹 개발자를 위한 자바스크립트 빌드 툴과 SWC

🔗 [번역] 2023년 버전 리액트 프로젝트를 시작하는 방법

 

반응형