자바스크립트
개발 환경 설정
pnpm 패키지 매니저

pnpm 설치: npm, yarn 보다 2배 빠른 차세대 JS 패키지 매니저

JS 패키지 매니저 3형제의 막내, pnpm 을 소개합니다. pnpm 이 npm, yarn 보다 빠른 동작 원리를 쉽게 설명하고, 설치 및 사용 방법을 정리하려고 합니다.

1. pnpm 소개

pnpm 로고 pnpm 공식 홈페이지: https://pnpm.io/ko/ (opens in a new tab)

pnpm 은 기존 JS 패키지 매니저가 가진 성능적인 한계를 극복하고자 설계된 차세대 JS 패키지 매니저입니다. pnpm 이란 이름도 Performant npm 의 약자로, 네이밍부터 효율적인 성능을 타겟팅하고 있음을 알 수 있습니다.

사용 방법은 다른 패키지 매니저와 거의 동일합니다. package.json 파일 기반으로 동작하며, npm, yarn 의 거의 모든 기능을 대체할 수 있습니다. 커맨드라인 명렁어는 yarn 에 더 가깝게 만들어져 있습니다.

2. pnpm 의 3가지 장점

pnpm 을 사용해서 얻을 수 있는 이점은 크게 3가지입니다. 디스크 용량을 아낄 수 있고, 의존성 설치를 더 빨리 수행하며, 더 효율적으로 node_modules 디렉토리 구조를 구성합니다.

하나씩 살펴봅시다.

2.1. 더 적은 디스크 사용량

pnpm 이 같은 작업을 하면서도 더 적은 디스크 용량을 사용할 수 있는 이유는 Content-addressable storage(CAS) 라고 하는 중앙 저장소를 사용하기 때문입니다. pnpm 으로 패키지를 설치하면, 모든 패키지는 이 저장소에 저장됩니다. 그리고 pnpm 을 사용하는 모든 프로젝트는 이 저장소에서 필요한 패키지를 가져가서 사용합니다.

예를 들어, npm 으로 3개의 React 프로젝트를 만들었다면, react 패키지는 각 프로젝트의 node_modules 디렉토리에 모두 설치됩니다. 내 컴퓨터에는 동일한 패키지가 3개 설치되는 셈입니다. 하지만 pnpm 을 사용한다면 1개의 react 패키지만 CAS 저장소에 설치하고 3개의 프로젝트는 이 패키지를 함께 사용합니다.

pnpm 의 CAS 동작원리 묘사: CAS 동작 원리 설명

만약 필요한 패키지의 버전이 다르다고 해도 CAS 저장소는 이를 효율적으로 처리합니다. 각 버전마다 변화된 파일만 다운받아 저장해두는 것이죠. Docker 에서 의존성을 관리하는 알고리즘과 유사합니다.

JS/TS 개발을 하면, 각 프로젝트마다 중복된 패키지나, 여러 패키지가 동시에 의존하는 패키지를 쉽게 찾아볼 수 있습니다. 이런 경우에, pnpm 덕분에 아끼는 디스크 용량은 상당히 클 수 밖에 없습니다.

pnpm progress 로그

위 화면은 pnpm 으로 몇 가지 패키지를 설치할 때 출력되는 진행 상황 로그 입니다. 마지막 문장의 각 숫자는 다음을 의미합니다.

  • resolved : 총 필요한 패키지 수
  • reused : resolved 중 CAS 저장소에 이미 설치된 패키지 수
  • downloaded : resolved 중 새로 다운받은 패키지 수
  • added : 결과적으로 프로젝트에 추가한 패키지 수

위의 경우, 새로 다운 받아야 하는 패키지가 없으니 디스크 사용이나 네트워크 사용이 없고, 그만큼 설치 시간을 단축할 것입니다. 심지어 CAS 저장소에 설치된 패키지라면 오프라인에서도 프로젝트에 추가가 가능합니다.

2.2. 더 빠른 의존성 패키지 설치 속도

pnpm 이 더 빠르게 의존성 설치를 완료할 수 있는 첫번째 이유는 위에서도 언급한 CAS 저장소 덕분입니다. 일단, 저장소에 이미 설치한 패키지는 다시 설치하지 않고, 버전이 업그레이드 되어도 다운받는 파일 수가 현저히 적으니, 속도가 빠른 건 당연한 일입니다.

두번째로, pnpm 은 패키지 병렬 설치를 지원합니다.

JS 패키지 매니저는 의존성 설치의 3단계인 [ 필요한 의존성 파악 -> 다운로드 -> 패키지 컴파일 ] 을 진행합니다. npm 같은 경우에는, 모든 패키지가 하나의 단계를 완료해야 다음 단계로 넘어갈 수 있습니다. pnpm 은 패키지끼리 서로 영향을 주지 않는지를 먼저 계산하기 때문에, 각 패키지마다 따로 3단계를 완료할 수 있습니다.

아래 도표에서 가로 축은 시간의 흐름, 세로 축은 각 패키지입니다. 모든 패키지가 전 단계를 끝마치지 않더라도 이전 단계가 먼저 끝난 패키지는 다음 단계로 넘어가고, 덕분에 모든 설치 과정을 더 빨리 완료합니다.

pnpm 패키지 병렬 설치

pnpm 의 패키지 병렬 설치 예시 (출처 (opens in a new tab))

2.3. 더 엄격하고 효율적인 node_modules 디렉토리

위에서 언급한 의존성 설치 3단계 중 1단계인 필요한 의존성 파악 단계를 영어로, Dependency resolution 이라고 합니다. 이 단계에서, 패키지 매니저는 이 프로젝트가 사용하는 패키지와, 이 패키지들이 의존하는 패키지들을 추적하면서 모두 설치할 계획을 세웁니다.

npm 2버전 까지는, 이 단계에서 모든 패키지를 그대로 설치했는데요 그로 인해 2가지 문제점이 발생했습니다.

우선, 패키지 A패키지 B 가 모두 패키지 C 를 의존성으로 가지고 있다면, 중복된다 할지라도 2번 설치가 되었습니다. 불필요한 중복이죠.

두 번째로, 각 패키지의 node_modules 디렉토리가 꼬리에 꼬리를 물고 설치되었습니다. 아래 디렉토리 구조처럼요. 이것을 nested node_modules 라고 합니다.

nested node_modules
▾ node_modules
    ▾ 패키지 A
        ▾ node_modules
            ▸ 패키지 C
    ▾ 패키지 B
        ▾ node_modules
            ▾ 패키지 C
                ▸ node_modules

npm 3버전 이상과 yarn 에서는 이런 문제를 해결하려고 flat node_modules 구조를 채택합니다. 의존성 깊이에 상관없이 모든 패키지를 프로젝트의 node_modules 아래에 설치하는 방법입니다.

flat node_modules
▾ node_modules
    ▾ .bin
    ▾ accepts
    ▾ array-flatten
    ...
    ▾ etag
    ▾ express

위와 같은 flat한 구조를 통해 중복 설치는 막았지만, 또 다른 문제점이 발생합니다. 내가 package.json 을 통해 명시적으로 설치한 패키지가 아니더라도 의존성으로 설치한 패키지를 이 프로젝트에서 사용할 수 있다는 점입니다.

예를 들어, 패키지 A 의 의존성으로 설치한 패키지 S 1버전을 package.json 에 작성하지 않고 사용하고 있다가, 패키지 S 2버전으로 업그레이드해야 하는 상황이 온다면 어떻게 될까요? 우리는 패키지 S 를 트래킹하지 않고 있기 때문에 자동 업그레이드가 불가능해서 런타임 오류가 일어날 것입니다.

pnpm은 이러한 경우를 미연에 방지합니다. pnpm으로 만들어지는 node_modules 속 패키지 파일들은 모두 CAS 저장소의 실제 파일들과 연결된 symlinks 혹은 hard links 이기 때문에 자유롭게 구조 변경 및 최적화가 가능합니다. 이 덕분에 flat한 구조를 사용하지 않고도 중복되는 패키지 문제를 해결하고, 명시적으로 설치한 패키지에만 접근하도록 제한할 수 있습니다.

3. 중간 정리

이처럼 pnpm은 CAS 저장소 사용을 통해 최대한 중복을 없애는 것 만으로도 기존 패키지 매니저보다 2배 이상의 효율을 내고 있습니다. 이와 함께 여러 가지 안정성도 확보할 수 있었고요.

그렇다면, 이제 pnpm 의 설치 및 사용 방법을 간단히 알아보겠습니다.

4. 설치 방법

pnpm 은 npm 을 이용해 전역으로 설치하는 것을 추천합니다. 간단한 명령어로 설치할 수 있습니다. 당연히 Windows 에서도 동작합니다.

npm install -g pnpm

실행 결과: npm으로 pnpm 설치

Node.js 의 Corepack 을 이용한 설치도 가능하지만, 아직 실험 단계라 따로 설명하지 않습니다.

5. 사용 방법

사용 방법은 npm, yarn 과 거의 같습니다.

5.1. 프로젝트 내 패키지 관리

  • pnpm init : pnpm 으로 관리하는 새로운 프로젝트를 시작합니다. pnpm init 예시

  • pnpm install : 미리 작성해놓은 프로젝트의 의존성 패키지 목록을 설치합니다. pnpm install 예시

  • pnpm add [패키지_이름]: 프로젝트에 하나 이상의 패키지를 새로 추가합니다. pnpm add 예시

  • pnpm exec [패키지_이름] : 프로젝트에 설치한 패키지를 커맨드라인에서 실행합니다. pnpm exec 예시

  • pnpm run [스크립트_이름] : 프로젝트에서 설정한 npm script 를 실행합니다. pnpm run 예시 pnpm run dev 결과

5.2. 전역 패키지 관리

  • pnpm dlx [패키지_이름] : 패키지를 설치 없이 임시로 다운 받은 후 실행합니다. phpm dlx 예시

  • pnpm create [패키지_이름] : create- 를 지원하는 패키지의 스타터 키트를 이용해 새 프로젝트를 생성합니다. pnpm create 예시

  • pnpm add -g [패키지_이름] : 전역 패키지를 추가합니다. pnpm add -g 예시

  • pnpm list -g : 설치한 전역 패키지를 모두 조회합니다. pnpm list -g 예시

그외 더 많은 명령어는 pnpm 을 입력해서 확인해보세요.

pnpm 명령어 모음

copyright for Javascript pnpm install

© 2023 All rights reserved.