JavaScript 패키지 매니저 아키텍처 분석(feat. npm, yarn, pnpm)

2025. 11. 5. 22:34·Dev Tools

JavaScript 패키지 관리 아키텍처 심층 분석

npm, Yarn, pnpm의 진화와 메커니즘


들어가며: 단순한 “패키지 설치 도구”가 아닌, 프로젝트의 인프라

개발을 하다 보면 누구나 한 번쯤 npm install을 입력해봤을 것입니다.
하지만 단 한 줄의 명령어 뒤에 숨은 복잡한 메커니즘과 아키텍처적 선택에 대해 진지하게 생각해본 적은 많지 않습니다.

사실, JavaScript 생태계에서 패키지 매니저의 선택은 단순한 도구 결정이 아닙니다.
그건 팀의 개발 문화와 철학을 반영하는 아키텍처적 결정입니다.
빌드 속도, 캐시 전략, CI/CD 효율성, 그리고 팀 전체의 생산성까지 이 한 선택에 영향을 받습니다.

JavaScript 패키지 매니저의 역사는 말 그대로 ‘문제 해결의 역사’입니다.
npm으로 시작해 Yarn으로 이어지고, 이제는 pnpm과 Yarn Berry가 새로운 패러다임을 제시하고 있습니다.
문제는 단순했습니다.
“더 빠르게, 더 안정적으로, 더 정확하게.”

하지만 그 단순한 문장은 지난 10년간 수많은 시행착오와 아키텍처적 실험을 낳았습니다.
이 글에서는 npm → Yarn → pnpm → Yarn Berry로 이어지는 진화의 궤적을 따라가며,
각 패키지 매니저가 어떤 문제를 해결했고, 어떤 대가를 치렀는지를 살펴보려 합니다.


I. 패키지 매니저가 풀어야 할 세 가지 과제

JavaScript 생태계의 패키지 매니저가 맞닥뜨린 핵심 과제는 다음 세 가지로 요약됩니다.

  1. 효율성 (Efficiency) – 얼마나 빠르게 설치할 수 있고, 얼마나 적은 디스크 공간을 쓰는가
  2. 신뢰성 (Reliability) – 동일한 코드베이스에서 항상 같은 결과를 재현할 수 있는가
  3. 정확성 (Correctness) – 의존성이 올바르게 격리되어 있는가

이 세 가지를 모두 만족시키는 도구는 지금도 없습니다.
대부분의 패키지 매니저는 셋 중 둘을 선택해야만 했습니다.
npm은 ‘호환성’과 ‘단순함’을, Yarn은 ‘속도’와 ‘결정성’을, pnpm은 ‘정확성’과 ‘효율성’을 택했습니다.


II. node_modules의 탄생과 진화

1. npm v1/v2 – 완벽했지만 비효율적인 구조

초기의 npm(v1~v2)은 Node.js의 require() 규칙을 가장 순수하게 구현했습니다.
각 패키지가 자기 자신의 node_modules를 가지며, 그 안에 필요한 의존성을 중첩 설치했습니다.

A/node_modules/B/node_modules/C

이 구조는 ‘의존성 격리’ 측면에서는 완벽했습니다.
하지만 두 가지 문제가 있었습니다.

  • 패키지 중복:
    A와 B가 둘 다 C를 사용한다면, 디스크에는 C가 두 번 설치됩니다.
  • Windows의 파일 경로 한계:
    의존성이 깊어질수록 경로가 너무 길어져, 삭제 명령조차 실패하는 경우가 많았습니다.

한마디로, 완벽했지만 현실적으로 쓸 수 없는 구조였습니다.
이때부터 “효율성 vs. 정확성”의 전쟁이 시작되었습니다.


2. npm v3 / Yarn Classic – 호이스팅으로 인한 부작용

npm v3는 이 문제를 해결하기 위해 호이스팅(Hoisting)이라는 전략을 도입합니다.
가능한 모든 의존성을 루트 node_modules로 끌어올려 구조를 평탄화하는 방식이죠.

node_modules/
├── express
├── react
├── accepts (express의 내부 의존성이지만 루트로 올라옴)

이 방식은 경로 문제를 해결했지만, 새로운 악몽을 만들었습니다.

① 팬텀 의존성 (Phantom Dependency)

package.json에 명시하지 않았더라도,
호이스팅 덕분에 “우연히” require가 성공하는 경우가 생깁니다.
이건 정말 교묘한 문제입니다.
당장 눈앞에서는 잘 작동하지만, 나중에 pnpm이나 Yarn Berry로 이전하면
“Cannot find module” 오류가 터집니다.
왜냐하면 그 의존성은 사실상 존재하지 않는 의존성이었기 때문입니다.

② 비결정적 설치 (Non-Deterministic Install)

npm v3는 설치 순서에 따라 결과가 달랐습니다.
동일한 package.json이라도, 누가 먼저 설치되느냐에 따라 node_modules의 구조가 달라졌습니다.
그래서 생긴 유명한 말이 있습니다.

“It works on my machine.”

호이스팅은 구조적으로 불안정했고,
결과적으로 개발자들은 “node_modules 폴더가 신뢰할 수 없다”는 걸 깨닫게 됩니다.


3. Yarn의 등장과 ‘결정론적 설치’

이 문제를 해결하기 위해 Yarn이 등장했습니다.
Yarn은 yarn.lock 파일을 통해 결정론적 설치(Deterministic Install)를 구현했습니다.
즉, 동일한 코드베이스라면 항상 동일한 버전이 설치됩니다.

이후 npm도 package-lock.json을 도입하며 이 개념을 받아들였습니다.
하지만 이 시스템에도 한계가 있었습니다.
“어디에 설치되는가?”는 여전히 결정되지 않았기 때문입니다.
즉, 버전은 같아도 구조는 달라질 수 있었습니다.


III. pnpm이 가져온 혁신: 링크를 통한 정확성

이제 pnpm의 차례입니다.
pnpm은 한마디로 말해 “npm의 이상을 현대적으로 복원한 시스템”입니다.
이 아키텍처의 핵심은 링크(Link)입니다.

1. 하드 링크 vs. 심볼릭 링크

  • 하드 링크(Hard Link):
    파일 데이터를 직접 공유합니다.
    동일한 파일이 여러 곳에 존재하더라도 실제 디스크 용량은 하나만 차지합니다.
  • 심볼릭 링크(Symbolic Link):
    원본 파일의 경로를 가리키는 포인터 역할을 합니다.
    유연하지만, 원본이 삭제되면 링크가 깨집니다.

pnpm은 이 두 가지를 조합합니다.

  1. 전역 저장소(~/.pnpm-store)에 모든 패키지를 하드 링크로 저장
  2. 프로젝트 내 .pnpm 폴더에 하드 링크 복제
  3. 루트 node_modules에는 직접 의존성만 심볼릭 링크로 노출

이 구조는 디스크 공간을 거의 쓰지 않으면서도,
npm v2의 격리성을 완벽히 복원했습니다.

이제 express가 의존하는 accepts 패키지는
루트가 아닌 node_modules/.pnpm/express@버전/node_modules/에 위치합니다.
즉, 팬텀 의존성은 원천적으로 차단됩니다.


IV. Yarn Berry의 급진적 실험: “node_modules를 없애자”

Yarn Berry(v2+)는 완전히 다른 길을 택했습니다.
node_modules 자체를 없애버린 것입니다.

Yarn Berry를 설치해보면 node_modules 폴더가 보이지 않습니다.
대신 .pnp.cjs라는 파일 하나가 생깁니다.
이 파일은 Node.js의 require를 가로채서,
“express는 .yarn/cache/express-v1.zip 안에 있다”라고 직접 알려줍니다.

즉, 파일 시스템 탐색을 하지 않습니다.
그 결과 설치 속도는 거의 0초에 가까워집니다.

이 방식은 ‘Zero-Install’이라 불리며,
CI/CD 환경에서 특히 압도적인 속도 차이를 보여줍니다.
게다가 팬텀 의존성도 완전히 제거됩니다.

단점은 호환성입니다.
Node.js의 표준 해석 방식을 완전히 대체하기 때문에,
IDE나 Linter, TypeScript 컴파일러 등이 이 시스템을 인식해야 합니다.
즉, “가장 빠르지만 가장 불편한 방식”입니다.


V. 종합 비교: 어떤 매니저를 선택해야 할까?

기준 npm / Yarn Classic pnpm Yarn Berry (PnP)
구조 평탄화 / 호이스팅 링크 기반 가상 저장소 Zip 캐시 + PnP 로더
팬텀 의존성 발생 차단 차단
디스크 효율 낮음 매우 높음 높음
설치 속도 중간 빠름 매우 빠름
결정론 수준 버전만 결정 버전 + 구조 결정 버전 + 구조 결정 (최고)
호환성 매우 높음 높음 중간

🔹 추천 시나리오별 선택 가이드

  1. 레거시 프로젝트 유지 중이라면 → npm / Yarn Classic
    이미 팬텀 의존성에 의존하고 있을 가능성이 큽니다.
    새로운 매니저로 전환하면 의외의 오류가 발생할 수 있으니, 점진적인 개선이 바람직합니다.
  2. 대규모 모노레포 / CI 최적화가 필요하다면 → pnpm
    전역 저장소 공유로 디스크 낭비를 최소화하고,
    캐시를 활용해 설치 속도를 극적으로 줄일 수 있습니다.
    정확성과 실용성의 균형이 가장 뛰어납니다.
  3. 완전한 의존성 통제가 목표라면 → Yarn Berry (PnP)
    node_modules 없는 세상을 꿈꾼다면 최고의 선택입니다.
    다만 초기 설정 난이도와 툴 호환성은 직접 감수해야 합니다.

VI. 마무리하며: 팬텀 의존성을 넘어 “정확성”의 시대로

JavaScript 패키지 매니저의 진화는 단순한 속도 경쟁이 아니었습니다.
npm v3의 호이스팅은 문제 해결을 위한 ‘필요악’이었고,
그로 인해 “팬텀 의존성”이라는 부채가 생겼습니다.

이제는 정확성(Dependency Correctness)이 새로운 기준이 되었습니다.

  • pnpm은 하드 링크와 심볼릭 링크를 정교하게 결합해
    Node.js 표준을 유지하면서도 완벽한 격리성을 구현했습니다.
  • Yarn Berry는 아예 기존 패러다임을 버리고
    새로운 의존성 로더 체계를 만들어냈습니다.

결국, 패키지 매니저의 선택은 “무엇이 더 좋냐”의 문제가 아닙니다.
우리 팀이 ‘생태계 호환성’과 ‘아키텍처 정확성’ 중 어느 쪽에 더 가치를 두는가의 문제입니다.

잠시 눈을 감고 생각해보세요.
여러분의 프로젝트는 어떤 철학 위에 서 있나요?
“모든 게 잘 돌아가는 시스템”을 원하시나요,
아니면 “모든 게 명확한 시스템”을 원하시나요?

그 대답이 바로,
당신의 패키지 매니저를 결정짓는 진짜 기준이 될 것입니다.


💡 요약

  • npm: 호환성 최고, 하지만 팬텀 의존성 존재
  • pnpm: 정확성과 효율성의 균형
  • Yarn Berry: 급진적 혁신, 속도는 최고지만 호환성 주의

저작자표시 비영리 변경금지 (새창열림)

'Dev Tools' 카테고리의 다른 글

openapi generator를 사용하여 api에 대한 typescript 코드젠  (0) 2024.07.23
VS CODE 확장 기능 2탄 (VS CODE Extensions)  (0) 2020.11.02
centos 8 vscode ssh 접속 설정  (0) 2020.05.27
vscode setting  (0) 2019.08.20
vscode 특정 확장자를 원하는 확장자로 취급 하는 방법  (0) 2019.07.23
'Dev Tools' 카테고리의 다른 글
  • openapi generator를 사용하여 api에 대한 typescript 코드젠
  • VS CODE 확장 기능 2탄 (VS CODE Extensions)
  • centos 8 vscode ssh 접속 설정
  • vscode setting
vitnal
vitnal
4년차 프론트엔드 개발자입니다. react를 사용하여 웹 서비스를 개발한 경험이 있습니다. github: https://github.com/jch1223
  • vitnal
    vitnal 아카이브
    vitnal
  • 전체
    오늘
    어제
    • 분류 전체보기 (146)
      • AI (1)
      • WEB (76)
        • React (21)
        • Nextjs (17)
        • JavaScript (16)
        • React Native (5)
        • HTML & CSS (7)
      • CS (3)
      • Git (15)
      • Dev Tools (23)
      • Deploy (12)
      • Tech Memo (8)
      • Retrospect (7)
  • 반응형
  • hELLO· Designed By정상우.v4.10.5
vitnal
JavaScript 패키지 매니저 아키텍처 분석(feat. npm, yarn, pnpm)
상단으로

티스토리툴바