Webpack vs Vite vs Rollup

keemsebeen·2026년 1월 12일
post-thumbnail

프론트엔드 프로젝트를 시작할 때 가장 먼저 마주하는 선택 중 하나가 바로 번들러입니다. "Webpack을 쓸까, Vite를 쓸까?" 혹은 "라이브러리를 배포할 땐 뭘 써야 하지?"라는 고민을 하게 됩니다.

이 글에서는 Webpack, Rollup, Vite 세 가지 번들러의 차이점을 살펴보고, 각각 어떤 상황에 적합한지 정리해보겠습니다.

Webpack

등장 배경

웹팩은 2012년에 등장했습니다. 당시 프론트엔드 문제는 자바스크립트에 공식 모듈 시스템이 없었다는 점입니다. 개발자들은 여러 개의 <script> 태그를 순서대로 나열하거나, require 문을 통한 AMD 방식을 사용해야 했습니다.
웹팩은 이 문제를 해결하기 위해 "모든 것을 모듈로 취급하고, 하나의 번들로 만들자"라는 접근을 했습니다. 자바스크립트뿐만 아니라 CSS, 이미지, 폰트까지 모두 JavaScript 모듈처럼 import해서 사용할 수 있게 했습니다. 이 개념은 당시로서는 혁신적이었고, React, Vue, Angular 등 모던 프레임워크의 빌드 도구로 자리잡게 됩니다.

동작 원리

module.exports = {
  entry: './src/index.js',
  output: { filename: 'bundle.js' },
  module: {
    rules: [
      { test: /\.css$/, use: ['style-loader', 'css-loader'] },
      { test: /\.png$/, type: 'asset/resource' },
    ],
  },
  plugins: [new HtmlWebpackPlugin()],
};

웹팩의 핵심 개념은 의존성 그래프(Dependency Graph)입니다. entry 파일에서 시작해서 import/require를 따라가며 모든 의존성을 분석합니다. 그리고 이 모든 파일을 하나 또는 여러 개의 번들 파일로 합칩니다.

예를 들어 index.jsApp.js를 import하고, App.jsHeader.jsstyles.css를 import한다면, 웹팩은 이 관계를 모두 파악해서 하나의 bundle.jsstyles.css로 만들어냅니다.

이 과정에서 Loader와 Plugin이라는 개념이 중요합니다. Loader는 자바스크립트가 아닌 파일을 처리하는 변환기입니다. 예를 들어 babel-loader는 최신 자바스크립트를 구버전으로 변환하고, css-loader는 CSS 파일을 자바스크립트 모듈로 변환합니다. Plugin은 번들링 과정 전체에 개입해서 최적화, 환경변수 주입, HTML 생성 등의 작업을 수행합니다.

요약하자면 다음과 같습니다.

  1. Entry point에서 시작해 의존성 그래프(Dependency Graph)를 구축
  2. Loader를 통해 다양한 파일 타입을 JavaScript로 변환
  3. Plugin을 통해 번들링 과정 전반에 개입 가능
  4. 최종적으로 하나 또는 여러 개의 번들 파일 생성

장점

  1. 성숙한 생태계
    10년 넘게 발전해오면서 거의 모든 상황에 대응하는 플러그인과 로더가 존재합니다. 복잡한 요구사항이 있어도 대부분 해결책을 찾을 수 있습니다.
  2. Code Splitting
    큰 번들을 여러 개의 작은 청크로 나누고, 필요할 때만 로드하는 Lazy Loading을 세밀하게 제어할 수 있습니다. React의 React.lazy()나 동적 import()와 자연스럽게 연동됩니다.
  3. 다양한 환경에 대한 지원
    레거시 브라우저, Node.js, Electron 등 다양한 타겟을 지원하고, 복잡한 폴리필 설정도 가능합니다.

단점

  1. 복잡한 설정
    기본 설정만으로는 제대로 동작하지 않는 경우가 많고, TypeScript, CSS Module, 환경별 빌드 등을 설정하려면 수백 줄의 설정 파일이 필요할 수 있습니다.
  2. 느린 빌드 속도
    대규모 프로젝트에서 초기 빌드와 HMR이 느립니다.
  3. 번들 크기
    Webpack은 모듈 시스템을 구현하기 위한 런타임 코드를 번들에 포함시키는데, 이 오버헤드가 작은 라이브러리에서는 부담이 될 수 있습니다.

Rollup

등장배경

Rollup은 2015년에 Svelte 창시자가 만들었습니다. 당시 ES6가 발표되면서 JavaScript에 공식 모듈 시스템인 ES Module이 도입되었습니다. Rollup은 이 ES Module을 기반으로 처음부터 설계된 번들러입니다.
Rollup의 핵심 목표는 "가능한 가장 작고 효율적인 번들을 만들자"였습니다. 특히 라이브러리를 배포할 때, 사용자가 실제로 사용하는 코드만 포함되어야 한다는 철학을 가지고 있었습니다.

동작 원리

// rollup.config.js
export default {
  input: 'src/index.js',
  output: [
    {
      file: 'dist/bundle.esm.js',
      format: 'esm'
    },
    {
      file: 'dist/bundle.cjs.js',
      format: 'cjs'
    },
    {
      file: 'dist/bundle.umd.js',
      format: 'umd',
      name: 'MyLibrary'
    }
  ],
  plugins: [
    resolve(),    // node_modules 패키지 해석
    commonjs(),   // CommonJS → ESM 변환
    terser()      // 압축
  ]
};

Rollup의 가장 중요한 특징은 Tree Shaking입니다.

이게 가능한 이유는 ES Module의 정적 구조 때문입니다. ES Module의 import/export는 파일 최상위에서만 사용할 수 있고, 조건문 안에서 동적으로 사용할 수 없습니다. 이 덕분에 Rollup은 코드를 실행하지 않고도 이 함수는 어디서도 import되지 않았으니 제거해도 된다고 판단할 수 있습니다.

예를 들어 lodash 라이브러리에서 debounce 함수 하나만 import했다면, Rollup은 lodash의 나머지 수백 개 함수를 번들에 포함시키지 않습니다. Webpack도 Tree Shaking을 지원하지만, Rollup이 이 분야에서 더 뛰어난 결과를 보여줍니다.

Rollup의 또 다른 특징은 번들 출력 형식입니다. 하나의 소스 코드로 ESM, CommonJS, UMD, IIFE 등 여러 포맷의 번들을 동시에 생성할 수 있습니다. 라이브러리를 배포할 때 ESM을 지원하는 환경에서는 ESM 번들을, Node.js 구버전에서는 CommonJS 번들을 사용하도록 할 수 있습니다.

요약하자면 다음과 같습니다.

  1. ES Module만을 입력으로 받음 (CommonJS는 플러그인 필요)
  2. 전체 코드를 하나의 스코프로 합침 (Scope Hoisting)
  3. 사용되지 않는 export를 제거 (Tree Shaking의 원조)
  4. 다양한 포맷으로 출력 가능 (ESM, CJS, UMD, IIFE)

장점

  1. 최소한의 번들 크기
    Rollup은 Webpack과 달리 모듈 로딩을 위한 런타임 코드를 최소화합니다. 여러 모듈을 하나의 스코프로 합쳐버리는 "Scope Hoisting" 기법을 사용해서, 번들이 마치 처음부터 하나의 파일이었던 것처럼 깔끔하게 만들어집니다.
  2. 우수한 Tree Shaking
    ES Module 정적 분석 기반으로 Webpack보다 효과적입니다.
  3. 다중 출력 포맷
    하나의 소스로 ESM, CJS 동시가 가능합니다.

단점

  1. 제한된 Code Splitting
    Webpack만큼 정교하지 않습니다.
  2. 개발 서버 부재
    자체 dev server가 없어 별도 도구가 필요합니다.
  3. CommonJS 처리
    npm의 많은 패키지가 아직 CommonJS로 배포되는데, Rollup은 ES Module을 기본으로 설계되어서 CommonJS 패키지를 사용하려면 @rollup/plugin-commonjs 플러그인이 필요하고, 때때로 호환성 문제가 발생합니다.

Vite

등장배경

Vite가 등장한 배경에는 두 가지 변화가 있었습니다.

첫째, 브라우저가 ES Module을 네이티브로 지원하기 시작했습니다. 2018년부터 모던 브라우저들은 <script type="module">을 통해 ES Module을 직접 실행할 수 있게 되었습니다. 이전에는 브라우저가 모듈을 이해하지 못해서 반드시 번들링이 필요했지만, 이제는 개발 중에 번들링 없이도 모듈을 사용할 수 있게 된 것입니다.

둘째, esbuild의 등장입니다. esbuild는 Go 언어로 작성된 JavaScript 번들러로, Webpack이나 Rollup보다 10~100배 빠릅니다. Vite는 개발 중 필요한 변환 작업에 esbuild를 사용합니다.

동작 원리 - 개발 모드

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  // 대부분 설정 없이 동작
});

Vite의 개발 서버는 Webpack과 완전히 다른 방식으로 동작합니다. 서버를 시작할 때 번들링을 하지 않습니다. 대신 브라우저가 파일을 요청하면 그때그때 해당 파일만 변환해서 제공합니다.

예를 들어 브라우저가 index.html을 요청하면, Vite는 HTML을 그대로 제공합니다. HTML 안에 <script type="module" src="/src/main.jsx">가 있으면, 브라우저가 /src/main.jsx를 요청합니다. 그러면 Vite는 그 파일만 JSX를 JavaScript로 변환해서 제공합니다. main.jsxApp.jsx를 import한다면, 브라우저가 다시 /src/App.jsx를 요청하고, Vite는 그 파일을 변환해서 제공합니다.

이 방식의 장점은 명확합니다. 프로젝트에 1000개의 모듈이 있어도, 현재 페이지에서 실제로 사용하는 50개만 로드하면 됩니다. 개발 서버 시작이 거의 즉각적이고, 콜드 스타트가 프로젝트 규모와 거의 무관합니다.

HMR도 훨씬 빠릅니다. 파일을 수정하면 Vite는 그 파일과 직접적으로 연관된 모듈만 교체합니다. 전체 의존성 그래프를 다시 계산할 필요가 없어서, 프로젝트가 아무리 커도 HMR은 밀리초 단위로 동작합니다.

다만 의존성(node_modules의 패키지들)은 미리 번들링합니다. 이를 "dependency pre-bundling"이라고 합니다. lodash 같은 패키지는 수백 개의 모듈로 구성되어 있는데, 이를 매번 개별 요청하면 네트워크 오버헤드가 크기 때문입니다. Vite는 esbuild를 사용해서 의존성을 미리 하나의 번들로 만들어두고, 캐싱해서 재사용합니다.

요약하자면 다음과 같습니다.

  1. No-bundle: 번들링하지 않고 브라우저의 Native ESM을 직접 활용
  2. 요청 시점에 필요한 모듈만 변환 (On-demand compilation)
  3. esbuild(Go로 작성)로 의존성 사전 번들링 (Pre-bundling)
  4. HMR이 모듈 그래프 전체가 아닌 해당 모듈만 교체
전통적 번들러:    Entry → 전체 번들링 → 브라우저
Vite 개발 모드:   Entry → 브라우저 → 필요한 모듈만 요청 시 변환

동작 원리 - 프로덕션 모드

프로덕션 빌드에서는 여전히 번들링이 필요합니다. 개발 모드처럼 모듈을 개별 요청하면 네트워크 요청이 너무 많아지기 때문입니다. Vite는 프로덕션 빌드에 Rollup을 사용합니다. Rollup의 뛰어난 Tree Shaking과 작은 번들 크기를 그대로 활용할 수 있습니다.
이 점이 Vite의 독특한 특징입니다. 개발 환경과 빌드 환경에서 다른 도구를 사용합니다. 개발에서는 Native ESM + esbuild, 빌드에서는 Rollup입니다

장점

  1. 개발자 개발 경험
    번들링 없이 바로 시작하기 때문에 서버 시작이 즉각적이고, HMR이 체감상 즉시 반영됩니다.
  2. 설정이 거의 필요 없음
    TypeScript, JSX, CSS, CSS Modules, PostCSS 등이 기본 지원됩니다. React나 Vue 프로젝트를 시작할 때 설정 파일 없이 바로 개발을 시작할 수 있습니다.

단점

  1. 개발/프로덕션 불일치
    개발에서는 잘 동작하다가 빌드 후에 문제가 발생하는 경우가 드물게 있습니다. 특히 CommonJS 패키지나 특수한 import 패턴에서 이런 문제가 나타날 수 있습니다.
  2. 레거시 브라우저에는 추가 설정 필요
    Vite는 기본적으로 ES Module을 지원하는 브라우저를 타겟으로 합니다. IE11이나 구버전 브라우저를 지원하려면 @vitejs/plugin-legacy 플러그인이 필요합니다.
  3. 상대적으로 젊은 생태계
    특수한 요구사항이 있을 때 Webpack만큼 다양한 플러그인이 존재하지 않을 수 있습니다. 다만 Vite는 Rollup 플러그인과 호환되므로, Rollup 생태계를 어느 정도 활용할 수 있습니다.

언제 무엇을 선택할까?

개인적으로 각 번들러를 선택하는 기준은 다음과 같습니다.

Webpack을 선택해야 할 때

  • 이미 Webpack 기반으로 구축된 프로젝트를 유지보수할 때
  • 복잡한 빌드 파이프라인이 필요하고, 세밀한 제어가 필요할 때
  • 레거시 브라우저 지원이 중요한 엔터프라이즈 환경

Rollup을 선택해야 할 때

  • npm에 배포할 라이브러리나 패키지를 개발할 때
  • 번들 크기 최적화가 최우선일 때
  • ESM, CommonJS, UMD 등 여러 포맷으로 동시 배포해야 할 때

Vite를 선택해야 할 때

  • 새로운 프로젝트를 빠르게 시작하고 싶을 때
  • 개발 서버 속도와 HMR이 중요할 때
  • React, Vue, Svelte 등 모던 프레임워크를 사용할 때
  • 복잡한 설정 없이 바로 개발에 집중하고 싶을 때
profile
프론트엔드 개발자 김세빈입니다. 👩🏻‍💻

0개의 댓글