오늘날 node.js의 패키지 매니저의 대표주자로는 npm과 yarn이 있을 것입니다. 10년에 나온 npm과 16년에 나온 yarn은 시간이 많이 지났음에도 불구하고 대부분의 프로젝트에서 사용되고 있습니다. 하지만 이들이 가지고 있는 문제들을 해결하기 위해 pnpm,yarn berry이 출시되었으며 해당 글에서는 pnpm에 대해 알아보겠습니다.
들어가기에 앞서 package manager에 대해 알아보겠습니다.
팩키지 매니저는 개발자가 프로젝트 진행 중 패키지의 설치, 구성, 업그레이드, 자동화 등에 사용하는 일종의 도구입니다.
What does Package Manager do for us?
1 프로젝트에 맞는 패키지들을 찾고 설치해줍니다.
2 패키지에 문제점이 있는 지 알려줍니다.
3 패키지를 설치할 수 있습니다.
4 특정 위치에 설치할 수도 있습니다.
5 원하는 버전 혹은 새로운 버전으로 패키지를 업데이트할 수 있습니다.
6 삭제 또한 가능합니다.
만약, 패키지 매니저가 없었더라면 이러한 작업을 직접 수행했어야 할 것입니다.
그럼 이렇게 좋은 npm과 yarn에는 어떠한 문제점이 있었을까요?
바로 의존성에 대한 문제가 따라왔었습니다.
우리의 모든 프로젝트를 보면 package.json이라는 파일이 존재하며 그 안에는 Dependency라는 key값이 존재합니다. 여기서 dependency를 무엇일까요? 프로젝트를 진행하면서 필요한 라이브러리, 프레임워크, 즉, 프로젝트를 구동시키기 위한 패키지들인 것입니다. 그럼 npm, yarn에서 언급되는 dependency는 무엇이고 왜 필요할까요?
하나의 패키지는 다른 패키지들이 설치되어 있어야만 작동하는 경우가 있습니다. 가령 예를 들어 a라는 패키지는 b라는 패키지를 사용하여 만들어졌다고 가정했을 때, a를 사용하기 위해서는 b도 필요한 것이죠. 그리고 만약 이 b라는 패키지가 c와 d 패키지로 만들어졌다면 a는 c와 d도 필요할 것입니다.
이러한 패키지의 의존성을 기록하고 관리하기 위해 필요한 것입니다.
그럼 어쩌다가 npm과 yarn은 dependency hell 의존성 문제라는 것이 발생하게 된 것일까요?
위 그림에서 각 각의 화살표는 의존성을 나타낸다고 합시다.
어떠한 문제점이 보이시나요?
중복이 이루어 진다는 것이죠. 하지만 이 문제는 용량을 제외하면 프로그램을 구동하는데 크게 문제가 되지 않습니다.
그럼 서로 다른 버전이 생긴다면 어떻게 될까요?
npm과 yarn은 플랫한 구조를 가지기 때문에 설치 순서에 따라 의존성 구조가 달라지는 문제가 발생합니다. 이렇다보면 의존성이 꼬여버려 의존성 지옥 즉 Dependency hell이 발생하게 되는 것이죠
또한, 유령 의존성이라는 문제점도 가지고 있습니다.
예를 들어 저는 프로젝트에 ballGame이라는 라이브러리를 설치하였습니다.
"dependencies": {
footballGame: "1.1.1"
},
그리고 이 것을 app.js에서 사용하려합니다.
const footballGame = require("footballGame")
const rule = require("rule")
const ball = require("ball")
자 여기서 오류가 나야 정상입니다.
하지만, 실제로는 100% 오류가 난다고 할 수 없습니다.
왜냐면 유령 의존성때문입니다. package.json에 명시되어 있지 않은 footballGame의 의존성에 있는 패키지를 가져다 사용하기 때문입니다.
이는 버전이 명확하지 않기 때문에 내가 아닌 다른 사용자에게 버전 호환 오류를 발생시킵니다.
이러한 문제점들을 해결하기 위해 만들어 진 것이 pnpm입니다.
npm이나 yarn을 사용할 때, 의존성을 사용하는 프로젝트 100개가 있다면 디스크 상에 해당 의존성관련 파일을 100개 복사하여 저장합니다. 하지만 pnpm을 사용하면 의존성이 content-addressable 저장소에 저장되므로, 하나의 의존성파일만을 갖게됩니다.
content-addressable memory
매우 빠른 속도를 요하는 탐색 애플리케이션에서 사용되는 특수한 메모리
주소에 의해 접근하지 않고, 기억된 내용의 일부를 이용하여 접근할 수 있는 기억장치
보통 CAM으로 줄여 말하며, 연관 메모리(associative memory), 연관기억장치라고도 한다.
pnpm은 npm이나 yarn처럼 다른 버전의 의존성이 필요하면 어떻게 행동할까 ?
pnpm의 경우 다른 파일만을 저장소에 추가하게됩니다. 100개의 의존성 중 다른 버전에 해당되는 파일만을 저장소에 새로운 파일로 추가합니다.
pnpm은 어떻게 npm,yarn보다 디스크를 효율적으로 관리하는가 ?
프로젝트에 express를 설치하는 것을 예를들어 설명해보겠습니다.
우선 npm,yarn을 예를 들겠습니다.
우리는 npm install express라는 명령어를 통해 프로젝트에 설치를하고 프로젝트 내 node_modules안에는 express, 그리고 express와 관련된 모든 파일이 설치될 것 입니다.
.bin
accepts
array-flatten
body-parser
bytes
content-disposition
cookie-signature
cookie
debug
depd
destroy
ee-first
encodeurl
escape-html
etag
express
이는 yarn,npm이 플랫한 구조를 가지기에 모든 파일들을 동일 선상에 다운받게되는 구조입니다.
다음으로는 pnpm을 확인해보겠습니다. pnpm add express로 express를 설치 시 node_modules안에는
.pnpm
.modules.yaml
express
이 정도의 파일트리를 이루고 있습니다. 그럼
"express에 필요한 의존성들은 어디에 있나?"
라는 의구심이 생길 것입니다.
.pnpmd이라는 폴더에 들어가면 express와 이에 필요한 모든 의존성들이 들어있는 것을 확인하실 수 있습니다.
자 여기서 우리는 기존의 패키지 매니저와 pnpm의 차이를 알 수 있습니다.
.pnpm안에 의존성들이 들어있고 내가 설치한 express라는 패키지는 이 의존성들을 가져다 사용을합니다. 그리고 express와 하나의 의존성(body-parser)만 다른 fakeExpress라는 새로운 패키지를 다운받는 다면 npm,yarn은 동일한 것과 다른 것(body-parser)을 모두 다운받겠지만, pnpm은 다른 의존성(body-parser) 하나만은 추가로 다운받는 것입니다.
pnpm이 이러한 것이 가능한 이유는 symbolic link를 사용하기 때문입니다.
Symbolic link
링크를 연결하여 원본 파일을 직접 사용하는 것과 같은 효과를 내는 링크
윈도의 바로가기와 비슷한 개념
우선 모노레포가 뭔지 알아보도록 하겠습니다.
모노레포란 ?
버전 관리 시스템에서 많은 프로젝트 코드가 같은 레포지토리에 저장되는 소프트웨어 개발 전략
이로한 모노레포는 쉬운 코드 재사용, 간단한 의존성 관리, 원자적 커밋(커밋할 때마다 모든 것이 함께 작동동, 변경 사항의 영향을 받는 곳에서 쉽게 변화를 탐지), 큰 스케일의 코드 리팩토링, 팀 간의 협업, 테스트 빌드 범위 최소화 등을 멀티레포 보다 쉽게 가능하게 해줍니다.
그럼 npm과 yarn보다 모노레포에서 뭐가 더 편한가 ? (라이브러리 제외)
monorepo를 위한 명령어가 존재하기 때문입니다.
Dockerfile monorepo build를 위해 RUN fetch 명령어를 사용하여 하위 폴더에 있는 package.json을 설치한다든지 등 monorepo만을 위한 명령어가 존재하기에 npm,yarn보다 수월한 build 및 사용이 가능합니다.
그렇기에 2021.stateofjs에서 pnpm의 만족도는 89% 1위입니다.
lockfile, cache, node_modules 없이 install만을 통해 대부분의 수치에서 pnpm이 빠르다는 것을 알 수 있습니다. 그렇기에
20년 대비 21년 pnpm의 다운로드 수는 3배가 증가할 수 있었습니다.
하지만 pnpm만이 답은 아닙니다.
yarn beryy와 같은 새로운 yarn의 버전 출시와 yarn v3.1의 링커 추가 및 콘텐츠 주소 지정 가능 스토리지 구현, npm 심볼릭 링크된 node)modules 구조 계획 등 다른 패키지 매니저들의 발전이 있기에 pnpm이 무조건 옳은 것은 아닙니다. 변해가는 package manager를 통해 본인에게 많은, 혹은 보다 좋은 성능과 기능을 제공하는 package manager를 고르고 그에 맞는 사용을 하는 것. 그리고 이러한 경쟁을 지켜보는 것이 좋을 것 같습니다.
출처
- https://pnpm.io/ko/
- https://ko.wikipedia.org/wiki/%EC%8B%AC%EB%B3%BC%EB%A6%AD_%EB%A7%81%ED%81%AC
- https://en.wikipedia.org/wiki/Monorepo
- https://2021.stateofjs.com/en-US/libraries/monorepo-tools/
- https://medium.com/zigbang/%ED%8C%A8%ED%82%A4%EC%A7%80-%EB%A7%A4%EB%8B%88%EC%A0%80-%EA%B7%B8%EA%B2%83%EC%9D%B4-%EA%B6%81%EA%B8%88%ED%95%98%EB%8B%A4-5bacc65fb05d
- https://npm-stat.com/charts.html?package=pnpm&from=2016-12-01&to=2021-12-29
- https://blog.logrocket.com/javascript-package-managers-compared/
- https://www.lesstif.com/laravelprog/dependency-hell-26083775.html
- https://lovemewithoutall.github.io/it/node-module-phantom-dependency/
- https://rushjs.io/pages/advanced/phantom_deps/