번들러가 코드 크기를 줄이는 진정한 방법 — Tree Shaking의 원리와 한계
프론트엔드 프로젝트가 대형화될수록 빌드 결과물의 크기가 점차 증가하는 것은 자연스러운 현상입니다.
기능의 확장과 라이브러리의 누적 사용으로 인해 번들 사이즈(Bundle Size) 가 커지게 되며, 이는 로딩 속도 및 사용자 경험 저하로 직결됩니다.
그러나 실제로 애플리케이션이 사용하는 코드는 전체 의존성의 일부에 불과합니다.
이때 사용되지 않는 코드를 제거하여 번들을 경량화하는 과정을 수행하는 기술이 바로 Tree Shaking입니다.
현대의 번들러(Webpack, Vite, Rollup 등)는 이 기능을 통해 불필요한 코드를 제거하고 최적화된 빌드 결과를 생성합니다.
🧠 Tree Shaking의 개념
Tree Shaking은 “사용되지 않는 코드(Dead Code)를 탐지하여 빌드 결과에서 제거하는 최적화 기법”을 의미합니다.
말 그대로 “트리의 불필요한 가지를 흔들어 떨어뜨리는 과정”에 비유할 수 있습니다.
모듈 그래프를 트리로 가정하면, 엔트리 포인트(루트)에서 실제로 참조되는 함수·변수·클래스만 남기고,
그 외로 연결되지 않은 export는 모두 제거됩니다.
예시
// utils.js
export function usedFn() {
console.log("I'm used");
}
export function unusedFn() {
console.log("I'm never used");
}
// main.js
import { usedFn } from './utils.js';
usedFn();
빌드 결과:
function usedFn() {
console.log("I'm used");
}
usedFn();
unusedFn()은 import되지 않았기 때문에 최종 번들에서 제거됩니다.
이와 같이, Tree Shaking은 코드 의존성을 정적으로 분석하여 사용되지 않는 export를 안전하게 제거합니다.
⚙️ Tree Shaking의 전제 조건
Tree Shaking은 정적 분석(Static Analysis) 을 기반으로 작동하므로,
“어떤 코드가 사용되는가”를 빌드 시점에 정확히 판단할 수 있어야 합니다.
이때 가장 중요한 전제는 모듈 시스템의 형태입니다.
즉, 코드가 ES Module(import/export) 인지, CommonJS(require/module.exports) 인지에 따라 Tree Shaking의 가능 여부가 달라집니다.
🧩 ES Module (ESM) — 정적 분석이 가능한 모듈 시스템
ESM은 ECMAScript(ES6, 2015)에서 표준으로 도입된 정적 모듈 시스템입니다.
모듈 간 의존 관계가 코드 실행 이전에 확정되므로, 번들러가 전체 import/export 구조를 파악할 수 있습니다.
// math.js
export function add(a, b) { return a + b; }
export function sub(a, b) { return a - b; }
// main.js
import { add } from './math.js';
console.log(add(1, 2));
위 예시에서 번들러는 add만 사용되고 sub는 사용되지 않는다는 사실을 컴파일 타임에 알 수 있습니다.
따라서 sub()는 안전하게 제거되어 번들이 경량화됩니다.
| 항목 | 설명 |
| 로딩 시점 | 정적(빌드 시점) |
| 불러오기 문법 | import ... from |
| 내보내기 문법 | export, export default |
| Tree Shaking 가능 여부 | ✅ 가능 |
| 주요 환경 | 브라우저, Vite, Rollup, Webpack(ESM 모드) |
🧱 CommonJS — 런타임 기반의 동적 모듈 시스템
CommonJS는 Node.js에서 채택된 런타임 모듈 시스템으로,
모듈의 구조와 export 내용이 실행 시점에 결정됩니다.
이는 번들러가 정적으로 분석할 수 없다는 의미이기도 합니다.
// math.js
function add(a, b) { return a + b; }
function sub(a, b) { return a - b; }
module.exports = { add, sub };
// main.js
const math = require('./math');
console.log(math.add(1, 2));
이 구조에서는 번들러가 sub()이 실제로 사용되지 않는다고 판단할 수 없습니다.math 객체의 구성은 런타임에 결정되므로 Tree Shaking이 불가능합니다.
| 항목 | 설명 |
| 로딩 시점 | 런타임(동적) |
| 불러오기 문법 | require() |
| 내보내기 문법 | module.exports |
| Tree Shaking 가능 여부 | ❌ 불가능 |
| 주요 환경 | Node.js (서버 환경 중심) |
🧭 Tree Shaking이 ES Module 기반이어야 하는 이유
Tree Shaking은 정적 의존성 그래프를 분석하여 불필요한 export를 제거하는 방식으로 동작합니다.
따라서 모듈 간 관계를 빌드 시점에 확정할 수 있는 ESM(ES Module) 환경에서만 작동합니다.
// ✅ ESM (가능)
import { add } from './math.js'; // add만 사용 → sub 제거 가능
// ❌ CJS (불가능)
const math = require('./math'); // 런타임에서 math의 구조를 알 수 없음
Rollup과 Vite는 ESM 전용 번들러이며, Webpack 역시 ESM을 사용할 때만 Tree Shaking 최적화를 수행합니다.
📦 package.json의 "sideEffects" 속성 — Tree Shaking의 보호 장치
Tree Shaking은 미사용 export를 제거하지만,
일부 파일은 export가 없더라도 import 자체로 실행 효과(부수효과) 를 발생시킵니다.
대표적인 예시가 .css 파일의 import입니다.
🎨 .css 파일 import의 부수효과 (Side Effect)
// main.js
import './styles.css';
이 구문은 자바스크립트 실행 시점에 CSS를 브라우저에 주입하라는 의미입니다.
즉, .css 파일은 export를 제공하지 않더라도 DOM에 스타일을 적용하는 부수효과(Side Effect) 를 가집니다.
문제는 번들러 입장에서 .css 파일은 어떠한 변수를 export하지 않기 때문에
정적 분석 시 “사용되지 않는 코드(dead code)” 로 오인될 수 있다는 점입니다.
이를 방지하기 위해 package.json에 다음과 같이 설정합니다.
{
"sideEffects": ["*.css"]
}
이 설정은 “CSS 파일은 단순 import만으로도 실행 효과가 있으므로 제거하지 말라”는 의미를 갖습니다.
해당 설정이 없을 경우, Tree Shaking 과정에서 CSS가 제거되어 UI가 정상적으로 표시되지 않는 문제가 발생할 수 있습니다.
🔍 Tree Shaking이 작동하지 않는 일반적인 사례
| 원인 | 설명 |
| CommonJS 의존성 | require() 기반 구조 → 정적 분석 불가 |
| Babel 설정 문제 | preset-env에서 modules: commonjs로 변환 시 ESM 손실 |
| sideEffects 누락 | CSS 또는 전역 실행 스크립트가 제거됨 |
| 동적 import 사용 | require(variable) 등 런타임 import는 분석 불가 |
| 라이브러리 자체의 ESM 미지원 | Rollup·Webpack이 dead code를 판별할 수 없음 |
⚡ 번들러별 Tree Shaking 적용 방식
| 번들러 | 작동 방식 | 특징 |
| Webpack | optimization.usedExports와 "sideEffects" 설정을 조합 | 앱 빌드 중심, 플러그인 제어 강력 |
| Vite | 내부적으로 Rollup을 사용 | 개발 시 HMR, 빌드 시 Rollup 기반 최적화 |
| Rollup | 완전한 ESM 정적 분석 | 라이브러리 번들용으로 가장 깨끗한 결과 제공 |
💡 결론 — 코드가 가벼워지는 이유는 “분석 가능한 구조”에 있다
Tree Shaking은 단순한 번들러의 기능이 아니라,
코드가 정적으로 분석 가능한 구조로 작성되어 있는가에 따라 성패가 갈립니다.
이를 위해 다음 세 가지를 실천하는 것이 중요합니다.
ESM(ES Module) 문법을 사용하여 의존성을 정적으로 선언할 것
부수효과(Side Effect) 가 있는 파일(
.css, 전역 초기화 스크립트 등)은sideEffects에 명시할 것CommonJS 기반 라이브러리는 ESM 대체 버전(
lodash-es,dayjs등)으로 교체할 것
이러한 구조적 기반이 마련되어야 번들러가 불필요한 코드를 안전하게 제거할 수 있습니다.
정리
Tree Shaking은 번들러의 기술적 마법이 아니라, 모듈 시스템의 설계가 만들어낸 결과입니다.
ES Module을 기반으로 부수효과를 명확히 정의하는 것이
곧 코드 크기 최적화의 근본적인 출발점입니다
🧾 작성 참고
이 글은 ChatGPT의 도움을 받아 내용을 정리하였습니다.