패키지 매니저는 패키지를 다루는 작업을 편리하고 안전하게 수행하기 위해 사용되는 툴이다.
여기서 패키지를 다루는 작업이란 아래와 같다.
👉 각각의 패키지가 자신의 dependency에 대한 정보를 가지게 한다면, 사용하고자 하는 패키지의 dependency를 패키지 매니저를 통해 쉽게 설치할 수 있다.
Language | Package manager | Software repository |
---|---|---|
Python | pip | PyPI |
PHP | Composer | Packagist |
Node.js | NPM, Yarn | NPM, Yarn |
Java | Maven, Gradle | Maven |
Ruby | RubyGems, Bundler | RubyGems, Bundler |
오늘날 세 가지 주요 Javascript 패키지 매니저가 있다.
사실상, 모든 패키지 매니저의 기능은 거의 동일하다. 그래서 설치 속도나 스토리지 사용량, 기존 워크플로와 결합되는 방식 등 기능 외적인 요구 사항을 기준으로 사용할 패키지 매니저를 결정하게 됩니다.
패키지 매니저로 수행할 수 있는 일
따라서 설치 속도나 디스크 사용량, 또는 기존 워크 플로우 등과 어떻게 매칭 시킬지와 같은 기능 외적인 요구 사항에 따라 패키지 관리자를 선택하는 시대가 도래했다.
겉으로는 기능적으로 비슷해보이고 무엇을 선택하든 별 차이는 없어보이지만, 패키지 관리자들의 내부 동장은 매우 다르다.
최초의 자바스크립트 패키지 매니저는 2010년 1월에 나온 npm이다.
10여년이 넘는 시간 npm이 사용되었는데 yarn, pnpm 등이 등장하게 된 이유는 무엇일까?
npm이 등장한 이후 아래와 같은 니즈가 발생했다.
node_modules
효율화를 위한 다른 구조 (nested vs flat, node_modules vs pnp mode)lock
파일 형식npm이 최초로 등장한 이래로 이러한 니즈가 어떻게 나타났는지, yarn classic은 그 이후 등장해서 어떻게 해결했는지, pnpm이 이러한 개념을 어떻게 확장했는지, yarn berry가 전통적인 개념과 프로세스에 의해 설정된 틀을 깨기 위해 어떠한 노력을 했는지 간략한 역사를 파악해보자.
npm
은 무엇의 약자일까? 많은 사람들이 npm
이 node package manager
의 약자라고 생각하지만 사실 아니다!
npm의 전신은 사실 pm
이라 불리는 bash 유틸리티인데, 이는 pkgmakeinst
의 약자이다. 그리고 이의 node 버전이 npm
인 것이다.
npm 이전에는 프로젝트의 dependency를 수동으로 다운로드하고 관리하였기 때문에 엄청난 혁명을 가져왔다고 볼 수 있다. 이와 더불어 metadata를 가지고 있는 package.json
와 같은 개념, dependency를 node_modules
라 불리는 폴더에 설치한다는 개념, 커스텀 스크립트, public & private 패키지 레지스트리와 같은 개념들 모두 npm에 의해 도입되었다.
2017년 페이스북은 구글과 몇몇 다른 개발자들과 함께 npm이 가지고 있던 일관성, 보안, 성능 문제 등을 해결하기 위한 새로운 패키지 매니저인 yarn(Yet Another Resource Negotiator
)를 발표했다.
yarn은 대부분의 개념과 프로세스를 npm 기반으로 설계했지만, 이외에 패키지 관리자 환경에 큰 영향을 미쳤다. npm과 큰 차이점은, yarn은 초기 버전의 npm의 주요 문제점 중 하나였던 설치 프로세스의 속도를 높이기 위해 작업을 병렬화 했다.
yarn의 DX(개발자 경험), 보안 및 성능에 대한 기준을 높였으며 다음과 같은 개념을 패키지 매니저에 도입하였다.
yarn classic은 2020년부터 유지보수 모드로 전환되었다. 그리고 1.x 버전은 모두 레거시로 간주하고 yarn classic으로 이름이 바뀌었다.
pnpm은 2017년에 만들어졌으며, npm의 drop-in replacement(설정을 바꿀 필요 없이 바로 사용가능하며, 속도와 안정성 등 다양한 기능 향상이 이루어지는 대체품)으로, npm만 있다면 바로 사용할 수 있다.
pnpm 제작자들이 생각한 npm과 yarn의 가장 큰 문제는 프로젝트 간에 사용되는 dependency의 중복 저장이다. yarn classic이 물론 npm보다 빠르지만, 두 매니저 모두 node_modules 내부에 flat하게 패키지를 설치하여 (= 동일한 디렉토리에 flat하게 저장) 관리했다.
pnpm은 이러한 호이스트 방식 대신, 다른 dependency를 해결하는 전략인 content-addressable storage
를 사용했다. 이 방법을 사용하면, home 폴더의 글로벌 저장소('~/.pnpm-store'
)에 패키지를 저장하는 중첩된 node_modules 폴더가 생성된다. 따라서 모든 버전의 dependency는 해당 폴더에 물리적으로 한번만 저장되므로, single source of truth를 구성하고, 상당한 디스크 공간을 절약할 수 있다.
이는 node_modules의 레이아웃을 통해 이루어지고 'symlinks'
를 사용하여 dependency의 중첩된 구조를 생성한다. 여기서 폴더 내부의 모든 패키지 파일은 저장소에 대한 하드 링크로 구성되어 있다.
yarn berry는 2020년 1월에 출시되었으며 yarn classic의 업그레이드 버전이다.
yarn berry에서 눈여겨 봐야 할 것은 plug n play
로 node_modules를 수정하기 위한 전략이다. node_modules를 생성하는 대신, '.pnp.cjs'
라 불리는 의존성 lookup 파일이 생성되는데, 이는 중첩된 폴더 구조 대신 단일 파일이기 때문에 더 효율적으로 처리할 수 있다. 또한 모든 패키지는 '.yarn/cache'
폴더 내부에 zip 파일로 저장되므로 node_modules 폴더보다 더 디스크 공간을 적게 차지한다.
이 모든 변화들은 너무나 빨랐고 출시 후 많은 논란을 불러일으켰다. pnp의 주요 변경 사항으로 인해 메인테이너들은 기존 패키지와 호환 되도록 업데이트해야 했다. 새로운 pnp 접근 방식이 기본적으로 사용되었으며 node_modules로 되돌리는 것이 처음에는 간단하지 않았다.
yarn berry 팀은 이후 후속 릴리스에서 많은 문제를 해결했다. pnp의 비호환성을 해결하기 위해 팀은 기본 작동 모드를 쉽게 변경할 수 있는 몇 가지 방법을 제공했다. → node_modules 플러그인의 도움으로 기존의 node_modules 접근 방식을 사용하는데 설정 하는데 설정 한 줄이면 가능하다.
또한, 자바스크립트 생태계는 시간이 지남에 따라 pnp에 대한 지원을 점점 더 많이 제공했으며 일부 대규모 프로젝트에서는 Yarn Berry를 채택했다.
패키지 매니저를 사용하기 위해서는, 개발자의 로컬 혹은 CI/CD 시스템에 설치해야 한다.
node.js 내부에 npm이 내장되어 있으므로, 추가적으로 작업을 할 필요가 없다. nvm이나 volta를 사용하면 node와 npm 버전을 관리하는데 매우 유용하게 쓸 수 있다.
'npm i -g yarn'
으로 설치할 수 있다.
yarn classic에서 yarn berry로 넘어가는 방법은 다음과 같다.
yarn set version berry
명령어 입력사실 권장하는 방법은 Corepack을 사용하는 것이다.
Corepack은 yarn berry 개발자에 의해 만들어진 도구이다.
Corepack의 도움으로 node는 yarn classic, yarn berry, pnpm의 바이너리를 shim으로 가지고 있기 때문에 npm의 대체 패키지 매니저를 별도로 설치할 필요는 없다. 이 shim을 활용하면, yarn 과 pnpm 명령어를 명시적으로 설치할 필요 없이, 실행할 수 있다.
Corepack은 nodejs@16.9.0
부터 사전 설치되며, 이전 버전에서는 'npm install -g corepack'
으로 설치할 수 있다.
❗️Corepack을 사용하기 위해서는, 먼저 활성화를 해야 한다❗️
$ corepack enable
$ corepack prepare yarn@3.1.1 -- activate
pnpm도 마찬가지 두 가지 방법으로 설치할 수 있다.
$ npm i -g pnpm
$ corepack prepare pnpm@6.24.2 --activate
프로젝트 구조를 살펴보면, 특정 패키지 매니저를 구성하는데 사용되는 파일과, 설치 단계에서 생성되는 파일을 쉽게 알아볼 수 있다.
기본적으로, 모든 패키지 매니저는 모든 중요한 메타 정보를 package.json
에 저장한다. 또한 루트 레벨에 설정파일을 사용하여 프라이빗 레지스트리나 dependency를 파일 구조에 저장하고 lock 파일이 생성된다.
$npm install
또는 $npm i
명령어를 입력하면 package-lock.json
이 생성되고 node-modules
폴더도 생성된다. 이외에도 .npmrc
설정 파일도 생성될 수 있다.
.
├── node_modules/
├── .npmrc
├── package-lock.json
└── package.json
$yarn
을 실행하면, 'yarn.lock'
과 node_modules
폴더가 생성된다. 마찬가지로 '.yarnrc'
파일도 optional로 생성할 수 있다. 이에 더해 .npmrc
파일이 있으면 이를 이용할 수도 있다. 그리고 캐시 폴더인 'yarn/cache/'
와 현재 yarn classic의 버전을 저장하는 '.yarn/releases/'
도 생성될 수 있다. 이처럼 설정에 따라서 다양하게 변경될 수 있다.
.
├── .yarn/
│ ├── cache/
│ └── releases/
│ └── yarn-1.22.17.cjs
├── node_modules/
├── .yarnrc
├── package.json
└── yarn.lock
'node_modules'
yarn berry는 더이상 .npmrc
나 .yarnrc
를 사용하지 않는다. 대신 'yarnrc.yml'
설정 파일을 필요로 한다. 전통적인 'node_modules'
를 생성하는 워크플로우가 존재하는 경우, nodeLinker config 파일을 아래와 같은 형태로 제공해야 한다.
# .yarnrc.yml
nodeLinker: node-modules # or pnpm
'$ yarn'
을 실행하면, 모든 의존성을 'node_modules'
에 설치한다. 'yarn.lock'
파일이 생성되는데, 이 파일은 기존 'yarn classic'
과 호환되지는 않는다. 또한 오프라인 모드에서 설치를 위해 '.yarn/cache'
폴더도 생성된다. 'releases'
폴더는 프로젝트에서 사용하는 yarn berry의 버전을 저장하기 위해 optional로 생성된다.
.
├── .yarn/
│ ├── cache/
│ └── releases/
│ └── yarn-3.1.1.cjs
├── node_modules/
├── .yarnrc.yml
├── package.json
└── yarn.lock
pnp 모드에는 strict와 loose 모드가 있는데, 일단은 모드에 상관없이 yarn
을 실행하면 '.yarn/cache'
와 '.yarn/unplugged'
, '.pnp.cjs'
'yarn.lock'
파일이 생성된다. strict 모드는 기본 값이고, loose는 아래처럼 optional로 설정해두어야 한다.
# .yarnrc.yml
nodeLinker: pnp
pnpMode: loose
pnp 프로젝트에서 '.yarn/'
폴더 내부에는 'release/'
외에도 ide 지원을 위한 'sdk/'
폴더를 포함할 가능성이 높다.
.
├── .yarn/
│ ├── cache/
│ ├── releases/
│ │ └── yarn-3.1.1.cjs
│ ├── sdk/
│ └── unplugged/
├── .pnp.cjs
├── .pnp.loader.mjs
├── .yarnrc.yml
├── package.json
└── yarn.lock
pnpm
도 다른 패키지 매니저와 마찬가지로 package.json
이 필요하다. $ pnpm i
를 실행하면, node_modules
가 생성되는 것까지는 대른 패키지 관리자와 동일하지만, 앞서 언급한 content-addressable storage approach
라는 특성 때문에 이후의 구조가 완전히 다른다.
pnpm은 자체 lock 파일인 pnp-lock.yml
을 생성한다. 그리고 마찬가지로 .npmrc
로 설정을 추가할 수도 있다.
성능으로 미뤄 보건데, yarn berry
+ Plug n Play strict
가 가장 설치도 빠르고 디스크 효율적인 모습을 보여주었고, 그 다음으로는 pnpm이 뒤를 이었다.
npm은 역사가 오래된만큼 사건사고도 많았다.
sudo npm
명령어를 사용하면, 시스템 파일의 소유권을 변경하게 되어 os를 사용할 수 없게된 적이 있다.이러한 문제를 해결하기 위해, 요즘 최신버전의 npm에서는 package-lock.json에서 SHA-512 알고리즘을 확인하여 설치하고자 하는 패키지의 무결성을 확인한다.
yarn classic, yarn berry 둘 다 처음부터 yarn.lock
에 지정된 체크섬을 활용하여 각 패키지의 무결성을 확인한다. 또한 package.json
내부에 선언되지 않은 의심스러운 패키지가 존재하면 설치가 중단된다.
yarn berry는 이에 더해 package.json에서 명시한 의존성의 바이너리 파일만 실행할 수 있다. 이는 pnpm과 유사하다.
pnpm 또한 체크섬을 활용하여 패키지의 무결성을 확인한다. pnpm은 npm과 yarn classic에서 이슈가 되는 패키지 호이스팅을 하지 않기 때문에 이러한 문제를 피한다. 대신, 위험한 dependency 액세스의 위험성을 제거하는 내부에 중첩된 node_modules
폴더를 생성한다.
pnpm은 npm과 비슷해보이지만, 종속성 관리 측면에서 매우 다른 모습을 보인다. pnpm을 사용하면 성능이 향상되고, 디스크 효율성을 극대화 할 수 있다.
yarn classic도 훌륭한 선택지이지만, 레거시로 간주되고 가까운 미래에 지원이 중단될 수도 있는 가능성이 존재해서 선택하는 것을 추천하지는 않는다.
yarn berry의 plug n play는 완전히 새로운 혁신으로 다가왔지만, 아직 그 모든 잠재력을 달성한 것 같지는 않다. 그럼에도 요즘 사람들이 많이 쓰는 패키지 매니저는 yarn berry의 pnp인 것으로 보인다. 성능과 디스크 효율성, 속도 모두에서 뛰어난 모습을 보이고 있다.