TL;DR

  • pnpm은 글로벌 스토어와 링크 기반 구조로 디스크 사용량을 크게 줄인다.
  • 엄격한 node_modules 구조로 Phantom Dependency를 차단한다.
  • v10 이후 보안 기본값 강화로 공급망 공격 대응이 개선됐다.

1. 개념

pnpm은 성능과 디스크 효율을 개선한 Node.js 패키지 매니저로, 콘텐츠 주소 기반 스토어와 링크 구조를 사용한다.

2. 배경

npm/yarn의 중복 설치, 느린 설치, Phantom Dependency 문제와 공급망 보안 이슈가 누적되며 근본적 개선이 요구됐다.

3. 이유

대규모 프로젝트에서 설치 속도와 저장 공간을 줄이고, 의존성 사용을 명시적으로 제한하기 위해 pnpm이 선택된다.

4. 특징

글로벌 스토어 + 하드링크, 엄격한 의존성 격리, 워크스페이스 최적화, 보안 기본값 강화가 핵심이다.

5. 상세 내용

pnpm 패키지 매니저

작성일: 2026-02-09 수정일: 2026-02-26 카테고리: Build / Package Manager 포함 내용: pnpm, npm, yarn, Content-Addressable Store, Phantom Dependencies, Monorepo, workspace, 보안, 역사, Corepack, Catalogs


1. pnpm이란?

개념

pnpm = Performant npm
      └── "빠르고 디스크 효율적인" 패키지 매니저
      └── npm/yarn의 문제점을 해결하기 위해 등장

패키지 매니저 기초 설명

┌─────────────────────────────────────────────────────────────────┐
│              패키지 매니저가 뭔가요? (완전 기초)                  │
│                                                                   │
│  비유: 레고 블록 조달 시스템                                     │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  프로그래밍 = 레고로 집을 짓는 것                        │    │
│  │  패키지 = 다른 사람이 만든 레고 블록 세트                │    │
│  │  패키지 매니저 = 레고 블록 배달/관리 서비스              │    │
│  │                                                          │    │
│  │  직접 만들기: 벽돌 하나하나 직접 제작 → 시간 낭비        │    │
│  │  패키지 사용: "벽돌 10개 주세요" → 자동 배달             │    │
│  │                                                          │    │
│  │  npm/yarn/pnpm 모두 이 "배달 서비스" 역할                │    │
│  │  차이점: 배달 방식과 보관 방식이 다름                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  핵심 개념들:                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  package.json = 주문서 (어떤 블록이 필요한지 목록)       │    │
│  │  node_modules = 배달된 블록 보관 창고                    │    │
│  │  lock 파일 = 정확한 블록 버전 기록표                     │    │
│  │  registry = 블록을 파는 중앙 시장 (npmjs.com)            │    │
│  │  의존성(dependency) = 내가 쓰는 블록이 필요로 하는 블록  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

2. 등장 배경

npm/yarn의 문제점

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  기존 방식 (npm/yarn):                                           │
│                                                                   │
│  프로젝트 A          프로젝트 B          프로젝트 C              │
│  └── node_modules/   └── node_modules/   └── node_modules/      │
│      └── lodash@4        └── lodash@4        └── lodash@4       │
│      └── react@18        └── react@18        └── react@18       │
│      └── axios@1         └── axios@1         └── axios@1        │
│      └── ...             └── ...             └── ...            │
│                                                                   │
│  문제점:                                                         │
│  ├── 같은 패키지가 프로젝트마다 중복 설치                        │
│  ├── 디스크 공간 낭비 (프로젝트당 수백 MB ~ 수 GB)              │
│  ├── 설치 시간 증가                                              │
│  └── 네트워크 대역폭 낭비                                        │
│                                                                   │
│  실제 사례:                                                      │
│  "10개 프로젝트 × 500MB = 5GB 디스크 사용"                      │
│  "같은 lodash가 10번 복사됨"                                     │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

Flat node_modules의 문제

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  npm v3+, yarn의 "Flat" 구조:                                   │
│                                                                   │
│  의도: 중첩 깊이 줄이기 (Windows 경로 길이 제한 등)             │
│                                                                   │
│  node_modules/                                                   │
│  ├── A/               ← 직접 의존성                              │
│  ├── B/               ← A의 의존성인데 최상위로 끌어올림        │
│  ├── C/               ← B의 의존성인데 최상위로 끌어올림        │
│  └── ...                                                         │
│                                                                   │
│  부작용: Phantom Dependencies 발생                               │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3. 패키지 매니저의 역사 - 누가 왜 만들었나?

3.1 npm의 탄생 (2010)

┌─────────────────────────────────────────────────────────────────┐
│                   npm의 탄생 이야기                               │
│                                                                   │
│  시대: 2009-2010년                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Ryan Dahl이 Node.js를 만듦 (2009년 5월)                │    │
│  │  → 서버에서 JavaScript를 실행할 수 있게 됨!              │    │
│  │                                                          │    │
│  │  문제: 코드를 공유할 방법이 없음                         │    │
│  │  ├── ZIP 파일 다운로드                                   │    │
│  │  ├── GitHub에서 직접 복사                                 │    │
│  │  └── 버전 관리? 그런 건 없었음                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  Isaac Z. Schlueter (Yahoo 개발자):                              │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  "이거 너무 불편하다. 패키지 매니저를 만들자!"           │    │
│  │                                                          │    │
│  │  2010년 1월 12일: npm 첫 공개 릴리스                     │    │
│  │  2011년: Node.js에 기본 포함됨                            │    │
│  │  2014년: npm Inc. 설립 (상업화)                           │    │
│  │  2020년: GitHub(Microsoft)에 인수됨                       │    │
│  │                                                          │    │
│  │  현재: 130만+ 패키지, 월 750억+ 다운로드                 │    │
│  │  → 세계 최대 소프트웨어 레지스트리                        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  npm이 만든 3가지 핵심 개념:                                     │
│  ├── 1. package.json: 프로젝트의 "주문서"                       │
│  ├── 2. 중앙 레지스트리: npmjs.com이라는 "시장"                  │
│  └── 3. Semantic Versioning: 1.2.3 (주.부.패치) 버전 체계       │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.2 left-pad 사건 (2016년 3월) - npm 생태계의 충격

┌─────────────────────────────────────────────────────────────────┐
│           left-pad 사건 - 11줄의 코드가 인터넷을 멈추다          │
│                                                                   │
│  2016년 3월 22일, 세계적인 사건 발생:                             │
│                                                                   │
│  배경:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Azer Koculu라는 개발자가 'kik'이라는 npm 패키지를 소유  │    │
│  │  → Kik 메신저 회사가 상표권 주장                          │    │
│  │  → npm이 Kik 회사 편을 들어 패키지명을 빼앗음            │    │
│  │  → 분노한 Koculu가 자신의 273개 패키지를 전부 삭제!      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  문제:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  삭제된 패키지 중 'left-pad' = 문자열 왼쪽 채우기 함수   │    │
│  │  고작 11줄짜리 코드                                       │    │
│  │                                                          │    │
│  │  그런데... 이 패키지를 의존하는 프로젝트:                 │    │
│  │  ├── Babel (JavaScript 컴파일러)                          │    │
│  │  ├── React 개발 도구                                      │    │
│  │  ├── Webpack                                              │    │
│  │  └── Facebook, Netflix, Spotify 등 수천 개 프로젝트       │    │
│  │                                                          │    │
│  │  결과: 전 세계적으로 빌드가 깨짐                          │    │
│  │  "11줄의 코드가 인터넷을 멈췄다"                          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  이 사건이 드러낸 npm의 구조적 문제:                              │
│  ├── 1. 레지스트리 연결 없으면 빌드 불가                         │
│  ├── 2. Lock 파일이 없어서 설치 재현 불가                        │
│  ├── 3. 누구나 패키지를 삭제할 수 있었음                         │
│  └── 4. 의존성 체인의 취약성이 명백해짐                          │
│                                                                   │
│  → 이 사건이 yarn과 pnpm 탄생의 직접적 촉매가 됨                │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.3 yarn의 등장 (2016년 10월) - Facebook의 해결책

┌─────────────────────────────────────────────────────────────────┐
│                 yarn - Facebook이 만든 이유                       │
│                                                                   │
│  Facebook 내부 상황 (2016년):                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  JavaScript 코드베이스가 거대해지면서:                    │    │
│  │                                                          │    │
│  │  문제 1: 비결정적 설치                                    │    │
│  │  → npm install을 2번 실행하면 다른 결과가 나옴            │    │
│  │  → "내 컴퓨터에선 되는데?" 문제가 빈번                    │    │
│  │                                                          │    │
│  │  문제 2: 엄청난 용량                                      │    │
│  │  → React Native의 68개 의존성 → 121,358개 파일 생성       │    │
│  │  → Babel 업데이트 한 번에 80만 줄 diff                    │    │
│  │                                                          │    │
│  │  문제 3: 느린 순차 설치                                   │    │
│  │  → npm은 패키지를 하나씩 순서대로 다운로드                │    │
│  │  → 대규모 프로젝트에서 수 분씩 걸림                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  핵심 인물:                                                      │
│  ├── Sebastian McKenzie: Babel 창시자, yarn 프로젝트 시작        │
│  ├── Christoph Nakazawa: Facebook 런던, JS 도구 팀장             │
│  └── 협력: Google, Exponent(Expo), Tilde(Ember.js)               │
│                                                                   │
│  yarn이 가져온 혁신:                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  1. yarn.lock: 정확한 버전 고정 → 재현 가능한 설치       │    │
│  │  2. 병렬 다운로드: 동시에 여러 패키지 다운로드            │    │
│  │  3. 오프라인 캐시: 한 번 다운로드하면 오프라인 설치 가능  │    │
│  │  4. 체크섬 검증: 패키지 변조 방지                         │    │
│  │                                                          │    │
│  │  결과: 설치 시간 수 분 → 수 초로 단축                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  하지만 yarn도 해결하지 못한 것:                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Flat node_modules 구조 유지 (npm과 동일)                │    │
│  │  Phantom Dependencies 여전히 발생                         │    │
│  │  디스크 공간 낭비 (프로젝트별 복사)                       │    │
│  │                                                          │    │
│  │  → 이것이 pnpm이 탄생한 이유!                            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.4 pnpm의 탄생 (2017) - 근본적 해결책

┌─────────────────────────────────────────────────────────────────┐
│              pnpm - Zoltan Kochan의 근본적 해결책                │
│                                                                   │
│  Zoltan Kochan의 불만:                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  "yarn이 나왔을 때 기대했지만 실망했다."                  │    │
│  │  "빠르긴 한데, 근본적인 문제를 안 고쳤다."               │    │
│  │  "Flat node_modules가 진짜 문제인데 왜 아무도 안 고치지?" │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  선구자: Alexander Gugel의 'ied' (2015)                          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  심볼릭 링크 기반 의존성 관리 실험                        │    │
│  │  → 이 아이디어에서 pnpm이 영감을 받음                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  타임라인:                                                       │
│  ├── 2016년 1월: Rico Sta. Cruz가 초기 커밋                      │
│  ├── 2017년 6월: pnpm v1 정식 릴리스 (Zoltan Kochan 주도)       │
│  ├── 2020년: Yarn Classic 유지보수 모드 진입 → pnpm 급성장       │
│  ├── 2021년: Vue.js, Vite가 pnpm으로 전환                        │
│  ├── 2025년 1월: pnpm v10 "보안 기본값" 마일스톤                 │
│  └── 2026년 현재: 다운로드 수 매년 2배 성장 중                   │
│                                                                   │
│  pnpm의 핵심 철학:                                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  "문제의 근본을 고치자"                                   │    │
│  │                                                          │    │
│  │  npm/yarn: Flat 구조의 부작용을 땜질로 해결               │    │
│  │  pnpm: Flat 구조 자체를 버리고 새로운 구조 도입           │    │
│  │                                                          │    │
│  │  = Content-Addressable Store + 심볼릭 링크                │    │
│  │  = 디스크 절약 + Phantom Dependencies 원천 차단           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

3.5 Yarn Berry (v2+)와 PnP - 그리고 실패

┌─────────────────────────────────────────────────────────────────┐
│           Yarn Berry의 급진적 실험과 생태계의 거부                │
│                                                                   │
│  Yarn Classic이 유지보수 모드에 들어가며 (2020):                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Mael Nison(arcanis)이 Yarn을 완전히 새로 작성           │    │
│  │  → Yarn Berry (v2+)                                       │    │
│  │  → 핵심 기능: Plug'n'Play (PnP)                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  PnP의 아이디어:                                                 │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  "node_modules 폴더 자체를 없애버리자!"                   │    │
│  │                                                          │    │
│  │  방법:                                                    │    │
│  │  1. 패키지를 ZIP 파일로 .yarn/cache/에 저장               │    │
│  │  2. .pnp.cjs 파일로 모든 경로를 매핑                      │    │
│  │  3. Node.js의 fs 모듈을 덮어씌워서 ZIP에서 직접 읽기     │    │
│  │                                                          │    │
│  │  장점: 설치 거의 즉시, Zero-Install 가능                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  왜 실패했나:                                                    │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  - Node.js 표준이 아님 (런타임 패치 필요)                │    │
│  │  - 수많은 라이브러리/도구와 호환 안 됨                    │    │
│  │  - Webpack, Jest, IDE 플러그인 등이 깨짐                  │    │
│  │  - Facebook 자체도 Yarn Classic에 머무름                   │    │
│  │  - 2024년 기준 주요 오픈소스 프로젝트 중 PnP 사용 = 0    │    │
│  │                                                          │    │
│  │  교훈: 너무 급진적인 변화는 생태계가 따라오지 않는다      │    │
│  │                                                          │    │
│  │  반면 pnpm은:                                             │    │
│  │  + node_modules 유지 (호환성 보장)                        │    │
│  │  + Node.js 표준 그대로 동작                               │    │
│  │  + 기존 도구와 거의 100% 호환                             │    │
│  │  → "현실적인 혁신"이 승리함                               │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

4. pnpm의 해결책

Content-Addressable Store

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  pnpm 방식:                                                      │
│                                                                   │
│  비유: 도시 중앙 도서관 시스템                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  npm/yarn = 각 집(프로젝트)마다 책(패키지)을 구매         │    │
│  │  pnpm = 중앙 도서관(store)에 책 1권, 각 집은 대출카드    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ~/.pnpm-store/              ← 글로벌 저장소 (딱 1곳)            │
│  └── v3/                                                         │
│      └── files/                                                  │
│          └── 00/                                                 │
│          └── 01/                                                 │
│          └── ...             ← 해시 기반 파일 저장               │
│                                                                   │
│  프로젝트 A                  프로젝트 B                          │
│  └── node_modules/           └── node_modules/                   │
│      └── .pnpm/              └── .pnpm/                          │
│      └── lodash → 하드링크   └── lodash → 하드링크              │
│      └── react → 하드링크    └── react → 하드링크               │
│                                                                   │
│  핵심:                                                           │
│  ├── 패키지 파일은 store에 1번만 저장                            │
│  ├── 프로젝트에서는 하드 링크로 참조                             │
│  ├── 디스크 사용량 대폭 감소 (70-90%)                           │
│  └── 설치 속도 향상 (이미 store에 있으면 링크만)                │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

심볼릭 링크 vs 하드 링크

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  심볼릭 링크 (Symbolic Link):                                    │
│  ├── "바로가기" 같은 개념                                        │
│  ├── 원본 파일을 가리키는 포인터                                 │
│  └── 원본 삭제되면 깨짐                                          │
│                                                                   │
│  하드 링크 (Hard Link):                                          │
│  ├── 같은 파일 데이터를 가리키는 또 다른 이름                    │
│  ├── 원본과 동등한 존재                                          │
│  ├── 원본 삭제해도 데이터 유지                                   │
│  └── 디스크 공간 추가 사용 없음                                  │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  심볼릭 링크 = "3층 302호에 가세요" (안내문)             │    │
│  │  → 302호가 사라지면 안내문은 무의미                       │    │
│  │                                                          │    │
│  │  하드 링크 = 같은 방에 문이 2개 있는 것                   │    │
│  │  → 문 하나를 막아도 다른 문으로 들어갈 수 있음            │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  pnpm:                                                           │
│  ├── 패키지 폴더 → .pnpm/ : 심볼릭 링크                         │
│  └── 파일 → store : 하드 링크                                   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

5. Phantom Dependencies 차단

문제 설명

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  Phantom Dependency (유령 의존성):                               │
│                                                                   │
│  비유: 친구의 친구 물건을 마음대로 쓰는 것                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  내가 A한테 빌린 도구 → 정당한 사용                       │    │
│  │  A가 갖고 있는 B의 도구를 내가 마음대로 사용 → 위험!     │    │
│  │  → A가 B와 절교하면 나도 그 도구를 못 씀                 │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  package.json:                                                   │
│    dependencies: { "express": "4.18" }                           │
│                                                                   │
│  express가 내부적으로 "accepts" 패키지 사용                      │
│                                                                   │
│  npm/yarn (flat node_modules):                                   │
│  node_modules/                                                   │
│  ├── express/                                                    │
│  └── accepts/  ← express의 의존성이지만 최상위에 노출됨         │
│                                                                   │
│  내 코드에서:                                                    │
│  import accepts from 'accepts'  // 동작함! (하지만 위험)        │
│                                                                   │
│  문제:                                                           │
│  ├── package.json에 없는 패키지를 사용 가능                      │
│  ├── express가 accepts 제거하면 내 코드도 깨짐                   │
│  └── 의존성 그래프가 불명확해짐                                  │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

pnpm의 엄격한 구조

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  pnpm node_modules 구조:                                         │
│                                                                   │
│  node_modules/                                                   │
│  ├── .pnpm/                      ← 실제 패키지들 (격리됨)        │
│  │   ├── express@4.18.0/                                        │
│  │   │   └── node_modules/                                      │
│  │   │       ├── express/        ← 실제 파일                    │
│  │   │       └── accepts/        ← express만 접근 가능          │
│  │   └── accepts@1.3.8/                                         │
│  │       └── node_modules/                                      │
│  │           └── accepts/                                       │
│  └── express/ → .pnpm/express@4.18.0/.../express                │
│                                                                   │
│  내 코드에서:                                                    │
│  import express from 'express'   // 정상                         │
│  import accepts from 'accepts'   // 에러! (올바른 동작)          │
│                                                                   │
│  → 명시적으로 선언한 의존성만 접근 가능                          │
│  → 더 안전하고 예측 가능한 의존성 관리                           │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

6. npm vs yarn vs pnpm 비교

기능 비교

항목 npm yarn (classic) pnpm
출시 2010 2016 2017
저장 방식 프로젝트별 복사 프로젝트별 복사 글로벌 store + 링크
디스크 사용 높음 높음 매우 낮음
설치 속도 보통 빠름 가장 빠름
Lock 파일 package-lock.json yarn.lock pnpm-lock.yaml
node_modules flat flat (PnP 옵션) 엄격한 중첩
Phantom Deps 허용 허용 차단
Monorepo workspaces workspaces 최적화된 workspace

성능 벤치마크 (일반적인 경우)

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  설치 시간 (cold cache):                                         │
│  npm:  ████████████████████  100%                                │
│  yarn: ████████████████      80%                                 │
│  pnpm: ████████████          60%                                 │
│                                                                   │
│  설치 시간 (warm cache - 이미 store에 있음):                     │
│  npm:  ████████████████████  100%                                │
│  yarn: ████████████████      80%                                 │
│  pnpm: ████                  20%  ← 링크만 생성                  │
│                                                                   │
│  디스크 사용량:                                                  │
│  npm:  ████████████████████  100%                                │
│  yarn: ████████████████████  100%                                │
│  pnpm: ██████                30%  ← 공유 store                   │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

7. 대형 프로젝트들의 pnpm 전환 사례

┌─────────────────────────────────────────────────────────────────┐
│            왜 대형 오픈소스가 pnpm을 선택했나?                    │
│                                                                   │
│  Vue.js (2021년 10월):                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  전환 이유: esbuild v0.13이 플랫폼별 바이너리를          │    │
│  │  optionalDependencies로 변경                              │    │
│  │                                                          │    │
│  │  Yarn: 모든 플랫폼 바이너리 다운로드 (~102MB)            │    │
│  │  pnpm: 현재 플랫폼 바이너리만 다운로드 (~14.5MB)         │    │
│  │  → 7배 차이!                                              │    │
│  │                                                          │    │
│  │  커뮤니티 투표: 90%가 pnpm 추천                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  Vite (Vue와 동시기):                                            │
│  ├── 공식 저장소가 pnpm 모노레포                                 │
│  ├── 현대 프론트엔드 빌드 도구의 표준                            │
│  └── React, Svelte, Lit 생태계 전체에 영향                       │
│                                                                   │
│  Turborepo + Vercel:                                             │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  pnpm workspace + Turborepo 조합이                        │    │
│  │  모노레포의 사실상 표준(de facto standard)이 됨           │    │
│  │                                                          │    │
│  │  pnpm: 의존성 관리 + 워크스페이스 연결                    │    │
│  │  Turborepo: 태스크 오케스트레이션 + 캐싱                  │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  기타 주요 채택:                                                 │
│  ├── Prisma (DB ORM)                                             │
│  ├── SvelteKit (공식 템플릿에 포함)                              │
│  ├── Nuxt 3 (모노레포에 pnpm 권장)                              │
│  └── VueUse, Slidev 등 Anthony Fu의 프로젝트들                  │
│                                                                   │
│  2024년 사용률 (State of Frontend 설문, 6000명+):                │
│  ├── npm: 56.6% (Node.js 기본 포함이라 높음)                     │
│  ├── Yarn Classic: 21.5%                                         │
│  └── pnpm: 19.9% (만족도 93%로 최고)                            │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

8. pnpm의 보안 장점 - 공급망 공격 방어

8.1 event-stream 사건 (2018)

┌─────────────────────────────────────────────────────────────────┐
│           event-stream 사건과 pnpm의 보안 대응                   │
│                                                                   │
│  2018년 11월, 주당 200만 다운로드의 event-stream 패키지:         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  공격 과정:                                               │    │
│  │  1. 공격자가 원래 관리자에게 관리 권한을 양도받음          │    │
│  │  2. flatmap-stream이라는 악성 의존성을 추가                │    │
│  │  3. postinstall 스크립트로 악성 코드 실행                  │    │
│  │  4. 비트코인 지갑(Copay) 정보 탈취                        │    │
│  │                                                          │    │
│  │  pnpm v10이었다면?                                        │    │
│  │  → postinstall 스크립트 기본 차단!                        │    │
│  │  → 이 공격은 원천 봉쇄되었을 것                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  npm/yarn = 택배 받자마자 자동으로 상자를 열어줌          │    │
│  │  → 상자 안에 폭탄이 있어도 열어버림                       │    │
│  │                                                          │    │
│  │  pnpm v10 = 택배는 받되, 열기 전에 허가를 받아야 함      │    │
│  │  → "이 택배 열어도 되나요?" 물어봄                        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

8.2 pnpm v10의 보안 기본값 (2025)

┌─────────────────────────────────────────────────────────────────┐
│              pnpm v10 - "보안을 기본값으로"                       │
│                                                                   │
│  1. Lifecycle Scripts 기본 차단                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  npm/yarn: postinstall 스크립트 자동 실행 (위험!)         │    │
│  │  pnpm v10: 기본적으로 차단, 명시적 허용 필요              │    │
│  │                                                          │    │
│  │  // package.json                                          │    │
│  │  "pnpm": {                                                │    │
│  │    "allowedBuilds": ["sharp", "esbuild"]                  │    │
│  │  }                                                        │    │
│  │  → 신뢰하는 패키지만 빌드 스크립트 실행 허용              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  2. 엄격한 node_modules = 공급망 공격 방어                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Phantom Dependency 차단의 보안적 의미:                   │    │
│  │                                                          │    │
│  │  npm/yarn: 악성 패키지가 다른 패키지의 파일에 접근 가능   │    │
│  │  pnpm: 각 패키지는 자기 의존성에만 접근 가능              │    │
│  │  → 악성 코드의 피해 범위가 제한됨                         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  3. 릴리스 나이 필터링 (minimumReleaseAge)                       │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  // .npmrc                                                │    │
│  │  minimum-release-age=1440  // 24시간 (분 단위)            │    │
│  │                                                          │    │
│  │  → 새로 릴리스된 패키지를 24시간 대기 후 설치             │    │
│  │  → 제로데이 공격을 발견할 시간 확보                        │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  4. 이상한 출처 차단 (blockExoticSubdeps)                        │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  // .npmrc                                                │    │
│  │  block-exotic-subdeps=true                                │    │
│  │                                                          │    │
│  │  → 의존성이 git URL이나 tarball에서 오는 것을 차단        │    │
│  │  → npm 레지스트리를 통한 패키지만 허용                    │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

9. 주요 명령어

기본 명령어

# pnpm 설치
npm install -g pnpm
# 또는
corepack enable  # Node.js 16.13+ 내장

# 버전 확인
pnpm --version

# 패키지 설치
pnpm install                  # 모든 의존성 설치
pnpm add lodash               # 프로덕션 의존성 추가
pnpm add -D typescript        # 개발 의존성 추가
pnpm add -g tsx               # 전역 설치

# 패키지 제거
pnpm remove lodash

# 스크립트 실행
pnpm run build                # npm run build와 동일
pnpm build                    # run 생략 가능
pnpm test
pnpm start

Store 관리

# store 경로 확인
pnpm store path
# → ~/.local/share/pnpm/store/v3

# store 상태 확인
pnpm store status

# 사용하지 않는 패키지 정리
pnpm store prune

유용한 옵션

# 프로젝트의 모든 의존성 업데이트
pnpm update

# 특정 패키지만 업데이트
pnpm update lodash

# 의존성 트리 확인
pnpm list
pnpm list --depth=2

# 왜 이 패키지가 설치됐는지 확인
pnpm why lodash

10. Monorepo 지원

pnpm-workspace.yaml

# pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'apps/*'
  - '!**/test/**'  # test 폴더 제외

Monorepo 구조

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  my-monorepo/                                                    │
│  ├── package.json           ← 루트 package.json                  │
│  ├── pnpm-workspace.yaml    ← workspace 설정                    │
│  ├── pnpm-lock.yaml         ← 단일 lock 파일                    │
│  ├── node_modules/          ← 공유 의존성                        │
│  ├── apps/                                                       │
│  │   ├── web/               ← Next.js 앱                        │
│  │   │   └── package.json                                       │
│  │   └── api/               ← Express 서버                      │
│  │       └── package.json                                       │
│  └── packages/                                                   │
│      ├── ui/                ← 공유 컴포넌트                      │
│      │   └── package.json                                       │
│      └── utils/             ← 공유 유틸리티                      │
│          └── package.json                                       │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

Workspace 명령어

# 특정 패키지에서 명령 실행
pnpm --filter web build
pnpm --filter @my/utils test

# 모든 패키지에서 명령 실행
pnpm -r run build           # recursive
pnpm -r run test

# 의존성 있는 패키지들만 빌드
pnpm --filter web... build  # web과 web이 의존하는 모든 패키지

# 워크스페이스 패키지를 의존성으로 추가
pnpm add @my/utils --filter web --workspace

11. pnpm 최신 기능 (2024-2025)

11.1 Corepack - Node.js 공식 인정

┌─────────────────────────────────────────────────────────────────┐
│                Corepack으로 pnpm 관리하기                         │
│                                                                   │
│  Corepack이란?                                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Node.js 16.9+에 내장된 패키지 매니저 버전 관리 도구      │    │
│  │  → pnpm이 Node.js에 "공식 인정"받은 것                    │    │
│  │                                                          │    │
│  │  비유: 운영체제에 기본 설치된 앱 스토어                    │    │
│  │  npm = 기본 앱, pnpm = 앱 스토어에서 받을 수 있는 앱     │    │
│  │                                                          │    │
│  │  // package.json                                          │    │
│  │  {                                                        │    │
│  │    "packageManager": "pnpm@9.15.0"                        │    │
│  │  }                                                        │    │
│  │                                                          │    │
│  │  corepack enable    // 활성화                             │    │
│  │  pnpm install       // 자동으로 9.15.0 버전 사용          │    │
│  │                                                          │    │
│  │  팀원 모두 같은 pnpm 버전 보장!                           │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.2 Catalogs (pnpm 9.5+) - 모노레포 의존성 통합

┌─────────────────────────────────────────────────────────────────┐
│              Catalogs - 버전을 한 곳에서 관리                     │
│                                                                   │
│  문제: 모노레포에서 10개 패키지가 React를 사용                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  기존: 각 package.json에 버전이 흩어져 있음               │    │
│  │  app-a/package.json: "react": "^18.2.0"                   │    │
│  │  app-b/package.json: "react": "^18.3.0"  // 다른 버전!   │    │
│  │  lib-c/package.json: "react": "^18.2.0"                   │    │
│  │  → 버전 드리프트, 머지 충돌, 업그레이드 번거로움          │    │
│  │                                                          │    │
│  │  Catalogs: 버전을 한 곳에서 선언                          │    │
│  │  // pnpm-workspace.yaml                                   │    │
│  │  catalog:                                                 │    │
│  │    react: ^18.3.0                                         │    │
│  │    typescript: ^5.5.0                                     │    │
│  │                                                          │    │
│  │  // 각 package.json                                       │    │
│  │  "dependencies": { "react": "catalog:" }                  │    │
│  │  → 모든 패키지가 같은 버전 사용 보장!                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  기존 = 각 부서가 따로 사무용품 주문 (규격이 다 다름)    │    │
│  │  Catalogs = 본사에서 공용 규격을 정해놓고 일괄 주문       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.3 pnpm patch - 의존성 직접 수정

┌─────────────────────────────────────────────────────────────────┐
│              pnpm patch - 라이브러리 버그 직접 고치기             │
│                                                                   │
│  상황: lodash에 버그가 있는데 PR이 머지되려면 한참 걸림          │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  pnpm patch lodash          // lodash를 임시 폴더에 추출  │    │
│  │  # → 임시 폴더에서 버그 수정                              │    │
│  │  pnpm patch-commit /tmp/xxx // .patch 파일 생성 및 등록   │    │
│  │                                                          │    │
│  │  // package.json에 자동 기록                              │    │
│  │  "pnpm": {                                                │    │
│  │    "patchedDependencies": {                               │    │
│  │      "lodash": "patches/lodash.patch"                     │    │
│  │    }                                                      │    │
│  │  }                                                        │    │
│  │  → 매번 install할 때 자동으로 패치 적용                   │    │
│  │  → 포크 없이 의존성 버그 수정 가능!                       │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  기존 = 차에 결함이 있으면 리콜 올 때까지 기다림          │    │
│  │  pnpm patch = 직접 수리하고 매뉴얼에 기록해둠             │    │
│  │  → 다음에 차를 빌려도 자동으로 같은 수리 적용             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

11.4 pnpm deploy - 프로덕션 배포 최적화

┌─────────────────────────────────────────────────────────────────┐
│            pnpm deploy - Docker 이미지 최적화                    │
│                                                                   │
│  모노레포에서 하나의 앱만 배포하고 싶을 때:                      │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  pnpm deploy --filter=my-app /output                      │    │
│  │                                                          │    │
│  │  → my-app에 필요한 의존성만 추출                          │    │
│  │  → Docker 이미지 크기 최소화                              │    │
│  │  → 불필요한 devDependencies 제외                          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  비유:                                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  기존 = 이사할 때 집 전체 짐을 다 가져감                  │    │
│  │  pnpm deploy = 새 집에 필요한 것만 골라서 가져감          │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

12. 마이그레이션

npm/yarn에서 pnpm으로

# 1. 기존 node_modules 삭제
rm -rf node_modules
rm package-lock.json  # 또는 yarn.lock

# 2. pnpm으로 설치
pnpm install

# 3. pnpm-lock.yaml 생성됨 (커밋 필요)
git add pnpm-lock.yaml
git commit -m "chore: migrate to pnpm"

주의사항

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  마이그레이션 시 주의:                                           │
│                                                                   │
│  1. Phantom Dependencies 에러                                    │
│     → package.json에 누락된 의존성 추가 필요                     │
│     → pnpm add <missing-package>                                │
│                                                                   │
│  2. 일부 패키지 호환성                                           │
│     → shamefully-hoist 옵션으로 flat 구조 허용 가능             │
│     → .npmrc에 shamefully-hoist=true                            │
│     → (권장하지 않음, 임시 해결책)                               │
│                                                                   │
│  3. CI/CD 수정 필요                                              │
│     → npm install → pnpm install                                │
│     → npm run → pnpm run                                        │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

13. 설정 (.npmrc)

주요 설정

# .npmrc

# store 경로 변경 (기본: ~/.local/share/pnpm/store)
store-dir=~/.pnpm-store

# 엄격 모드 (기본값, phantom deps 차단)
strict-peer-dependencies=true

# 호환성 모드 (npm처럼 flat 구조, 권장 안 함)
shamefully-hoist=true

# Node.js 버전 관리
use-node-version=18.17.0

# 레지스트리 설정
registry=https://registry.npmjs.org/

# [pnpm v10] 보안 설정
minimum-release-age=1440
block-exotic-subdeps=true

14. 언제 무엇을 쓰면 좋은가? - 실전 선택 가이드

┌─────────────────────────────────────────────────────────────────┐
│                   실전 패키지 매니저 선택 가이드                  │
│                                                                   │
│  pnpm을 쓰면 좋은 경우:                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  + 모노레포를 운영할 때 (최적의 선택)                     │    │
│  │  + 디스크 공간이 제한적일 때 (CI/CD 러너, 노트북)        │    │
│  │  + 보안이 중요한 프로젝트 (금융, 의료)                    │    │
│  │  + 여러 프로젝트를 동시에 개발할 때                       │    │
│  │  + CI/CD 빌드 시간을 줄이고 싶을 때                       │    │
│  │  + 새 프로젝트를 시작할 때 (처음부터 pnpm 추천)          │    │
│  │  + 의존성을 엄격하게 관리하고 싶을 때                     │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  npm을 쓰면 좋은 경우:                                           │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  + 팀원들이 패키지 매니저에 익숙하지 않을 때              │    │
│  │  + 특별한 요구사항 없이 간단한 프로젝트                   │    │
│  │  + Node.js 설치만으로 바로 시작하고 싶을 때               │    │
│  │  + 기존 npm 프로젝트를 유지보수할 때                      │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  yarn을 쓰면 좋은 경우:                                         │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  + 이미 yarn을 쓰고 있는 프로젝트                         │    │
│  │  + 팀이 yarn에 익숙한 경우                                │    │
│  │  ! 신규 프로젝트라면 pnpm 고려 권장                       │    │
│  │  ! Yarn Classic은 유지보수 모드 (신기능 없음)             │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
│  판단 플로우차트:                                                │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  Q: 모노레포인가?                                        │    │
│  │  ├── YES → pnpm (최적의 선택)                             │    │
│  │  └── NO                                                   │    │
│  │      Q: 보안이 중요한가?                                  │    │
│  │      ├── YES → pnpm (Lifecycle 차단, 엄격한 격리)        │    │
│  │      └── NO                                               │    │
│  │          Q: 디스크/속도 최적화가 필요한가?                │    │
│  │          ├── YES → pnpm                                   │    │
│  │          └── NO                                           │    │
│  │              Q: 팀 학습 비용을 최소화하고 싶은가?         │    │
│  │              ├── YES → npm (가장 간단)                    │    │
│  │              └── NO → pnpm (장기적으로 이점)              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

15. 정리

┌─────────────────────────────────────────────────────────────────┐
│                                                                   │
│  pnpm = 빠르고 디스크 효율적인 패키지 매니저                     │
│                                                                   │
│  핵심 특징:                                                      │
│  ├── Content-Addressable Store: 패키지 1번만 저장               │
│  ├── 하드 링크: 디스크 공간 절약 (70-90%)                       │
│  ├── 엄격한 node_modules: Phantom Dependencies 차단             │
│  ├── Monorepo 최적화: 효율적인 workspace 관리                   │
│  ├── 보안 기본값: Lifecycle 차단, 릴리스 나이 필터링            │
│  └── 최신 기능: Catalogs, patch, deploy, Corepack               │
│                                                                   │
│  역사적 맥락:                                                    │
│  ├── npm (2010): 최초의 Node.js 패키지 매니저                   │
│  ├── left-pad 사건 (2016): npm 생태계 취약성 드러남             │
│  ├── yarn (2016): Facebook이 만든 빠른 대안                     │
│  ├── pnpm (2017): 근본적 구조 혁신                               │
│  └── Yarn Berry PnP (2020): 급진적 실험, 생태계 거부            │
│                                                                   │
│  보안:                                                           │
│  ├── event-stream 같은 공급망 공격 방어                          │
│  ├── postinstall 스크립트 기본 차단 (v10)                       │
│  ├── minimumReleaseAge로 제로데이 방어                           │
│  └── 엄격한 격리로 악성 코드 피해 범위 제한                     │
│                                                                   │
│  선택 기준:                                                      │
│  ├── 모노레포/보안/디스크/속도 → pnpm                           │
│  ├── 간단한 프로젝트/학습 비용 최소화 → npm                     │
│  └── 기존 yarn 프로젝트 유지보수 → yarn                         │
│                                                                   │
│  주요 기업 사용:                                                 │
│  Vue.js, Vite, Nuxt, Turborepo, Vercel, Prisma,                 │
│  SvelteKit 등                                                    │
│                                                                   │
└─────────────────────────────────────────────────────────────────┘

관련 키워드

pnpm, npm, yarn, 패키지 매니저, node_modules, Content-Addressable Store, 하드 링크, 심볼릭 링크, Phantom Dependencies, 유령 의존성, Monorepo, workspace, pnpm-workspace.yaml, pnpm-lock.yaml, .npmrc, left-pad, Isaac Z. Schlueter, Zoltan Kochan, Sebastian McKenzie, Yarn Berry, PnP, Plug'n'Play, Corepack, pnpm patch, Catalogs, pnpm deploy, Supply Chain Attack, 공급망 공격, event-stream, Lifecycle Scripts, minimumReleaseAge, Turborepo, Vue.js