Webpack vs Vite

psi·2025년 4월 7일

Webpack

Webpack은 모듈 번들러(Module Bundler)

JS, CSS, 이미지 등 다양한 자원을 하나 또는 여러 개의 번들 파일로 묶어주는 도구. → 브라우저가 이해할 수 있는 단 하나의 JS 파일로 변환해주는 도구

🔷 주요 목적

  • 의존성 관리 (import/export)
  • 정적 리소스 통합 (이미지, CSS, 폰트 등)
  • 브라우저가 처리 가능한 형태로 변환
  • Tree Shaking, Code Splitting 등의 최적화

번들링이란?

번들링(Bundle)이란, 여러 개의 파일(모듈)을 하나 또는 몇 개의 파일로 합치는 과정.

왜 번들링을 하는가?

📌 이유 1. 브라우저는 수백 개의 JS 파일을 잘 못 처리함

  • React 프로젝트는 보통 수십~수백 개의 JS/TS, CSS, 이미지 파일로 구성

  • 이걸 하나하나 다 로딩하면 HTTP 요청이 수백 개

    그래서 “파일 수를 줄이기 위해” 번들링을 함

    → 왜냐하면, 요청이 많을수록 브라우저는 느려지고 복잡해짐

📌 이유 2. 의존성 관리 자동화

  • 우리가 import / require를 사용해서 모듈을 나눠 쓰면

  • 번들러는 이걸 자동으로 따라가서 하나로 묶어줌

    🔷 의존성 분석이란?

    파일 간에 import, require 같은 관계를 자동으로 찾아내는 작업

    // Test.jsx
    import a from './a';
    import b from './b';
    • Webpack은 Test.jsx에서 시작해서, a.jsx, b.jsx 찾아서 읽음
    • 그 안에서 또 다른 모듈을 import 하면, 계속 따라감

    이걸 의존성 그래프(Dependency Graph)라고 함.

📌 이유 3. 최신 문법(ES6+, JSX, TS 등)은 브라우저가 이해 못 함

  • const, async/await, import 같은 문법은 구형 브라우저에서 안 돌아감
  • React의 JSX는 애초에 JS가 아님

    Babel + 번들링 도구가 이걸 변환 + 묶어줌

📌 이유 4. 성능 최적화

  • Tree shaking으로 쓸모없는 코드는 제거
  • Lazy loading, 코드 스플리팅으로 필요한 시점에만 로딩
  • 번들 크기 줄이고, UX 개선

주요 개념

  • Entry: 번들링을 시작할 파일 (예: index.js)
  • Dependency Graph: 의존성 파악을 통해 전체 그래프 구성
  • Loaders: CSS, 이미지, TypeScript 등 JS가 아닌 파일을 변환하는 역할
  • Plugins: 번들 최적화, 환경 변수 주입, HTML 생성 등 다양한 기능을 수행
  • Output: 번들 결과가 저장될 경로 및 파일 이름
  • Mode: development / production 모드로 빌드 최적화 정도가 달라짐

동작 흐름

  1. Entry 파일부터 시작해 모든 의존성을 분석
  2. 각 자원을 loader를 통해 변환
  3. plugins을 적용해 최종 output 생성
  4. 결과물을 하나 또는 여러 개의 JS 파일로 저장

📦 전체 구조 예시

index.js
├── A.js
│   ├── a.js
│   ├── b.js
│   └── c.js
└── B.js
    ├── a.js (공통)
    ├── d.
    └── e.js

Webpack의 동작 순서

  1. Entry Point 설정 (index.jsx)

    //index.jsx
    import A from './A';
    import B from './B';

    → Webpack은 index.jsx를 entry로 시작함

  2. 의존성 그래프 생성

    • index → A → a, b, c
    • index → B → a, d, e
    • a.jsx는 A와 B 양쪽에서 쓰이니까 한 번만 포함됨 (중복 제거)
  3. 최종 번들링 결과

    • Webpack은 위 모든 파일을 읽고, a, b, c, d, e, A, B, index 등 필요한 코드를 하나의 파일(bundle.js)로 묶음
    • 번들 파일 안에는 a가 중복되지 않게 잘 정리되어 들어감


✅ 중복 모듈은?

Webpack은 a가 두 군데서 import되더라도

→ 내부적으로는 딱 한 번만 포함시킴 (고유 ID 또는 경로 기반 키)

→ 필요한 곳에서 require("a") 형태로 공유하게 만들어.

필요한 모든 모듈을 모아서 하나의 번들 파일로 만든다.

(혹은 성능을 위해 여러 청크로 나눌 수도 있지만 기본은 하나)

✅ 그래프와 번들 결과물은 다르다

🔷 의존성 그래프 - 파일간 참조관계를 나타내는 구조 .

  • 즉, 그래프에서는 여러 갈래로 퍼질수 있음.

그래프에선 여러 번 등장해도, 번들 결과물엔 한 번만 포함
✅ 그래프 구조는 참조 관계를 나타내고,
✅ 실제 번들링 결과는 중복 없이 최적화된 코드로 구성됨.
✅ 같은 모듈은 한 번만 로딩되고, 어디서든 참조 가능.

✅ Code Splitting이란?

  • 구조도(Tree 구조)
         index.js
       /           \
     A              B
   / | \          / | \
  a  b  c        d  e  f

Webpack에 명시적으로 “이 부분은 따로 나눠줘” 라고 알려주면
→ A와 B를 각각 별도의 번들 파일로 나눌 수 있음.

[main.js]       → index + 공통 모듈
[chunk-A.js]A + a + b + c
[chunk-B.js]B + d + e + f
// App.jsx
import React, { Suspense, lazy } from 'react';

const PageA = lazy(() => import('./PageA'));
const PageB = lazy(() => import('./PageB'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading A...</div>}>
        <PageA />
      </Suspense>
    </div>
  );
}

이 경우:

  • Webpack은 PageA, PageB를 각각 다른 chunk 파일로 분리함
  • 브라우저는 App을 먼저 받고, PageA를 필요한 순간에 네트워크로 불러옴

즉, 번들 전체를 나무라고 보면:

  • 기본 번들: App
  • 가지를 타고 들어가는 PageA, PageB는 각각 비동기 청크

✅ 왜 이렇게 나누는가?

이유설명
🚀 초기 로딩 최적화앱이 클수록 한 번에 다 불러오면 느려짐
🧠 사용자 흐름에 맞게메인 화면만 빠르게, 하위 페이지는 나중에
💾 캐싱 및 청크 업데이트 용이A만 바뀌면 A만 새로 받으면 됨

⚒️ Entry-based splitting vs Dynamic import splitting

webpack.config.ts 파일에서 entry에 명시해서 나누는 것보다
Dynamic을 통해 분리하는게 일반적.

✔️ Webpack 파일 분리

  • webpack.prod.ts, webpack.dev.ts, webpack.common.ts 로 나눠 관리
  • Webpack-merge (Library)를 통해 합칠 수 있음

🔸 결론

코드 스플리팅을 해도 초기 빌드 or HMR이 느린 건 동일

왜냐하면 Webpack은 "빌드 중심" 구조라서 전체 트리를 항상 계산하고 캐싱함

✅ 라우팅 시 페이지 컴포넌트는 어떻게 로딩되는가?

📌 Webpack

  • 초기 번들에 모든 라우팅 페이지가 포함되어 있는 경우 (SPA에서 흔함)
  • 또는 React.lazy 등으로 import()한 청크가 미리 번들링되어 있음
  • 결과적으로, 라우팅 시 이미 로딩된 코드 or 캐시된 청크 사용 → 빠름

📌 Vite (dev 환경)

  • 라우팅 시, 브라우저가 직접 그 페이지 컴포넌트 모듈을 네트워크로 요청
  • 즉, 해당 페이지에서 import하는 모든 모듈도 새로 요청
    • 라우팅 시, 최초 진입 시 조금 느릴 수 있음 (최초 1회 요청)
    • 이후, HTTP 캐시 + 모듈 캐시로 빠름

Vite

✅ 0. Vite의 핵심 철학: 왜 생겼는가?

📌 Vite는 Webpack의 이런 점들을 개선하기 위해 등장:

문제Webpack에서
❌ 개발 서버 시작 느림모든 모듈을 미리 번들링
❌ HMR 느림전체 의존성 그래프 재계산
❌ 설정 복잡너무 많은 loader, plugin 의존

✅ 그래서 Vite의 철학은?

개발은 가볍고 빠르게,
배포는 완전히 최적화된 번들

✅ 1. 개발 환경에서의 Vite 작동 원리

🔧 핵심 차이: “번들링 안 함”, “브라우저가 직접 import”

예시 프로젝트 구조

src/
├── main.ts
├── App.tsx
├── components/
│   └── Header.tsx

🔹 Webpack이라면?

  • main.ts 기준으로 전부 번들링 → main.bundle.js

🔹 Vite라면?

  • index.html을 entry로 삼고
  • 브라우저가 아래처럼 모듈을 직접 요청
GET /src/main.ts
GET /src/App.tsx
GET /src/components/Header.tsx

✅ 즉, Vite는 브라우저가 import를 따라가는 구조

→ 이것이 가능한 이유? ESM (ES Modules) 기반이기 때문. - 사전 번들링

🔹사전 번들링(Pre-Bundling) 이란?

외부 라이브러리인 경우, esm을 지원안하거나 내부가 복잡할 수 있음
node_modules 내의 의존성을 .vite/deps/ 디렉토리에 따로 번들링을 함으로써, 브라우저 요청시 한번의 요청으로 불러올 수 있게 해줌

✅ Vite의 실행 흐름 (DEV 환경)

  1. vite.config.ts 로드
  2. 해당 파일을 entry point로 삼아, import를 따라 실시간 HTTP 요청 처리
  3. TypeScript, JSX 등은 esbuild를 통해 빠르게 변환됨
  4. 변경 감지(HMR)는 ws(웹소켓) 통해 반영

→ 특징:

  • 의존성 그래프가 “전체적으로 한 번에” 계산되는 게 아니라, 브라우저가 요청할 때마다 on-demand로 처리
  • 그래서 빠르고, 메모리도 덜 먹음

✅ 2. HMR (Hot Module Replacement)이 빠른 이유

Webpack:

  • 전체 의존성 그래프를 다시 돌고, 청크를 재생성

Vite:

  • 변경된 .ts, .vue, .css만 감지해서 해당 모듈만 브라우저에 푸시

ms 단위의 빠른 HMR

✅ 상태도 유지 가능 (React Fast Refresh)

✅ 3. Vite에서의 build (= 배포)

⚠️ 이때는 “번들링 한다”

  • 내부적으로 Rollup 사용
  • 모든 모듈을 분석해서 Tree Shaking, Code Splitting, Lazy Loading, Minify 수행
  • 파일 시스템 단위로 청크 나눔

⚙️ Rollup

  • Webpack과 같은 모듈 번들러
  • 라이브러리 번들링에 적합하며, 주로 라이브러리 제작에 많이 사용됨
  • 대부분 기본설정으로 통해 최적화 해주지만(Tree shaking, Code Splitting)
  • external, manualChunks 등 customizing도 가능함

⚡ Vite의 코드 스플리팅

🔸 개발 환경(dev)

  • import()는 실제로 브라우저가 네트워크를 통해 개별 JS 모듈 요청
  • 즉, 스플리팅된 모듈은 진짜 파일 단위로 존재하고 → 필요할 때만 브라우저가 가져옴 (on-demand module fetching)

🔸 결론

Vite는 스플리팅 자체가 브라우저 주도 방식

→ 개발 중에도 네트워크로 개별 모듈을 요청하니

번들링 자체가 필요 없음, 빠르고 효율적임

✅ 예시

// App.jsx
import PageA from './PageA'
  • Vite(dev): 브라우저가 PageA를 import하면 → GET /src/PageA.jsx 요청을 브라우저가 보냄 → Vite dev server가 그 파일을 즉시 변환해서 전송

✅ 4. Vite의 주요 특징 정리

항목설명
⚡ 빠른 개발 서버esbuild 기반 실시간 변환 + ESM 요청
💥 빠른 HMR모듈 단위 감지 + 즉시 갱신
🎯 명확한 설정Rollup 기반 설정이라 구조가 단순
🌐 HTTP/2 최적화ESM + 파일 단위 요청에 최적화
🧩 강력한 플러그인 생태계Rollup/Vite 플러그인 모두 사용 가능
📦 완성도 높은 빌드Rollup으로 tree shaking + 청크 분리 자동

❗빠른 개발 서버란?

  • npm run dev 를 통해 프로젝트를 시작할때
  • HMR은 코드 변경시 갱신 속도

결론

🔧 언제 Webpack을 써야 할까?

상황설명
레거시 시스템 유지보수이미 Webpack으로 복잡하게 구성된 시스템은 갈아엎기 어려움
IE11 등 구형 브라우저 지원 필요Vite는 ESM 기반이라 IE 지원이 까다로움 (polyfill도 제한적)
커스터마이징이 복잡함Microfrontend, Module Federation, Custom Plugin 등
코드 분할, 캐싱 최적화, 복잡한 의존성 그래프고급 빌드 최적화 기능을 적극적으로 사용하는 경우
사내 개발 규칙이 Webpack 기반전통적인 엔터프라이즈 환경에선 여전히 Webpack 많이 사용

✅ 대기업 프론트엔드 구조나 내부 플랫폼에서 Webpack 기반의 구조가 많음

⚡️ 언제 Vite를 써야 할까?

상황설명
새로운 프로젝트 시작빠른 초기 개발과 설정 최소화가 가능
SPA (Single Page Application)모듈 기반의 구조에 최적화 되어 있고, React/Vue/Svelte 다 잘 됨
빠른 개발 서버, HMR 필요번들 없이 ESM 기반으로 dev server 속도는 진짜 압도적
최신 브라우저만 타겟크롬, 사파리, 엣지 중심이면 ESM 기반으로 문제 없음
TS + JSX + CSS 모듈 등 최신 기술 스택 사용설정 거의 없이 잘 돌아감
SSR을 포함한 모던 프레임워크 사용 시Nuxt, SvelteKit, Astro, SolidStart 등은 대부분 Vite 기반

⚠️ Vite는 모던 웹 생태계에서 기본값이 되어가는 중임 (React 공식 템플릿도 Vite 권장)

✅ 실제 SPA에서는 왜 Vite를 많이 쓸까?

SPA는 다음 특성을 가짐:

  • 초기 로딩 최적화
  • 클라이언트 라우팅
  • 빠른 개발 속도와 반복 작업 중요
  • 최신 JS/TS 문법 적극 사용

Vite는 이런 SPA 특성과 아주 잘 맞음:

  • 개발 시 번들링 없이 ESM 기반으로 모듈 로딩 → 시작 속도, HMR 속도 빠름
  • vite-plugin-react, vite-plugin-vue 등 생태계 풍부
  • TS, PostCSS, Tailwind 등도 손쉽게 통합
  • Rollup 기반 빌드 최적화도 좋아서 배포 단계에서도 문제 없음

→ 그래서 요즘 신생 프로젝트는 SPA + Vite가 기본이 됨.

✅ 실무에서 선택 기준 정리

기준WebpackVite
구형 브라우저 지원✅ 좋음 (IE까지 가능)❌ ESM 중심
레거시 시스템 호환✅ 적합❌ 어려움
속도 (dev server, HMR)❌ 느림✅ 빠름
설정 난이도❌ 높음✅ 낮음
신규 프로젝트🔄 조건부✅ 매우 추천
커스터마이징 (복잡한 plugin 등)✅ 강력함🔄 일부 한계
SSR + 최신 프레임워크🔄 조건부✅ 최적화됨
  • Polyfil - 기능 지원
    • 구형 브라우저의 경우 최신 기능을 모름 읽지 못함
    • 이를 위해 최신 JS기능을 직접 구현해서 덧 씌우는 것.
  • Babel - 문법 번역
    • 형 브라우저의 경우 최신 문법(promise 등)을 읽지 못함
    • 이를 위해 이해를 시키기 위해 코드 변환을 함



REFERENCE

https://wonsss.github.io/webpack/webpack-all-in-one/
https://webpack.kr/guides/code-splitting/#dynamic-imports

profile
사용자 경험을 최우선하며 논리적 문제 해결을 즐기는 개발자

0개의 댓글