프론트엔드 개발자들의 영원한 숙제
자바스크립트 개발자라면 누구나 한 번쯤은 npm install 명령어를 실행한 뒤, 상상 이상으로 거대해진 node_modules 폴더를 보며 의문을 품어본 경험이 있을 겁니다. "분명히 내가 추가한 패키지는 몇 개 안 되는데, 왜 폴더 용량은 수백 MB에 달하고 파일 개수는 수만 개에 육박할까?" 하는 생각 말이죠.
이러한 현상은 커뮤니티에서 '우주에서 가장 무거운 물체'라는 밈(meme)으로 표현될 정도로 모든 자바스크립트 개발자들의 공통된 골칫거리입니다.
실제로 책의 예시를 보면, dependencies가 고작 8개인 작은 리액트 프로젝트의 node_modules 폴더 용량이 무려 307MB에 달하고, 파일 개수는 43,545개에 이르는 것을 확인할 수 있습니다. 단순히 패키지 하나를 추가했을 뿐인데 package-lock.json 파일에 3만 줄이 넘는 변경 사항이 생기는 일도 비일비재하죠.
이번 글에서는 제공된 책의 내용을 바탕으로 이 미스터리의 근본적인 원인을 파헤치고, 왜 우리가 yarn이나 pnpm 같은 새로운 대안에 주목해야 하는지 알아보겠습니다.
문제의 근원: npm의 '평탄화된' 의존성 트리
이 모든 문제의 시작점에는 npm이 의존성을 관리하는 방식, 바로 '평탄화(flattening)' 구조가 있습니다.
과거 npm 초기 버전에서는 패키지가 의존하는 모든 하위 패키지들을 각자의 node_modules 폴더 안에 중첩해서 설치했습니다. 이는 의존성 구조를 명확하게 파악할 수 있다는 장점이 있었지만, 동일한 패키지가 여러 곳에서 중복으로 설치되어 극심한 디스크 공간 낭비를 유발했습니다.
이 문제를 해결하기 위해 npm v3부터 도입된 것이 바로 '평탄화' 방식입니다. 여러 패키지가 공통으로 의존하는 패키지가 있다면, 이를 최상위 node_modules 폴더로 끌어올려(hoisting) 한 번만 설치하고 모두가 참조하게 만든 것이죠.
예를 들어, 우리 프로젝트(App)가 A와 C라는 패키지에 의존하고, A와 C가 모두 B라는 패키지에 의존하는 상황을 가정해 보겠습니다.
이 경우, npm은 중복을 피하기 위해 B 패키지를 최상위 node_modules로 끌어올려 다음과 같은 구조를 만듭니다.
이 방식은 분명 디스크 공간을 절약하고 의존성 깊이를 줄이는 데 성공했습니다. 하지만 예상치 못한 부작용을 낳았는데, 그것이 바로 '유령 의존성' 문제입니다.
보이지 않는 위협: 유령 의존성 (Phantom Dependencies)
유령 의존성(Phantom Dependency)이란, package.json 파일에 명시적으로 선언하지 않았음에도 불구하고 코드 내에서 require()나 import로 불러와 사용할 수 있는 패키지를 의미합니다.
앞선 예시에서 우리 프로젝트(App)는 B 패키지를 직접 설치한 적이 없습니다. 오직 A와 C의 하위 의존성일 뿐이죠. 하지만 평탄화 구조 덕분에 B가 최상위 node_modules에 위치하게 되면서, 우리 프로젝트 코드에서 아무런 문제 없이 require('B')를 실행할 수 있게 됩니다.
이는 몇 가지 심각한 위험을 초래합니다.
1. 예측 불가능한 오류 발생
만약 패키지 A와 C가 업데이트되면서 더 이상 B에 의존하지 않게 되면 어떻게 될까요? 다음 npm install 실행 시 B는 더 이상 node_modules에 설치되지 않을 것이고, 그동안 B를 암묵적으로 사용해왔던 우리 프로젝트 코드는 갑자기 Cannot find module 'B' 오류를 내뿜으며 중단될 것입니다.
2. 패키지 관리자 변경의 어려움
모든 패키지 관리자가 npm처럼 의존성을 평탄화하지는 않습니다. 예를 들어 pnpm은 npm과 다른 방식으로 의존성을 관리하여 유령 의존성 문제를 원천적으로 방지합니다. 만약 유령 의존성에 기대어 작성된 npm 기반 프로젝트를 pnpm으로 전환하려고 하면, 숨어있던 모든 의존성 문제가 한꺼번에 터져 나오며 빌드가 실패하게 될 가능성이 매우 높습니다.
이처럼 유령 의존성은 프로젝트의 안정성을 크게 해치는, 반드시 해결해야 할 문제입니다. 하지만 npm의 구조상 개발자가 세심한 주의를 기울이지 않으면 발견하기가 매우 어렵습니다.
실질적인 고통: 왜 node_modules는 거대해지는가?
유령 의존성 문제와 더불어, npm의 node_modules는 실제 프로젝트 코드보다 훨씬 더 큰 용량을 차지하는 문제점을 안고 있습니다.
책에서 소개된 한 웹 서비스 프로젝트의 경우, 개발자가 직접 작성한 코드(src 폴더)의 크기는 33MB에 불과했지만, 95개의 의존성을 설치한 node_modules의 크기는 무려 744MB에 달했습니다.
Bash
$ npm list --parseable | wc -l
95 $ du -sh ./node_modules
744M ./node_modules
$ du -sh src
33M src
이렇게 거대한 node_modules는 다음과 같은 실질적인 문제들을 야기합니다.
불필요한 디스크 공간 낭비: 개발자의 로컬 환경은 물론, CI/CD 및 배포 환경에서도 막대한 디스크 공간과 비용을 낭비하게 됩니다.
빌드 및 배포 시간 증가: 의존성 설치, 파일 탐색, 유효성 검사 등에 걸리는 시간이 길어져 개발 생산성과 배포 속도를 저하시킵니다.
성능 저하: npm install 과정에서 node_modules의 현재 상태를 확인하고 의존성 트리를 재구성하는 작업의 부하가 커져 npm 명령어 자체의 성능 또한 떨어지게 됩니다.
결론: 새로운 대안, yarn과 pnpm의 필요성
지금까지 우리는 node_modules가 비대해지는 원인이 npm의 '평탄화'된 의존성 관리 방식에 있으며, 이로 인해 '유령 의존성'이라는 잠재적 위험까지 안게 된다는 사실을 알게 되었습니다.
물론 이러한 문제들이 전적으로 npm만의 탓이라고 할 수는 없지만, 패키지 관리자 차원에서 충분히 개선될 수 있는 부분이기도 합니다. 바로 이러한 npm의 구조적인 한계를 극복하기 위해 yarn과 pnpm 같은 대안 패키지 관리자들이 등장했습니다.
이들은 npm과는 다른 혁신적인 방식으로 의존성을 관리하여 디스크 공간을 획기적으로 절약하고, 설치 속도를 크게 향상시키며, 유령 의존성 문제를 원천적으로 차단합니다.
다음 글에서는 이 새로운 주역들이 어떻게 node_modules의 문제를 해결했는지, 그 구체적인 작동 방식과 특징을 깊이 있게 다뤄보겠습니다. 더 이상 node_modules 때문에 고통받지 않고, 프로젝트를 완벽하게 제어할 수 있는 개발자로 성장하기 위한 첫걸음이 될 것입니다.
https://wikibook.co.kr/npm-deep-dive/
자바스크립트는 단순한 프로그래밍 언어를 넘어 전 세계 개발자들이 활용하는 방대한 생태계를 만들어냈습니다. 하지만 매일 사용하는 npm과 package.json, 다양한 번들 도구와 모듈 시스템의 작동 원리, 그리고 모노레포 환경까지 제대로 이해하기는 쉽지 않습니다. 이 책은 이러한 자바스크립트 생태계의 복잡한 퍼즐을 하나씩 풀어가며 실무에 꼭 필요한 지식을 제공합니다.