프론트엔드 프로젝트를 시작할 때 가장 먼저 마주하는 선택 중 하나가 바로 번들러입니다. "Webpack을 쓸까, Vite를 쓸까?" 혹은 "라이브러리를 배포할 땐 뭘 써야 하지?"라는 고민을 하게 됩니다.
이 글에서는 Webpack, Rollup, Vite 세 가지 번들러의 차이점을 살펴보고, 각각 어떤 상황에 적합한지 정리해보겠습니다.
웹팩은 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.js가 App.js를 import하고, App.js가 Header.js와 styles.css를 import한다면, 웹팩은 이 관계를 모두 파악해서 하나의 bundle.js와 styles.css로 만들어냅니다.
이 과정에서 Loader와 Plugin이라는 개념이 중요합니다. Loader는 자바스크립트가 아닌 파일을 처리하는 변환기입니다. 예를 들어 babel-loader는 최신 자바스크립트를 구버전으로 변환하고, css-loader는 CSS 파일을 자바스크립트 모듈로 변환합니다. Plugin은 번들링 과정 전체에 개입해서 최적화, 환경변수 주입, HTML 생성 등의 작업을 수행합니다.
요약하자면 다음과 같습니다.
React.lazy()나 동적 import()와 자연스럽게 연동됩니다.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 번들을 사용하도록 할 수 있습니다.
요약하자면 다음과 같습니다.
@rollup/plugin-commonjs 플러그인이 필요하고, 때때로 호환성 문제가 발생합니다.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.jsx가 App.jsx를 import한다면, 브라우저가 다시 /src/App.jsx를 요청하고, Vite는 그 파일을 변환해서 제공합니다.
이 방식의 장점은 명확합니다. 프로젝트에 1000개의 모듈이 있어도, 현재 페이지에서 실제로 사용하는 50개만 로드하면 됩니다. 개발 서버 시작이 거의 즉각적이고, 콜드 스타트가 프로젝트 규모와 거의 무관합니다.
HMR도 훨씬 빠릅니다. 파일을 수정하면 Vite는 그 파일과 직접적으로 연관된 모듈만 교체합니다. 전체 의존성 그래프를 다시 계산할 필요가 없어서, 프로젝트가 아무리 커도 HMR은 밀리초 단위로 동작합니다.
다만 의존성(node_modules의 패키지들)은 미리 번들링합니다. 이를 "dependency pre-bundling"이라고 합니다. lodash 같은 패키지는 수백 개의 모듈로 구성되어 있는데, 이를 매번 개별 요청하면 네트워크 오버헤드가 크기 때문입니다. Vite는 esbuild를 사용해서 의존성을 미리 하나의 번들로 만들어두고, 캐싱해서 재사용합니다.
요약하자면 다음과 같습니다.
전통적 번들러: Entry → 전체 번들링 → 브라우저
Vite 개발 모드: Entry → 브라우저 → 필요한 모듈만 요청 시 변환
프로덕션 빌드에서는 여전히 번들링이 필요합니다. 개발 모드처럼 모듈을 개별 요청하면 네트워크 요청이 너무 많아지기 때문입니다. Vite는 프로덕션 빌드에 Rollup을 사용합니다. Rollup의 뛰어난 Tree Shaking과 작은 번들 크기를 그대로 활용할 수 있습니다.
이 점이 Vite의 독특한 특징입니다. 개발 환경과 빌드 환경에서 다른 도구를 사용합니다. 개발에서는 Native ESM + esbuild, 빌드에서는 Rollup입니다
@vitejs/plugin-legacy 플러그인이 필요합니다.개인적으로 각 번들러를 선택하는 기준은 다음과 같습니다.
Webpack을 선택해야 할 때
Rollup을 선택해야 할 때
Vite를 선택해야 할 때