Tree Shaking이란
Tree Shaking은 사용하지 않는 코드를 번들에서 제거하는 최적화 기법이다. 나무를 흔들면 죽은 잎이 떨어지듯, 번들에서 죽은 코드(dead code)를 털어낸다는 의미다.
왜 필요한가
lodash 같은 유틸리티 라이브러리를 생각해보자.
import { debounce } from 'lodash';
debounce 하나만 쓰는데 lodash 전체가 번들에 포함되면 낭비다. Tree Shaking이 제대로 동작하면 실제로 사용하는 debounce 함수만 번들에 포함된다.
동작 원리
Tree Shaking은 ES6 모듈의 정적 구조에 의존한다.
// ES6 모듈 - import/export가 정적으로 분석 가능
import { a } from './module';
// CommonJS - 런타임에 결정되므로 정적 분석 불가
const { a } = require('./module');
ES6의 import/export는 파일 최상단에만 올 수 있고, 조건문 안에 넣을 수 없다. 덕분에 번들러가 코드를 실행하지 않고도 어떤 모듈이 사용되는지 파악할 수 있다.
적용 조건
Tree Shaking이 제대로 동작하려면 몇 가지 조건이 필요하다.
1. ES6 모듈 사용
CommonJS(require)가 아닌 ES6 모듈(import/export)을 써야 한다.
// Good
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// Bad - 전체가 하나의 객체로 export됨
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
2. sideEffects 설정
package.json에 sideEffects 필드를 설정하면 번들러에게 힌트를 줄 수 있다.
{
"sideEffects": false
}
false로 설정하면 "이 패키지의 모든 모듈은 부수 효과가 없다"는 의미다. 번들러가 더 공격적으로 코드를 제거할 수 있다.
CSS 파일처럼 import만 해도 효과가 있는 파일은 예외로 지정한다.
{
"sideEffects": ["*.css", "*.scss"]
}
3. production 모드
Webpack에서는 mode: 'production'일 때 Tree Shaking이 활성화된다.
module.exports = {
mode: 'production'
};
development 모드에서는 디버깅 편의를 위해 비활성화되어 있다.
주의할 점
부수 효과가 있는 코드
// utils.js
export const log = (msg) => console.log(msg);
log('모듈 로드됨'); // 부수 효과 - import만 해도 실행됨
export const add = (a, b) => a + b;
이런 코드는 add만 import해도 log('모듈 로드됨')이 실행된다. 번들러 입장에서는 이 코드를 제거해도 되는지 판단하기 어렵다.
클래스는 제거가 어렵다
class Utils {
static add(a, b) { return a + b; }
static subtract(a, b) { return a - b; }
}
클래스는 하나의 덩어리라서 메서드 단위로 제거하기 어렵다. 개별 함수로 export하는 게 Tree Shaking에 유리하다.
라이브러리 선택
lodash를 쓴다면 lodash-es를 쓰는 게 좋다. ES 모듈로 제공되어서 Tree Shaking이 잘 된다.
// lodash - CommonJS, Tree Shaking 안됨
import { debounce } from 'lodash';
// lodash-es - ES 모듈, Tree Shaking 됨
import { debounce } from 'lodash-es';
확인 방법
번들 결과물을 분석해서 Tree Shaking이 제대로 됐는지 확인할 수 있다.
npm install webpack-bundle-analyzer -D
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin()
]
};
빌드하면 번들 구성을 시각적으로 보여준다. 예상보다 큰 라이브러리가 있으면 Tree Shaking이 안 된 거다.
정리
- Tree Shaking은 사용하지 않는 코드를 제거하는 최적화 기법
- ES6 모듈을 써야 동작함
sideEffects: false설정으로 더 효과적으로 동작- 라이브러리 선택 시 ES 모듈 버전이 있는지 확인