- npm 생태계에서 의존성 트리의 비대화가 주요 문제로 지적되며, 이는 오래된 런타임 지원, 원자적 패키지 구조, 오래된 ponyfill 사용에서 비롯됨
- 구형 엔진 호환성과 cross-realm 안전성을 이유로 유지되는 작은 유틸리티 패키지들이 현대 환경에서도 불필요하게 남아 있음
- 원자적 아키텍처는 재사용성을 높이려 했으나, 실제로는 중복·보안·유지보수 비용을 증가시키는 비효율적 구조로 작용함
- 이미 모든 엔진이 지원하는 기능을 위한 낡은 ponyfill 패키지들이 제거되지 않아 불필요한 다운로드와 관리 부담을 초래함
- 커뮤니티는 e18e, knip, module-replacements 같은 도구를 통해 불필요한 의존성 정리와 네이티브 기능 전환을 추진 중임
JavaScript 의존성 비대화의 세 가지 축
- e18e 커뮤니티의 성장과 함께 성능 중심의 기여가 늘어나며, 불필요하거나 유지되지 않는 패키지를 정리하는 cleanup 활동이 진행 중임
- npm 생태계에서 의존성 트리의 비대화(dependency bloat) 가 주요 문제로 지적되며, 오래된 런타임 지원, 원자적 패키지 구조, 오래된 ponyfill 사용이 핵심 원인으로 꼽힘
1. 오래된 런타임 지원 (안전성과 realm 포함)
- npm 트리에는
is-string,hasown같은 작은 유틸리티 패키지들이 다수 존재하며, 이는 다음 세 가지 이유로 유지됨- 매우 오래된 엔진(예: ES3, IE6/7, 초기 Node.js) 지원
-
전역 네임스페이스 변조 방지
- cross-realm 값 처리
-
오래된 엔진 지원
- ES3 환경에는
Array.prototype.forEach,Object.keys,Object.defineProperty등 ES5 기능이 존재하지 않음 - 이런 환경에서는 직접 구현하거나 polyfill을 사용해야 함
- 가장 좋은 해결책은 업그레이드이지만, 일부 사용자는 여전히 구버전을 유지함
- ES3 환경에는
-
전역 네임스페이스 변조 방지
- Node는 내부적으로 primordials 개념을 사용해 전역 객체를 초기 시점에 래핑하여 변조로부터 보호함
- 예를 들어
Map을 재정의하면 Node 자체가 깨질 수 있으므로, Node는 원본 참조를 유지함 - 일부 패키지 유지자는 이 방식을 일반 패키지에도 적용해
math-intrinsics같은 안전성 중심 패키지를 사용함
-
Cross-realm 값
- iframe 간 객체 전달 시
instanceof검사가 실패하는 문제 발생 - 예:
window.RegExp !== iframeWindow.RegExp -
chai같은 테스트 프레임워크는Object.prototype.toString.call(val)방식으로 realm 간 타입 검사를 수행함 -
is-string같은 패키지는 이런 cross-realm 호환성을 위해 존재함
- iframe 간 객체 전달 시
-
문제점
- 대부분의 개발자는 현대 Node나 evergreen 브라우저를 사용하므로 이런 호환성이 불필요함
- 그러나 이들 패키지가 일반 의존성 트리의 “핫패스”에 포함되어 모두가 비용을 지불하게 됨
2. 원자적(Atomic) 아키텍처
- 일부 개발자는 패키지를 가능한 한 작은 단위로 분리해 재사용 가능한 빌딩 블록으로 구성해야 한다고 주장함
- 결과적으로
shebang-regex,arrify,slash,path-key,onetime,is-wsl등 극도로 세분화된 패키지들이 다수 존재함 - 예:
shebang-regex는 단 한 줄의 정규식만 포함 (/^#!(.*)/) -
문제점
- 대부분의 원자적 패키지는 재사용되지 않거나 단일 소비자만 존재
- 예:
-
shebang-regex→shebang-command만 사용 -
cli-boxes→boxen,ink만 사용 -
onetime→restore-cursor만 사용
-
- 이런 경우 인라인 코드와 동일하지만 npm 요청, 압축 해제, 대역폭 등의 추가 비용이 발생
-
중복 문제
- 예:
nuxt@4.4.2의존성 트리에서is-docker,is-stream,is-wsl,path-key등이 2개 버전씩 중복 존재 - 인라인 코드로 대체하면 버전 충돌이나 해상 비용이 사라져 중복 비용이 거의 없음
- 예:
-
공급망 위험 확대
- 패키지 수가 많을수록 보안·유지보수 리스크 증가
- 실제로 한 유지자가 다수의 작은 패키지를 관리하다 계정이 해킹되어 수백 개 패키지가 동시에 손상된 사례 존재
- 단순한 코드(
Array.isArray(val) ? val : [val])는 별도 패키지로 둘 필요 없이 인라인으로 처리 가능
-
결론
- 원자적 아키텍처는 의도와 달리 비효율적이고 위험한 구조로 변질
- 대부분의 사용자에게 실질적 이득 없이 전체 생태계가 비용을 부담
3. 오래된 Ponyfill
- Polyfill은 엔진이 지원하지 않는 기능을 환경에 추가하는 코드이며, Ponyfill은 환경을 수정하지 않고 직접 import하여 사용하는 대체 구현임
- 예:
@fastly/performance-observer-polyfill은 polyfill과 ponyfill을 모두 제공 -
문제점
- Ponyfill은 과거에는 유용했으나, 대상 기능이 이미 모든 엔진에서 지원됨에도 제거되지 않음
- 예시:
-
globalthis(2019년부터 지원, 주간 4,900만 다운로드) -
indexof(2010년부터 지원, 주간 230만 다운로드) -
object.entries(2017년부터 지원, 주간 3,500만 다운로드)
-
- 이런 패키지는 대부분 단순히 제거되지 않았기 때문에 남아 있음
- 모든 LTS 엔진이 기능을 지원하면 ponyfill은 제거되어야 함
비대화 해소 방안
- 의존성 트리의 깊은 중첩으로 인해 정리 작업은 어렵지만, 커뮤니티 협력으로 개선 가능
- 각 개발자는 “이 패키지가 정말 필요한가?”를 자문하고, 불필요하다면 이슈를 제기하거나 대체 패키지 탐색 필요
- module-replacements 프로젝트는 네이티브 기능으로 대체 가능한 패키지 목록을 제공
-
knip 사용
- knip은 사용되지 않는 의존성 및 죽은 코드 탐지 도구
- 직접적인 해결책은 아니지만, 정리의 출발점으로 유용
-
e18e CLI 활용
-
@e18e/cli analyze명령으로 대체 가능한 의존성 탐지 가능 - 예:
chalk→picocolors로 자동 마이그레이션 - 향후에는 환경에 따라 Node의
styleText같은 네이티브 기능 추천 예정
-
-
npmgraph 활용
- npmgraph.js.org는 의존성 트리 시각화 도구
- 예:
eslint@10.1.0트리에서find-up브랜치가 고립되어 있음 - 단순한 파일 탐색 기능에 6개 패키지가 필요하지 않으므로,
empathic같은 더 작은 대안 사용 가능
-
module replacements 프로젝트
- 커뮤니티가 대체 가능한 패키지와 네이티브 기능 매핑 데이터셋을 유지
- codemods 프로젝트를 통해 자동 마이그레이션도 지원
결론
- 현재의 비대화는 소수의 구형 호환성·특이한 구조를 유지하려는 사용자 때문에 전체가 비용을 부담하는 구조
- 과거에는 불가피했지만, 현대 엔진과 API가 충분히 발전한 지금은 불필요한 부담
- 앞으로는 이 소수가 별도의 스택을 유지하고, 나머지는 가볍고 현대적인 코드 기반을 사용하는 방향으로 전환 필요
- e18e와 npmx 같은 프로젝트가 문서화·도구화를 통해 이를 지원 중이며, 각 개발자도 자신의 의존성을 점검하고 “왜 필요한가?”를 질문해야 함
- 모두가 함께 정리할 수 있음