트리 쉐이킹
1. 개요
1. 개요
트리 쉐이킹은 자바스크립트 애플리케이션의 최종 번들 크기를 줄이기 위해 사용하지 않는 코드를 제거하는 최적화 기술이다. 주로 프론트엔드 개발에서 웹팩이나 롤업 같은 번들러를 사용하여 라이브러리 크기를 경량화하는 데 활용된다.
이 기술은 2015년 롤업 번들러에서 처음 도입되었다. 트리 쉐이킹이 가능해진 핵심적인 배경은 ES6 모듈 시스템, 즉 import와 export 문법의 도입이다. 이 모듈 시스템은 정적 분석이 가능한 구조를 제공하여, 번들링 과정에서 어떤 모듈이 실제로 사용되는지 명확하게 식별할 수 있게 해준다.
트리 쉐이킹의 기본 원리는 의존성 그래프를 구축하고, 진입점부터 시작해 실제로 사용되는 모듈과 함수, 변수만을 추적하는 것이다. 이를 통해 프로젝트에 포함된 거대한 서드파티 라이브러리에서도 실제로 호출된 기능만을 최종 번들에 포함시키고, 나머지 사용되지 않는 코드는 안전하게 제거할 수 있다.
2. 원리
2. 원리
트리 쉐이킹의 핵심 원리는 ES6 모듈 시스템의 정적 구조를 활용하는 데 있다. ES6 모듈은 import와 export 문법이 모듈의 최상위 스코프에 위치해야 하며, 동적 임포트를 제외하면 런타임에 변경되지 않는 정적인 특성을 지닌다. 이러한 정적 특성 덕분에 번들러나 빌드 도구는 소스 코드를 실행하지 않고도 코드의 의존성 그래프를 분석할 수 있다. 도구는 진입점 파일(예: 메인 애플리케이션 파일)에서 시작하여 import 문을 따라가며 실제로 사용되는 모든 모듈과 함수, 변수들을 추적한다. 이 과정에서 의존성 그래프에 포함되지 않은, 즉 어떤 경로로도 도달할 수 없는 코드는 '사용되지 않는 코드'로 식별된다.
이렇게 식별된 사용되지 않는 코드는 최종 번들 파일에서 제거된다. 예를 들어, 자바스크립트 라이브러리에서 여러 개의 유틸리티 함수를 내보냈지만, 애플리케이션에서 그중 하나만 가져다 쓴다면, 나머지 사용되지 않은 함수들은 최종 번들에 포함되지 않는다. 이 원리는 클래스의 메서드나 객체의 프로퍼티 수준까지 적용될 수 있어, 매우 세밀한 수준의 코드 제거가 가능하다. 따라서 트리 쉐이킹은 프론트엔드 개발에서 웹 애플리케이션의 초기 로딩 속도를 개선하는 핵심적인 번들 최적화 기법으로 자리 잡았다.
3. 구현 방식
3. 구현 방식
3.1. 정적 분석
3.1. 정적 분석
트리 쉐이킹의 핵심 구현 방식 중 하나는 정적 분석이다. 이는 코드가 실제로 실행되기 전에, 즉 빌드 타임에 코드의 구조와 의존성을 분석하는 과정을 의미한다. 이 분석은 ES6 모듈 시스템의 import와 export 문법이 정적이라는 특성에 기반한다. 즉, 모듈 간의 의존 관계가 런타임이 아닌 코드 작성 시점에 명확하게 정의되기 때문에, 번들러는 소스 코드를 실행하지 않고도 모든 모듈의 내보내기와 가져오기 경로를 파악할 수 있다.
번들러는 이 정적 분석을 통해 애플리케이션의 진입점부터 시작하여 모든 import 문을 따라가며 의존성 그래프를 생성한다. 이 그래프는 어떤 모듈이 어떤 모듈을 필요로 하는지, 그리고 각 모듈에서 어떤 함수나 변수, 클래스가 외부로 내보내졌는지를 나타낸다. 이 과정에서 직접적으로 사용되지 않는 모듈이나 모듈 내부의 특정 export는 사용되지 않는 코드로 식별된다. 예를 들어, 라이브러리에서 여러 함수를 가져왔지만 그중 일부만 사용했다면, 사용하지 않은 함수들은 최종 번들에서 제거될 후보가 된다.
정적 분석의 효과는 순수 함수와 결정론적인 코드 구조에서 가장 잘 발휘된다. 반면에 동적으로 모듈을 가져오는 import() 함수나 런타임에 결정되는 변수를 통한 프로퍼티 접근 등은 분석을 어렵게 만든다. 따라서 트리 쉐이킹을 최대한 활용하기 위해서는 코드 스플리팅과 같은 동적 임포트를 제외하고는 가능한 한 정적인 모듈 문법을 사용하고, 사이드 이펙트가 있는 코드를 최소화하는 것이 중요하다.
3.2. 사이드 이펙트 플래그
3.2. 사이드 이펙트 플래그
사이드 이펙트 플래그는 트리 쉐이킹 과정에서 특정 모듈이나 코드가 순수 함수처럼 동작하지 않고 외부 상태를 변경하거나 영향을 미칠 수 있는 가능성을 명시적으로 표시하는 데 사용되는 메타데이터이다. 이 플래그는 번들러가 해당 코드를 안전하게 제거할 수 있는지 판단하는 중요한 근거가 된다. 예를 들어, 모듈이 전역 변수를 수정하거나 DOM을 직접 조작하는 경우, 해당 모듈은 사용되지 않더라도 제거되면 애플리케이션의 동작에 영향을 줄 수 있다. 이러한 모듈은 "사이드 이펙트를 가진다"고 표현하며, 번들러는 기본적으로 모든 모듈이 사이드 이펙트를 가질 수 있다고 가정하여 보수적으로 동작한다.
이러한 보수적 가정을 극복하고 더 공격적인 트리 쉐이킹을 가능하게 하기 위해, 개발자는 package.json 파일의 "sideEffects" 속성을 사용하거나, 웹팩의 경우 모듈 규칙에 sideEffects 옵션을 설정하여 사이드 이펙트가 없다고 명시할 수 있다. 이 속성의 값은 불리언(false)이거나, 사이드 이펙트가 있는 파일의 경로를 배열로 지정할 수 있다. 예를 들어, "sideEffects": false로 설정하면 해당 패키지의 모든 파일이 순수하고 사이드 이펙트가 없음을 선언하여, 사용되지 않는 익스포트가 모두 제거될 수 있다.
주요 번들러인 롤업과 웹팩은 이 플래그를 적극적으로 활용한다. 특히 롤업은 ES6 모듈의 정적 분석을 기반으로 트리 쉐이킹을 처음 대중화했으며, 사이드 이펙트 플래그를 통한 최적화를 핵심 기능으로 삼았다. CSS 파일이나 폴리필과 같이 부수 효과가 명확한 파일들은 해당 경로를 "sideEffects" 배열에 명시하여, 이 파일들 자체는 제거되지 않도록 하면서도 패키지 내 다른 모듈들은 안전하게 제거되도록 할 수 있다.
따라서, 라이브러리 개발자는 자신의 코드를 분석하여 사이드 이펙트의 유무를 정확히 표시하는 것이 중요하다. 이를 통해 라이브러리 사용자는 불필요한 코드가 번들에 포함되는 것을 방지하고, 최종 웹 애플리케이션의 성능을 크게 향상시킬 수 있다. 반대로, 사이드 이펙트가 있는 코드를 잘못 제거하면 런타임 오류가 발생할 수 있으므로 주의가 필요하다.
4. 주요 도구 및 환경
4. 주요 도구 및 환경
4.1. 웹팩
4.1. 웹팩
웹팩은 자바스크립트 모듈 번들러로서, 트리 쉐이킹 기능을 내장하여 프론트엔드 개발에서 번들 크기를 효과적으로 줄일 수 있다. 웹팩 2 버전부터 공식적으로 트리 쉐이킹을 지원하기 시작했으며, 기본적으로 ES6 모듈 구문(import와 export)을 사용하는 코드에 대해 정적 분석을 수행한다. 이를 통해 애플리케이션의 진입점부터 시작해 의존 관계 그래프를 만들고, 실제로 사용되지 않는 모듈이나 모듈 내부의 특정 export를 최종 번들 결과물에서 제거한다.
웹팩에서 트리 쉐이킹이 효과적으로 동작하려면 몇 가지 조건을 충족해야 한다. 먼저, 모듈 시스템으로 ES6의 import/export 문을 사용해야 하며, CommonJS 형식의 require() 문은 정적 분석이 어려워 트리 쉐이킹의 대상이 되지 않을 수 있다. 또한, 패키지의 package.json 파일에 "sideEffects": false 속성을 명시하거나, 웹팩 설정에서 특정 파일을 사이드 이펙트가 없다고 표시해야 불필요한 코드 제거가 보다 적극적으로 이루어진다. 개발 모드에서는 트리 쉐이킹이 기본적으로 비활성화되어 있으며, 프로덕션 모드로 번들링할 때 자동으로 최적화 과정에 포함된다.
웹팩의 트리 쉐이킹은 UglifyJS나 Terser 같은 코드 난독화 및 압축 도구와 연동되어 동작한다. 웹팩이 사용하지 않는 코드를 표시(/* unused harmony export ... */)하면, 이후 압축 단계에서 이 코드들이 완전히 제거된다. 그러나 동적 임포트나 런타임에 결정되는 모듈 경로 사용과 같은 경우에는 정적 분석의 한계로 인해 트리 쉐이킹이 제대로 적용되지 않을 수 있어 주의가 필요하다.
4.2. 롤업
4.2. 롤업
롤업(Rollup)은 2015년에 처음 공개된 자바스크립트 모듈 번들러이다. 이 도구는 ES6 모듈 시스템의 정적 구조를 활용하여 트리 쉐이킹을 효율적으로 구현한 것으로 유명해졌다. 롤업은 import와 export 구문을 정적으로 분석하여 애플리케이션에서 실제로 사용되는 코드 경로만을 식별하고, 나머지 사용되지 않는 모듈이나 코드 조각들을 최종 번들에서 제거한다. 이 방식은 라이브러리 개발자들이 사용자에게 불필요한 코드를 제공하지 않도록 하고, 프론트엔드 개발에서 웹 애플리케이션의 번들 크기를 효과적으로 경량화하는 데 기여했다.
롤업의 트리 쉐이킹 구현은 ES6 모듈의 명시적인 정적 모듈 구조에 크게 의존한다. 이는 CommonJS와 같은 동적 모듈 시스템보다 분석이 용이하기 때문이다. 롤업은 번들링 과정에서 각 모듈의 의존성 그래프를 생성하고, 진입점부터 시작해 실제로 참조되는 함수, 클래스, 변수만을 추적한다. 이 정적 분석을 통해 사이드 이펙트가 없다고 판단되는 사용되지 않는 코드들을 안전하게 제거할 수 있다. 이러한 접근법은 이후 등장한 다른 번들러들에도 영향을 미쳤다.
특징 | 설명 |
|---|---|
주요 목적 | 효율적인 라이브러리 및 애플리케이션 번들 생성 |
트리 쉐이킹 지원 | ES6 모듈 기반의 정적 분석을 통한 고급 최적화 |
출력 형식 |
롤업은 특히 라이브러리나 프레임워크를 배포용으로 패키징할 때 널리 사용된다. Vue.js와 같은 많은 현대적 자바스크립트 라이브러리들이 롤업을 빌드 도구로 채택하여 최종 사용자에게 보다 작고 효율적인 번들을 제공하고 있다.
4.3. ES6 모듈
4.3. ES6 모듈
트리 쉐이킹이 효과적으로 동작하기 위한 핵심 전제 조건은 ES6 모듈 문법을 사용하는 것이다. ES6 (ECMAScript 2015)에서 도입된 import와 export 문은 정적 모듈 구조를 가지며, 이는 번들러가 코드를 실행하지 않고도 모듈 간의 의존 관계를 분석할 수 있게 해준다. 즉, 소스 코드를 파싱하는 단계에서 어떤 모듈이 어떤 값을 내보내고, 또 다른 모듈이 어떤 값을 가져오는지 명확히 파악할 수 있다. 이 정적 분석이 가능하기 때문에, 최종 번들에 포함되지 않는 모듈과 함수를 안전하게 제거하는 트리 쉐이킹이 구현될 수 있다.
반면, CommonJS와 같은 이전의 모듈 시스템은 require() 문이 동적으로 실행될 수 있어 번들러가 빌드 타임에 모든 의존성을 확정하기 어렵다. 예를 들어, 조건문 안에서 모듈을 불러오는 동적 임포트는 트리 쉐이킹의 분석을 방해할 수 있다. 따라서 웹팩이나 롤업 같은 현대적 번들러에서 트리 쉐이킹을 활용하려면, 패키지의 진입점이 package.json의 module 또는 main 필드에서 ES6 모듈 형식(.mjs 또는 type: "module"이 설정된 .js 파일)을 가리키도록 구성되어야 한다.
대부분의 최신 자바스크립트 라이브러리들은 트리 쉐이킹을 지원하기 위해 ES6 모듈 형식으로 빌드된 버전을 별도로 제공한다. 개발자는 이러한 라이브러리를 사용할 때, 번들러가 라이브러리의 ES6 모듈 버전을 선택하도록 올바르게 설정해야 불필요한 코드가 제거되는 이점을 누릴 수 있다. 결국, ES6 모듈은 모던 프론트엔드 개발 생태계에서 번들 크기 최적화의 토대를 제공하는 필수 기술 표준이다.
5. 장점
5. 장점
트리 쉐이킹은 ES6 모듈 시스템의 정적 분석을 기반으로 하여, 최종 번들러에 포함되지 않는 코드를 제거한다. 이 과정은 웹 애플리케이션의 배포 파일 크기를 효과적으로 줄여준다. 번들 크기가 감소하면 네트워크를 통한 파일 다운로드 시간이 단축되고, 이는 사용자 경험 개선과 페이지 로딩 속도 향상으로 직접적으로 연결된다. 특히 모바일 환경이나 느린 네트워크 연결에서 이 장점은 더욱 두드러진다.
번들 크기 감소는 자바스크립트 엔진의 파싱 및 컴파일 시간을 단축시키므로, 런타임 성능에도 긍정적인 영향을 미친다. 또한, 개발자는 서드파티 라이브러리를 전체적으로 불러오는 대신 필요한 부분만 선택적으로 가져와 사용할 수 있게 되어, 프로젝트 의존성을 보다 효율적으로 관리할 수 있다. 이는 프론트엔드 개발 생태계에서 라이브러리 모듈화를 촉진하는 계기가 되었다.
트리 쉐이킹은 롤업과 웹팩 같은 현대적인 번들러에 기본적으로 통합되어 있어, 추가적인 복잡한 설정 없이도 비교적 쉽게 혜택을 얻을 수 있다. 이 기술은 애플리케이션의 유지보수성을 높이는 데도 기여한다. 사용하지 않는 데드 코드가 제거되므로 코드베이스가 깔끔해지고, 향후 기능 변경이나 리팩토링 시 불필요한 코드에 대한 고민을 줄여준다.
6. 한계 및 주의사항
6. 한계 및 주의사항
트리 쉐이킹은 번들러가 ES6 모듈의 정적 구조를 분석하여 사용하지 않는 코드를 제거하는 강력한 최적화 기법이지만, 몇 가지 명확한 한계와 적용 시 주의해야 할 점이 존재한다.
가장 큰 한계는 트리 쉐이킹이 정적 분석에 기반하기 때문에 동적으로 불러오는 모듈이나 런타임에 결정되는 코드 경로는 효과적으로 제거하지 못한다는 점이다. 예를 들어, import() 문을 사용한 동적 임포트나 객체의 프로퍼티를 통해 간접적으로 접근하는 모듈은 사용 여부를 판단하기 어렵다. 또한, 라이브러리가 사이드 이펙트를 포함하고 있을 경우, 해당 모듈이 실제로 사용되지 않더라도 번들에 포함될 수 있다. 대부분의 번들러는 기본적으로 모든 모듈이 사이드 이펙트를 가진다고 가정하기 때문에, 라이브러리 개발자가 package.json의 sideEffects 플래그를 명시적으로 false로 설정하거나, 웹팩과 롤업이 이해할 수 있는 주석을 추가하지 않으면 최적화가 제대로 이루어지지 않는다.
실제 적용 시에는 바벨과 같은 트랜스파일러 설정에 주의를 기울여야 한다. 바벨이 ES6 모듈 문법을 CommonJS 형식으로 변환해버리면, 모듈 간의 정적 의존성 관계가 끊겨 트리 쉐이킹이 동작하지 않을 수 있다. 따라서 프로젝트 설정에서 바벨이 모듈 문법을 변환하지 않도록 하는 것이 중요하다. 또한, 타입스크립트 컴파일러의 module 설정도 esnext 또는 es2015와 같이 ES6 모듈을 유지하는 값을 사용해야 한다.
마지막으로, 트리 쉐이킹은 주로 프로덕션 빌드 시에 수행되는 최적화이므로, 개발 단계에서는 번들 크기가 줄어드는 효과를 직접 확인하기 어렵다. 빌드 결과물을 분석하는 번들 애널라이저 도구를 활용하여 어떤 모듈이 실제로 제거되었는지 꼼꼼히 검증하는 과정이 필요하다.
7. 관련 문서
7. 관련 문서
Google Developers - Reduce JavaScript Payloads with Tree Shaking
JavaScript.info - Modules, introduction (모듈 시스템 기초)
ESLint - no-unused-vars (사용하지 않는 코드 탐지)
TypeScript - Using TypeScript with Module Bundlers (모듈 해석 및 출력)
Babel - @babel/preset-env (모듈 변환 관련)
