✍️ 필사 모드: 프론트엔드 번들러 전쟁 완전 정복 — Webpack, Vite, esbuild, Turbopack, Rspack, Rolldown, Bun (2025)
한국어"We spent 10 years teaching browsers to understand JavaScript. Then we spent another 10 years teaching build tools to undo what browsers learned." — Sebastian McKenzie (Babel, Yarn, Rome 창시자)
2015년 웹팩 설정 파일을 처음 열어본 개발자들의 공통된 반응은 "이게 뭐야?"였다. 500줄짜리 webpack.config.js, loader와 plugin의 무한 조합, 빌드 한 번에 10분. 10년이 지난 2025년, 우리는 Vite로 프로젝트를 시작하고 3초 만에 개발 서버를 띄운다. 이 극적인 변화 뒤에는 무엇이 있었나?
이 글은 번들러의 탄생부터 Rust 혁명, 그리고 2025년 RolldownBun 시대까지의 프론트엔드 빌드 진화사를 파헤친다.
1. 태초에 script 태그가 있었다
2009년 이전 — 파일 나열의 시대
<script src="jquery.js"></script>
<script src="jquery-ui.js"></script>
<script src="myapp-utils.js"></script>
<script src="myapp.js"></script>
- 순서가 깨지면 버그
- 전역 변수 오염
- 수십 개 요청 → 성능 지옥
- 의존성 관리 불가
CommonJS — Node.js의 선물 (2009)
const _ = require('lodash');
module.exports = { ... };
Node가 서버에서 잘 작동했지만, 브라우저는 require를 모른다. 누군가 이를 브라우저에서 돌리는 법을 만들어야 했다.
Browserify — 번들링의 탄생 (2011)
James Halliday(substack)가 만든 도구. require가 있는 Node 코드를 하나의 큰 JS 파일로 묶어 브라우저에 넘긴다.
browserify main.js -o bundle.js
"모듈을 합친다"는 개념의 시작. 모든 이후 번들러의 조상.
2. Webpack의 왕좌 (2014-2020)
등장
Tobias Koppers가 2012년에 시작. 2014년 v1.0. 2016-2020년 사이 사실상 독점.
왜 승자가 되었나
- 모든 것이 모듈 — JS뿐 아니라 CSS, 이미지, 폰트까지
import가능 - Code Splitting — 동적
import()로 청크 분할 - Loader 생태계 — babel-loader, css-loader, file-loader, 수천 개
- Plugin 시스템 — HtmlWebpackPlugin, MiniCssExtract 등
- Dev Server + HMR — 수정하면 페이지 새로고침 없이 반영
Webpack의 철학 — Dependency Graph
entry 지점에서 시작해 모든 import를 따라가며 의존성 그래프를 만든다. 그래프의 각 노드가 모듈이고, 각 모듈은 loader 체인을 거쳐 JS로 변환된다.
악명 — 복잡한 설정
module.exports = {
entry: './src/index.js',
output: { ... },
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' },
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.(png|jpg)$/, type: 'asset/resource' },
]
},
plugins: [
new HtmlWebpackPlugin({ template: './src/index.html' }),
new MiniCssExtractPlugin(),
],
optimization: { splitChunks: { chunks: 'all' } },
resolve: { alias: { ... }, extensions: [...] },
};
10줄이 50줄이 되고 500줄이 된다. "Webpack Config Engineer"라는 농담이 생긴 이유.
한계 — 왜 내려가는가
- JavaScript 기반 — V8 한계, 싱글 스레드
- 재빌드 속도 — 큰 프로젝트에서 HMR도 느림
- 번들 최적화 복잡 — tree-shaking이 제대로 안 되는 경우 많음
- 문서가 방대 — 초심자 진입장벽
3. Rollup — 라이브러리의 표준
등장
Rich Harris(Svelte 창시자)가 2015년에 시작. 목표는 라이브러리 번들링.
왜 Rollup인가
- ES Module 우선 — Webpack은 CommonJS 호환성 위주, Rollup은 ESM 네이티브
- Tree-shaking 탁월 — 사용 안 하는 export 제거
- 출력 포맷 다양 — ESM, CJS, UMD, IIFE 모두
- 작고 깨끗한 번들
한계
- 애플리케이션 용도 아님 — Code splitting이 초기엔 약했음
- Dev server 자체 제공 X — Vite가 이 역할
- 플러그인 생태계 작음 (Webpack 대비)
현실
- 라이브러리는 Rollup (React, Vue, Svelte 모두)
- 애플리케이션은 Webpack 또는 Vite
- Vite가 내부적으로 Rollup을 빌드 시 사용
4. esbuild — Go가 연 속도 혁명 (2020)
등장
Evan Wallace(Figma CTO)가 2020년 공개. Go로 작성.
속도 — 100배
Webpack: 20초
esbuild: 0.2초
같은 프로젝트를 빌드했을 때 이런 차이가 실제로 난다.
어떻게 이렇게 빠른가
- Go — 네이티브 컴파일, JS보다 10-100배 빠름
- 병렬화 — Go의 goroutine으로 파일을 병렬 파싱/링크
- 최소한의 abstraction — 순수 파싱 → 번들링 직선형
- 단일 패스 알고리즘 — 스캔/변환/링크를 한 번에
- 메모리 공유 — 파싱 결과를 스레드 간 공유
esbuild의 함정
- 플러그인 API 제한 — Webpack 수준의 복잡한 변환 어려움
- Dev server 간단 — 고급 HMR 없음
- Code splitting 제한적
- CSS 처리 기본만 — PostCSS 체인 복잡
용도
- Vite의 의존성 프리번들 —
node_modules를 ESM으로 변환 - Bun/Deno의 내부 번들러
- 간단한 라이브러리 빌드
- 테스트 러너 트랜스파일 (Vitest, Jest에 통합)
5. Vite — Evan You의 "개발은 ESM, 빌드는 Rollup" (2020)
아이디어의 단순함
Evan You가 Vue 3 작업 중 Webpack 느림에 질려 만든 도구:
- 개발 중: 브라우저의 네이티브 ESM을 활용 → 번들링 안 함
- 프로덕션: Rollup으로 최적 번들
Native ESM Dev Server
<script type="module" src="/src/main.js"></script>
브라우저가 import 구문을 직접 처리. Vite는:
.vue,.tsx등을 on-demand로 ESM JS로 변환 (esbuild 사용)- 브라우저가 필요한 파일만 요청
- 수정 시 해당 모듈만 다시 보냄 (HMR)
→ 초기 로드 수 초, HMR 수십 ms
왜 혁명이었나
- Webpack/Rollup이 모든 파일을 번들하는 동안, Vite는 필요한 것만 변환
- 규모에 무관하게 빠름 (파일 1000개나 10000개나 비슷)
- 설정 거의 없음 (컨벤션 중심)
Vite의 구성
- 개발: esbuild로 의존성 프리번들 + 소스 on-demand 변환
- 프로덕션: Rollup으로 풀 번들
- 플러그인 API: Rollup 플러그인과 호환
현재 — 기본 선택
- React, Vue, Svelte 모두 템플릿 제공
- Nuxt, SvelteKit, Remix, Astro 내부에 Vite
- 2024년 Stack Overflow 조사에서 Webpack 사용률을 넘어섰다
문제점
- esbuild(개발) + Rollup(빌드) 이중 체인 — 두 도구의 차이로 버그
- 큰 프로덕션 빌드가 여전히 느림 — Rollup 한계
- 플러그인 호환성 갈등
→ 이 문제가 Rolldown 등장의 원인.
6. Turbopack — Vercel의 Rust 베팅 (2022)
등장
Next.js 팀이 Webpack 후속작으로 Rust로 작성. 2022년 알파, 2024년 안정, 2025년 Next.js 15에서 기본 기본값 목표.
핵심 아이디어 — Incremental Computation
Tobias Koppers(Webpack 창시자)가 Vercel 합류 후 이끄는 프로젝트. 아이디어의 핵심:
"변경된 것만 다시 계산한다."
함수 수준에서 캐싱. f(x, y)를 호출했는데 x만 바뀌면, y 관련 계산은 재사용.
Turbo Engine
- Rust로 작성된 범용 incremental computation 프레임워크
- Turbopack은 이 엔진 위의 한 응용
- 빌드만이 아니라 테스트, 린트, 타입체크도 가능
성능 주장
- "Webpack 대비 700배 빠른 콜드 스타트"
- "Vite 대비 10배 빠른 HMR"
- 단, 벤치마크 논쟁이 있음 — 커뮤니티에서 반박 글도 많이 나옴
한계
- Next.js 전용 (현재)
- 플러그인 생태계 미숙
- 자체 프로젝트에 바로 도입 불가
7. Rspack — 바이트댄스의 Rust Webpack (2023)
등장
TikTok 모회사 바이트댄스가 "Webpack 설정 그대로, 속도는 10배"를 목표로 만듦.
핵심 — Webpack API 호환
- 기존 Webpack config와 대부분 호환
- loader/plugin 대부분 호환
- 마이그레이션 비용 극소
어떻게 10배 빠른가
- Rust 네이티브
- 병렬 컴파일
- 캐시 친화적 자료구조
- SWC를 트랜스파일러로 내장
빠른 채택
- Discord, ByteDance 내부, 수많은 중국/글로벌 회사
- 모듈 페더레이션 지원 (Webpack의 킬러 기능)
- 2024년 v1.0 릴리스
Rsbuild / Rslib
- Rspack 기반 빌드 도구 (Vite 스타일 DX)
- 라이브러리용 Rslib
8. Rolldown — Vite의 다음 진화 (2024-2025)
왜 필요한가
Vite는 개발용 esbuild + 빌드용 Rollup 이중 체인:
- 두 도구의 파싱 차이로 미묘한 버그
- 큰 빌드는 여전히 느림 (Rollup JS)
해결 — 하나의 Rust 번들러
- Evan You 팀 + Void(0) 재단이 주도
- Rollup API + 속도 (10-100배)
- 개발과 빌드 모두 같은 도구
성능
- Rollup 대비 10-30배 빠른 빌드
- esbuild 대비 약간 느리지만 기능 훨씬 풍부
현재 상태 (2025)
- Alpha → Beta
- Vite 6에서 점진적 통합
- 2026년 Vite 7에서 기본값 목표
의의
- Vite가 완성되는 마지막 조각
- 프론트엔드 빌드의 단일 표준이 될 가능성
9. Bun — 런타임 + 번들러 + 패키지 매니저
등장
Jarred Sumner가 2022년 공개. Zig로 작성.
통합 전략
한 도구로:
- Node.js 대체 런타임
- npm/pnpm/yarn 대체 패키지 매니저
- 번들러 (esbuild 수준 속도)
- 테스트 러너 (Jest 호환)
- 트랜스파일러 (TS/JSX 네이티브)
bun install — 30배 빠른 npm
npm install: 12초
bun install: 0.4초
병렬 다운로드 + 네이티브 symlink 조작 + 캐시 최적화.
번들러
- Go/Rust 아닌 Zig지만 유사 속도
- ESM/CJS 모두
- 2024년 안정화
현실
- 서버 런타임으로는 생산 사례 증가
- 번들러로는 Vite/Turbopack 대비 아직 생태계 약함
- 그러나 올인원 DX는 독보적
10. Tree-shaking의 과학
아이디어
// utils.js
export function a() { return 1 }
export function b() { return 2 }
// main.js
import { a } from './utils'
→ b는 사용되지 않음 → 번들에서 제거.
ESM이 열쇠
- 정적 분석 가능 (import/export가 top-level)
- CommonJS의
require('x').y는 동적이라 불가
Side Effects
// package.json
"sideEffects": false // 또는 ["*.css"]
번들러에게 "import만 해도 부수효과 없다"고 알림. false가 기본이 아님 → 라이브러리가 명시해야 함.
Tree-shaking이 실패하는 이유
- CommonJS 의존성 — require는 정적 분석 불가
- 사이드 이펙트 못 증명 —
new Class()의 효과? - Polyfill을 전역에 주입
- Barrel file (
index.ts에서 re-export) — 느린 해결, Turbopack/Rspack이 최적화
실전 검증
npx webpack-bundle-analyzer stats.json
# 또는 rollup-plugin-visualizer
시각화로 진짜 불필요한 게 들어갔는지 확인. 사용도 안 하는 moment.js가 전체 번들의 30%인 경우가 흔함.
11. Code Splitting — 분할의 예술
Dynamic Import
const Chart = React.lazy(() => import('./Chart'))
→ Chart는 별도 청크로 분리, 필요할 때 네트워크 로드.
라우트 기반 분할
- Next.js, Nuxt, SvelteKit이 자동 처리
- 페이지마다 별도 청크
Vendor Splitting
node_modules→vendor.js로 분리- 긴 캐시 기간 (라이브러리는 잘 안 바뀜)
- 앱 코드와 별도 투명
너무 많은 분할의 함정
- HTTP/1.1에서는 요청당 오버헤드 큼
- HTTP/2/3에서는 괜찮지만 여전히 총 요청 수 주의
- 스위트 스팟: 청크당 20-100KB
12. HMR (Hot Module Replacement) — 새로고침 없는 개발
기본 원리
- Dev server가 파일 변경 감지
- WebSocket으로 브라우저에 알림
- 브라우저가 변경된 모듈만 다시 요청
- 상태 유지하면서 교체
React Fast Refresh
- React 컴포넌트를 교체하면서 state 보존
useState값이 유지됨- Vite/Turbopack/Rspack 모두 네이티브 지원
왜 어려운가
- 어떤 변경이 "안전히 교체 가능"한지 판단
- 의존성 트리 따라 re-validate
- 부수 효과 처리 (timers, subscriptions)
초기 vs 점진 업데이트
- 초기: 전체 번들 로드 (Vite는 없음, 네이티브 ESM)
- 점진: 변경된 모듈만 (WebSocket)
13. Module Federation — 마이크로 프론트엔드
개념
- 런타임에 다른 앱의 모듈을 가져옴
- 서로 다른 팀이 독립 배포
- Shell + micro-frontend 구조
Webpack 5 (2020)이 최초 네이티브 지원
new ModuleFederationPlugin({
name: 'shell',
remotes: {
checkout: 'checkout@https://cdn.example.com/checkout/remoteEntry.js'
}
})
현재 — Rspack, Vite도 지원
- Rspack은 Webpack 호환이라 그대로
- Vite는
@originjs/vite-plugin-federation
트레이드오프
- 장점: 독립 배포, 팀 자율성, 점진 마이그레이션
- 단점: 런타임 복잡성, 공유 의존성 버전 충돌, 디버그 어려움
14. Import Maps & ESM in Production
Import Map
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18",
"lodash-es/": "https://esm.sh/lodash-es/"
}
}
</script>
<script type="module">
import React from 'react'
</script>
- 브라우저 네이티브 (2023년부터 모든 주요 브라우저)
- 번들 없는 프로덕션 가능
- CDN(esm.sh, jsdelivr)에서 바로 로드
Deno / Bun과 통합
- Deno는 URL import가 기본
- Bun도 지원
여전히 번들러가 필요한 이유
- 요청 수 최소화 (HTTP/2도 한계)
- Tree-shaking, minify
- Code splitting 최적화
- 레거시 브라우저 지원
→ 2030년쯤이면 상당수가 번들 없이 배포될 가능성도.
15. 2025년 선택 가이드
신규 프로젝트
| 용도 | 추천 |
|---|---|
| React/Vue/Svelte 앱 | Vite (5% 확률로 Turbopack) |
| Next.js | Turbopack (기본값 전환 중) |
| 대형 Webpack 레거시 마이그레이션 | Rspack |
| 라이브러리 | Rolldown (또는 Rollup) |
| 최소 의존성 CLI 도구 | esbuild |
| 백엔드/CLI 런타임까지 | Bun |
| 파이썬/Ruby 급 간편함 | Bun 또는 Deno |
기존 Webpack 탈출
- Create React App — 2023년 공식 deprecated → Vite로
- Next.js — Turbopack으로 자동 이행
- Vue CLI → Vite
- Angular — 자체 빌더 + esbuild 옵션
16. 안티패턴 TOP 10
- Create React App 고수 — 2025년 완전 deprecated, Vite로
- Webpack config 500줄+ — 리팩토링 또는 Rspack으로
- 번들 크기 측정 안 함 —
webpack-bundle-analyzer상시 - CommonJS 라이브러리 범람 — tree-shaking 실패 → ESM 라이브러리 선호
- Barrel file 남용 —
index.ts에서 전체 re-export → 트리셰이킹 방해 - 모든 이미지 번들링 — 큰 이미지는 CDN, import는 최소
process.env런타임 사용 — 빌드 시 substitute 되므로import.meta.env- source map을 프로덕션에 포함 — 유출 + 파일 크기
lodash전체 import —import _ from 'lodash'→ 개별 함수만- Hydration mismatch 방치 — SSR/CSR 결과가 달라서 flash
17. 번들러 현명하게 쓰기 체크리스트
- 빌드 시간 측정 — 경계선 기록 (로컬/CI)
- 번들 크기 모니터링 —
size-limitCI 체크 - ESM 라이브러리 선호 — tree-shaking 가능
- dynamic import — 라우트/큰 컴포넌트 분리
- vendor splitting 활성화
- Source map 분리 — 프로덕션 번들과 별도 업로드 (Sentry)
- Polyfill 최소화 — 대상 브라우저 명시 (
browserslist) - CSS code splitting — 페이지별 CSS 청크
- Asset optimization — 이미지 압축, 폰트 subset
- 환경 변수 정의 체크 — 빌드 시 substitute 누락 방지
- HMR 작동 확인 — 상태 유지되는지
- 마이그레이션 플랜 — Vite/Turbopack/Rspack 검토
마치며 — 번들러의 종말?
"Bundless future"는 2015년부터 회자되었다. HTTP/2와 ESM이 등장하면 번들러가 필요 없어질 거라고. 10년 지난 지금, 번들러는 더 복잡해졌고 더 빨라졌다. 이유는 간단하다: "최적화"의 끝이 없기 때문.
하지만 변화의 방향은 분명하다:
- Rust/Go로의 이주 — JavaScript 런타임의 한계 극복
- 통합 — 번들러+런타임+테스트의 경계 붕괴 (Bun, Deno)
- Incremental everywhere — Turbopack의 철학 확산
- ESM 표준화 — CommonJS의 느린 퇴장
- GitHub Copilot/Cursor 생성 코드 — 설정 자체가 덜 중요해짐
Webpack 설정으로 3시간씩 보내던 시절은 저물었다. 2030년의 개발자는 번들러를 당연한 것으로 여길 것이다. 마치 우리가 C 컴파일러를 그렇게 여기듯이.
다음 글 예고 — React 서버 컴포넌트(RSC)와 Next.js App Router의 내부
번들러가 빌드 시간을 해결했다면, 서버 컴포넌트는 "무엇을 서버에서 렌더링할 것인가"의 질문을 재정의했다. 다음 글에서는:
- RSC의 철학 — 2020년 Sebastian이 제안한 이유
- RSC 프로토콜 — Flight Format, 스트리밍 JSON
- "use server" / "use client" 경계 — 실제로 무엇이 일어나는가
- Server Actions — 왜 fetch를 대체하는가
- App Router 내부 — 레이아웃, 템플릿, 병렬 라우팅
- 캐싱의 4계층 — Request / Data / Full Route / Router Cache
- PPR (Partial Prerendering) — Next.js 15의 승부수
- Turbopack 통합
- React Server Components vs Solid Start vs Qwik City
- 왜 "Use the Platform"이 다시 뜨는가 (Remix 철학)
- RSC가 바꾼 데이터 페칭 멘탈 모델
2026년 React 개발자의 필수 지식을 한 번에 정리하는 여정.
"The best bundler is the one you don't have to configure." — Evan You (Vite 창시자, VueConf 2024)
현재 단락 (1/276)
2015년 웹팩 설정 파일을 처음 열어본 개발자들의 공통된 반응은 "이게 뭐야?"였다. 500줄짜리 `webpack.config.js`, loader와 plugin의 무한 조합, ...