생각/react

[리액트 컴포넌트 라이브러리 고도화] 트리 쉐이킹 도입으로 번들 크기 최적화하기

kyunghoonk00k 2025. 3. 12. 15:24
반응형

컴포넌트 라이브러리를 개발하면서 번들 크기 문제로 한 고민했다. 사자가 우리 라이브러리에 단 하나의 컴포넌트 사용하더라도 전체 라이브러리가 번들에 포함된다면 성능에 좋지 않을 거라고 생각했다. 그래서 트리 쉐이킹 최적화 작업을 진행했고, 이 과정에서 배운 내용을 공유하려고 한다.

 

요 UI 이브러리 들 크기 비교

먼저 다른 기 라이브러리들 우리 라이브러리의 번들 크기를 비교봤다.
| 라이브러 | Unpacked Size | Gzipped Size | 일 컴포넌트 사용 시 |
| 리 라이브러리 | 947 kB | ~185 kB | ~15-30 kB |
| Chakra UI | 2.5 MB | ~520 kB | ~30-45 kB |
| Mantine | 1.8 MB | ~410 kB | ~20-35 kB |
| Material UI | 3.3 MB | ~680 kB | ~35-50 kB |
각보다 우리 라이브러리가 른 인기 라이브러리들보다 전체 크기가 작았다. 하지만 더 중요한  사용자가 실제로 필요한 컴포넌트만 가져갈 때의 크기다. 여기서 트리 쉐이킹이 중요한 이유가 나다.

 

트리 쉐이킹과 preserveModules 옵션의 

트리 쉐이킹이란 사용하지 않는 드를 최 들에서 제외하는 기술이다. 간단히 말하  흔들어서 죽은 잎사귀만 떨어뜨리는 것과 슷하다.Rollup 설정에서 preserveModules: true 옵션은 리 쉐이킹 구현의 핵심인데, 처음에는 이 정의 요성을 몰랐다.

output: [
  {
    dir: 'dist/esm',
    format: 'esm',
    preserveModules: true,
    preserveModulesRoot: 'src'
  }
]

 

이 옵션 왜 그렇게 중요할까? 처음에는 단순히 "모듈 구조를 유지한다"는 설명만 보고 별로 중요하지 않다고 생각했다. 하지만 실제로 적용해보니 차이가 확 닿았다.preserveModules: true 는 일:

  1. 일 구조 그대로 유지:
  •  정이 없으 Rollup은 모든 코드를 하나의 큰 일로 뭉쳐버린다.
  • true 설정하면 src 폴더 구조를 그대로 dist 유지해준다.
  1.  컴포넌트 포트 가능:
  • 용자가 이 식으로 져올 수 있게 해준다:
     
     
 import Button from 'library/Button'; // 파일 구조가 유지되어서 가능

 

  1. 빌드 도구의 정 분석 :
  • 모듈 계가 유지되어 Webpack나 Rollup 같은 번들러가 사용하 는 코드를  정확히 식별할 수 있다.

을 시착오 겪으서 이해 용을 드로 표현하면 이렇다:

 

// preserveModules: false (기본값)일 때:
// dist/index.js
import React from 'react';
// 모든 컴포넌트 코드가 이 파일에 포함됨
export const Button = () => {...};
export const Slider = () => {...};
// ... 다른 모든 컴포넌트

// preserveModules: true일 때:
// dist/components/button/Button.js
import React from 'react';
export const Button = () => {...};

// dist/components/slider/Slider.js
import React from 'react';
export const Slider = () => {...};

 

 작은 차이가 트리 쉐이킹 효 엄청난 향을 미친!

Mantine의 접근 방식 분석

Mantine의 Accordion 컴포넌트 코드를 살펴보면서 은 인사이트를 얻었다.

 

// Mantine의 코드에서 배운 패턴
import { useId, useUncontrolled } from '@mantine/hooks';
import {
  Box,
  BoxProps,
  createVarsResolver,
  // ... 다른 가져오기
} from '../../core';

// 하위 컴포넌트 구조
Accordion.Item = AccordionItem;
Accordion.Panel = AccordionPanel;
Accordion.Control = AccordionControl;
Accordion.Chevron = AccordionChevron;

Mantine이 리 쉐이킹을 위해 사용하는 방법:

  1. 요한 것만 정히 가져오: import * 대신 요한 것만 시적으로 가져온다.
  2. 위 컴포넌트를  성으로: 번들러가 컴포넌트 계를 쉽게 파악할 수 있게 한다.
  3. 통 코드 재사용: 유틸리티 함수를 절히 분리하고 재사용한다.

이런 턴을 보 우리 라이브러리도 비슷하게 구현봤다.

른 라이브러리들과 비교

Chakra UI는 어떻게 하나?

Chakra UI는  다른 접근 방식을 용한다:

 

// Chakra UI 임포트 방식
import { Button } from '@chakra-ui/react' // 전체 라이브러리에서
import Button from '@chakra-ui/button' // 개별 패키지에서

Chakra UI는 포 구조로  컴포넌트를 별 키지로 발행한다. 효과적이지만, 여러 컴포넌트를 용할 때 의존성 관리가 복잡해질 수 있다고 각했다.

Mantine은?

Mantine은 일 패키지 내에서 듈 구조를 잘 유지한:

 

/ Mantine 임포트 방식
import { Button } from '@mantine/core' // 전체 라이브러리에서

 

Mantine도 부적으로 preserveModules: true를 사용하는  같았다. 그래서 우리도  접근 방식을 라가기로 했다.

리 라이브러리의 선

우리는 두 장점을 모두 취하는 식을 선택했다:

 

// 우리 라이브러리의 임포트 방식
import { Button } from 'react-common-components-library' // 전체에서
import Button from 'react-common-components-library/Button' // 개별 컴포넌트

 

package.json의 exports 필드를 설정해서 두 가지 방식 모두 지원하도록 했다:

 "exports": {
    ".": {
      "import": "./dist/esm/index.js",
      "require": "./dist/cjs/index.js",
      "types": "./dist/types/index.d.ts"
    },
    "./styles.css": "./dist/styles.css",
    "./*/styles.css": "./dist/*/styles.css",
    "./*": {
      "import": "./dist/esm/components/*/index.js",
      "require": "./dist/cjs/components/*/index.js",
      "types": "./dist/types/components/*/index.d.ts"
    }
  },

 

제 번들 크기 정 결과

테스트 앱을 만들어서  라이브러리의 Button 컴포넌트만 가져와 사용을 때 번들 크기를 교해봤다:

 

// 각 라이브러리에서 Button만 사용
import { Button } from 'react-common-components-library';
// vs
import { Button } from '@chakra-ui/react';
// vs
import { Button } from '@mantine/core';

| 라이브러리 | Button만 사 시 번들 크기 |

| 우리 라이브러리 | ~18 kB |

| Chakra UI | ~42 kB |

| Mantine | ~32 kB |

리 쉐이킹 최적화 

트리 쉐이킹 최적화 업을 하면서 배운 몇 가지 팁 공유한다:

  1. preserveModules: true 꼭 사용하기:
  •  나로 리 쉐이킹 효과가 대화된다.

   1. 사이드 이펙트 표시하기:

  • CSS 파일만 사이드 이펙트로 표시하고 머지는 제 가능하 다.
  1. ESM 형식 지:
  • CJS와 ESM 모두 지원하, ESM을 본으로 설정하는 게 좋다.
  1. 별 컴포넌트  하기:
  • exports 필드로 별 컴포넌트 접근을 원한다.
  // 이렇게 하면 안 된다
   import { Button } from './components';
   
   // 이렇게 하는 게 좋다
   import Button from './components/Button';

 

마무리

번들 크기 비교를 통해 우리 라이브러리가 Chakra UI나 Mantine보다 더 은 번들 크기를 가지고 있음을 확인했다. preserveModules: true 정은 음에는 요한지 몰랐는데, 알고보니 트리 쉐이킹 최적화의 핵심이었다.요한 건 전체 패키지 크기가 아니라 실제 사용자 애플리케이션에서의 능이라  깨달았다. 우리 라이브러리는 은 번들 크기와 효율적인 트리 쉐이킹 원으로 사용자 경험을 최적화하는 데 초점을 맞고 있다.다음에는 성능 최적화와 근성에 대한 이야기를 해볼까 한다. 

반응형