[Daily morning study] Webpack vs Vite — 번들러 비교와 동작 원리

#daily morning study

Image


번들러란

현대 프론트엔드 개발은 수백~수천 개의 모듈로 이루어진다. 브라우저가 파일마다 별도 HTTP 요청을 보내면 성능이 급격히 떨어지기 때문에, 번들러가 여러 모듈을 하나(또는 몇 개)의 파일로 합쳐준다. 번들러는 JS뿐 아니라 CSS, 이미지, 폰트 같은 에셋도 처리한다.


Webpack

동작 원리

Webpack은 의존성 그래프(Dependency Graph) 방식으로 동작한다.

  1. Entry Point — 설정에서 지정한 진입 파일(예: src/index.js)부터 시작
  2. 모듈 탐색 — import, require를 따라가며 모든 의존 모듈을 재귀적으로 탐색
  3. 로더 적용 — JS가 아닌 파일(TypeScript, CSS, 이미지 등)을 JS로 변환
  4. 번들 생성 — 의존성 그래프를 하나(또는 청크로 분리된 여러)의 파일로 합침
  5. 플러그인 — 번들링 생명주기의 특정 훅에서 추가 작업 수행 (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 비교

항목WebpackVite
번들 방식(개발)전체 번들 후 서빙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을 유지한다.