npm과 pnpm의 가장 근본적인 차이는 node_modules 디렉토리를 구성하는 방식에 있다. 이 차이 하나가 디스크 공간 효율성, 설치 속도, 그리고 프로젝트의 안정성까지 모든 것을 결정한다.
node_modules와 그 문제점npm v3부터는 의존성 중복 설치 문제를 해결하기 위해 평탄화된(flat) node_modules 구조를 사용한다. 모든 패키지를 가능한 한 최상단 디렉토리로 끌어올려서 중복을 최소화하는 방식이다.
유령 의존성 (Phantom Dependency): 이 평탄화 구조는 심각한 부작용을 낳았다. 내가 package.json에 직접 추가하지 않은 패키지(다른 패키지의 의존성)에도 코드에서 import로 접근할 수 있게 된 것이다. 이는 "내 컴퓨터에서는 잘 되는데, CI 서버에서는 빌드가 실패해요" 같은 예측 불가능한 버그의 주된 원인이 된다.
디스크 공간 낭비: 여러 프로젝트에서 동일한 버전의 라이브러리를 사용하더라도, npm은 각 프로젝트의 node_modules 폴더에 해당 라이브러리 파일 전체를 복사한다. 10개의 프로젝트에서 react를 사용하면, 내 컴퓨터에는 10개의 똑같은 react 코드가 존재하게 되어 디스크 공간을 비효율적으로 사용한다.
느린 설치 속도: 수만 개의 파일을 디스크에 복사하는 작업은 본질적으로 느릴 수밖에 없다.
pnpm은 node_modules의 문제를 완전히 다른 방식으로 해결한다.
콘텐츠 주소 지정 저장소 (Content-addressable store): pnpm은 패키지를 다운로드하면, 프로젝트의 node_modules가 아닌 컴퓨터의 중앙 저장소(기본적으로 ~/.pnpm-store)에 딱 한 번만 저장한다.
심볼릭 링크(Symbolic Links)를 이용한 node_modules: pnpm install을 실행하면, node_modules에 실제 파일을 복사하는 대신, 중앙 저장소에 있는 실제 파일을 가리키는 바로가기(심볼릭 링크)만 생성한다.
엄격한 의존성 관리: node_modules의 최상단에는 package.json에 명시된 패키지들의 바로가기만 존재한다. 다른 패키지의 의존성에 접근하려면, Node.js의 원래 의존성 탐색 알고리즘처럼 해당 패키지 폴더 내부의 node_modules를 거쳐야 한다. 이 구조 덕분에 유령 의존성 문제가 원천적으로 차단된다.
| 구분 | npm | pnpm |
|---|---|---|
node_modules 구조 | 평탄화(Flat) | 심볼릭 링크(Symlinked) |
| 디스크 공간 효율성 | 비효율적 (프로젝트마다 중복 저장) | 매우 효율적 (중앙 저장소 공유) |
| 설치 속도 | 보통 | 매우 빠름 (파일 복사 대신 링크 생성) |
| 유령 의존성 문제 | 발생 가능성 높음 | 원천적으로 차단 (엄격한 구조) |
결론적으로, pnpm은 npm의 고질적인 문제였던 디스크 공간 낭비와 유령 의존성 문제를 아주 우아하게 해결한, 더 빠르고 안정적인 차세대 패키지 매니저라고 할 수 있다.