FrontEnd Roadmap 07 - Package Manager(npm, pnpm, yarn)

SANGHYUN KIM·2025년 3월 2일
0

frontend-roadmap

목록 보기
7/9

A. Package Manager

다양한 패키지 매니저들이 있고 글들이 있는데 이번 기회에 빌려 공부를 해봤다.

A-1. 패키지 매니저의 필요성 대두

기존 웹사이트들은 크기가 크지 않았고, JavaScript의 역할도 단순히 이벤트를 연결해주는 것에 한정되어 있었다.

그러나 웹사이트가 점점 복잡해지면서 공통 기능을 라이브러리로 묶어 개발하는 방식이 보편화되었다.

이 과정에서 라이브러리 관리가 어려워지자, 이를 효율적으로 다룰 수 있는 패키지 매니저가 등장하게 되었다.

특히, Node.js의 등장과 함께 기본적으로 제공된 npm(Node Package Manager)이 대표적인 패키지 매니저로 자리 잡으며, 이후 다양한 패키지 매니저들이 등장했다.

그렇다면, 패키지 매니저가 정확히 하는 역할은 무엇일까?
Devocean(SK 개발 블로그)의 글을 인용하자면 패키지 매니저는 프로그래밍에 필요한 라이브러리를 관리하며 다음 역할을 한다고 한다:

기능설명
의존성 관리필요한 다른 프로드램들을 자동으로 찾아 설치
버전 관리특정 버전의 프로그램을 설치하거나 업데이트
편리한 설치 및 업데이트명령어 하나로 여러 프로그램을 쉽게 설치하거나 최신 상태로 업데이트
보안관리패키지가 신뢰할 수 있으면 손상되지 않음을 보장
일관성 유지개발자 모두가 동일한 프로그램 버전과 설정을 사용

현재(2025년 3월 기준) 가장 많이 등장하는 패키지 매니저로는 npm, yarn, pnpm이 있으며, 각각의 특징을 아래에서 살펴본다.

A-2. npm

현재 프론트엔드의 가장 기본으로 자리잡은 npm은 node.js의 기본적인 package manager로 방대한 javascript ecosystem을 활용할 수 있게 해준다.

공식 문서를 보면 npm은 다음 3가지로 구성(three distinct components)되어 있다고 한다

  • website: 프로파일 설정, 패키지 탐색, organization 설정 등등
  • Command Line Interface(CLI): 터미널을 통해서 개발자가 npm과 상호작용(npm something)
  • registry: 자바스크립트 소프트웨어와 이를 둘러싼 메타정보의 대규모 공개 데이터베이스

A-1-1. CLI를 통해 만들어지는 package.json 탐구

프론트엔드 기준 project를 설정할 때 대부분 npm init을 하면 package.json이 아래와 같이 설정된다.

  • package.json은 해당 프로젝트가 의존하는 패키지 및 애플리케이션, 소스 제어에 대한 정보, 프로젝트 이름, 설정 ,작성자 등의 메타데이터를 포함하는 메니페스트
  • 2025년 3월 기준으로 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

  • npm ci는 package-lock.json을 기반으로 패키지를 설치하는 명령어다.
  • npm install과 다르게, npm ci는 package.json의 버전 범위를 분석하지 않고 package-lock.json에 기록된 버전 그대로 패키지를 설치한다.
  • 따라서 불필요한 의존성 계산 과정을 건너뛰기 때문에 설치 속도가 더 빠르다.
  • 또한, npm ci는 기존 node_modules 폴더를 삭제하고 다시 설치하기 때문에 항상 깨끗한 상태의 의존성을 유지할 수 있다.

A-1-3. npm의 문제점

npm은 크게 다음 3가지 문제가 있었다.

  1. 유령 의존성(Phantom Dependency) 문제
    1. npm을 통해서 설치를 하면 모든 패키지가 node_modules라는 폴더 내부에 설치
    2. 설치를 최적화하기 위해 호이스팅 기법을 도입하여 공통된 패캐지를 상위 디렉토리로 끌어올려 중복 설치를 방지
      1. 아래 사진과 같이 A와 B라는 라이브러리는 중복적으로 설치가 되지 않는 것을 확인

    3. 그러나 위 기능으로 인해 직접적으로 설치되지 않은 B라는 라이브러리를 코드에서 import하여 사용할 수 있는 유령 의존성(Phantom Dependency)라는 부작용
  2. 설치 속도 문재
    1. node_modules를 직접 복사하는 방식으로 설치가 느림
  3. node_modules폴더 크기 문제
    1. 불필요한 파일까지 저장되어 프로젝트 크기 증가

이러한 문제점을 해결하기 위해서 yarn이 개발되었다.

A-2. Yarn(yet another resource negotiator)

페이스북 및 구글을 주축으로 개발자들이 모여서 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을 지원하는 것은 아니다.

A-3. pnpm(performant npm)

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

A-4. Package Manager 선택

개인적으로는 npm으로 시작하는 것이 제일 좋다고 생각한다. 가장 사용자가 많으며 문서 및 시행착오에 대한 링크카 많다.
실제로도 레퍼런스로 찾아본 사이트 대부분 npm으로 시작할 것을 권장한다. 그러다가 속도 측면에서 저하 발생 시 바꿔 보는 것이 좋아보일 것 같다.

Reference

패키지 매니저 선택을 위한 여정: NPM에서 Yarn으로 그리고 다시 pNPM

Peer Dependencies 에 대하여

package-lock.json은 왜 필요할까?

npm ci vs npm install

npm, yarn, pnpm 비교해보기

패키지 매니저의 과거, 토스의 선택, 그리고 미래

profile
꾸준히 공부하자

0개의 댓글