[Daily morning study] Webpack vs Vite — 번들러 비교와 동작 원리
#daily morning study
번들러란
현대 프론트엔드 개발은 수백~수천 개의 모듈로 이루어진다. 브라우저가 파일마다 별도 HTTP 요청을 보내면 성능이 급격히 떨어지기 때문에, 번들러가 여러 모듈을 하나(또는 몇 개)의 파일로 합쳐준다. 번들러는 JS뿐 아니라 CSS, 이미지, 폰트 같은 에셋도 처리한다.
Webpack
동작 원리
Webpack은 의존성 그래프(Dependency Graph) 방식으로 동작한다.
- Entry Point — 설정에서 지정한 진입 파일(예:
src/index.js)부터 시작 - 모듈 탐색 —
import,require를 따라가며 모든 의존 모듈을 재귀적으로 탐색 - 로더 적용 — JS가 아닌 파일(TypeScript, CSS, 이미지 등)을 JS로 변환
- 번들 생성 — 의존성 그래프를 하나(또는 청크로 분리된 여러)의 파일로 합침
- 플러그인 — 번들링 생명주기의 특정 훅에서 추가 작업 수행 (minify, HTML 생성 등)
// webpack.config.js 기본 구조
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
],
},
plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
};
개발 서버와 HMR
Webpack Dev Server는 전체 번들을 메모리에 생성한 뒤 서빙한다. 코드 변경 시 영향받는 모듈만 다시 컴파일하고 HMR(Hot Module Replacement)로 브라우저에 패치를 전달한다. 그러나 프로젝트가 커질수록 초기 번들 생성 시간이 늘어나는 구조적 한계가 있다.
Vite
개발 환경 — ESM 기반 서버
Vite는 개발 환경에서 번들링을 하지 않는다. 대신 브라우저의 Native ESM(<script type="module">)을 그대로 활용한다.
브라우저 요청 → Vite 서버 → 파일 단위로 변환(esbuild) → 브라우저가 ESM으로 직접 실행
- 서버 시작 시 번들을 생성하지 않으므로 콜드 스타트가 수십 ms 수준으로 빠름
- 변경된 파일만 다시 변환하면 되므로 HMR 속도도 프로젝트 크기와 무관하게 일정
Pre-bundling (의존성 사전 번들링)
node_modules의 라이브러리는 ESM이 아닌 경우가 많고, 파일 수가 수백 개에 달할 수 있다. Vite는 esbuild로 이런 의존성을 한 번만 사전 번들링해 캐시에 저장한다.
- esbuild는 Go로 작성되어 Webpack 대비 10~100배 빠른 변환 속도
- 개발 중에는 캐시된 번들을 그대로 재사용
프로덕션 빌드 — Rollup
개발 환경과 달리 프로덕션 빌드는 Rollup을 사용한다. Rollup은 Tree-shaking(사용하지 않는 코드 제거)에 특화되어 있고 번들 크기를 최소화하는 데 유리하다.
vite build
→ Rollup으로 전체 번들 생성
→ chunk 분리(코드 스플리팅)
→ 최적화된 정적 파일 출력
Webpack vs Vite 비교
| 항목 | Webpack | Vite |
|---|---|---|
| 번들 방식(개발) | 전체 번들 후 서빙 | No-bundle (Native ESM) |
| 콜드 스타트 | 프로젝트 크기에 비례 | 수십 ms (거의 일정) |
| HMR 속도 | 의존 모듈 수에 비례 | 변경 파일만 처리, 빠름 |
| 프로덕션 번들러 | Webpack 자체 | Rollup |
| 설정 복잡도 | 높음 | 낮음 (기본값이 합리적) |
| 생태계 성숙도 | 매우 성숙 (플러그인 풍부) | 빠르게 성장 중 |
| 대형 레거시 프로젝트 | 강점 (세밀한 제어 가능) | 점진적 마이그레이션 필요 |
핵심 차이: 번들 vs No-bundle
Webpack 개발 서버
[entry] → 전체 의존성 탐색 → 번들 생성 → 서버에서 서빙
(프로젝트 클수록 느려짐)
Vite 개발 서버
[브라우저 요청] → 요청받은 파일만 그때그때 변환 → ESM으로 전달
(프로젝트 커져도 속도 유지)
Tree-shaking
두 도구 모두 Tree-shaking을 지원하지만 방식이 다르다.
- Webpack:
sideEffects필드를package.json에 명시해야 제대로 동작하는 경우가 많음 - Vite/Rollup: ESM의 정적 구조를 활용하므로 더 공격적인 Tree-shaking이 가능
// ESM은 정적 임포트 → 빌드 타임에 사용 여부 판단 가능
import { add } from './math.js'; // subtract는 번들에서 제거됨
// CommonJS는 동적 → Tree-shaking 어려움
const math = require('./math.js'); // 전체를 포함해야 안전
코드 스플리팅
두 도구 모두 Dynamic Import로 코드 스플리팅을 지원한다.
// 라우트별로 분리
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
- Webpack:
SplitChunksPlugin으로 세밀한 청크 제어 - Vite/Rollup:
build.rollupOptions.output.manualChunks로 설정
언제 무엇을 선택할까
Vite를 선택할 때
- 새 프로젝트 시작 (React, Vue, Svelte 등)
- 빠른 개발 경험이 중요할 때
- 설정을 최소화하고 싶을 때
Webpack을 선택할 때
- 기존 Webpack 기반 레거시 프로젝트 유지보수
- 매우 세밀한 번들링 제어가 필요할 때
- Module Federation으로 마이크로프론트엔드를 구성할 때 (Webpack 5 고유 기능)
Module Federation (Webpack 5)
Webpack 5에서 추가된 기능으로, 여러 독립 빌드가 런타임에 서로 코드를 공유할 수 있다. 마이크로프론트엔드 아키텍처의 핵심 구성 요소다.
// host app webpack.config.js
new ModuleFederationPlugin({
name: 'host',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
});
Vite는 공식 플러그인(@originjs/vite-plugin-federation)으로 유사한 기능을 제공하지만 Webpack만큼 성숙하지는 않다.
정리
- Webpack은 의존성 그래프 기반 번들링으로 오랫동안 표준이었지만, 대규모 프로젝트에서 개발 서버 속도가 느려지는 한계가 있다.
- Vite는 Native ESM으로 개발 서버를 운영해 프로젝트 크기와 무관하게 빠른 속도를 유지하고, 프로덕션은 Rollup으로 최적화된 번들을 생성한다.
- 신규 프로젝트라면 Vite가 기본 선택이 되었고, 레거시 프로젝트나 Module Federation 등 Webpack 고유 기능이 필요한 경우엔 Webpack을 유지한다.