(짧) Tree Shaking과 이를 사용하는 곳에서 해야할 일
흔히 말하는 Tree Shaking은 사실 Tree Shaking과 Dead Code Elimination이 존재하는데, Tree Shaking은 정적 분석을 통해 필요한 코드만을 추가하는 방식이고 Dead Code Elimination은 정적 분석을 진행한 후 사용하지 않는 것을 제거하는 방식이다. 따라서 Dead Code Elimination 방식을 사용하는 Webpack의 경우 사용하지 않는 코드를 제거해주는 Terser가 필요한 것이다.
또한 외부 라이브러리에서 트리셰이킹을 열어주는 방법으로는 package.json의 sideEffect을 false로 지정하는 것과 cjs, esm 방식으로 접근할때의 exports를 제대로 명시해주는 방법이 있는데, 이와 별개로 사용하는 곳에서도 잘 사용해주어야한다.
일반적으로 해당 라이브러리의 루트로부터 네임스페이스에 접근하여 사용하는 방식을 택할 경우 해당하는 코드를 전부 불러온 뒤 타입 체크도 진행하고 제거하기때문에 좋지 않고, 규모가 큰 라이브러리로부터 필요한 것을 가지고올때에는 일반적으로 서브패스까지 접근해서 가지고오는 것이 좋다. 이렇게 하면 네임스페이스로 접근할때와 다르게 필요한 path로만 접근하여 해당 라이브러리의 코드를 전부 불러오지 않기때문에 더 효율적이다.
// X - Namespace 방식
import { Button } from '@mui/material'
// O - SubPath 방식
import Button from '@mui/material/button'
실제로 Nextjs에서는 외부 라이브러리의 transpile을 담당해주는 transpilePackages 옵션과 위와 같은 케이스에서 네임스페이스 방식으로 접근한 외부 라이브러리 import문을 자체적으로 분석하고 Sub Path 방식으로 변환해주는 optimizePackageImports 옵션도 존재하지만, optimizePackageImports는 빌드 결과물 측면에서는 Sub Path를 사용할때와 같지만, 빌드 시 메모리 사용량 관점에서는 빌드 도중 외부 라이브러리에 대한 전체 타입 체킹을 먼저 수행하고 이를 Sub Path로 변경해주기때문에 Sub Path로 접근했을때보다 메모리 사용량이 높다.
결론적으로 큰 규모의 외부 라이브러리를 사용하는 경우에는 Sub Path로 접근하는 것이 가장 좋다.
2025.1.10 추가 사항
확인해보니 라이브러리에서의 배럴파일 사용이 영향을 주는 경우가 있다고 한다. 배럴파일을 사용해서 내보내는 경우는 해당 경로 아래에 있는 모든 파일을 분석하고 의존성 그래프를 만든 후 그 중에서 사용하지 않는 코드를 제거하기때문에 번들러가 의존성 파악이 어려워 안전성을 위해 전체 모듈을 번들에 포함시킬 수 있어 Tree Shaking을 안정적으로 수행하지 못하는 경우도 있다고 한다. 따라서 규모가 큰 외부 라이브러리를 사용할때에는 위에서의 결론과 동일하게 Sub Path로 접근한다면 사용하는 컴포넌트의 정확한 경로에 존재하는 파일만 분석하여 분석 범위가 적으며, 배럴파일을 통해 전체를 분석할때보다 의존성 그래프가 간결하고 명확하게 작성되어 트리쉐이킹이 완벽하게 동작한다.
// 트리쉐이킹이 불안정할 수 있음
import { Button } from '@mui/material';
// 확실한 트리쉐이킹
import { Button } from '@mui/material/Button';
배럴 파일을 사용하지 않게 된다면 라이브러리쪽에서 package.json에 아래와 같은 방식으로 명시해주어야하는데, 이런 방식은 규모가 큰 라이브러리에서는 부담이 크기때문에 이런 방식은 사용하지 않는 것으로 보이고 Sub path로 접근하는 방식을 권장한다.
// 규모가 큰 프로젝트에서 실제로 이렇게 하기란 어렵다.
"exports": {
// 각 컴포넌트별 경로 명시
"./components/Button": "./dist/components/Button.js",
"./components/Input": "./dist/components/Input.js",
"./components/Modal": "./dist/components/Modal.js",
// hooks 경로 명시
"./hooks/useTheme": "./dist/hooks/useTheme.js",
"./hooks/useModal": "./dist/hooks/useModal.js",
// utils 경로 명시
"./utils/format": "./dist/utils/format.js",
"./utils/validate": "./dist/utils/validate.js",
// etc ...
}
참조