Vite 빌드 시스템 깊이 이해하기
Vite의 핵심 아키텍처는 어떻게 구성되나요?
Vite는 개발 단계에서 ES 모듈(ECMAScript Module, ESM) 기반 번들링을 생략하고, 프로덕션 빌드 시에만 Rollup 번들러를 적용하는 하이브리드 방식을 채택합니다. 개발 서버는 HTTP/2 멀티플렉싱과 브라우저 네이티브 ESM 지원을 활용해 즉각적인 모듈 로딩을 구현합니다. 프로덕션 빌드에서는 트리셰이킹(tree-shaking)과 코드 스플리팅을 통해 번들 크기를 최적화합니다.
Vite의 개발 서버(Dev Server)는 Koa 프레임워크 기반으로 구성되며, 요청받은 파일을 실시간으로 변환합니다. 번들 파일 변경 감지는 Chokidar 파일 감시 라이브러리를 사용하여 1050ms 단위로 모니터링하며, 수정된 모듈만을 HMR(Hot Module Replacement) 프로토콜로 브라우저에 전송합니다. 이 방식은 웹팩(Webpack) 4.x의 전체 번들 재생성 방식과 달리 개발 단계 빌드 시간을 평균 6080% 단축합니다.
Vite v5.0부터는 Rollup v4.x 엔진을 탑재하여 프로덕션 빌드 시 동시 병렬 처리(Parallelization) 기능을 지원합니다. JavaScript 최소화는 esbuild(Go 언어 구현, 네이티브 바이너리)를 통해 처리되며, 단일 파일 최소화 속도는 약 10~20ms/MB 수준입니다.
Vite 개발 서버의 모듈 변환 메커니즘은 어떻게 작동하나요?
개발 모드에서 브라우저가 파일을 요청하면, Vite 서버는 다음 순서로 변환을 수행합니다.
단계 1: 의존성 사전 번들링(Pre-bundling)
node_modules 폴더 내 의존성들은 초기 서버 시작 시 esbuild로 사전 번들링됩니다. 이 과정에서 CommonJS 모듈들이 ESM으로 변환되며, 번들된 의존성은 .vite/deps/ 캐시 디렉토리에 저장됩니다. 의존성 버전이나 package-lock.json 해시 변경이 감지되면 캐시를 무효화합니다.
단계 2: 소스 모듈 변환
src/ 디렉토리의 .jsx, .ts, .vue 파일은 각 언어별 플러그인(plugin)을 거쳐 ESM 호환 JavaScript로 변환됩니다. 예를 들어:
- TypeScript: esbuild 트랜스파일 (타입 제거만, 타입 체크 X)
- Vue SFC:
@vitejs/plugin-vue가 template, script, style을 분리 후 재조합 - JSX: React나 Preact 플러그인이
React.createElement호출로 변환
각 변환 후 응답 헤더에 Cache-Control: max-age=31536000, immutable을 설정하여 브라우저 캐싱을 활용합니다.
단계 3: HMR 경로 주입
모든 모듈에 HMR 관련 메타데이터(import.meta.hot)가 자동 주입되어, 모듈 수정 시 브라우저 WebSocket을 통해 갱신 신호를 수신합니다. 이 오버헤드는 약 2~3KB입니다.
프로덕션 빌드에서 Rollup 번들러는 어떤 역할을 하나요?
Vite의 프로덕션 빌드는 내부적으로 Rollup 4.x를 호출합니다. 빌드 파이프라인은 다음과 같습니다.
트리셰이킹(Tree-shaking)
미사용 코드를 정적 분석으로 제거합니다. Rollup은 각 모듈의 정적 import/export 그래프를 추적하며, package.json의 sideEffects: false 필드를 참조하여 부수효과 없는 모듈의 미사용 exports를 안전하게 제거합니다. 예를 들어:
// utils.js
export function used() { }
export function unused() { }
// main.js
import { used } from './utils.js';
used();
위 경우 빌드 후 unused 함수는 최종 번들에서 제외됩니다. 트리셰이킹 효율은 의존성 구조에 따라 20~50% 크기 감소를 달성합니다.
코드 스플리팅(Code Splitting)
Rollup은 동적 import를 감지하여 별도 청크(chunk)로 분리합니다.
// 동적 import는 별도 파일로 분리
const modal = await import('./modal.js');
기본 설정에서는 30KB 이상의 모듈을 자동으로 분리하며, vite.config.js의 rollupOptions.output.manualChunks 옵션으로 수동 제어 가능합니다.
최소화(Minification)
esbuild를 기본 최소화 엔진으로 사용하며, 옵션으로 Terser(JavaScript 전문 최소화)로 전환 가능합니다. esbuild는 약 10배 빠르지만, 극도의 압축이 필요한 경우 Terser를 사용합니다.
| 최소화 도구 | 속도 | 압축률 | 사용 시점 |
|---|---|---|---|
| esbuild | 10~20ms/MB | 95% | 기본값 (대부분의 프로젝트) |
| Terser | 100~300ms/MB | 97% | 초소형 번들 필수 시 |
Vite의 플러그인 시스템은 어떤 구조로 동작하나요?
Vite는 Rollup의 플러그인 인터페이스를 확장한 플러그인 API를 제공합니다. 플러그인은 모듈 해석(resolution), 변환(transformation), HMR 처리를 커스터마이징할 수 있습니다.
플러그인 생명주기
- 모듈 해석 단계:
resolveId훅에서 import 경로를 실제 파일 경로로 매핑 - 로드 단계:
load훅에서 파일 내용 로드 (기본은 파일시스템 읽기) - 변환 단계:
transform훅에서 코드 변환 - HMR 단계 (개발 모드):
handleHotUpdate훅에서 핫 갱신 처리
예를 들어 커스텀 .md 파일 형식을 JavaScript로 변환하는 플러그인:
export default function markdownPlugin() {
return {
name: 'markdown-loader',
resolveId(id) {
if (id.endsWith('.md')) return id;
},
load(id) {
if (id.endsWith('.md')) {
const html = convertMarkdownToHtml(fs.readFileSync(id, 'utf-8'));
return `export default ${JSON.stringify(html)}`;
}
},
};
}
공식 플러그인으로는 @vitejs/plugin-vue (Vue 3), @vitejs/plugin-react (React 17+), @vitejs/plugin-legacy (IE11 호환성)가 제공됩니다.
실제 프로젝트에서 Vite는 어떻게 적용되나요?
사례 1: React 기반 대규모 SPA (Single Page Application)
글로벌 협력사인 다국적 소프트웨어 기업은 기존 Create React App(CRA) 기반 프로젝트를 Vite로 마이그레이션했습니다. 개발 서버 시작 시간이 45초에서 2초로 단축되었으며, 모듈 수정 시 HMR 반영 시간이 평균 800ms에서 120ms로 개선되었습니다. 의존성 사전 번들링으로 인한 추가 메모리 사용은 약 150MB 증가했으나, 개발 생산성 향상으로 상쇄됩니다.
사례 2: Vue 3 풀스택 애플리케이션
한국 규모 IT 서비스 회사의 관리자 대시보드 프로젝트에서 Vite + @vitejs/plugin-vue를 채택했습니다. 빌드 결과물 크기는 Webpack 설정 대비 약 35% 감소했으며(1.2MB → 780KB), 프로덕션 빌드 시간은 120초에서 18초로 단축되었습니다. CSS-in-JS 라이브러리(Tailwind CSS)와의 통합에서 JIT(Just-In-Time) 모드를 활용해 번들 크기를 추가로 15% 감축했습니다.
사례 3: 모노레포(Monorepo) 환경
대형 디지털 플랫폼 회사가 pnpm 패키지 매니저와 함께 Vite를 모노레포 환경에 적용했습니다. vite.config.js의 optimizeDeps.include 옵션으로 공유 의존성을 명시적으로 관리하여, 각 워크스페이스 간 중복 번들링을 제거했습니다. 이를 통해 전체 node_modules 크기를 25% 감축하고, 개발 서버 초기화 시간을 60초 단축했습니다.
정리하면 Vite의 기술적 위치는 어떤가요?
Vite는 ES 모듈 기반 개발 환경과 Rollup 기반 프로덕션 빌드라는 명확한 이원화 전략으로, 개발 생산성과 프로덕션 최적화를 동시에 달성합니다. 개발 단계의 빌드 시간 단축(60~80% 개선), HMR 성능(120ms 수준 반영), 프로덕션 번들 최소화(트리셰이킹, 코드 스플리팅)는 모두 정량화된 이점입니다. 플러그인 인터페이스를 통해 프레임워크 및 언어별 확장성이 높으며, 공식 플러그인 생태계도 지속 확대 중입니다.
Webpack 5.x 대비 낮은 러닝커브와 단순한 설정 문법도 채택 장점입니다. 다만 CommonJS 모듈 호환성에 대한 런타임 오버헤드와 IE11 이하 레거시 환경 지원의 복잡성(plugin-legacy 필수)은 도입 시 고려사항입니다.
자주 묻는 질문
Vite의 사전 번들링은 왜 필요한가요?
node_modules의 의존성 중 상당수는 CommonJS 또는 혼합 형식(UMD)으로 배포됩니다. 브라우저는 이들을 직접 실행할 수 없으므로, 초기 서버 시작 시 esbuild로 ESM 형식으로 변환하여 캐시합니다. 또한 의존성 내부의 깊은 import 경로들(예: lodash/map)을 번들링하여 HTTP 요청 수를 줄입니다. 사전 번들링 덕분에 개발 단계에서 브라우저가 네이티브 ESM 기반 모듈을 직접 로드할 수 있습니다.
TypeScript 타입 체크는 어떻게 처리되나요?
Vite의 esbuild는 TypeScript를 JavaScript로 트랜스파일하되, 타입 검증은 수행하지 않습니다. 타입 안정성을 위해서는 별도의 tsc --noEmit 커맨드를 CI/CD 파이프라인이나 사전 커밋 훅(pre-commit hook)에서 실행해야 합니다. 이는 의도적 설계로, 빌드 속도 우선 원칙을 따릅니다.
프로덕션 빌드 후 번들 크기를 분석하려면 어떻게 하나요?
Vite는 기본적으로 빌드 후 번들 통계를 콘솔에 출력합니다. 상세 분석을 위해서는 rollup-plugin-visualizer 패키지를 설치하여 시각적 번들 맵을 생성할 수 있습니다. 또는 Vite 공식 문서의 Build Target 섹션에서 vite build --mode analyze를 지원하는 커스텀 플러그인 구현 방식을 안내합니다.
레거시 브라우저(IE11)를 지원해야 하면 어떻게 하나요?
@vitejs/plugin-legacy를 설치하여 vite.config.js에 등록합니다. 이 플러그인은 빌드 시 두 개의 번들을 생성합니다: 최신 구문을 사용한 모던 번들(약 2030% 더 작음)과 ES5로 트랜스파일된 레거시 번들. 브라우저는 <script type="module">과 <script nomodule>을 통해 각각 로드하므로, 레거시 환경에서도 호환성을 유지합니다. 다만 레거시 번들 생성으로 인한 빌드 시간 증가(약 2040%)는 불가피합니다.
HMR이 작동하지 않을 때 디버깅 방법은 무엇인가요?
HMR 실패의 주된 원인은 WebSocket 연결 실패 또는 모듈 변환 오류입니다. 먼저 브라우저 개발자 도구 Network 탭에서 WebSocket 연결 상태를 확인합니다. 연결 실패 시 vite.config.js의 server.hmr 옵션을 명시적으로 설정하여 호스트와 포트를 지정합니다(예: server: { hmr: { host: 'localhost', port: 5173 } }). 모듈 변환 오류는 브라우저 콘솔 또는 터미널의 Vite 서버 로그에서 확인할 수 있습니다.