Skip to content

✍️ 필사 모드: 프론트엔드 번들러 전쟁 완전 정복 — Webpack, Vite, esbuild, Turbopack, Rspack, Rolldown, Bun (2025)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

"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년 사이 사실상 독점.

왜 승자가 되었나

  1. 모든 것이 모듈 — JS뿐 아니라 CSS, 이미지, 폰트까지 import 가능
  2. Code Splitting — 동적 import()로 청크 분할
  3. Loader 생태계 — babel-loader, css-loader, file-loader, 수천 개
  4. Plugin 시스템 — HtmlWebpackPlugin, MiniCssExtract 등
  5. 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: 20esbuild: 0.2

같은 프로젝트를 빌드했을 때 이런 차이가 실제로 난다.

어떻게 이렇게 빠른가

  1. Go — 네이티브 컴파일, JS보다 10-100배 빠름
  2. 병렬화 — Go의 goroutine으로 파일을 병렬 파싱/링크
  3. 최소한의 abstraction — 순수 파싱 → 번들링 직선형
  4. 단일 패스 알고리즘 — 스캔/변환/링크를 한 번에
  5. 메모리 공유 — 파싱 결과를 스레드 간 공유

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는:

  1. .vue, .tsx 등을 on-demand로 ESM JS로 변환 (esbuild 사용)
  2. 브라우저가 필요한 파일만 요청
  3. 수정 시 해당 모듈만 다시 보냄 (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:  12bun 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이 실패하는 이유

  1. CommonJS 의존성 — require는 정적 분석 불가
  2. 사이드 이펙트 못 증명new Class()의 효과?
  3. Polyfill을 전역에 주입
  4. 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_modulesvendor.js로 분리
  • 긴 캐시 기간 (라이브러리는 잘 안 바뀜)
  • 앱 코드와 별도 투명

너무 많은 분할의 함정

  • HTTP/1.1에서는 요청당 오버헤드 큼
  • HTTP/2/3에서는 괜찮지만 여전히 총 요청 수 주의
  • 스위트 스팟: 청크당 20-100KB

12. HMR (Hot Module Replacement) — 새로고침 없는 개발

기본 원리

  1. Dev server가 파일 변경 감지
  2. WebSocket으로 브라우저에 알림
  3. 브라우저가 변경된 모듈만 다시 요청
  4. 상태 유지하면서 교체

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.jsTurbopack (기본값 전환 중)
대형 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

  1. Create React App 고수 — 2025년 완전 deprecated, Vite로
  2. Webpack config 500줄+ — 리팩토링 또는 Rspack으로
  3. 번들 크기 측정 안 함webpack-bundle-analyzer 상시
  4. CommonJS 라이브러리 범람 — tree-shaking 실패 → ESM 라이브러리 선호
  5. Barrel file 남용index.ts에서 전체 re-export → 트리셰이킹 방해
  6. 모든 이미지 번들링 — 큰 이미지는 CDN, import는 최소
  7. process.env 런타임 사용 — 빌드 시 substitute 되므로 import.meta.env
  8. source map을 프로덕션에 포함 — 유출 + 파일 크기
  9. lodash 전체 importimport _ from 'lodash' → 개별 함수만
  10. Hydration mismatch 방치 — SSR/CSR 결과가 달라서 flash

17. 번들러 현명하게 쓰기 체크리스트

  • 빌드 시간 측정 — 경계선 기록 (로컬/CI)
  • 번들 크기 모니터링size-limit CI 체크
  • 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년 지난 지금, 번들러는 더 복잡해졌고 더 빨라졌다. 이유는 간단하다: "최적화"의 끝이 없기 때문.

하지만 변화의 방향은 분명하다:

  1. Rust/Go로의 이주 — JavaScript 런타임의 한계 극복
  2. 통합 — 번들러+런타임+테스트의 경계 붕괴 (Bun, Deno)
  3. Incremental everywhere — Turbopack의 철학 확산
  4. ESM 표준화 — CommonJS의 느린 퇴장
  5. 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의 무한 조합, ...

작성 글자: 0원문 글자: 9,415작성 단락: 0/276