한양과학기술고등학교

에듀테크 프로젝트 대시보드

번들러와 트리 셰이킹

브라우저가 <script> 태그 10개, 100개를 각각 따로 요청하면 느리다. 그래서 번들러(Webpack, Vite, esbuild, Rollup)는 진입점(entry)부터 import 그래프를 전부 따라가며 여러 모듈을 하나의 파일로 묶는다. 이때 실제로 import되어 쓰이는 함수만 남기고 한 번도 참조되지 않는 코드(dead code)를 정적 분석으로 잘라내는 것이 트리 셰이킹(Tree Shaking)이다 — 나무를 흔들어 죽은 잎만 떨어뜨리는 것에 비유한 이름. 아래 모듈에서 체크박스를 켜고 꺼 보며, 실제로 import한 함수만 번들에 남는 과정을 직접 확인해보자.
📦 소스 모듈 클릭해서 사용/미사용 토글
📄 entry.js import 선언
🧹 번들 결과 (트리 셰이킹 후)
📊 번들 크기 비교
셰이킹 전 (전체 export) 0 B
셰이킹 후 (실사용만) 0 B
절감률 0%
⚙️ 번들러 처리 흐름
① entry.js부터 시작
→
② import 그래프 순회
↓
③ 어떤 export가 실제 쓰였는지 정적 분석
→
④ 미사용 export 제거 (Tree Shaking)
↓
⑤ 모듈들을 하나로 결합 (Concatenation)
→
⑥ 압축(Minify) → bundle.js 1개 출력
주의: 트리 셰이킹은 minify(압축)와 다른 단계다. 셰이킹은 "어떤 코드를 아예 뺄지" 결정하고, minify는 "남은 코드를 얼마나 짧게 쓸지"를 결정한다.
🔬 왜 ESM에서만 가능한가
✅ ESM (정적 구조) — import/export는 코드 실행 전에 미리 분석 가능한 위치에만 고정적으로 등장한다.
import { add } from './math.js'; // 번들러가 파일을 실행하지 않고도 // "add만 쓴다"를 텍스트 분석만으로 앎
❌ CommonJS (동적 구조) — require()는 함수 호출이라 조건문 안에서 실행 중에 어떤 값이 들어올지 바뀔 수 있다.
const lib = cond ? require('./math.js') : require('./legacy.js'); // 실행 전엔 어떤 모듈이 로드될지, 그 중 // 무엇이 쓰일지 정적으로 알 수 없음
그래서 CommonJS 기반 라이브러리는 트리 셰이킹이 거의 안 되고, 패키지 전체가 번들에 포함된다.
🛠️ 실제 도구 & 관련 개념
Webpack — usedExports + sideEffects: false 표시로 셰이킹 대상 지정
Rollup — ESM 트리 셰이킹을 최초로 대중화한 번들러
esbuild / Vite — Go로 작성, 병렬 처리로 100배 가까이 빠른 빌드

Code Splitting — 하나로 뭉치지 않고 라우트별로 나눠 필요할 때만 로드 (이 페이지의 반대 방향 최적화)
sideEffects — 모듈이 import되는 것만으로 전역 상태를 바꾸면(polyfill 등) 셰이킹 시 실수로 지워질 수 있어 명시 필요