1 / 2

06. 빌드 도구: Vite vs esbuild

예상 시간: 5분

06. 빌드 도구: Vite vs esbuild

이 문서에서 배우는 것

  • esbuild: Go로 작성된 초고속 번들러, 모든 것을 직접 설정
  • Vite: esbuild + Rollup을 감싼 고수준 도구, 설정 최소화
  • "esbuild = 엔진, Vite = 자동차" 비유
  • 같은 작업 비교: esbuild는 68줄, Vite는 12줄
  • Vite 내부 구조: 개발 모드(esbuild) vs 프로덕션(Rollup)
  • vite.config.js 각 옵션의 역할
  • 언제 뭘 써야 하는지

PM 요청 (김도연)

김도연 PM: 준혁님, A003 프로젝트에 build.js(esbuild)랑 vite.config.js(Vite) 두 개가 있잖아요. 둘 다 빌드 도구인 것 같은데, 뭐가 다른 거예요? 왜 두 개나 있는 건가요?

이준혁 시니어: 좋은 질문이야. A003 프로젝트는 실험 프로젝트라서 두 가지 방식을 모두 시도해봤거든. esbuild가 "엔진"이라면 Vite는 "자동차"야. 하나씩 비교해볼게.


시니어 멘토링 (이준혁)

esbuild: Go로 만든 초고속 엔진

이준혁: esbuild는 Go 언어로 작성된 JavaScript 번들러야. 핵심 특징은 속도.

esbuild의 특징:
 
  언어:     Go (컴파일 언어 → 네이티브 바이너리)
  속도:     기존 번들러(Webpack) 대비 10~100배 빠름
  레벨:     저수준 도구 (Low-level)
  철학:     "빠르게 번들링. 나머지는 니가 알아서"
  설정:     JavaScript API로 직접 호출

김도연: "저수준"이라는 게 무슨 뜻이에요?

이준혁: 기본 기능만 제공하고, 나머지는 전부 직접 설정해야 한다는 뜻이야. 예를 들어:

esbuild가 해주는 것:
  ✅ JavaScript/TypeScript 번들링
  ✅ JSX 변환
  ✅ 코드 최소화 (minify)
  ✅ 트리 셰이킹
 
esbuild가 해주지 않는 것:
  ❌ HMR (Hot Module Replacement)
  ❌ 개발 서버
  ❌ manifest.json 자동 생성
  ❌ CSS 모듈
  ❌ 환경 변수 자동 주입
  ❌ HTML 생성

Vite: esbuild + Rollup을 감싼 자동차

이준혁: Vite는 esbuild와 Rollup을 내부적으로 사용하는 고수준 빌드 도구야.

Vite의 특징:
 
  내부 엔진:  esbuild (개발) + Rollup (프로덕션)
  레벨:       고수준 도구 (High-level)
  철학:       "개발 경험을 최적화. 설정은 최소화"
  설정:       vite.config.js 하나로 끝
Vite가 해주는 것:
  ✅ JavaScript/TypeScript 번들링
  ✅ JSX 변환
  ✅ 코드 최소화 (minify)
  ✅ 트리 셰이킹
  ✅ HMR (Hot Module Replacement)       ← esbuild에는 없음
  ✅ 개발 서버                           ← esbuild에는 없음
  ✅ manifest.json 자동 생성             ← esbuild에는 없음
  ✅ CSS 모듈 / PostCSS                 ← esbuild에는 없음
  ✅ 환경 변수 (.env)                    ← esbuild에는 없음
  ✅ 코드 스플리팅                       ← esbuild는 제한적

비유: esbuild = 엔진, Vite = 자동차

이준혁: 이 비유가 가장 정확해.

esbuild = 자동차 엔진
 
  ┌────────────────────────────────┐
  │          esbuild               │
  │                                │
  │  초고속 번들링 엔진            │
  │  Go로 만들어서 매우 빠름       │
  │                                │
  │  하지만 엔진만으로는           │
  │  자동차가 되지 않음            │
  │                                │
  │  핸들, 바퀴, 시트, 계기판...   │
  │  전부 직접 조립해야 함         │
  └────────────────────────────────┘
 
Vite = 완성된 자동차
 
  ┌────────────────────────────────┐
  │            Vite                │
  │  ┌─────────────────────────┐  │
  │  │    esbuild (개발 엔진)   │  │
  │  │    Rollup (프로덕션 엔진)│  │
  │  └─────────────────────────┘  │
  │                                │
  │  + HMR (자동 변속기)           │
  │  + 개발 서버 (에어컨)          │
  │  + manifest (계기판)           │
  │  + 플러그인 (옵션 장착)        │
  │                                │
  │  시동만 걸면 바로 출발!        │
  └────────────────────────────────┘

김도연: 아, 그러니까 Vite 안에 esbuild가 들어있는 거군요!

이준혁: 맞아. Vite는 esbuild를 개발 모드에서 번들링 엔진으로 사용하고 있어.


같은 작업 비교: 68줄 vs 12줄

이준혁: A003 프로젝트에서 같은 작업(SSR 빌드)을 하는 코드를 비교해보자.

esbuild 방식 — build.js (68줄):

// build.js — 68줄
import esbuild from 'esbuild';
import sveltePlugin from 'esbuild-svelte';
import { mkdirSync } from 'fs';
 
mkdirSync('./svelte-pages/build', { recursive: true });
mkdirSync('./public', { recursive: true });
mkdirSync('./dist', { recursive: true });
 
console.log('Building...\n');
 
// 1/4. Svelte SSR 컴포넌트 빌드
console.log('1/4  Svelte SSR component');
await esbuild.build({
  entryPoints: ['./svelte-pages/About.svelte'],
  outfile: './svelte-pages/build/About.js',
  bundle: true,
  format: 'esm',
  platform: 'node',
  plugins: [
    sveltePlugin({
      compilerOptions: { generate: 'ssr', hydratable: true },
    }),
  ],
});
 
// 2/4. Express 서버 번들
console.log('2/4  Server bundle');
await esbuild.build({
  entryPoints: ['./server.js'],
  outfile: './dist/server.js',
  bundle: true,
  format: 'esm',
  platform: 'node',
  packages: 'external',
  jsx: 'automatic',
  jsxImportSource: 'react',
});
 
// 3/4. React 클라이언트 번들
console.log('3/4  React client bundle');
await esbuild.build({
  entryPoints: ['./client/react-entry.jsx'],
  outfile: './public/react-bundle.js',
  bundle: true,
  format: 'iife',
  platform: 'browser',
  jsx: 'automatic',
  jsxImportSource: 'react',
  minify: true,
});
 
// 4/4. Svelte 클라이언트 번들
console.log('4/4  Svelte client bundle');
await esbuild.build({
  entryPoints: ['./client/svelte-entry.js'],
  outfile: './public/svelte-bundle.js',
  bundle: true,
  format: 'iife',
  platform: 'browser',
  plugins: [
    sveltePlugin({
      compilerOptions: { generate: 'dom', hydratable: true },
    }),
  ],
  minify: true,
});
 
console.log('\nBuild complete! Run: npm start');

Vite 방식 — vite.config.js (12줄):

// vite.config.js — 12줄
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
 
export default defineConfig({
  plugins: [react()],
  build: {
    manifest: true,
    rollupOptions: {
      input: 'entry-client.jsx',
    }
  }
})
코드 양 비교:
 
  esbuild:  build.js 68줄 + 디렉토리 생성 + 4단계 수동 빌드 + 로그 출력
  Vite:     vite.config.js 12줄 + package.json scripts 2줄
 
  Vite가 자동으로 해주는 것:
    ✅ 디렉토리 생성 (자동)
    ✅ manifest.json 생성 (manifest: true 한 줄)
    ✅ 파일명 해시 (기본 동작)
    ✅ 코드 스플리팅 (자동)
    ✅ 서버/클라이언트 분리 빌드 (--ssr 플래그)
    ✅ 진행 상황 표시 (자동)

김도연: 12줄로 68줄의 일을 다 하는 거예요?

이준혁: 그게 "엔진 vs 자동차"의 차이야. 엔진을 직접 조립하면 많은 걸 직접 해야 하지만, 자동차는 시동만 걸면 돼.


Vite 내부 구조: 개발 모드 vs 프로덕션 모드

이준혁: Vite가 영리한 점은 모드에 따라 다른 도구를 사용한다는 거야.

Vite의 이중 구조:
 
  ┌─── 개발 모드 (vite dev) ───────────────────┐
  │                                              │
  │  esbuild로 JSX/TS 변환                      │
  │  네이티브 ESM (번들링 안 함!)                │
  │  HMR (수정 즉시 반영)                        │
  │  개발 서버 (localhost:5173)                   │
  │                                              │
  │  핵심: 빠른 시작, 빠른 반영                  │
  └──────────────────────────────────────────────┘
 
  ┌─── 프로덕션 모드 (vite build) ─────────────┐
  │                                              │
  │  Rollup으로 번들링                           │
  │  트리 셰이킹 + 코드 스플리팅                 │
  │  해시 파일명 + manifest.json                 │
  │  코드 최소화 (minify)                        │
  │                                              │
  │  핵심: 최적화된 프로덕션 번들                │
  └──────────────────────────────────────────────┘

김도연: 왜 개발이랑 프로덕션에서 다른 도구를 써요?

이준혁: 개발 때는 "속도"가 가장 중요하고, 프로덕션에서는 "최적화"가 가장 중요해.

개발 모드에서 esbuild를 쓰는 이유:
  esbuild는 번들링 속도가 극단적으로 빠름
  + 개발 모드에서는 네이티브 ESM으로 번들링 자체를 건너뜀
  → 코드 수정 → 반영까지 밀리초 단위
 
프로덕션에서 Rollup을 쓰는 이유:
  Rollup은 트리 셰이킹과 코드 스플리팅이 더 정교함
  + 다양한 출력 형식 지원 (ESM, CJS, IIFE)
  + 풍부한 플러그인 생태계
  → 최적화된 프로덕션 번들 생성

vite.config.js 각 옵션 분석

이준혁: A003 프로젝트의 vite.config.js를 한 줄씩 뜯어볼게.

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
 
export default defineConfig({
  plugins: [react()],
  // ↑ React 지원 플러그인
  //   - JSX 변환 (esbuild 또는 Babel 사용)
  //   - React Fast Refresh (개발 모드 HMR)
  //   - react-dom/server 자동 처리
 
  build: {
    manifest: true,
    // ↑ manifest.json 생성 활성화
    //   → dist/client/.vite/manifest.json
    //   → "entry-client.jsx" → "assets/entry-client-D6usOwnO.js" 매핑
 
    rollupOptions: {
      input: 'entry-client.jsx',
      // ↑ 클라이언트 빌드의 진입점 지정
      //   기본값은 index.html이지만
      //   SSR에서는 HTML 없이 JSX 파일을 직접 진입점으로 사용
    }
  }
})
plugins: [react()]가 하는 일:
 
  1. JSX 변환
     <h1>안녕</h1> → React.createElement('h1', null, '안녕')
 
  2. React Fast Refresh (개발 모드)
     컴포넌트 수정 시 상태를 유지하면서 화면만 업데이트
 
  3. 서버/클라이언트 자동 처리
     --ssr 플래그에 따라 적절한 React DOM 패키지 사용
 
build.manifest: true가 하는 일:
 
  빌드 시 .vite/manifest.json 파일 생성
  서버가 "빌드된 파일명을 알아내는 데" 사용 (03-manifest-and-hash.md 참조)
 
build.rollupOptions.input이 하는 일:
 
  Rollup에게 "이 파일부터 의존성을 따라가면서 번들링해"라고 지시
  일반 웹앱은 index.html이 진입점이지만
  SSR에서는 entry-client.jsx가 진입점

빌드 명령어 비교

이준혁: package.json의 scripts를 비교해보면 차이가 명확해.

{
  "scripts": {
    // esbuild 방식 — 직접 빌드 스크립트 실행
    "dev": "node build.js && node dist/server.js",
 
    // Vite 방식 — Vite CLI 사용
    "build:client": "vite build --outDir dist/client",
    "build:server": "vite build --outDir dist/server --ssr entry-server.jsx"
  }
}
esbuild 방식:
  1. node build.js 실행
  2. build.js 안의 4개 esbuild.build() 호출이 순서대로 실행
  3. dist/server.js, public/react-bundle.js 등 생성
  4. node dist/server.js로 서버 실행
 
Vite 방식:
  1. vite build --outDir dist/client
     → entry-client.jsx 기반 클라이언트 번들 + manifest.json 생성
  2. vite build --outDir dist/server --ssr entry-server.jsx
     → entry-server.jsx 기반 서버 번들 생성
  3. node test.js로 서버 실행 (manifest.json 읽어서 사용)

언제 뭘 쓰나

김도연: 결국 Vite가 더 좋은 거 아닌가요? esbuild를 직접 쓸 이유가 있나요?

이준혁: 상황에 따라 달라.

상황추천 도구이유
웹 앱 (SPA/SSR)ViteHMR, 코드 스플리팅, manifest 등 자동
라이브러리 빌드esbuild단순 번들링만 필요, 설정 최소
CLI 도구 빌드esbuild브라우저 기능 불필요, 빠른 빌드
학습/실험esbuild빌드 과정의 각 단계를 이해하기 좋음
프로덕션 웹 서비스Vite최적화, 캐시 무효화 등 프로덕션 기능
Svelte + React 혼합esbuild직접 플러그인 조합 가능
정리:
 
  esbuild를 직접 쓰는 경우:
    - 빌드 과정을 완전히 통제하고 싶을 때
    - 브라우저 기능(HMR, 개발 서버)이 필요 없을 때
    - 라이브러리나 CLI 도구를 빌드할 때
    - 빌드 도구의 동작 원리를 학습할 때
 
  Vite를 쓰는 경우:
    - 웹 앱을 개발할 때 (대부분의 경우)
    - 빠른 개발 경험(HMR)이 중요할 때
    - manifest, 해시, 코드 스플리팅이 필요할 때
    - "빌드 설정은 최소화하고 개발에 집중"하고 싶을 때

이준혁: A003 프로젝트에서 esbuild(시스템 A)를 먼저 만든 이유가 있어. 빌드 과정의 각 단계를 직접 보면서 이해하기 위해서야. Vite는 너무 많은 걸 자동으로 해주니까, 처음부터 Vite를 쓰면 "뭘 하고 있는지" 모르게 되거든.

김도연: 아, 그래서 이 튜토리얼에서도 esbuild를 먼저 다룬 거군요!

이준혁: 맞아. 원리를 이해한 다음에 Vite를 쓰면, Vite가 뭘 해주는지 정확히 알 수 있어.


핵심 정리

  1. esbuild는 Go로 작성된 초고속 번들러. 저수준 도구로, 빌드의 모든 것을 직접 설정해야 한다.

  2. Vite는 esbuild(개발) + Rollup(프로덕션)을 내부에 가진 고수준 빌드 도구. 설정 최소화, 개발 경험 최적화가 목표다.

  3. "esbuild = 엔진, Vite = 자동차". Vite 내부에 esbuild가 들어있고, HMR, manifest, 코드 스플리팅 등을 추가로 제공한다.

  4. 같은 작업: esbuild는 build.js 68줄, Vite는 vite.config.js 12줄. Vite가 대부분을 자동 처리한다.

  5. Vite의 이중 구조: 개발 모드에서는 esbuild(속도), 프로덕션에서는 Rollup(최적화)을 사용한다.

  6. 사용 기준: 웹 앱이면 Vite, 라이브러리/CLI이면 esbuild, 학습 목적이면 esbuild부터 시작.