Webpack의 구조적 한계
이전 글에서 번들러의 동작 과정을 살펴봤습니다. Webpack은 그 과정을 충실하게 따릅니다, 개발 서버를 시작하기 전에 모든 모듈을 번들링합니다.
Webpack 개발 서버 시작 과정:
1. 엔트리 포인트 읽기
2. 모든 import를 재귀적으로 따라가기
3. 각 모듈에 로더 적용 (babel-loader, ts-loader, css-loader ...)
4. 의존성 그래프 기반으로 번들 생성
5. 개발 서버 시작
6. 브라우저에 번들 전달프로젝트에 모듈이 100개든 10,000개든, 모든 모듈을 처리한 뒤에야 서버가 시작됩니다. 프로젝트가 커질수록 cold start가 선형적으로 느려지는 구조입니다.
Vite의 접근, 서빙 먼저, 변환은 나중에
Vite는 발상을 뒤집었습니다. 번들링을 먼저 하고 서버를 시작하는 대신, 서버를 먼저 시작하고 요청이 올 때 변환합니다.
Vite 개발 서버 시작 과정:
1. 의존성 사전 번들링 (esbuild, 매우 빠름)
2. 개발 서버 즉시 시작
3. 브라우저가 페이지 요청
4. import를 만날 때마다 해당 모듈만 변환하여 응답핵심 차이는 네이티브 ESM 을 활용한다는 점입니다. 브라우저가 import 문을 만나면 HTTP 요청으로 해당 모듈을 가져옵니다. Vite 개발 서버가 이 요청을 가로채서, 그 모듈만 변환하여 응답합니다.
의존성 사전 번들링
Vite가 서버 시작 전에 수행하는 유일한 작업이 의존성 사전 번들링 (dependency pre-bundling) 입니다. 두 가지 이유가 있습니다.
1. CommonJS → ESM 변환
npm 패키지 중 상당수가 여전히 CommonJS로 배포됩니다. 브라우저의 네이티브 ESM은 require()를 이해하지 못하므로, 미리 ESM으로 변환해야 합니다.
// node_modules/lodash/index.js, CommonJS
module.exports = { debounce, throttle, ... };
// Vite가 사전 번들링한 결과, ESM
export { debounce, throttle, ... };2. 요청 수 최적화
lodash-es 같은 패키지는 내부적으로 수백 개의 ESM 파일로 구성되어 있습니다. import { debounce } from 'lodash-es' 하나가 수백 개의 HTTP 요청을 유발할 수 있습니다. 사전 번들링으로 하나의 모듈로 합칩니다.
이 작업에 Vite는 esbuild를 사용합니다. esbuild는 Go로 작성되어 JavaScript 기반 도구보다 10~100배 빠르게 동작합니다.
HMR 비교, 모듈 교체 속도
개발 중 파일을 수정하면 HMR (Hot Module Replacement) 이 변경 사항을 브라우저에 실시간 반영합니다. Webpack과 Vite의 HMR 메커니즘은 근본적으로 다릅니다.
Webpack HMR
파일 수정 → 영향받는 모듈 그래프 재구축 → 청크 재번들링 → 브라우저 전송Webpack은 변경된 모듈이 속한 청크 전체를 재구축 합니다. 공유 의존성이 많은 모듈이 변경되면 재구축 범위가 넓어집니다. 프로젝트 규모에 비례하여 HMR 속도가 느려질 수 있습니다.
Vite HMR
파일 수정 → 해당 모듈만 무효화 → 브라우저가 새 모듈을 ESM으로 요청 → 즉시 교체Vite는 변경된 모듈과 가장 가까운 HMR 경계 사이의 체인만 무효화합니다. 번들을 재구축할 필요가 없으므로, 프로젝트 크기와 관계없이 일관되게 빠른 업데이트가 가능합니다.
// Vite HMR API, 모듈 스스로 핫 리로드를 처리
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
// 새 모듈로 교체
});
}프로덕션 빌드
개발 환경에서 Vite가 네이티브 ESM을 활용하는 것은 이해했습니다. 그렇다면 프로덕션에서는 어떨까요?
프로덕션 환경에서는 여전히 번들링이 필요합니다. 수백 개의 모듈을 개별 요청하면 네트워크 워터폴이 발생하고, tree-shaking이나 코드 스플리팅 같은 최적화도 불가능합니다.
| 버전 | 프로덕션 번들러 | 특징 |
|---|---|---|
| Vite 2~5 | Rollup | ESM 기반 설계, 작은 번들 크기, 풍부한 플러그인 |
| Vite 6~7 | Rollup + Rolldown 실험적 지원 | Rust 기반 Rolldown을 점진적으로 도입 |
| Vite 8 | Rolldown | Rust 기반 단일 번들러, Rollup 대비 10~30배 빠른 빌드 |
Vite 8 (2026년 3월 안정 버전 출시) 은 Rolldown을 단일 번들러로 채택했습니다. Rolldown은 Rust로 작성되었으며, Rollup의 플러그인 API와 호환됩니다. Linear의 프로덕션 빌드가 46초에서 6초로 개선된 사례가 보고되었습니다.
Webpack은 왜 여전히 쓰이는가
Vite가 이렇게 빠르다면, 모두 Vite로 갈아타면 되지 않을까요? 현실은 조금 다릅니다.
- 거대한 플러그인 생태계 , 10년 넘게 축적된 Webpack 플러그인과 로더가 있습니다. 특수한 빌드 요구사항이 있는 프로젝트에서는 대체가 어렵습니다
- Module Federation , 마이크로 프론트엔드 아키텍처에서 Webpack의 Module Federation은 독보적입니다
- 레거시 호환성 , IE11 지원 등 네이티브 ESM을 쓸 수 없는 환경이 여전히 존재합니다
- 마이그레이션 비용 , 대규모 프로젝트의 빌드 설정 전환은 상당한 작업량입니다
그리고 Rspack 같은 Webpack 호환 도구도 등장했습니다. Rspack은 Rust로 작성된 Webpack 호환 번들러로, 기존 Webpack 설정을 거의 그대로 사용하면서 빌드 속도를 크게 개선합니다.
정리
| Webpack | Vite | |
|---|---|---|
| 개발 서버 시작 | 모든 모듈 번들링 후 시작 (느림) | 즉시 시작, 요청 시 변환 (빠름) |
| HMR | 청크 재구축 (프로젝트 크기에 비례) | 모듈 단위 무효화 (크기 무관) |
| 프로덕션 빌드 | Webpack 자체 | Rolldown (Vite 8) |
| 설정 | 복잡하고 유연함 | 관례 기반, 대부분 zero-config |
| 언어 | JavaScript | JavaScript + Rust/Go (의존성) |
다음 단계
번들러가 모듈을 합치기 전에, 각 모듈의 코드를 브라우저가 이해할 수 있는 JavaScript로 변환해야 합니다. 이 변환을 담당하는 것이 트랜스파일러 입니다. 다음 글에서는 Babel, SWC, esbuild, 세 트랜스파일러의 내부 동작과 진화를 살펴보겠습니다.