본문 바로가기
프로그래밍/JS

[JS] lodash 사용의 득과 실 점검

by webcodur 2025. 12. 20.
728x90
반응형

 

목차

     

    프로젝트에 npm install lodash를 입력하는 순간, 우리는 무엇을 얻고 무엇을 잃나?

     

    lodash는 JavaScript 생태계에서 가장 널리 사용되는 유틸리티 라이브러리다. 주간 다운로드 수가 5천만을 넘는다. 거의 모든 프로젝트에서 한 번쯤 마주친다. 그만큼 유용하다는 증거다. 하지만 최근 트리 쉐이킹, 번들 크기 최적화, 네이티브 JavaScript 발전 같은 이야기를 접하면서 의문이 생겼다. "정말 lodash가 필요한가?" "있으면 편한데, 대가는 없는가?" 오늘은 lodash 사용에 대한 아이디어를 정리한다.

     

    1. 왜 lodash를 쓰는가

    lodash는 편리하다. debounce, throttle, cloneDeep 같은 함수를 즉시 사용할 수 있다. 직접 구현할 필요 없이 import 한 줄이면 된다. 수년간 검증된 코드라 버그 걱정도 없다. 엣지 케이스도 이미 처리되어 있다. 팀 프로젝트에서는 코드 일관성도 확보된다. 모두가 같은 라이브러리를 쓰면 리뷰도 쉽다. 이것이 수많은 프로젝트가 lodash를 의존성에 포함시키는 이유다. 개발 속도가 중요한 상황에서 합리적인 선택처럼 보인다.

     

    장점:

    • 즉시 사용 가능한 유틸리티 함수
    • 검증된 구현으로 버그 리스크 최소화
    • 엣지 케이스 처리 완료
    • 팀 내 코드 일관성 확보
    • 개발 속도 향상

     

    2. 보이지 않는 비용

    하지만 import _ from 'lodash'라고 쓰는 순간, 71KB가 번들에 추가된다. gzip 압축 후에도 24KB다. 프로젝트에서 실제로 사용하는 함수가 5~10개라도 전체가 포함된다. 이것이 트리 쉐이킹의 한계다. 트리 쉐이킹은 사용하지 않는 코드를 번들에서 제거하는 최적화 기법이다. 나무를 흔들어 죽은 잎을 떨어뜨리듯, 죽은 코드를 제거한다는 의미다. 하지만 일반 lodash는 모든 함수가 하나의 거대한 객체로 묶여 있다. 번들러가 분리할 수 없다.

     

    비용 :

    • 71KB(gzip 24KB) 번들 크기 증가
    • 5~10개 함수만 써도 300개 전체가 포함
    • 트리 쉐이킹 불가능한 구조
    • 3G 환경에서 0.5~1초 로딩 지연
    • 저사양 기기의 파싱/컴파일 시간 증가
     
    // 이렇게 쓰면 전체 71KB가 포함된다
    import _ from 'lodash';
    _.debounce(fn, 300);

     

    반면 ES 모듈 형식으로 구성된 라이브러리는 트리 쉐이킹이 작동한다. date-fns가 moment.js를 대체한 이유이고, lodash-es가 존재하는 이유다. 각 함수가 독립된 모듈이라 필요한 것만 가져올 수 있다.

    // lodash-es는 필요한 부분만 ~2KB
    import { debounce } from 'lodash-es';
    debounce(fn, 300);

     

    번들 크기 차이는 로딩 속도로 직결된다. 3G 환경에서 50KB는 0.5~1초의 차이이고, 이는 모바일 사용자 경험에 직접 영향을 미친다.

     

    3. 절충안

    선택지는 여러 가지이다.

     

    방법 1: lodash-es 사용

    가장 간단한 해결책이다. 기존 코드에서 lodash를 lodash-es로 바꾸기만 하면 된다. API는 동일하고 트리 쉐이킹이 작동한다.

    방법 2: 선택적 사용

    모든 lodash 함수가 라이브러리로 필요한 것은 아니다. 네이티브로 대체 가능한 것들:

    • map, filter, find → 배열 메서드
    • assign → 스프레드 연산자, Object.assign
    • get → 옵셔널 체이닝 (?.)

    정말 필요한 것은 debounce, throttle, cloneDeep 정도다. 기본 구현은 각기 10줄 내외이다:

    function debounce(func, wait) {
      let timeout;
      return function(...args) {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
      };
    }

     

    복잡한 옵션(leading, trailing, maxWait)이 필요하면 lodash-es 에서 해당 함수만 가져온다. 간단한 용도라면 직접 구현한다.

     

    방법 3: 네이티브 API 활용

    cloneDeep은 이제 structuredClone() Web API로 대체 가능하다. 브라우저 지원도 충분하다. 순환 참조, Date, Map, Set 등을 올바르게 처리한다.

     

    각 방법의 장단점:

    • lodash-es: 마이그레이션 쉬움, 트리 쉐이킹 가능
    • 선택적 사용: 최소 번들 크기, 유지보수 부담
    • 네이티브 API: 의존성 제로, 브라우저 호환성 체크 필요

     

    결론

    lodash는 편의성과 번들 크기 사이의 트레이드오프다. 정답은 없다. 프로젝트 규모, 성능 요구사항, 팀 상황에 따라 선택이 달라진다. 작은 프로토타입이라면 일반 lodash도 괜찮다. 대규모 프로덕션이라면 lodash-es나 선택적 사용을 고려한다. 중요한 것은 의식적인 선택이다. "다들 쓰니까"가 아니라 "우리 프로젝트에 맞는가"를 판단해야 한다. 번들 분석 도구(webpack-bundle-analyzer)로 실제 크기를 확인하고, 필요하다면 최적화한다. 모든 의존성에는 비용이 있다는 것을 기억하면 된다.

    반응형