번들링 최적화를 통한 import cost 줄이기

JavaScript 애플리케이션을 개발할 때 번들 크기는 성능에 직접적인 영향을 미치는 중요한 요소이다. 특히 웹 애플리케이션에서는 초기 로딩 시간과 직결되어 사용자 경험을 좌우한다고 생각한다. 이번 글에서는 번들링 최적화를 통해 import cost를 줄이는 방법과 흔히 사용되는 배럴 파일의 문제점에 대해 알아보고자 한다.
배럴 파일의 함정
배럴 파일(Barrel Files)은 여러 모듈의 내보내기를 하나의 파일로 통합하여 가져오기를 단순화하는 패턴으로, 일반적으로 index.js
또는 index.ts
파일을 통해 구현된다.
예를 들어, 다음과 같은 구조의 컴포넌트가 있다고 가정해 보면:
/components
└── /Modal
├── Modal.js
├── ModalHeader.js
├── ModalContent.js
└── ModalFooter.js
배럴 파일은 ./components/Modal/index.js
에 다음과 같이 정의된다:
export { Modal } from "./Modal";
export { ModalHeader } from "./ModalHeader";
export { ModalContent } from "./ModalContent";
export { ModalFooter } from "./ModalFooter";
이렇게 하면 다음과 같이 단일 import 문으로 여러 컴포넌트를 가져올 수 있다:
import { Modal, ModalHeader, ModalContent, ModalFooter } from "./Modal";
언뜻 보기에는 코드 구성을 개선하고 import를 깔끔하게 만드는 좋은 방법처럼 보이지만, 배럴 파일에는 몇 가지 심각한 단점이 있다.
1. 번들 크기 증가
트리 쉐이킹(tree-shaking)이 활성화되지 않은 환경에서는 배럴 파일에서 가져온 모든 파일이 번들에 포함된다. 즉, 실제로 사용하지 않는 컴포넌트도 번들에 포함되어 불필요한 코드가 증가한다.
예를 들어, Material UI의 배럴 파일을 통해 Button 컴포넌트만 가져오는 경우와 직접 가져오는 경우의 번들 크기 차이는 상당하다고 볼 수 있다:
// 배럴 파일 사용 (번들 크기: 151.47 kB)
import { Button } from "@mui/material";
// 직접 가져오기 (번들 크기가 크게 감소)
import Button from "@mui/material/Button";
2. 빌드 시간 증가
배럴 파일은 대규모 프로젝트에서 도구 속도가 느려지는 주요 원인 중 하나다. 모든 모듈이 배럴 파일을 로드하면 각 모듈이 다른 모듈에 의존하는 복잡한 의존성 그래프가 생성된다.
배럴 파일을 사용할 때와 직접 가져올 때의 빌드 시간 차이:
배럴 파일 사용: 10초
직접 가져오기: 7초
3. 린트 시간 증가
배럴 파일은 린팅 성능에도 영향을 미친다. eslint-plugin-import
의 import/no-cycle
규칙과 같은 도구를 사용할 때, 배럴 파일의 모든 내보내기를 해결해야 하므로 린트 시간이 길어진다.
4. 개발자 경험 저하
대부분의 IDE는 자동 완성 및 IntelliSense 기능을 제공하므로 함수 이름을 입력하면 자동으로 올바른 import를 가져올 수 있다. 배럴 파일이 있으면 코드 탐색이 어려워진다. CMD + click
을 사용하면 실제 모듈 정의가 아닌 배럴 파일로 이동하는 것을 확인할 수 있다.
번들링 최적화 전략
이제 import cost를 줄이기 위한 번들링 최적화 전략을 생각해보자면,
1. 패키지 타입을 모듈로 설정
package.json
에서 type
필드를 module
로 설정하여 ESM(ECMAScript Modules)을 기본으로 사용할 수 있다:
{
"type": "module"
}
ESM과 CJS의 주요 차이점
문법 차이
ESM(ECMAScript Modules):
import
와export
키워드 사용CJS(CommonJS):
require()
와module.exports
사용
로딩 방식
ESM: 비동기적으로 모듈 로드
CJS: 동기적으로 모듈 로드
구조적 특성
ESM: 정적 구조를 가지며, 컴파일 타임에 모듈 의존성을 분석 가능
CJS: 동적 구조를 가지며, 런타임에 의존성을 분석
최적화 가능성
ESM: 정적 분석이 가능해 트리 쉐이킹과 같은 최적화 기법을 사용할 수 있음
CJS: 동적 로딩을 지원하지만, 정적 분석이 어려움
호환성
ESM: 브라우저와 Node.js 모두에서 사용 가능
CJS: 주로 Node.js 환경에서 사용됨
2. ESM 지원 및 진입점 분리
다양한 환경에서의 호환성을 위해 여러 진입점 설정하기:
{
"main": "dist/index.js", // cjs
"module": "dist/esm/index.js", // ems
"types": "dist/index.d.ts",
"files": [
"dist",
"dist/esm"
]
}
3. Tree Shaking 지원 강화
Tree Shaking을 최대한 활용하기 위해 sideEffects
필드를 false
로 설정한다:
{
"sideEffects": false
}
이 설정은 번들러에게 패키지의 모든 파일이 부작용이 없으며 사용되지 않는 코드를 안전하게 제거할 수 있음을 알려준다.
sideEffects 필드
sideEffects
필드는 package.json
에 명시하는 속성으로, 패키지의 어떤 파일이 사이드 이펙트를 가지고 있는지 번들러에게 알려주는 역할을 한다.
사이드 이펙트란?
모듈이 import될 때 전역 스코프에 영향을 미치는 코드를 의미하며, 예를 들어 전역 객체를 수정하거나, 프로토타입을 확장하는 등의 작업이 이에 해당한다.
설정 방법
Boolean 값 설정:
{ "sideEffects": false }
false
로 설정하면 해당 패키지의 모든 파일이 사이드 이펙트가 없다고 선언하는 것이다.배열로 설정:
{ "sideEffects": [ "*.css", "./src/some-side-effect.js" ] }
특정 파일이나 패턴만 사이드 이펙트가 있다는 식으로도 표시할 수 있다.
장점
번들 크기 감소: 사용되지 않는 코드를 안전하게 제거할 수 있어 번들 크기가 줄어듦
빌드 속도 향상: 번들러가 코드를 분석하는 과정이 줄어들어 빌드 속도가 향상됨
트리 쉐이킹 최적화: 번들러가 사용되지 않는 export를 효과적으로 제거할 수 있음
주의사항
CSS 파일이나 폴리필과 같은 사이드 이펙트가 있는 파일은 반드시
sideEffects
배열에 포함시켜야 함잘못 설정할 경우 필요한 코드가 제거될 수 있으므로 주의해야 함
4. Rollup 설정 최적화
Rollup 설정에서 treeshake
옵션을 활성화하고 preserveModules
를 사용하여 모듈 구조 유지하기
import pkg from "./package.json"
export default {
input: ['src/index.ts', 'src/colors.ts'],
output: [
{
dir: pkg.files[0],
format: 'cjs',
preserveModules: true,
},
{
dir: pkg.files[1],
format: 'esm',
preserveModules: true,
}
],
treeshake: true
}
5. CJS 지원을 위한 설정
Node.js 환경에서의 호환성을 위해 CommonJS(CJS) 형식도 지원해야 한다. CJS 형식의 파일은 .cjs
확장자를 사용하여 명확히 구분하면 된다.
배럴 파일 대안
배럴 파일을 사용하는 대신 다음과 같은 접근 방식을 고려해볼 수 있음
직접 가져오기: 필요한 모듈만 직접 가져오기
import Button from "@mui/material/Button";
별도의 진입점 제공: 자주 사용되는 기능에 대해 별도의 진입점을 제공하기
import { http } from 'msw/http';
접근 방식 | 장점 | 단점 |
직접 가져오기 | 불필요한 코드 로드를 방지하여 번들 크기 감소 | import 문이 길어질 수 있음 |
별도의 진입점 제공 | 자주 사용하는 기능을 쉽게 접근 가능 | 유지보수 시 추가적인 관리 필요 |
확인
- 기존(cjs로 번들링)
- esm 만 적용했을 때(type = module)
- treeshake = true 까지 적용했을 때
결론
번들링 최적화는 애플리케이션 성능 향상에 중요한 역할이라고 생각한다. 배럴 파일은 코드 구성을 개선하는 것처럼 보이지만, 번들 크기 증가, 빌드 시간 증가, 개발자 경험 저하 등의 문제를 일으킬 수 있다.
대신 ESM 지원, 트리 쉐이킹 최적화, 다중 진입점 제공 등의 전략을 통해 import cost를 효과적으로 줄일 수 있다. 이러한 최적화는 특히 대규모 프로젝트에서 빌드 시간과 번들 크기를 크게 개선할 수 있을 것으로 보여진다.
참고자료
Subscribe to my newsletter
Read articles from InseoYang directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
