다양한 패키지 매니저들이 있고 글들이 있는데 이번 기회에 빌려 공부를 해봤다.
기존 웹사이트들은 크기가 크지 않았고, JavaScript의 역할도 단순히 이벤트를 연결해주는 것에 한정되어 있었다.
그러나 웹사이트가 점점 복잡해지면서 공통 기능을 라이브러리로 묶어 개발하는 방식이 보편화되었다.
이 과정에서 라이브러리 관리가 어려워지자, 이를 효율적으로 다룰 수 있는 패키지 매니저가 등장하게 되었다.
특히, Node.js의 등장과 함께 기본적으로 제공된 npm(Node Package Manager)이 대표적인 패키지 매니저로 자리 잡으며, 이후 다양한 패키지 매니저들이 등장했다.
그렇다면, 패키지 매니저가 정확히 하는 역할은 무엇일까?
Devocean(SK 개발 블로그)의 글을 인용하자면 패키지 매니저는 프로그래밍에 필요한 라이브러리를 관리하며 다음 역할을 한다고 한다:
| 기능 | 설명 |
|---|---|
| 의존성 관리 | 필요한 다른 프로드램들을 자동으로 찾아 설치 |
| 버전 관리 | 특정 버전의 프로그램을 설치하거나 업데이트 |
| 편리한 설치 및 업데이트 | 명령어 하나로 여러 프로그램을 쉽게 설치하거나 최신 상태로 업데이트 |
| 보안관리 | 패키지가 신뢰할 수 있으면 손상되지 않음을 보장 |
| 일관성 유지 | 개발자 모두가 동일한 프로그램 버전과 설정을 사용 |
현재(2025년 3월 기준) 가장 많이 등장하는 패키지 매니저로는 npm, yarn, pnpm이 있으며, 각각의 특징을 아래에서 살펴본다.
현재 프론트엔드의 가장 기본으로 자리잡은 npm은 node.js의 기본적인 package manager로 방대한 javascript ecosystem을 활용할 수 있게 해준다.
공식 문서를 보면 npm은 다음 3가지로 구성(three distinct components)되어 있다고 한다
npm something)A-1-1. CLI를 통해 만들어지는 package.json 탐구
프론트엔드 기준 project를 설정할 때 대부분 npm init을 하면 package.json이 아래와 같이 설정된다.
npm init 실행 시 아래 사진과 같은 가이드가 나옴
```tsx
{
"name": "metaverse", // 프로젝트의 이름
"version": "0.92.12", // 프로젝트의 버전
"description": "The Metaverse virtual reality. The final outcome of all virtual worlds, augmented reality, and the Internet.", // The description of your project
"main": "index.js"
"license": "MIT" // The license of your project
}
{
"name": "npm_init", // 프로젝트의 이름
"version": "1.0.0", // 프로젝트의 버전
"description": "npm_init", // 프로젝트에 대한 설명
"main": "index.js",
"scripts": {
"test": "npm run test"
},
"repository": {
"type": "git",
"url": "somewhere"
},
"keywords": [
"npm_init"
],
"author": "kim", // 프로젝트 저자
"license": "ISC" // 프로젝트 라이센스 정보
// 하위는 추가한 정보
"dependencies": {
// 프로젝트가 런타임, 빌드타임, 개발 중에 의존하는 라이브러리 또는 패키지 리스트
"moment": "^2.19.1"
},
"devDependencies": {
// 프로젝트가 빌드타임, 개발 중에 의존하는 라이브러리 또는 패키지 리스트
"@babel/core": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"babel-loader": "^8.0.2",
"webpack": "^3.7.1",
"webpack-dev-server": "^3.1.6"
},
"peerDependencies": {
// 프로젝트가 직접 사용되지 않더라도 의존성 패키지 중에 특정 라이브러리의 버전을 명시하는 공간
"@repo/eslint-config": "*",
"@repo/typescript-config": "*",
"@repo/ui": "*"
},
}
```
A-1-2. package.lock.json이란?
package-lock.json은 흔히 잠금 파일(lockfile) 이라고 불리며, 프로젝트에서 사용하는 패키지들의 버전을 고정하는 역할을 한다.
보통 package.json에서 버전 명시 시 ^(caret)나 ~(tilde) 같은 문자를 사용해 semantic versioning(SemVer) 기준으로 minor 또는 patch 버전 업그레이드를 허용한다. 이렇게 하면 같은 프로젝트에서도 개발자마다 설치되는 패키지 버전이 다를 수 있다.
이를 방지하기 위해 package-lock.json을 활용하여 패키지의 정확한 버전을 고정하고, 협업 시 동일한 환경을 유지하는 것이 일반적이다.
A-1-2-1. package-lock.json이 설치 속도를 최적화하는 이유
npm install을 실행하면 의존성 트리를 분석하고 설치해야 할 패키지를 계산하는 과정이 필요하다. 하지만 package-lock.json이 있으면 이미 계산된 의존성 트리가 포함되어 있어 이 과정이 생략되므로 설치 속도가 빨라진다.
🚀 더 빠른 패키지 설치를 위한 npm ci
A-1-3. npm의 문제점
npm은 크게 다음 3가지 문제가 있었다.
node_modules라는 폴더 내부에 설치아래 사진과 같이 A와 B라는 라이브러리는 중복적으로 설치가 되지 않는 것을 확인
node_modules를 직접 복사하는 방식으로 설치가 느림node_modules폴더 크기 문제이러한 문제점을 해결하기 위해서 yarn이 개발되었다.
페이스북 및 구글을 주축으로 개발자들이 모여서 npm이 가지고 있던 문제를 해결하기 위해서 yarn을 만들었다.
현재 yarn은 기존에 사용되던 방식을 “Classic”이라고 명명하고 “Yarn Berry”라는 새로운 이름으로 서비스를 제공한다. 또한 Yarn Berry는 PnP와 Zero-Install 기능을 지원한다.
A-2-1. Plug’n’Play(PnP)
Yarn 2.X 이상 버전에서 도입된 기능으로 Classic에서 사용하던 node modules 방식을 벗어나 패키지를 전역적으로 저장하고 .pnp.cjs라는 단일 파일을 사용하여 의존성을 관리하는 방식이다. 또한, .yarn/cahce라는 경로 하위에 모든 패키지를 zip파일로 저장이되기에 디스크 공간 또한 적게 차지한다.
그러나 기존에 오랬동안 표준처럼 잡혔던 node_modules폴더가 없기에 호환성의 문제가 있다. 이를 해결하기 위해서 현재는 여러 옵션을 지원하지만 레거시 또는 PnP를 지원하지 않는 라이브러리는 따로 설정을 해줘야 한다.
A-2-2. Zero-Install
.yarn/cache내부에 패키지를 저장하여 yarn install없이 즉시 실행할 수 있는 방식이다. 이를 Git과 함께 같이 사용하여 저장소에 올려놓으면 네트워크가 없어도 사용이 가능하며 속도또한 줄일 수 있다. 또한, Git을 활용하기에 패키지 변경이 일어나면 diff로 체크를 할 수 있는 장점을 가지고 있다.
그러나 Git에 정보를 올려놓기에 저장소 크기가 커지며 모든 패키지가 Zero-Install을 지원하는 것은 아니다.
pnpm은 npm과 yarn이 node_modules에 패키지를 직접 복사하는 방식과 달리, 글로벌 스토리지에 저장하고 하드링크하는 방식으로 패키지를 재사용한다.
여기서 “하드링크”란 새로운 복사본을 만들어도 원본 파일과 같은 데이터를 공유, 즉 여러 위치에 존재하는 파일들이 동일한 데이터를 가르키는 것을 말한다.
A-3-1. pnpm의 동작방식
Medium의 예제를 보면 어떤 방식으로 작동하는 지 바로 알 수 있다.
{
"name": "application1",
"version": "0.0.0",
"private": true,
"main": "app.js",
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"csurf": "^1.10.0",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"express-session": "^1.17.0"
}
},
{
“name”: “application2”,
“version”: “0.0.0”,
“private”: true,
“main”: “app.js”,
“scripts”: {
“start”: “node ./bin/www”
},
“dependencies”: {
“cookie-parser”: “~1.4.4”,
“csurf”: “^.10.0”,
“debug”: “~2.6.9”,
“powerbi-client”: “^.16.5”,
“rxjs”: “^.5.3”
}
}
위와 같이 공유하는 파일들이 존재할 때, “application1”을 먼저 설치하고 “application2”를 설치한다면 다음과 같이 로그가 나온다.
# application1
rach@99:~/pnpm-demo$ pnpm install
Packages: +67
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: /home/rach/.pnpm-store/v3
Virtual store is at: node_modules/.pnpm
Progress: resolved 67, reused 0, downloaded 67, added 67, done
dependencies:
+ cookie-parser 1.4.6
+ csurf 1.11.0
+ debug 2.6.9 (4.3.3 is available)
+ ejs 2.6.2 (3.1.6 is available)
+ express 4.16.4 (4.17.2 is available)
+ express-session 1.17.2
# application2
rach@99:~/pnpm-demo2$ pnpm install
Packages: +27
+++++++++++++++++++++++++++
Packages are hard linked from the content-addressable store to the virtual store.
Content-addressable store is at: /home/rach/.pnpm-store/v3
Virtual store is at: node_modules/.pnpm
dependencies:
+ cookie-parser 1.4.6
+ csurf 1.11.0
+ debug 2.6.9 (4.3.3 is available)
+ powerbi-client 2.19.1
+ rxjs 6.6.7 (7.5.2 is available)
Progress: resolved 27, reused 18, downloaded 9, added 27, done
개인적으로는 npm으로 시작하는 것이 제일 좋다고 생각한다. 가장 사용자가 많으며 문서 및 시행착오에 대한 링크카 많다.
실제로도 레퍼런스로 찾아본 사이트 대부분 npm으로 시작할 것을 권장한다. 그러다가 속도 측면에서 저하 발생 시 바꿔 보는 것이 좋아보일 것 같다.