rollup-plugin-visualizer
프로덕션 빌드를 하고 나면 번들 파일이 하나 혹은 여러 개 생긴다. 근데 이 번들이 왜 이렇게 큰지, 어떤 라이브러리가 얼마나 차지하는지 알 수가 없다. npm run build 하면 파일 크기만 숫자로 나오는데, 300KB라는 숫자만 봐서는 뭐가 문제인지 감이 안 온다.
rollup-plugin-visualizer는 번들 내용을 시각적으로 분석해주는 도구다. 빌드 결과물을 HTML 파일로 만들어서 브라우저에서 열면, 각 모듈이 번들에서 차지하는 비율을 트리맵이나 선버스트 차트로 보여준다. "이 라이브러리가 번들의 40%를 차지하고 있다"는 걸 한눈에 파악할 수 있다.
왜 필요한가
번들 최적화는 측정부터 시작한다. 측정 없이 "이거 무거울 것 같으니까 빼자"는 감에 의존하는 방식이고, 실제로는 예상과 다른 곳에서 문제가 생기는 경우가 많다.
흔한 상황들:
lodash전체를 임포트해서 번들에 70KB가 추가됨- 사용하지 않는 아이콘 라이브러리 전체가 포함됨
- 차트 라이브러리 하나가 번들의 절반을 차지함
- 같은 라이브러리의 다른 버전이 중복 포함됨
이런 문제는 번들 내용을 시각화하지 않으면 발견하기 어렵다. rollup-plugin-visualizer는 이 시각화를 자동으로 해준다.
설치와 기본 설정
npm install -D rollup-plugin-visualizer
Vite는 내부적으로 Rollup을 번들러로 사용하기 때문에 Rollup 플러그인을 그대로 쓸 수 있다. vite.config.ts에 플러그인으로 추가하면 된다.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
react(),
visualizer({
open: true,
filename: "bundle-report.html",
gzipSize: true,
}),
],
});
이렇게 설정하고 npm run build를 하면 프로젝트 루트에 bundle-report.html이 생긴다. open: true를 설정하면 빌드 완료 후 브라우저에서 자동으로 열린다.
주요 옵션
filename
출력 파일 경로와 이름이다. 기본값은 stats.html.
visualizer({
filename: "dist/bundle-analysis.html",
});
dist/ 안에 넣으면 빌드 산출물과 함께 관리할 수 있다. .gitignore에 추가하는 걸 잊지 말자.
template
시각화 형태를 결정한다. 네 가지 옵션이 있다.
visualizer({
template: "treemap", // 기본값
});
| 템플릿 | 설명 | 적합한 상황 |
|---|---|---|
treemap | 면적 기반 사각형 차트 | 모듈별 크기 비교 (기본, 가장 직관적) |
sunburst | 원형 계층 차트 | 디렉토리/패키지 계층 구조 파악 |
network | 노드-엣지 그래프 | 모듈 간 의존 관계 추적 |
raw-data | JSON 원본 데이터 | 커스텀 분석이나 자동화 |
treemap이 가장 많이 쓰인다. 각 모듈이 사각형으로 표시되고, 면적이 클수록 번들에서 차지하는 비율이 높다. 색상은 모듈이 속한 패키지별로 구분된다. 사각형을 클릭하면 해당 모듈 내부로 들어가서 더 세부적인 구성을 볼 수 있다.
sunburst는 중심에서 바깥으로 갈수록 하위 모듈을 보여준다. node_modules/@radix-ui/react-dialog/dist/index.js 같은 깊은 경로의 모듈이 계층 구조에서 어디에 위치하는지 파악할 때 유용하다.
network는 어떤 모듈이 어떤 모듈을 임포트하는지 관계를 보여준다. 특정 라이브러리를 제거하면 어떤 의존성이 함께 빠지는지 확인할 때 쓸 수 있다.
gzipSize / brotliSize
실제 전송 크기를 기준으로 분석할 수 있다. 프로덕션 서버에서 gzip이나 brotli 압축을 사용한다면 이 옵션을 켜야 실질적인 크기를 볼 수 있다.
visualizer({
gzipSize: true,
brotliSize: true,
});
이 옵션을 켜면 리포트에서 원본 크기 외에 압축 후 크기도 표시된다. 텍스트 기반 코드는 압축률이 높기 때문에, 원본 크기만 보면 실제보다 과장된 판단을 할 수 있다. 예를 들어 원본 200KB인 라이브러리가 gzip 후 40KB라면, 생각만큼 심각한 문제가 아닐 수 있다.
sourcemap
소스맵을 활용해서 더 정확한 분석을 한다.
visualizer({
sourcemap: true,
});
소스맵이 있으면 번들러가 코드를 변환/병합한 후에도 원본 모듈 단위로 크기를 정확히 추적할 수 있다. 소스맵 없이는 번들러가 합쳐놓은 청크 단위로만 볼 수 있어서 정밀도가 떨어진다.
분석 결과 읽는 법
treemap 해석
빌드 후 생성된 HTML을 열면 큰 사각형들이 화면을 채우고 있다. 각 사각형은 하나의 모듈(파일)이고, 면적이 번들 내 차지 비율에 비례한다.
가장 먼저 볼 것:
- 가장 큰 사각형 — 번들에서 가장 많은 공간을 차지하는 모듈. 최적화 대상 1순위
- node_modules 비율 — 전체 번들에서 서드파티 코드가 차지하는 비율. 보통 70~90%
- 중복 모듈 — 같은 이름의 모듈이 여러 곳에 나타나면 중복 번들링 가능성
사각형 위에 마우스를 올리면 해당 모듈의 정확한 경로와 크기(원본/gzip)가 표시된다.
흔한 문제 패턴
1. 거대한 단일 라이브러리
차트 라이브러리, 날짜 라이브러리, UI 프레임워크 등이 번들의 큰 비율을 차지하는 경우. 해결 방법:
- 더 가벼운 대안으로 교체 (
moment→dayjs) - tree-shaking이 되는 ESM 버전 사용
- 동적 import로 코드 스플리팅
2. 전체 임포트
// ❌ 번들에 lodash 전체가 포함됨
import _ from "lodash";
// ✅ 필요한 함수만 가져옴
import debounce from "lodash/debounce";
visualizer에서 lodash 블록이 비정상적으로 크다면 이 패턴을 의심해볼 수 있다.
3. 중복 번들링
같은 라이브러리의 서로 다른 버전이 동시에 포함되는 경우. package-lock.json에서 버전이 꼬여 있거나, 서로 다른 패키지가 같은 의존성의 다른 버전을 요구할 때 발생한다. visualizer에서 같은 이름의 패키지가 여러 번 나타나면 이 문제다.
코드 스플리팅과 함께 사용
코드 스플리팅을 설정한 후에 visualizer를 돌리면, 각 청크의 구성을 확인할 수 있다. Vite에서 수동 청크를 설정하는 경우:
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
"ui-library": ["@radix-ui/react-dialog", "@radix-ui/react-tooltip"],
"chart": ["recharts"],
"animation": ["framer-motion"],
"vendor": ["react", "react-dom", "react-router-dom"],
},
},
},
},
plugins: [
visualizer({
template: "treemap",
gzipSize: true,
}),
],
});
이렇게 하면 visualizer 리포트에서 각 청크가 별도 블록으로 나타난다. vendor 청크에 React만 들어 있는지, 의도하지 않은 모듈이 섞여 들어가지 않았는지 확인할 수 있다.
manualChunks를 설정할 때 주의할 점은, 한 모듈이 여러 청크에서 임포트되면 Rollup이 알아서 공통 청크로 분리한다는 것이다. visualizer로 실제 결과를 확인하면서 조정하는 게 중요하다.
개발 환경에서만 사용하기
visualizer는 분석 도구이므로 프로덕션 빌드에는 포함시키지 않는 게 좋다. 환경 변수로 분기하면 된다.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig(({ mode }) => ({
plugins: [
react(),
mode === "analyze" &&
visualizer({
open: true,
gzipSize: true,
template: "treemap",
}),
].filter(Boolean),
}));
# 일반 빌드
npm run build
# 번들 분석 포함 빌드
npx vite build --mode analyze
이렇게 하면 분석이 필요할 때만 리포트를 생성할 수 있다. package.json에 스크립트로 등록해두면 편하다.
{
"scripts": {
"build": "vite build",
"analyze": "vite build --mode analyze"
}
}
다른 분석 도구와 비교
번들 분석 도구는 rollup-plugin-visualizer 외에도 몇 가지가 있다.
| 도구 | 번들러 | 특징 |
|---|---|---|
| rollup-plugin-visualizer | Rollup / Vite | Rollup 생태계 네이티브, 다양한 템플릿 |
| webpack-bundle-analyzer | Webpack | Webpack 전용, 가장 오래된 번들 분석 도구 |
| source-map-explorer | 아무거나 | 소스맵만 있으면 번들러 무관하게 사용 가능 |
| bundlephobia | - | npm 패키지 크기를 웹에서 미리 확인 |
Vite를 사용한다면 rollup-plugin-visualizer가 자연스러운 선택이다. Vite 내부의 Rollup과 직접 통합되기 때문에 별도 설정 없이 정확한 분석이 가능하다. webpack-bundle-analyzer는 Webpack 전용이라 Vite에서는 사용할 수 없다.
source-map-explorer는 빌드된 결과물의 소스맵만 있으면 번들러에 관계없이 분석할 수 있다는 장점이 있지만, 빌드 파이프라인에 통합되지 않아서 매번 수동으로 실행해야 한다.
실전 최적화 흐름
번들 최적화는 보통 이런 순서로 진행한다:
- 측정:
npm run build로 빌드하고 visualizer 리포트 확인 - 식별: 비정상적으로 큰 모듈, 중복 모듈, 불필요한 모듈 찾기
- 조치: 코드 스플리팅, tree-shaking, 라이브러리 교체, 동적 import 적용
- 검증: 다시 빌드하고 리포트에서 개선 확인
한 번에 모든 걸 최적화하려 하지 말고, 가장 큰 것부터 하나씩 처리하는 게 효율적이다. 번들 크기가 500KB에서 200KB로 줄었다면 추가 최적화의 효과는 점점 작아진다. 투자 대비 효과를 고려해서 적절한 선에서 멈추는 것도 중요하다.
정리
- Rollup/Vite 빌드 결과를 treemap/sunburst/network로 시각화해서 번들 구성을 한눈에 파악할 수 있다
- gzipSize/brotliSize 옵션으로 실제 전송 크기 기준 분석이 가능하고, 원본 크기만 보는 것보다 정확한 판단을 내릴 수 있다
- --mode analyze 분기로 필요할 때만 리포트를 생성하고, manualChunks 설정 후 검증 도구로 활용하는 게 실전 패턴이다