만족

[Nodejs] 패키지 관리 도구: pnpm vs npm vs Yarn 본문

[Nodejs] 패키지 관리 도구: pnpm vs npm vs Yarn

Nodejs Satisfaction 2025. 8. 20. 13:17

 

오늘날 프론트엔드와 백엔드를 막론하고 Node.js 생태계는 빠르게 성장하고 있으며, 그 중심에는 수많은 패키지와 의존성이 있다. 이 의존성들을 효율적으로 관리하는 것은 프로젝트의 성능과 안정성을 좌우하는 중요한 요소이다. npm, Yarn에 이어 새롭게 떠오르는 pnpm에 대해 자세히 알아보면서, 각 도구가 어떻게 패키지를 관리하고 디스크 공간을 사용하는지 함께 파헤쳐 본다.

 

npm과 Yarn, 그리고 그들의 node_modules 이야기

Node.js 생태계에서 가장 널리 사용되는 패키지 관리 도구는 역시 npm과 Yarn이다. 두 도구 모두 package.json 파일을 기반으로 프로젝트 의존성을 설치하고 관리하며, 설치된 패키지들은 대부분 프로젝트 루트의 node_modules 디렉토리에 저장된다.

 

1. npm과 node_modules의 진화: 호이스팅(Hoisting)

npm은 초기 버전에서 의존성을 중첩 구조로 설치했다. 예를 들어, A 패키지가 B 패키지에 의존하고 B 패키지가 C 패키지에 의존한다면, node_modules/A/node_modules/B/node_modules/C와 같은 깊은 구조가 만들어졌다. 이는 디스크 공간을 엄청나게 차지하고 모듈 해석에 비효율적인 문제가 있었다.

이러한 문제를 해결하기 위해 npm v3부터는 호이스팅(Hoisting)이라는 개념을 도입했다. 호이스팅은 의존성 트리를 가능한 한 평평하게 만들어, 여러 패키지가 공통으로 의존하는 패키지들을 node_modules 최상위 레벨로 끌어올리는 방식이다.

 

장점

  • 디스크 공간 절약: 중복 패키지 설치를 줄여 디스크 공간을 아낄 수 있다.
  • 모듈 로딩 효율화: node_modules 경로를 단순화하여 모듈 해석 시간을 단축한다.

단점

  • 유령 의존성(Phantom Dependencies): package.json에 명시적으로 선언하지 않은 패키지라도, 호이스팅으로 인해 node_modules 최상위에 존재한다면 코드에서 해당 패키지를 사용할 수 있게 된다. 이는 프로젝트의 실제 의존성을 파악하기 어렵게 만들고, 예상치 못한 버그를 유발할 수 있다.
  • 버전 충돌: 서로 다른 버전이 필요할 때 문제 발생 소지가 있다.

2. Yarn의 등장과 캐싱 시스템

Yarn은 npm의 초기 버전이 가지고 있던 성능과 보안 문제를 개선하기 위해 페이스북에서 개발되었다. Yarn 역시 npm과 유사한 평면화된 node_modules 구조와 호이스팅 개념을 사용하지만, 다음과 같은 특징으로 효율성을 높였다.

 

장점

  • 빠른 설치 속도: npm보다 빠른 설치 속도를 제공한다.
  • 정확한 버전 관리: yarn.lock 파일을 통해 모든 개발 환경에서 동일한 의존성을 보장한다.
  • 오프라인 설치: 패키지 전역 캐시를 통해 한 번 다운로드한 패키지는 오프라인에서도 재설치가 가능하다.

하지만 Yarn 역시 기본적으로 npm과 유사하게 각 프로젝트마다 node_modules 디렉토리를 생성하고 패키지 복사본을 저장하는 방식이어서, 대규모 프로젝트나 모노레포 환경에서는 여전히 디스크 공간 효율성에 아쉬움이 있었다.

 

pnpm: 패키지 관리의 새로운 대안

pnpm은 이러한 npm과 Yarn의 한계, 특히 디스크 공간 효율성과 유령 의존성 문제를 해결하기 위해 등장했다. pnpm은 이름 그대로 Performance NPM을 목표로 하며, 다음과 같은 독특한 방식으로 이 문제를 해결한다.

 

1. 콘텐츠 주소 지정 저장소와 심볼릭 링크

pnpm의 핵심은 콘텐츠 주소 지정 저장소(Content-addressable store)와 심볼릭 링크(Symbolic Link)이다.

  • pnpm은 모든 패키지를 전역적인 한 개의 저장소(일반적으로 ~/.pnpm-store)에 한 번만 설치한다. 이 저장소는 각 패키지 파일의 해시 값을 기반으로 경로가 결정되므로, 동일한 파일은 디스크에 한 번만 저장된다.
  • 프로젝트의 node_modules에는 실제 패키지 파일 대신, 이 전역 저장소에 있는 패키지를 가리키는 심볼릭 링크만 생성된다.

이 방식은 아래와 같은 다단계 node_modules 구조를 만든다:

 

my-project/node_modules/
├── .pnpm/                      <- pnpm이 관리하는 가상의 '호이스팅' 공간
│   ├── express@4.17.1/
│   │   └── node_modules/       <- 실제 패키지 파일을 가리키는 심볼릭 링크
│   │       ├── express -> ~/.pnpm-store/express/4.17.1/...
│   │       └── body-parser -> ~/.pnpm-store/body-parser/...
│   └── lodash@4.17.21/
│       └── node_modules/
│           └── lodash -> ~/.pnpm-store/lodash/4.17.21/...
├── express -> ./.pnpm/express@4.17.1/node_modules/express  <- 프로젝트에서 사용하는 'express' 링크
└── lodash -> ./.pnpm/lodash@4.17.21/node_modules/lodash    <- 프로젝트에서 사용하는 'lodash' 링크

 

2. 엄격한 의존성 관리 및 유령 의존성 방지

pnpm은 프로젝트의 package.json에 명시적으로 선언된 의존성만 최상위 node_modules에 심볼릭 링크로 연결한다. 즉, 내가 사용하겠다고 명시한 패키지만 직접 접근할 수 있다.

 

pnpm의 핵심 장점

  • 디스크 공간 초절약: 전역 저장소에 한 번만 설치되므로, 동일한 패키지가 수십 개의 프로젝트에서 사용되더라도 디스크에는 단 한 번만 저장된다. 모노레포 환경에서 특히 빛을 발한다.
  • 빠른 설치 속도: 패키지를 다시 다운로드할 필요가 없으므로 설치 및 업데이트 속도가 매우 빠르다.
  • 유령 의존성 제거: package.json에 명시된 패키지만 사용할 수 있게 하여, 예상치 못한 의존성 문제(버그)를 근본적으로 방지한다.
  • 안정적인 node_modules 구조: 예측 가능한 의존성 트리를 통해 빌드 환경의 안정성을 높인다.

 

왜 pnpm을 사용해야 할까?

  • 대규모 프로젝트 및 모노레포 환경: 수많은 하위 프로젝트가 같은 의존성을 공유하는 환경에서 디스크 공간과 설치 시간 면에서 압도적인 효율을 보여준다.
  • 빠른 CI/CD: 패키지 설치 시간이 곧 CI/CD 파이프라인의 속도에 직접적인 영향을 미치므로, pnpm의 빠른 속도는 개발 및 배포 워크플로우를 가속화한다.
  • 의존성 관리에 대한 엄격함 추구: '유령 의존성' 같은 잠재적 문제를 사전에 차단하여 코드 베이스의 건전성을 높이고 싶을 때 최적의 선택이다.

결론

npm과 Yarn은 오랜 시간 동안 Node.js 생태계를 지탱해 온 훌륭한 도구이지만, pnpm은 패키지 관리의 새로운 표준을 제시하며 기존 도구들이 가지고 있던 아쉬움을 효과적으로 해소하고 있다.

개발 효율성과 프로젝트의 안정성을 중요하게 생각한다면, 다음 프로젝트에서는 pnpm을 꼭 한번 경험해 보기를 강력히 추천한다.



Comments