pnpm 패키지 매니저
TL;DR
- pnpm 패키지 매니저의 핵심 개념과 사용 범위를 한눈에 정리
- 등장 배경과 필요한 이유를 짚고 실무 적용 포인트를 연결
- 주요 특징과 체크리스트를 빠르게 확인
1. 개념
pnpm = Performant npm └── "빠르고 디스크 효율적인" 패키지 매니저 └── npm/yarn의 문제점을 해결하기 위해 등장
2. 배경
pnpm 패키지 매니저이(가) 등장한 배경과 기존 한계를 정리한다.
3. 이유
이 주제를 이해하고 적용해야 하는 이유를 정리한다.
4. 특징
- 개념
- npm/yarn의 문제점
- Flat node_modules의 문제
- Content-Addressable Store
- 심볼릭 링크 vs 하드 링크
5. 상세 내용
작성일: 2026-02-09 카테고리: Build / Package Manager 포함 내용: pnpm, npm, yarn, Content-Addressable Store, Phantom Dependencies, Monorepo, workspace
1. pnpm이란?
개념
pnpm = Performant npm
└── "빠르고 디스크 효율적인" 패키지 매니저
└── npm/yarn의 문제점을 해결하기 위해 등장
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. pnpm의 해결책
Content-Addressable Store
┌─────────────────────────────────────────────────────────────┐
│ │
│ pnpm 방식: │
│ │
│ ~/.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): │
│ ├── 같은 파일 데이터를 가리키는 또 다른 이름 │
│ ├── 원본과 동등한 존재 │
│ ├── 원본 삭제해도 데이터 유지 │
│ └── 디스크 공간 추가 사용 없음 │
│ │
│ pnpm: │
│ ├── 패키지 폴더 → .pnpm/ : 심볼릭 링크 │
│ └── 파일 → store : 하드 링크 │
│ │
└─────────────────────────────────────────────────────────────┘
4. Phantom Dependencies 차단
문제 설명
┌─────────────────────────────────────────────────────────────┐
│ │
│ Phantom Dependency (유령 의존성): │
│ │
│ 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' // ❌ 에러! (올바른 동작) │
│ │
│ → 명시적으로 선언한 의존성만 접근 가능 │
│ → 더 안전하고 예측 가능한 의존성 관리 │
│ │
└─────────────────────────────────────────────────────────────┘
5. 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 │
│ │
└─────────────────────────────────────────────────────────────┘
6. 주요 명령어
기본 명령어
# 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
7. 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
8. 마이그레이션
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 │
│ │
└─────────────────────────────────────────────────────────────┘
9. 설정 (.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/
10. 정리
┌─────────────────────────────────────────────────────────────┐
│ │
│ pnpm = 빠르고 디스크 효율적인 패키지 매니저 │
│ │
│ 핵심 특징: │
│ ├── Content-Addressable Store: 패키지 1번만 저장 │
│ ├── 하드 링크: 디스크 공간 절약 (70-90%) │
│ ├── 엄격한 node_modules: Phantom Dependencies 차단 │
│ └── Monorepo 최적화: 효율적인 workspace 관리 │
│ │
│ 선택 기준: │
│ ├── 디스크 공간이 중요 → pnpm │
│ ├── Monorepo 운영 → pnpm │
│ ├── 엄격한 의존성 관리 → pnpm │
│ ├── 기존 프로젝트 호환성 → npm │
│ └── Yarn PnP 사용 중 → yarn │
│ │
│ 주요 기업 사용: │
│ Vue.js, Vite, Nuxt, Turborepo, Vercel 등 │
│ │
└─────────────────────────────────────────────────────────────┘
관련 키워드
pnpm, npm, yarn, 패키지 매니저, node_modules, Content-Addressable Store, 하드 링크, 심볼릭 링크, Phantom Dependencies, 유령 의존성, Monorepo, workspace, pnpm-workspace.yaml, pnpm-lock.yaml, .npmrc