Lodash 개별 import가 번들 크기를 키운 이유
Lodash는 보통 라이브러리 전체가 아니라 함수 몇 개만 골라 쓴다. 그래서 전체를 가져오는 것보다 필요한 모듈만 개별 import하면 번들이 줄어든다고 추상적으로 생각했다.
import _ from "lodash"; // 전체 import
import map from "lodash/map"; // 개별 import확신이 있었기에 lint 규칙으로 전체 import를 막아두고, 프로젝트의 약 180여 개 파일을 개별 import 방식으로 모두 고쳤다. 그리고 팀에 공유하려고 webpack-bundle-analyzer로 전후를 측정했는데, 결과가 예상과 정반대였다.
예상과 다른 결과
전체 import → Parsed size 74.56 KB
개별 import → Parsed size 92.13 KB번들을 줄이려고 한 작업이 오히려 17 KB 넘게 키운 것이다.
먼저, 흔한 오해 하나
개별 import가 더 크면 보통 "함수마다 같은 내부 헬퍼가 중복돼서"라고 설명한다. 그런데 이건 lodash/map이 아니라 lodash.map 얘기다. 슬래시와 점, 둘은 다르다.
lodash/map(슬래시, 서브패스) — 메인lodash패키지 안의 모듈을 직접 가리킨다. 여러 함수가 공유하는_baseEach같은 내부 파일을 번들러가 같은 모듈로 보고 한 번만 번들에 넣는다. 중복이 안 생기고, 공식 문서가 권장하는 방식이다.lodash.map(점, 개별 패키지) — 함수별로 따로 배포된 npm 패키지다. 각 패키지가 의존 코드를 자기 안에 복사해 갖고 있어, 여러 개 설치하면 내부 코드가 진짜로 중복된다. 공식 문서가 비권장하고 v5에서 제거 예정이다.
나는 서브패스(lodash/map)를 썼다. 그러니 "중복 때문"은 답이 아니었다.
진짜 원인: 모듈 래퍼 오버헤드
webpack 같은 번들러는 import하는 개별 파일(모듈)마다 이를 관리하는 자체 런타임 코드(모듈 래퍼 함수)를 붙인다. 180개 파일에서 lodash/xxx를 수십 개씩 개별로 가져오면, 번들러가 관리해야 할 모듈 수가 폭발적으로 늘어난다. 모듈마다 붙는 오버헤드(식별자, 래퍼 함수 등)가 수 바이트씩 더해지고, 그게 모이면 전체 번들이 소폭 커진다.
여기에 두 가지가 겹친다.
- Lodash 함수는 내부 의존 그래프가 깊다. 함수를 수십 개 cherry-pick하면, 끌려오는 내부 모듈을 다 합쳤을 때 결국 라이브러리 대부분이 들어온다.
- lodash 메서드 파일은 CommonJS다. webpack은 ES 모듈만 스코프 호이스팅(모듈 병합)으로 묶을 수 있어서, CommonJS인 이 모듈 수백 개는 하나로 합쳐지지 못하고 각자 래퍼를 그대로 달고 남는다.
반대로 전체 import는 lodash가 통째로 모듈 하나(lodash.js)라 래퍼가 딱 하나다. 함수를 폭넓게 쓰는 프로젝트라면, cherry-pick은 라이브러리 대부분을 어차피 가져오면서 모듈 오버헤드만 더 얹는 셈이다. (참고로 lodash 메인 패키지도 CommonJS라, 전체 import에 트리 쉐이킹을 기대할 수도 없다.)
그럼 어떻게 해야 하나
갈림길은 "새로 시작하느냐, 이미 CommonJS lodash에 묶여 있느냐"다.
새 프로젝트라면 → 고민 말고 lodash-es
ES 모듈 버전이라 트리 쉐이킹이 제대로 되고, ESM이라 webpack이 스코프 호이스팅으로 묶어 모듈 오버헤드도 안 생긴다. 함수를 많이 쓰든 적게 쓰든 실제 쓰는 것만 깔끔하게 담긴다. 위에서 본 "전체 vs 개별" 고민 자체가 사라진다. (테스트 러너 등 ESM 인터롭 설정만 한 번 확인하면 된다.)
npm install lodash-esimport { map } from "lodash-es";이미 CommonJS lodash를 쓰고 있다면
당장 패키지를 못 바꾸는 상황이고 함수를 폭넓게 쓴다면, cherry-pick보다 그냥 전체 import가 낫다. cherry-pick은 모듈 오버헤드만 더 얹는다. 내 경우가 그랬다. 다만 길게 보면 lodash-es로 옮기는 게 정답이다.
기존 코드를 크게 안 건드리고 줄이고 싶으면 babel-plugin-lodash로 빌드 시 사용한 함수만 cherry-pick하도록 변환하고, lodash-webpack-plugin으로 안 쓰는 기능 코드까지 떼어낼 수 있다.
npm install --save-dev babel-plugin-lodash{
"plugins": ["lodash"]
}그리고, 애초에 Lodash가 필요한지 본다
map, filter, find, flat처럼 ES6+ 표준으로 충분히 되는 함수는 굳이 Lodash를 쓸 이유가 없다. 가장 확실한 최적화는 의존성을 빼는 것이다.
정리
"개별 import 써라"는 흔한 조언은 반만 맞다. 함수를 몇 개만 쓰면 맞다. 트리 쉐이킹이든 cherry-pick이든 안 쓰는 코드를 덜어내니까. 하지만 함수를 폭넓게 쓰면 틀린다. cherry-pick이 라이브러리 대부분을 어차피 가져오면서 모듈 오버헤드만 더 얹어, 전체 import보다 커지기도 한다.
- 새 프로젝트 →
lodash-es(함수 수 상관없이 깔끔, 고민 끝) - 이미 CJS
lodash+ 함수 많이 씀 → 전체 import (cherry-pick은 모듈 오버헤드만 늘림) - 점진적 최적화 →
babel-plugin-lodash - 개별 패키지(
lodash.map)는 회피 - 표준으로 대체되면 Lodash 제거
내 프로젝트처럼 함수를 많이 쓰는 경우엔 전체 import가 더 나았다. 180개 파일을 lint로 막아가며 고치기 전에, 작은 범위에서 한 번만 재봤으면 바로 알았을 일이었다.