일반적으로 Webpack보다 Vite가 더 빠르다는 글을 볼 수 있다.
왜 Vite가 Webpack보다 빠르다는 것일까?
모듈
개발을 하는 과정에서 어플리케이션의 크기가 커지면 언젠가 파일을 분리해야 할 것이다.
이때 분리되는 파일 각각을 '모듈(module)'이라 부른다.
모듈은 대개 클래스 하나 혹은 특정한 목적을 가진 복수의 함수로 구성된다.
하지만 JS의 모듈 시스템인 ESM은 2015년 ES6가 도입되며 공직적으로 추가되었다.
ESM을 사용하면 import와 export 문법을 사용하여 서로 다른 모듈에서 변수나 함수 등을 쉽게 불러오거나 내보낼 수 있다.
브라우저에서 ESM을 지원하기 전까지는 JS의 모듈화를 네이티브 레벨에서 진행할 수 없었다.
여기서 "네이티브 레벨"이라는 것은 외부의 다른 것을 사용하지 않고 순수한 JS를 사용하는 것을 의미한다.
(현재의 경우 ESM의 import/export를 사용하여 모듈화를 하기 때문에 네이티브 레벨에서 모듈화를 수행한다고 할 수 있다)
따라서, 기존에는 소스 모듈을 브라우저에서 실행할 수 있는 파일로 크롤링, 처리 및 연결하는 번들링이라는 해결 방법을 사용해야 했다.
번들러
번들러는 JS 모듈을 한 파일로 연결해준다.
이를 위해서는 의존성 분석이 필수적이다.
하지만, React, babel 등 다양한 도구가 생기면서 단순히 JS 파일을 연결하는 것 만으로는 부족해졌다.
따라서 번들러는 다양한 기능을 수행하게 되었다.
번들링
번들러의 존재 의의이자 가장 중요한 개념이다.
의존성을 가진 JS 파일들을 정렬하고 포장한다.
트랜스파일
특정 언어를 추상화 단계가 비슷한 언어로 변환하는 것을 의미하며 컴파일 개념의 일부로도 볼 수 있다.
예를 들어 JS를 TS로 변환한다거나 JSX를 JS로 변환하는 과정 등이 포함된다.
코드 분할
SPA가 유행하며 물리적으로 하나의 HTML 페이지에 여러 페이지를 전달해야 하는 문제점이 발생했다.
당장 필요하지 않은 페이지의 소스도 번들링을 해야한다는 단점이 생기게 되었다.
따라서, 필요에 의해 번들을 나눠야 하는 요구사항이 새롭게 생기게 되었다.
트리 쉐이킹
코드 분할은 적절한 크기로 번들을 분할하는 전략이라면 트리 쉐이킹은 불필요한 코드를 삭제하는 전략이다.
정적 분석이 요구되며 번들러 별로 다양한 알고리즘, 접근 방식 등을 선택하고 있다.
소스 압축
소스코드의 변수명, 함수명 등을 짧은 이름으로 바꾸고 주석 등을 제거하는 과정이다.
이 과정에서 다양한 난독화 도구를 사용하기도 한다.
이는 번들러가 직접 수행하는 것이 아니라 다양한 서드파티 도구를 활용하여 수행한다.
추가적으로 압축된 번들을 배포할 때는 파일 이름에 .min.js를 붙이는 관례가 있다.
HMR
개발 과정에서 소스코드가 약간 수정된 뒤 동작을 확인해야 하는 경우가 많다.
이를 위해 수정된 부분 및 그 관련된 코드만 교체하는 기능을 HMR이라 한다.
서버를 재부팅하거나 페이지를 새로고치지 않아도 변경을 할 수 있는 장점이 있다.
일반적으로 캐싱을 쓰는 방법이 있고 개발 중에 번들링을 배제하는 방법을 사용하기도 한다.
Webpack
Webpack의 공식 문서를 보면 Webpack의 핵심 개념은 Entry, Output, Loaders, Plugins 등이라 한다.
각각에 대해 살펴보면 아래와 같다.
- Entry: 디펜던시 그래프를 생성하기 위해 사용해야 하는 모듈 (어플리케이션의 엔트리 포인트; 일반적으로 ./src/index.js)
- Output: 생성된 번들을 내보내는 위치와 파일의 이름을 지정
- Loader: webpack은 기본적으로 JS와 JSON 파일만 이해하기 때문에 Loader를 사용하여 다른 유형의 파일을 처리하거나 유효한 모듈로 변환하여 사용하거나 디펜던시 크래프에 추가 (v5.89.0 기준 트랜스파일링을 위해 Babel-loader, ts-loader 등을 사용)
- Plugins: Loader는 특정 유형의 모듈을 변환하는데 사용하지만 플러그인은 번들의 최적화, 애셋의 관리, 환경 변수 주입 등과 같은 작업을 수행
Webpack은 위의 개념을 활용하여 번들링을 수행한다.
위의 그림과 같이 다양한 의존 관계를 분석하고 번들링의 수행 결과를 Output에 반환한다.
전통적인 번들 기반 방식은 위의 그림과 같은 과정을 통해 서버를 실행한다.
엔트리로 부터 시작하여 번들링을 수행한 뒤 결과물을 가지고 서버를 구동했다.
만약, 소스 코드를 업데이트 하게 되면 번들링 과정을 다시 수행해야 했으며, 서비스가 커질수록 갱신 시간 또한 증가하게 되었다.
이를 해결하기 위해 일부 번들러는 메모리에서 작업을 수행하며 실제로 갱신에 영향을 받는 파일들만 새롭게 번들링 하도록 했지만(HMR) 결국 처음에는 모든 파일에 대한 번들링이 필요했다.
결론적으로 HMR 기능을 사용하더라도 변경된 파일을 적용하기 위해서는 추가적인 번들링 과정이 필요하며 이는 변경된 파일이 적용되는데 소요되는 시간이 적지 않음을 의미한다.
Vite
VIte는 브라우저에서 지원하는 ESM 및 네이티브 언어로 작성된 JS 도구 등을 활용해 이를 해결한다.
VIte는 애플리케이션의 모듈을 dependencies와 source code로 나누어 개발 서버의 시작 시간을 개선한다.
- Dependencies: 개발 시 내용이 바뀌지 않을 일반적인 JS 소스 코드
- Source Code: JSX, CSS 등과 같이 컴파일링이 필요하고 수정이 매우 잦은 Non-plain JS 소스 코드
Vite는 각각 Esbuild를 이용한 사전 번들링과 Native ESM을 활용한다.
Esbuild는 Go언어로 작성되어 있으며 Go 언어의 특성상 기존의 번들러 대비 빠른 속도를 제공할 수 있다.
또한, Native ESM 방식을 이용하므로 브라우저가 번들러 작업의 일부를 차지할 수 있도록 한다.
Vite는 브라우저의 요청에 따라 소스코드를 변환하고 제공하게 되며 조건부 동적 import 이후의 코드는 화면에서 실제로 사용되는 경우에만 처리된다.
위의 내용을 그림으로 살펴보면 아래와 같다.
Vite는 개발 단계에서는 위의 그림과 같이 ESM을 활용하기 때문에 별도의 번들링 과정을 수행하지 않는다.
만약, 어떤 모듈이 수정되는 경우 수정된 모듈과 관련된 부분만을 교체할 뿐이고 브라우저에서 해당 모듈을 요청하면 교체된 모듈만 전달할 뿐이다.
번들링을 수행하지 않기 때문에 개발 과정에서 반응 속도를 끌어올릴 수 있었다.
물론 개발 환경에서만 번들링을 수행하지 않을 뿐, 프로덕션을 위해서는 번들링을 수행한다.
프로덕션 환경에서 최적의 로딩 성능을 얻기 위해 기존 번들러의 다양한 기능을 이용하여 번들링을 수행한다.
Vite의 단점은 브라우저가 ESM을 지원하지 않는 경우 위의 장점을 활용할 수 없다는 것이다.
개발 단계에서는 별도의 번들링을 수행하지 않고 ESM 방식을 사용하기 때문이다.
(프로덕션 환경에서는 별도의 번들링을 수행하기 때문에 사용할 수 있을 것이다)
위의 단점은 IE의 지원 종료로 인해 해결되었다.
최신 브라우저 대부분은 ESM 방식을 지원하기 때문에 ESM 방식을 활용하는 Vite를 사용하는데 지장이 없을 것이다.
앞으로
Webpack이 처음 출시된 것은 2012년이고 현재도 많은 프로젝트에서 활용하고 있다.
2020년을 기점으로 JS 번들러 점유율 1위를 달성하고 이는 Webpack이 제공하는 다양한 기능들과 함께 안정성이 증명되었음을 의미한다고 생각한다.
2020년 이후 webpack은 여전히 점유율 1위를 유지하고 있다.
Vite는 비교적 최근인 2021년부터 통계가 잡히기 시작한다.
처음 출시되고 빠른 성장세를 보이며 점유율을 늘려나가고 있다.
스벨트 등에서도 메인 번들러로 채택하고 리액트 사용자들의 관심도 증가(원문; 번역)하고 있다.
결론
Vite의 관심이 증가하고 있다.
본인 역시 Vite를 사용했을 때 기존 webpack을 사용했을 때 보다 빠른 속도를 경험했었다.
(특히, Svelte와 Vite의 조합은 엄청난 속도를 자랑한다)
Vite는 어떻게 빠른 속도를 제공할 수 있었고 기존과 어떤 차이점이 있는지 알아봤다.
물론 종합적인 부분을 고려하여 번들러를 선택해야 겠지만 기존 번들러의 속도에 답답함을 느끼고 있다면 Vite를 한 번 사용해 보는 것을 추천한다.
[참고자료]
- https://ko.vitejs.dev/guide/why.html
- https://webpack.kr/
- https://github.com/webpack/webpack
- https://ko.javascript.info/modules-intro
- https://medium.com/naver-place-dev/javascript-%EB%B2%88%EB%93%A4%EB%9F%AC%EC%9D%98-%EC%9D%B4%ED%95%B4-3-%EB%B2%88%EB%93%A4%EB%9F%AC-%EA%B0%9C%EB%A1%A0-32e51414b503
- https://medium.com/naver-place-dev/javascript-%EB%B2%88%EB%93%A4%EB%9F%AC%EC%9D%98-%EC%9D%B4%ED%95%B4-4-webpack-%EB%B0%8F-%EB%8B%A4%EB%A5%B8-%EB%B2%88%EB%93%A4%EB%9F%AC%EB%93%A4-e5158e94ef60
- https://2022.stateofjs.com/en-US/libraries/build-tools/
- https://github.com/reactjs/react.dev/pull/5487#issuecomment-1409720741
- https://junghan92.medium.com/%EB%B2%88%EC%97%AD-create-react-app-%EA%B6%8C%EC%9E%A5%EC%9D%84-vite%EB%A1%9C-%EB%8C%80%EC%B2%B4-pr-%EB%8C%80%ED%95%9C-dan-abramov%EC%9D%98-%EB%8B%B5%EB%B3%80-3050b5678ac8
'개발 > 개념' 카테고리의 다른 글
선언형 프로그래밍과 명령형 프로그래밍 (feat. React 18 Concurrent Mode) (0) | 2024.05.04 |
---|---|
WebRTC란 무엇이며 어떤 과정을 갖는가? (0) | 2023.11.12 |
[객체지향] SOLID 예제(5) - 인터페이스 분리의 원칙(ISP) (0) | 2023.10.03 |
[객체지향] SOLID 예제(4) - 리스코프 치환의 원칙(LSP) (0) | 2023.09.29 |
[객체지향] SOLID 예제(3) - 개방-폐쇄의 원칙(OCP) (0) | 2023.09.26 |