JavaScript/TypeScript 번들러 및 빌드 도구 비교 학습 프로젝트. Bun 워크스페이스를 활용해 7개 도구를 동일한 소스 코드로 빌드하고 결과를 비교합니다.
결론부터 말하면 엄밀히는 아닙니다. 역할에 따라 두 가지 기준으로 나뉩니다.
| 도구 | 분류 | 설명 |
|---|---|---|
| webpack | 번들러 | 모듈 번들링을 직접 수행 |
| rollup | 번들러 | 모듈 번들링을 직접 수행 |
| esbuild | 번들러 + 트랜스파일러 | 번들링과 TS 변환을 직접 수행 |
| rolldown | 번들러 | 모듈 번들링을 직접 수행 (Rust) |
| vite | 빌드 도구 | Vite 6: esbuild+rollup / Vite 8: rolldown+Oxc로 통합 |
| tsup | 빌드 도구 | 내부적으로 esbuild 사용 |
| tsdown | 빌드 도구 | 내부적으로 rolldown 사용 |
| 도구 | 용도 | HMR | 개발 서버 |
|---|---|---|---|
| webpack | 앱 빌드 | ✅ | ✅ (webpack-dev-server) |
| rollup | 라이브러리 빌드 | ❌ | ❌ |
| esbuild | 라이브러리 빌드 | ❌ | ❌ (--serve 있지만 HMR 없음) |
| vite | 앱 빌드 | ✅ | ✅ |
| tsup | 라이브러리 빌드 | ❌ | ❌ |
| rolldown | 라이브러리 빌드 | ❌ | ❌ |
| tsdown | 라이브러리 빌드 | ❌ | ❌ |
번들러(Bundler): 여러 JS/TS 모듈 파일을 분석해 하나(또는 소수)의 파일로 합치는 도구.
빌드 도구(Build tool): 번들러를 내부적으로 호출하면서 추가 작업(.d.ts 생성, 포맷 변환, clean 등)을 처리해주는 상위 도구.
일상적인 대화에서는 7개 모두를 묶어서 "번들러"라고 부르는 경우가 많고, 이 문서에서도 편의상 그렇게 표현합니다.
가장 중요한 구분입니다. 비교 표에서 tsdown이 ESM·CJS·.d.ts·sourcemap을 전부 지원한다고 해서 vite나 Next.js를 대체할 수 있는 게 아닙니다. 그 체크박스들은 전부 "npm 패키지를 어떤 형식으로 만들어내느냐" 에 관한 것이기 때문입니다.
| 기능 | tsdown (라이브러리) | vite (앱) |
|---|---|---|
| 개발 서버 (localhost) | ❌ | ✅ |
| HMR | ❌ | ✅ |
| HTML 진입점 처리 | ❌ | ✅ |
| CSS / 이미지 / 폰트 처리 | ❌ | ✅ |
| React/Vue 컴포넌트 HMR | ❌ | ✅ (플러그인) |
| 브라우저 코드 분할 (lazy loading) | ❌ | ✅ |
환경변수 브라우저 주입 (.env) |
❌ | ✅ |
| ESM 출력 | ✅ | ✅ |
| CJS 출력 | ✅ | ✅ |
.d.ts 생성 |
✅ | ✅ (플러그인) |
tsdown은 브라우저가 없습니다. 입력은 .ts 파일이고 출력은 dist/index.js 파일입니다. 거기서 끝입니다. 반면 vite가 만들어내는 dist/는 브라우저에서 바로 열 수 있는 앱입니다.
비유: tsdown은 인쇄소 (원고 → 책), vite는 서점 + 인쇄소 (책도 찍고, 손님이 와서 바로 읽는 공간도 제공).
두 도구는 경쟁 관계가 아닙니다. React 앱 안에서 직접 만든 npm 라이브러리를 쓴다면, 라이브러리는 tsdown으로 빌드하고 앱은 vite로 개발하는 식으로 함께 씁니다.
HMR(Hot Module Replacement) 은 개발 중 파일을 저장하면 페이지 전체를 새로고침하지 않고 변경된 모듈만 교체해주는 기능입니다. 상태(state)가 유지된 채로 코드 변경이 반영됩니다.
7개 도구 중 HMR을 지원하는 것은 webpack과 vite 둘뿐입니다. 나머지는 --watch 옵션이 있어도 그것은 단순히 파일 변경 시 dist/를 재생성하는 것이지, 브라우저와 연결되어 있지 않습니다.
webpack HMR vs vite HMR:
webpack HMR
파일 변경 → 영향받는 모듈 전체 재번들링 → 브라우저에 전달
(번들링 과정이 있어 느림)
vite HMR
파일 변경 → 변경된 파일 하나만 esbuild로 변환 → 브라우저에 바로 전달
(번들링 없이 네이티브 ESM으로 서빙하므로 빠름)
프로젝트 규모가 커질수록 webpack은 HMR도 느려지지만 vite는 거의 일정하게 빠릅니다. 이것이 vite가 webpack을 빠르게 대체한 핵심 이유입니다.
모던 JS/TS 개발에서 번들러가 필요한 이유는 크게 세 가지 문제에서 출발합니다.
코드를 파일로 분리해서 작성하면 import './math.ts' 같은 구문을 사용하게 됩니다.
그런데 브라우저는 보안상 로컬 파일 시스템에 직접 접근할 수 없고,
서버에서 파일을 받아올 때도 파일 하나마다 네트워크 요청 1번이 발생합니다.
파일 100개짜리 프로젝트 → 네트워크 요청 100번 → 페이지 로딩 느림
번들러는 이 수백 개의 파일을 빌드 시점에 하나(또는 소수)의 파일로 합쳐 요청 횟수를 줄입니다.
브라우저와 Node.js의 런타임은 .ts 파일을 직접 실행할 수 없습니다.
TypeScript 코드는 반드시 JavaScript로 변환(트랜스파일) 되어야 합니다.
번들러(또는 번들러에 통합된 트랜스파일러)가 빌드 시점에 이 변환을 담당합니다.
src/index.ts → 번들러 → dist/index.js → 브라우저/Node.js 실행 가능
npm에 올린 패키지를 사용하는 환경은 다양합니다.
- ESM (
import) 환경 — 최신 번들러, 브라우저 native module - CJS (
require) 환경 — 구형 Node.js, Jest 등
번들러 없이 TypeScript 소스를 그대로 배포하면 대부분의 환경에서 바로 사용할 수 없습니다.
번들러는 하나의 소스 코드에서 ESM · CJS 두 포맷을 동시에 생성해주고, .d.ts 타입 선언 파일도 함께 만들어줍니다.
| 기능 | 설명 |
|---|---|
| Tree-shaking | 사용하지 않는 코드를 번들에서 자동 제거해 파일 크기 감소 |
| Minification | 공백·주석 제거, 변수명 단축으로 파일 크기 추가 감소 |
| 환경 변수 인라인 | process.env.NODE_ENV 같은 값을 빌드 시점에 치환 |
| 코드 분할(Code splitting) | 앱을 여러 청크로 나눠 필요한 시점에만 로드 |
출시 순서 기준으로 정렬되어 있습니다.
packages/
├── webpack-example/ 번들러 (2012) — loader 시스템, 앱 번들의 시작
├── rollup-example/ 번들러 (2015) — ES 모듈 중심, 정교한 tree-shaking
├── esbuild-example/ 번들러 (2020) — Go 기반, 극속
├── vite-example/ 빌드 도구 (2020) — 개발 서버 + 프로덕션 빌드 통합
├── tsup-example/ 빌드 도구 (2021) — esbuild 기반, TS 라이브러리용
├── rolldown-example/ 번들러 (2023) — Rust 기반, rollup 호환 후계자
└── tsdown-example/ 빌드 도구 (2024) — rolldown 기반, tsup의 후계자
모든 패키지는 동일한 src/ 파일을 사용합니다.
src/
├── math.ts 순수 함수 모음 (add, subtract, multiply, divide, factorial, calculate)
├── greet.ts 다국어 인사 함수 (greet, greetAll)
└── index.ts re-export 진입점 — tree-shaking 비교용
비교 의도: 동일한 소스를 각 도구로 빌드해 출력 크기, 형식, .d.ts 생성 여부, 빌드 속도를 직접 비교합니다.
bun install
# 전체 빌드
bun run build
# 개별 빌드 (출시 순서)
bun run build:webpack
bun run build:rollup
bun run build:esbuild
bun run build:vite
bun run build:tsup
bun run build:rolldown
bun run build:tsdown번들러 | 2012 | 앱 빌드 | HMR ✅ | 독립 설계 | Loader 시스템
- 공식 문서
- 최초의 현대적 JS 번들러. CommonJS 모듈 시스템 기반으로 설계됨
- Loader 시스템: 파일 형식별 변환 파이프라인 — TypeScript는
ts-loader필요 - Plugin 시스템: 번들 최적화, 코드 분할, HMR 등 앱 개발 전반을 담당
- HMR:
webpack-dev-server를 통해 지원. 변경 시 영향받는 모듈을 재번들링 후 교체 - 번들에 webpack 자체 런타임 코드 포함 (자체 모듈 시스템 구현체)
- rollup/rolldown과 완전히 독립적인 아키텍처
핵심 옵션:
// webpack.config.js (ESM — package.json에 "type": "module" 지정)
import path from 'path'
import { fileURLToPath } from 'url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
export default {
entry: './src/index.ts',
module: {
rules: [
{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ },
],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
library: { type: 'module' },
clean: true,
},
experiments: { outputModule: true },
mode: 'production',
}특이사항:
- 설정 파일 형식:
"type": "module"+.js→ ESM /"type"없음 +.cjs→ CJS 둘 다 가능 - webpack 자체는 CJS 기반으로 설계됐지만, 설정 파일은 ESM으로 작성 가능 (공식 지원)
production모드는 자동으로 minify + tree-shaking 적용- 단일
bundle.js출력이 기본 — 라이브러리보다 앱 번들에 적합 - 프로젝트가 커질수록 HMR 속도가 느려짐 → 이것이 vite 등장 배경
번들러 | 2015 | 라이브러리 빌드 | HMR ❌ | ES 모듈 중심
- 공식 문서
- ES 모듈 명세를 처음부터 고려해 설계된 번들러
- tree-shaking이 가장 정교 — 정적 분석으로 사용하지 않는 export를 정밀하게 제거
- TypeScript 지원은
@rollup/plugin-typescript플러그인 필요 .d.ts생성은rollup-plugin-dts플러그인 필요- 설정 파일은
.js/.mjs권장 (.ts설정 파일은 별도 플러그인 필요) --watch는 있지만 개발 서버/HMR 없음 — 라이브러리 재빌드 용도
핵심 옵션:
// rollup.config.js
export default [
{
input: 'src/index.ts',
output: [
{ file: 'dist/index.mjs', format: 'es' },
{ file: 'dist/index.cjs', format: 'cjs' },
],
plugins: [typescript()],
},
// .d.ts 번들을 별도 패스로 생성
{
input: 'src/index.ts',
output: { file: 'dist/index.d.ts', format: 'es' },
plugins: [dts()],
},
]지원 출력 포맷: es · cjs · umd · iife · amd · system
특이사항:
- TypeScript 처리를 직접 설정해야 하는 만큼, 번들러가 내부적으로 무슨 일을 하는지 가장 명확하게 배울 수 있음
- 플러그인 생태계가 풍부하고, rolldown이 이 플러그인 API를 그대로 계승
번들러 + 트랜스파일러 | 2020 | 라이브러리 빌드 | HMR ❌ | Go 기반 | 극속
- 공식 문서
- Go로 작성. 기존 번들러 대비 10~100배 빠른 빌드 속도
- TypeScript 트랜스파일 내장 (타입 검사는 하지 않음)
.d.ts생성 불가 —tsc --emitDeclarationOnly별도 실행 필요- 설정 파일 개념 없음 → JS/TS API를 직접 호출하는 빌드 스크립트를 작성
--serve옵션으로 파일 서빙은 가능하지만 HMR은 없음- CLI와 JS API 두 가지 방식 제공
핵심 옵션:
// build.ts
await esbuild.build({
entryPoints: ['src/index.ts'],
bundle: true,
format: 'esm', // 'esm' | 'cjs' | 'iife'
outfile: 'dist/index.mjs',
platform: 'node', // 'browser' | 'node' | 'neutral'
sourcemap: true,
minify: true,
})특이사항:
- CJS 출력 시
__toESM,__toCommonJS헬퍼가 자동 삽입되어 ESM보다 파일이 큼 - 타입 검사가 없어 빌드는 빠르지만, 타입 오류가 런타임까지 넘어갈 수 있음
- tsup의 내부 엔진으로 채택되어 실질적으로 영향력을 넓힘
빌드 도구 | 2020 | 앱 빌드 | HMR ✅ | 개발 서버 + 프로덕션 빌드 통합
- 공식 문서
- 빠른 개발 서버(HMR)와 프로덕션 빌드를 하나의 도구로 제공
- HMR: 변경된 파일 하나만 변환해서 브라우저에 전달 — 프로젝트 크기와 무관하게 빠름
- Library Mode를 통해 npm 라이브러리 배포용 번들도 생성 가능
.d.ts생성은vite-plugin-dts플러그인 필요
Vite 내부 아키텍처 변천 (중요):
Vite는 역사적으로 내부에서 두 개의 다른 도구를 동시에 사용해왔습니다.
Vite 1~6 (기존 구조) ─────────────────────────────────────────
개발 서버 deps 사전 번들링 → esbuild (빠른 변환용)
프로덕션 빌드 → rollup (최적화 번들링용)
두 파이프라인의 동작 차이로 dev/prod 불일치 문제 발생
공식 문서 직접 인용:
"Vite originally relied on two separate tools: esbuild for fast compilation during development, and Rollup for thorough optimization in production builds. This worked, but maintaining two pipelines introduced inconsistencies: different transformation behaviors, separate plugin systems, and growing glue code to keep them aligned."
rolldown은 바로 이 문제를 해결하기 위해 만들어졌습니다.
Vite 7 (rolldown-vite 실험 패키지) / Vite 8 (정식 통합) ──────────
개발 서버 deps 사전 번들링 → rolldown (esbuild 대체)
프로덕션 빌드 → rolldown (rollup 대체)
문법 변환 / JS 압축 → Oxc (esbuild 대체)
CSS 압축 → Lightning CSS (esbuild 대체)
단일 파이프라인 → dev/prod 동작 일치
rollupOptions → rolldownOptions (Vite 8에서 rollupOptions deprecated)
현재(2025) 상태: Vite 8에서 rolldown이 정식 통합됨. Vite 7에서
rolldown-vite별도 패키지로 실험 운용 후 Vite 8에 메인 병합.rollupOptions는 Vite 8에서 deprecated →rolldownOptions로 변경.
핵심 옵션 (Library Mode, Vite 8 기준):
// vite.config.ts
export default defineConfig({
plugins: [dts({ include: ['src'] })],
build: {
lib: {
entry: 'src/index.ts',
formats: ['es', 'cjs'],
fileName: (format) => format === 'es' ? 'index.js' : 'index.cjs',
},
// Vite 8: rolldownOptions (rolldown 정식 통합)
// Vite 6 이하: rollupOptions
rolldownOptions: {
external: [],
},
},
})특이사항:
- webpack과의 핵심 차이: 개발 서버에서 번들링을 하지 않아 HMR이 훨씬 빠름
- 내부적으로 rollup/rolldown을 쓰지만 추상화로 숨겨져 있어, 저수준 제어는 어려움
vite-plugin-dts가 생성한.d.ts는 파일별로 분산됨 (tsup/tsdown처럼 단일 파일 번들 아님)- Vite 팀은 향후 개발 서버도 번들링하는 "Full Bundle Mode" 도입 예정 (dev/prod 완전 일치)
빌드 도구 | 2021 | 라이브러리 빌드 | HMR ❌ | esbuild 기반
- 공식 문서
- esbuild 위에서 동작하는 라이브러리 번들링 도구
- esbuild가 해주지 않는 것들을 추가:
.d.ts생성, ESM+CJS 동시 출력, external 자동 감지 --watch는 파일 변경 시dist/재생성만 함 — HMR·개발 서버 없음
tsup이 만들어진 이유: 2021년 당시 rolldown은 존재하지 않았고, esbuild는 너무 저수준이라 라이브러리 배포에 필요한 편의 기능이 없었습니다. tsup은 그 공백을 채우기 위해 만들어졌습니다.
⚠️ 주의: 공식 README에 "not actively maintained anymore" 명시. tsdown으로 이전 권장
핵심 옵션:
// tsup.config.ts
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true, // .d.ts 자동 생성
clean: true, // 빌드 전 dist/ 정리
sourcemap: true,
})특이사항:
- tsdown과 설정 구조가 거의 동일 — 두 파일을 나란히 비교해보면 차이가 명확히 보임
- 내부 엔진(esbuild vs rolldown)의 차이가 출력 결과와 속도에 영향을 줌
번들러 | 2023 | 라이브러리 빌드 | HMR ❌ | Rust 기반 | rollup 호환 후계자
- 공식 문서
- VoidZero(Vite 팀)가 Rust로 개발한 차세대 번들러
- rollup API 완전 호환 — 동일한 config 구조, 기존 rollup 플러그인 재사용 가능
- TypeScript 내장 지원 (Oxc 기반) —
@rollup/plugin-typescript불필요 - Vite 7+의 기반 번들러로 채택됨
.d.ts생성은 미지원 → 라이브러리 번들링은 tsdown 사용 권장 (공식 문서 명시)--watch는 있지만 개발 서버/HMR 없음
핵심 옵션:
// rolldown.config.ts
import { defineConfig } from 'rolldown'
export default defineConfig([
{
input: 'src/index.ts',
output: { file: 'dist/index.mjs', format: 'esm' },
},
{
input: 'src/index.ts',
output: { file: 'dist/index.cjs', format: 'cjs' },
},
])특이사항:
- 설정 구조는 rollup과 거의 동일하지만 빌드 속도는 압도적 (아래 속도 비교 참고)
- esbuild와 속도가 비슷하면서 rollup의 플러그인 생태계 호환
- rolldown은 엔진이고 tsdown은 그 엔진을 탑재한 완성차 — 직접 쓸 일은 많지 않음
빌드 도구 | 2024 | 라이브러리 빌드 | HMR ❌ | rolldown 기반 | tsup의 공식 후계자
- 공식 문서
- VoidZero/Rolldown 팀이 만든 tsup의 공식 후계자
- rolldown 기반 → Rust 성능 / Oxc 기반
.d.ts생성 (tsup보다 빠름) - tsup과 설정 구조가 거의 동일 —
.d.ts생성, ESM+CJS 동시 출력, external 자동 감지 --watch는 파일 변경 시dist/재생성만 함 — HMR·개발 서버 없음- Rolldown Vite의 Library Mode 기반이 될 예정
⚠️ 주의: Node.js 22.18.0+ 필요. Bun 지원은 experimental.
핵심 옵션:
// tsdown.config.ts
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
sourcemap: true,
})특이사항:
- tsup과 설정이 거의 동일하지만 내부 엔진이 rolldown
- 현재(v0.12.x)
.d.ts파일명에 해시가 포함됨 (index-NRoTx8IK.d.ts) — 버전에 따라 변경될 수 있음
이 표는 "npm 라이브러리 배포용 출력물" 기준의 비교입니다. HMR·개발 서버 등 앱 개발 기능과는 별개입니다.
| 도구 | ESM | CJS | .d.ts | sourcemap |
|---|---|---|---|---|
| webpack | ❌ | ❌ | ❌ | ❌ |
| rollup | index.mjs |
index.cjs |
✅ 단일 파일 | ✅ |
| esbuild | index.mjs |
index.cjs |
❌ | ❌ |
| vite | index.js |
index.cjs |
✅ 파일별 분산 | ❌ |
| tsup | index.js |
index.cjs |
✅ 단일 파일 | ✅ |
| rolldown | index.mjs |
index.cjs |
❌ | ✅ |
| tsdown | index.js |
index.cjs |
✅ 단일 파일 | ✅ |
webpack은 단일 bundle.js 출력 (ESM/CJS 분리 없음, minified)
| 도구 | ESM | CJS |
|---|---|---|
webpack bundle.js |
560 B (minified) | — |
| rollup | 1,219 B | 1,367 B |
| esbuild | 898 B | 2,101 B |
| vite | 735 B | 673 B |
| tsup | 931 B | 2,135 B |
| rolldown | 1,038 B | 1,244 B |
| tsdown | 1,037 B | 1,244 B |
esbuild/tsup CJS가 유독 큰 이유: CJS 출력 시 __toESM, __toCommonJS interop 헬퍼를 자동 삽입합니다. rollup/rolldown 계열은 더 경량의 CJS 래퍼를 생성합니다.
vite가 작은 이유: Library Mode에서 rollup의 tree-shaking + minification이 기본 적용됩니다.
| 도구 | 빌드 시간 | 비고 |
|---|---|---|
| esbuild | ~즉시 | Go 네이티브 바이너리 |
| rolldown | ~9 ms | Rust 네이티브 바이너리 |
| vite | ~530 ms | rollup 내부 사용 + dts |
| tsup | ~650 ms | esbuild + dts 생성 |
| rollup | ~1,000 ms | JS 엔진, dts 포함 |
| webpack | ~1,000 ms | JS 엔진 |
| tsdown | ~1,500 ms | rolldown + Oxc dts 생성 |
[앱 빌드 도구] [라이브러리 빌드 도구]
HMR / 개발 서버 있음 HMR 없음 / npm 배포용
2012 webpack ────────────────────────────────────────────────────────
(CommonJS 중심, 독자 아키텍처)
2015 rollup ──────────────────────────────────── (라이브러리 번들러)
(ES 모듈 중심) │
▼
2020 esbuild ────────────────────────────── tsup (2021, esbuild 래퍼)
(Go, 극속) ↗ dep 사전 번들링 │ deprecated
│ ▼
2020 vite ─┤─────────────────────────────────── tsdown (2024, rolldown 래퍼)
v1~6│ 개발 서버: esbuild (변환) + 네이티브 ESM ▲
│ 프로덕션: rollup (번들링) │
│ → 두 파이프라인 → dev/prod 불일치 문제 │
│ │
2023 └──────────── rolldown ────────────────────────────┘
(Rust, rollup 호환) Vite 7+에서 esbuild + rollup 둘 다 대체
단일 파이프라인 → dev/prod 일치
Oxc가 변환·압축 담당
| 상황 | 추천 | 이유 |
|---|---|---|
| React/Vue 앱 개발 | vite | HMR, 개발 서버, 플러그인 생태계 |
| npm 라이브러리 배포 | tsdown | .d.ts, ESM+CJS, rolldown 성능 |
| 레거시 앱 (webpack 이미 사용 중) | webpack 유지 | 마이그레이션 비용 > 이점 |
| npm 라이브러리 (레거시) | tsup | tsdown 이전 전 유지보수 |
| 번들러 내부 동작 학습 | esbuild 또는 rollup | 저수준, 설정 직접 제어 |
| Vite/rollup 플러그인 개발 | rolldown | 동일한 플러그인 API |
| 번들러 없이 빠르게 실행 | Bun (bun build) |
별도 설정 불필요 |
| 도구 | 문서 | 저장소 |
|---|---|---|
| webpack | webpack.js.org | webpack/webpack |
| rollup | rollupjs.org | rollup/rollup |
| esbuild | esbuild.github.io | evanw/esbuild |
| vite | vite.dev | vitejs/vite |
| tsup | tsup.egoist.dev | egoist/tsup |
| rolldown | rolldown.rs | rolldown/rolldown |
| tsdown | tsdown.dev | rolldown/tsdown |