mason
mason-log.

npm, Yarn, pnpm.

mason

masonfe-hyunsu

🧵 1. 개요.

패키지 매니저 배너
  • 프론트 프로젝트를 열면 거의 항상 package.json이 있고, 옆에 package-lock.json이거나 yarn.lock, pnpm-lock.yaml이 붙어 있다.

  • 팀마다 쓰는 도구가 다르고, CI에서 설치가 어긋나면 로컬에선 됐는데 배포에서만 깨지는 일도 생긴다.

  • npm, Yarn, pnpm은 서로 어떤 차이가 있고, 어떤 기준으로 선택해야 할지 정리해 본다.


🧭 2. 대표적인 차이점.

  • 셋 다 하는 큰 그림은 같다. package.json에 적힌 패키지를 받아 오고, 버전을 lockfile에 고정해서 다음에 설치해도 같게 맞춘다.

  • 차이는 받은 파일을 프로젝트에 어떻게 풀어 두는지에 따라 달라진다. node_modules몽땅 펼쳐 둘지(npm), 링크만 걸어 둘지(pnpm), 아니면 폴더 없이 경로표만 둘지(Yarn PnP).

왜 구조가 중요한가.

  • 파일을 넓게 풀어 두면 대부분 도구가 잘 돌아간다. 대신 package.json에 안 적은 패키지도 import가 되는 일(phantom dependency)이 생기기 쉽다. 적지도 않은데 우연히 보이는 dependency라서다. 지금은 되는데, 나중에 옆 패키지 버전이 바뀌면 갑자기 깨질 수 있다.

  • 링크나 경로표처럼 엄격하게 두면, 안 적은 패키지는 아예 안 보인다. 그 실수는 막아주지만, 예전에 느슨한 구조를 전제로 만든 패키지와는 가끔 부딪친다.

의존성 레이아웃 비교
  • 왼쪽부터 npm(펼치기), pnpm(링크), Yarn(경로표)이다. 예전 Yarn v1은 npm처럼 펼치는 쪽에 가깝다.


📦 3. npm : 기본값, 최대 호환.

  • Node와 같이 오는 기본 패키지 매니저다. 별도 설치 없이 쓰는 팀이 많다.

  • 기본은 hoisting(공통 의존성을 위쪽으로 끌어올리는 방식)으로 node_modules를 평평하게 만든다.

  • lockfile은 package-lock.json. CI에서는 보통 npm ci로 lock을 기준으로 깨끗이 깐다.

언제 사용하는 게 적합할까.

  • 이미 npm이 기본인 CI, Docker 이미지처럼, 새 도구 설치 없이 가고 싶을 때.

  • "일단 돌아가게"가 우선이고, 의존성 엄격함은 나중에 다듬어도 될 때.

아쉬운 점.

  • 디스크에 같은 버전이 프로젝트마다 중복되기 쉽다.

  • 평평한 트리 덕분에 선언하지 않은 패키지를 import 해도 로컬에선 통과하는 경우가 있다. 나중에 상위 패키지 버전이 바뀌면 갑자기 깨진다.


🧶 4. Yarn : Classic과 Modern.

  • Yarn을 말할 때 헷갈리기 쉬운 지점이 여기다. Yarn Classic(v1)Yarn Modern(Berry, v2+)은 체감이 꽤 다르다.

  • Classic은 예전에 빠른 설치와 yarn.lock으로 유명했다. 지금도 Classic만 쓰는 레포가 남아 있다.

  • 지금 이야기하는 Yarn(Modern)의 기본은 PnP(Plug'n'Play). node_modules 대신 .pnp.cjs로 "패키지가 어디 있는지" 경로표를 만든다. (Yarn PnP)

  • lockfile은 yarn.lock. CI에서는 보통 yarn install --immutable로 lock을 건드리지 않고 깐다.

언제 사용하는 게 적합할까.

  • 새 프로젝트에서 PnP를 시험해 볼 여유가 있을 때.

  • Classic으로 굳은 레포는 당장 PnP까지 가지 않고, Modern + nodeLinker: node-modules로만 올려도 된다.

  • PnP가 부담이면 .yarnrc.yml에서 nodeLinker: node-modules로 되돌릴 수 있다.

아쉬운 점.

  • IDE, 일부 네이티브 툴, React Native/Expo처럼 node_modules전제로 하는 스택은 설정이 더 필요하다.

  • Classic과 Modern을 같은 Yarn으로 묶으면 팀 안에서 말이 엇갈리기 쉽다.


🔗 5. pnpm : 스토어와 엄격함.

  • pnpm의 핵심은 content-addressable store(같은 패키지 파일을 디스크에 한 번만 두고 재사용하는 저장소)다.

  • 프로젝트 node_modules에는 스토어에서 hard link로 가져오고, 보이는 쪽은 symlink로 묶는다. 루트에는 직접 선언한 의존성 위주로 보인다. (pnpm layout)

  • lockfile은 pnpm-lock.yaml. CI에서는 pnpm install --frozen-lockfilenpm ci에 가깝다.

pnpm 스토어와 프로젝트 연결
  • 여러 프로젝트가 같은 머신에서 같은 버전을 쓰면, 스토어 재사용 효과가 바로 느껴진다. 로컬보다 CI 캐시에서 더 티가 난다.

언제 사용하는 게 적합할까.

  • 디스크, 반복 설치, 모노레포(workspaces)에서 이득을 보고 싶을 때.

  • phantom dependency를 줄이고, 적지도 않은 패키지가 우연히 import되는 일을 막고 싶을 때.

아쉬운 점.

  • symlink / hard link를 전제로 해서, 일부 도구, 배포 환경에서 추가 설정이 필요할 수 있다.

  • 평평한 hoisting에 기대던 패키지는 public-hoist-pattern 같은 조정이 필요할 때가 있다. 이건 버그라기보다 엄격함이 드러나는 과정에 가깝다.


⚖️ 6. 그래서 뭘 고르나.

하나를 정답으로 두기보다, 팀이 감당할 비용과 이득을 기준으로 고른다.

우선순위

기울이는 쪽

호환, 온보딩 최소

npm

엄격함 + 디스크/CI

pnpm

PnP, 제로에 가까운 설치 실험

Yarn Modern

레거시 Classic 유지

Yarn v1 유지 또는 Modern + node-modules

  • 같은 레포에 lockfile을 두 종류 두지 않는다. 한 도구로 고정하고, CI도 그 도구의 freeze 명령으로 맞춘다.

  • packageManager 필드로 쓸 도구와 버전을 적어 두고, Corepack(프로젝트에 적힌 패키지 매니저 버전을 맞춰 주는 도구)로 맞추면 로컬 버전 차이를 줄일 수 있다. Node 25부터는 Corepack이 배포에 안 실려서, 쓸 거면 별도 설치가 필요하다.

  • Bun도 패키지 매니저 역할을 하지만, 이 포스팅 범위는 npm / Yarn / pnpm의 의존성 레이아웃 차이에 둔다.

속도 벤치만 보고 바꾸면 후회하기 쉽다. 깨지는 방식이 바뀌는 쪽을 먼저 본다.


✅ 7. 정리.

  • 셋의 공통 목표는 같다. 의존성 트리를 재현 가능하게 설치하는 것.

  • 차이는 레이아웃이다. hoisting, symlink store, PnP.

  • 지금은 개인 프로젝트와 모노레포에서 pnpm을 기본으로 두는 편이 편했다. 호환이 최우선인 레포는 npm을 유지한다.

  • Yarn은 Classic과 Modern을 같은 말로 묶지 않는 게 먼저다. PnP를 쓸지, node-modules로 둘지부터 정한다.


🔗 8. 참고 자료.

이전 글
스프라이트 애니메이션 패키지, mason-sprite.
다음 글
SEO와 AEO.

댓글

불러오는 중...
목록으로