Theme:

블로그: 빌드 결과물 뜯어보기을 어떤 기준으로 선택해야 할까요?

\n\n> 블로그: 빌드 결과물 뜯어보기을 어떤 기준으로 선택해야 할까요?\n이전 글에서 React.lazy()로 페이지별 코드 스플리팅을 적용했습니다. 그런데 빌드 결과를 자세히 보면 아직 최적화할 여지가 있습니다.

PLAINTEXT
dist/assets/index-BcJ88B4w.js  925.87 kB │ gzip: 283.02 kB

index.js가 여전히 925KB입니다. 여기에 React, React Router, React DOM, 유틸리티 라이브러리, 공통 컴포넌트가 전부 섞여 있습니다.

문제는 내 코드를 한 줄만 바꿔도 index.js의 해시가 달라져서 사용자가 전체를 다시 받아야 한다는 점입니다. 라이브러리 코드는 안 바뀌었는데도요.


manualChunks

Vite는 내부적으로 Rollup을 사용하는데, Rollup의 manualChunks 옵션으로 특정 패키지를 별도 chunk로 분리할 수 있습니다.

JAVASCRIPT
// vite.config.js
export default defineConfig({
    build: {
        rollupOptions: {
            output: {
                manualChunks: {
                    'vendor-react': [
                        'react',
                        'react-dom',
                        'react-router-dom',
                    ],
                    'vendor-markdown': [
                        'react-markdown',
                        'remark-gfm',
                        'rehype-raw',
                        'rehype-slug',
                        'rehype-highlight',
                    ],
                },
            },
        },
    },
});

이렇게 하면 빌드 결과가 이렇게 바뀝니다.

PLAINTEXT
vendor-react.js      47 kB  ← React 관련
vendor-markdown.js  523 kB  ← 마크다운 관련
index.js            350 kB  ← 내 코드 + 나머지

왜 분리하는가

캐싱 효율

Vite는 빌드할 때 파일 내용 기반으로 해시를 생성합니다. vendor-react-D2kupECk.js에서 D2kupECk가 그 해시입니다.

라이브러리를 별도 chunk로 분리하면:

  • 내 코드를 수정해도 vendor-react.js의 해시는 안 바뀜
  • 사용자 브라우저에 캐시된 vendor-react.js를 그대로 사용
  • 변경된 index.js만 새로 다운로드

라이브러리 업데이트 없이 코드만 수정하는 경우가 대부분이니까, 이 전략이 효과적입니다.

로딩 병렬화

브라우저는 같은 도메인에 대해 보통 6개의 동시 연결을 지원합니다. 파일이 하나면 직렬 다운로드지만, 여러 chunk로 나뉘면 병렬로 받을 수 있습니다.


어떤 기준으로 나누나

아무거나 막 나누면 오히려 역효과입니다. 기준은 이렇습니다.

기준예시이유
변경 빈도가 낮은 것React, React Router거의 안 바뀌니까 캐시 효과 큼
** 크기가 큰 것**마크다운 파서 (523KB)분리하면 필요할 때만 로드 가능
** 특정 페이지에서만 쓰는 것**rehype 플러그인들글 상세 안 가면 안 받아도 됨

반대로, 작은 유틸리티 라이브러리를 일일이 chunk로 만들면 HTTP 요청 수만 늘어나고 오히려 느려집니다. 어느 정도 크기가 있는 것들만 분리하는 게 좋습니다.


빌드 분석

빌드 결과를 시각적으로 분석하고 싶으면 rollup-plugin-visualizer를 쓸 수 있습니다.

BASH
npm install --save-dev rollup-plugin-visualizer
JAVASCRIPT
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
    plugins: [
        visualizer({ open: true }),
    ],
});

빌드하면 트리맵이 뜨는데, 어떤 라이브러리가 번들의 몇 퍼센트를 차지하는지 한눈에 볼 수 있습니다. 예상 외로 큰 라이브러리를 발견하는 경우가 종종 있습니다.


결과 비교

** 분리 전:**

PLAINTEXT
index.js  1.5 MB (gzip: ~450 KB)

** 분리 후:**

PLAINTEXT
vendor-react.js      47 kB (gzip: 17 kB)   ← 거의 안 바뀜, 캐시 적중률 높음
vendor-markdown.js  523 kB (gzip: 161 kB)  ← 글 상세에서만 로드
index.js            350 kB (gzip: 107 kB)  ← 내 코드만, 자주 바뀌지만 작음
페이지별 chunk       4~10 kB each           ← 필요할 때만 로드

총 크기는 비슷하지만, 사용자가 ** 실제로 받는 양 **은 크게 줄었습니다. 홈만 방문하면 마크다운 파서를 안 받아도 되고, 코드를 수정해 배포해도 라이브러리 chunk는 캐시에서 가져옵니다.


주의할 점

chunk를 너무 잘게 나누면 안 됩니다. HTTP/2에서 멀티플렉싱이 된다고 해도, 각 요청마다 오버헤드가 있습니다. 경험적으로 5~10개 정도의 chunk가 적당합니다.

그리고 manualChunks에서 라이브러리 간 의존성이 꼬이면 빌드가 실패하거나 런타임 에러가 날 수 있습니다. 같이 쓰이는 라이브러리는 같은 chunk에 넣는 게 안전합니다. React와 React DOM을 따로 분리하는 건 별 의미가 없는 것처럼요.

댓글 로딩 중...