공식문서 링크: https://pnpm.io/ko/symlinked-node-modules-structure
1. npm과 pnpm 차이
- npm: 프로젝트마다 필요한 패키지를 실물로 복사한다 → 용량 크고 설치 느림
- pnpm: 모든 패키지를 .pnpm 공용 스토어에 한 번만 넣고, 각 프로젝트에는 링크만 건다 → 용량 절약 + 설치 빠름
예시 상황
project라는 앱이 있고, 여기에 foo라는 라이브러리를 설치했는데 foo는 내부적으로 bar라는 라이브러리를 사용하는 상황입니다.
pnmp install foo@1.0.0 생기는 일
1. 실제 라이브러리 코드가 있는 파일이 하드링크를 활용하여 생성됩니다.
node_modules
└── .pnpm
├── bar@1.0.0
│ └── node_modules
│ └── bar -> <store>/bar
│ ├── index.js
│ └── package.json
└── foo@1.0.0
└── node_modules
└── foo -> <store>/foo
├── index.js
└── package.json
2. 노드 modules에서 링크로 .pnpm 내부를 바라봅니다.
node_modules/
├── foo → .pnpm/foo@1.0.0/node_modules/foo
└── .pnpm/
├── foo@1.0.0/
│ └── node_modules/
│ ├── foo → ../../../../store/foo
│ └── bar → ../../bar@1.0.0/node_modules/bar
└── bar@1.0.0/
└── node_modules/
└── bar → ../../../../store/bar
- foo와 bar는 .pnpm 디렉토리 내에 실제 파일이 저장되며, 이는 하드 링크를 통해 전역 저장소에서 가져옵니다.
- foo는 자신의 node_modules 내에 bar에 대한 심볼릭 링크를 생성하여 의존성을 관리합니다.
- 최상위 node_modules에는 foo에 대한 심볼릭 링크만 존재하며, 이는 프로젝트의 직접적인 의존성을 나타냅니다.
2. 평탄화란?
pnpm이 하드링크를 통해 중복을 최소화 한다면, npm은 평탄화를 합니다.
npm의 평탄화 (hoisting)
project/
└─ node_modules/
├─ foo/ # 실제 파일(복사본)
└─ bar/ # 실제 파일(복사본) ← 평탄화되어 루트로 올라옴
- 원래 bar는 foo의 의존성이지만, 루트에 끌어올림
- => require('bar') 같은 코드가 루트에서도 동작하게 하려고
3. 심링크와 하드링크
심링크
- 파일의 "경로"를 가리키는 링크
- 윈도우의 바로가기(lnk), macOS의 alias와 유사
- 삭제 대상이 사라지면 깨진 링크가 됨 (dangling symlink)
하드링크
- 파일의 실제 데이터(inode)를 가리키는 또 하나의 이름
- 원본 파일과 동등한 파일처럼 동작
- 원본 파일을 삭제해도 내용은 남아 있음 (다른 링크가 가리키고 있으면)
my-project/
├── node_modules/
│ ├── foo → .pnpm/foo@1.0.0/node_modules/foo # 심링크
│ ├── .bin/ # 실행 파일 링크
│ └── .pnpm/
│ ├── foo@1.0.0/
│ │ └── node_modules/
│ │ ├── foo → 하드 링크 → ~/.pnpm-store/v3/files/<hash>/foo
│ │ └── bar → 하드 링크 → ~/.pnpm-store/v3/files/<hash>/bar
│ └── bar@1.0.0/
│ └── node_modules/
│ └── bar → 하드 링크 → ~/.pnpm-store/v3/files/<hash>/bar
├── package.json
└── pnpm-lock.yaml