[FE] 패키지 매니저..?

wldud·2026년 5월 3일

다들 처음 프론트 개발 시작할 때 아래 명령어를 작성해봤을겁니다

npm install 
npm start

근데 저도 항상 그냥 쓰라고 해서 썼는데 갑자기 이것들이 대체 뭘까..라는 생각이 들며 아래와 같은 의문이 들었다.

  • 정확히 npm이 무엇인지
  • Node.js 설치하면서 왜 같이 설치되었는지
  • yarn, pnpm, bun들은 뭐가 다른건가..

패키지 매니저란 무엇일까?

패키지 매니저를 가장 쉽게 말하면, 외부 라이브러리를 설치하고 관리해주는 도구다.

좀 더 정확하게는 “내 코드가 참조하는 외부 패키지들을, 정확한 버전으로, 문제없이 가져와서, 어디서든 똑같이 실행되도록 만드는 것”이다.

우리는 흔히 아래와 같이 코드를 쓴다.

import React from "react";
import axios from "axios";

이 코드는 컴퓨터 입장에서 모호한 부분이 많다.

react가 정확히 어떤 버전인지 알 수 없다. React 18.2.0인지, 18.3.x인지, 혹은 다른 버전인지 코드만 보고 알 수 없다. 그리고 React가 내부적으로 어떤 라이브러리를 필요로 하는지도 개발자가 일일히 기억하지 않는다.

  • 이 React는 어느 버전인지
  • 그 버전은 어디서 내려받아야 하는지
  • React가 필요로 하는 다른 라이브러리들은 무엇인지
  • 그 파일들은 내 컴퓨터 어디에 있어야 하는지
  • 팀원이 같은 프로젝트를 받을 때도 똑같이 재현되는지?

→ 이런 문제를 정리해주는 도구가 바로 패키지 매니저이다.

패키지 매니저 꼭 필요할까?

만약 패키지 매니저가 없이 React 프로젝트를 만든다고 생각해보자.

우리는 React만 설치한다고 생각하지만 실제로는 React 자체뿐만 아니라 React가 의존하는 수많은 파일과 라이브러리들이 함께 필요할 수 있다. 여기에 Typescript, ESLint, 각종 라이브러리까지 더하면 프로젝트에서 필요한 패키지는 수백 개가 된다.

이거를 사람이 직접 관리…?

라이브러리를 일일이 사이트에서 찾아서 다운로드해야 하고,
각 라이브러리의 버전이 맞는지 확인해야 하고,
연관되어서 필요한 라이브러리들도 또 찾아서 설치해야 하고,
팀원이 같은 프로젝트를 실행했을 때 나와 똑같은 버전을 써야하므로 기록도 해야한다.

그렇기때문에 패키지 매니저가 꼭 필요하다

package.json과 lock 파일?

패키지 매니저를 이해하려면 먼저 package.json과 lock 파일의 역할을 알아야 한다.

package.json은 쉽게 말해서 이 프로젝트가 어떤 패키지를 필요로 하는지 적어둔 곳이다.

{
  "dependencies": {
    "react": "^18.2.0",
    "axios": "^1.7.0"
  }
}

이걸로 “이 프로젝트에서는 React와 axios가 필요하구나”를 알 수 있다.
여기서 ^18.2.0같은 표기는 정확히 18.2.0만 쓰겠다는 뜻이 아니라, 그 범위를 만족하는 다른 버전도 허용한다는 의미다.

문제는 이 상태로 두면 설치 시점마다 조금씩 다른 버전이 들어올 수 있다.
어제 설치한 사람과 오늘 설치한 사람이 서로 다른 하위 버전을 받을 수 있음
그러면 “왜 내 컴에서는 되는데 님 컴에서는 안됨” 시전을 하게 되는거입니다.

그래서 lock 파일이 있는거다

  • npm은 package-lock.json
  • yarn은 yarn.lock
  • pnpm은 pnpm-lock.yaml
  • bun은 bun.lock

이 파일들은 실제로 어떤 버전이 선택되었는지 정확하게 고정해서 기록하는 파일이다.
즉, package.json이 “이 정도 범위의 패키지가 필요하다”라고 작성해두면, lock 파일은 “실제로 정확히 이 버전들로 설치했다”라는 설치 기록과 같다.

이 lock 파일로 팀원 모두가 동일한 버전을 설치할 수 있다.

패키지 매니저는 실제로 어떻게 동작할까

패키지 매니저의 동작은 도구마다 조금씩 다르지만, 큰 흐름은 비슷하다.
보통 세 단계로 이해하면 쉽다.

1. Resolution: 무엇을 설치할지 결정하는 단계

프로젝트에 적혀 있는 의존성 목록을 보고, 실제로 어떤 버전을 써야 할지 계산한다.

예를 들어 react: ^18.2.0이라고 적혀 있다면, 패키지 매니저는 이 범위를 만족하는 실제 버전을 고른다.
그리고 React가 내부적으로 또 어떤 의존성을 가지는지도 계속 따라 내려가면서 함께 결정한다.

이때는 단순히 “React 설치”보다는 React와 그 하위 의존성까지 포함한 전체 의존성을 계산하는 것이다.

이때, 같은 package.json이라도 시간이 지나면 설치되는 결과가 달라질 수 있기 때문에 계산된 결과를 lock 파일에 남겨둔다.

2. Fetch: 실제 파일을 내려받는 단계

Resolultion이 끝나면 이제 무엇을 설치할지 정해지므로, 인터넷의 패키지 저장소에서 실제 파일을 가져오는 단계다.

대부분의 자바스크립트 패키지는 npm 레지스트리에서 받아온다.

npm은 두 가지 의미로 쓰임
npm Registry: 패키지들이 저장된 거대한 창고
npm CLI: 그 창고에서 패키지를 가져오는 도구

yarn, pnpm, bun을 써도 대부분 같은 npm 레지스트리에서 패키지를 받아온다. 차이는 어떻게 받아오고, 어떻게 연결하냐에 있음

이 단계가 각 패키지 매니저의 차이가 가장 크다.

Fetch 단계에서 패키지를 받아왔다고 끝이 아니다. 이제 개발자가 코드에서 import React from “react” 라고 썼을 때, 런타임이나 번들러가 실제 파일을 찾아갈 수 있어야 한다.

패키지 매니저는 받아온 패키지를 사용 가능하게 배치하고 연결하는 작업까지 해준다.
그리고 패키지 매니저들은 이 Link 방식이 다르다.

npm

npm은 가장 기본이 되는 패키지 매니저이다. Node.js를 설치하면 함께 설치된다.

npm은 설치한 패키지들을 프로젝트 안의 node_modules 폴더에 실제로 배치한다.

예를 들어 React와 axios를 설치하면 대략 이런 느낌이다.

node_modules/
  react/
  axios/

그리고 하위 의존성이 있다면 그 안쪽에도 또 필요한 구조가 생긴다.
즉, npm은 실제 파일과 폴더를 node_modules에 직접 추가하는 방식을 사용한다.

장점

명확하다. 가장 호환성이 좋고 보편적이다. 대부분의 라이브러리와 도구들이 npm의 node_modules 구조를 당연하게 가정하고 만들어져 있기 때문에 문제가 적다.

단점

같은 패키지가 프로젝트마다 중복 저장되기 쉽고, node_modules 폴더가 매우 커지며, 파일과 디렉터리를 엄청나게 많이 생성해야 해서 설치와 탐색 과정이 비효율적일 수 있다.

또한 npm 방식에서는 어떤 패키지가 직접 선언하지 않은 의존성을 우연히 참조하는, 유령 의존성 문제가 생기기도 한다.

예를 들어 A라는 라이브러리를 설치했는데 그 안에서 B를 같이 쓰고 있어서 npm이 A를 설치하면서 B도 같이 node_modules 안에 넣어둠 → 원래 내 프로젝트에서 직접 설치한 건 A 뿐이니까 B를 직접 쓰면 안되는게 맞음 → 근데 node_modules 구조상 B가 어딘가에 설치되어있어서 import B from “B” 가 가능해지는 문제.

pnpm

pnpm은 npm의 사용 방식과 호환성을 크게 해치지 않으면서, 속도와 디스크 효율을 개선하려는 방향에서 등장했다.

pnpm도 겉보기에는 node_modules를 사용한다. 그래서 npm과 크게 다르지 않아 보일 수 있지만, 중요한 차이가 있다.

바로바로 같은 패키지를 프로젝트마다 복사해서 저장하지 않는다는 것!!

npm은 프로젝트 A와 B가 둘 다 React 18.2.0을 사용하면, 각각의 프로젝트 안에 React 파일들이 따로 생긴다.
반면 pnpm은 이 패키지를 글로벌 저장소 같은 한 곳에 보관하고, 프로젝트에서는 그 위치를 링크로 연결한다.

이 방식 덕분에 디스크 사용량이 크게 줄고 설치 속도도 빨라진다.

또 pnpm은 의존성 관리가 더 엄격한 편이다. 직접 선언하지 않은 패키지를 우연히 참조하는 일을 줄여주기 때문에, 프로젝트 구조가 더 깔끔해지고 예측 가능해진다.

yarn

yarn은 처음 등장했을 때 npm의 느린 속도와 불안정한 의존성 관리 문제를 개선하기 위한 대안으로 주목받았다.

yarn은 나중에 등장한 yarn Berry와 PnP에 있다.

yarn PnP는 기존 node_modules 중심 방식 자체를 다시 생각한 접근이다.
기존 방식에서는 패키지를 찾기 위해 node_modules 디렉터리를 계속 뒤져야 했다.
하지만 Yarn PnP는 이런 식의 파일 시스템 탐색 자체가 비효율적이라고 본다.

그래서 아예 node_modules를 만들지 않고, “어떤 패키지가 어디에 있는지”를 별도의 맵 정보로 관리하는 방식을 택한다.

설치가 끝나면 .pnp.cjs 같은 파일이 생기고, 이 안에는 어떤 패키지가 어떤 위치에 있고, 어떤 의존성을 가지는지가 기록된다. Node.js는 이 정보를 읽어서 import나 require를 처리한다.

장점

설치가 빠르다. 그리고 import할 때 굳이 node_modules를 순회하지 않아도 되므로 효율적이다.
또 선언하지 않는 의존성 접근을 엄격하게 막기 때문에 프로젝트 구조가 더 정확해진다.

단점

node_modules를 전제로 동작하는 일부 도구와의 호환성 문제가 생길 수 있다. 실제로 Yarn PnP를 쓰다가 호환성 문제때문에 pnpm이나 node_modules 기반 방식으로 돌아가는 사례도 있음

Bun

Bun은 단순한 패키지 매니저가 아니라 자바스크립트 런타임이면서, 그 안에 패키지 매니저 기능까지 포함한 올인원 도구라고 볼 수 있다.

기존 Node.js 기반 개발에서는 보통 역할이 나뉜다.

자바스크립트 실행 → Node.js
패키지 관리 → npm이나 yarn
테스트 → Jest , vitest등
번들링 → Vite나 Wepback 같은 도구가 함

반면 Bun은 이런 여러 역할을 하나의 실행 파일 안에 통합하려는 방향이다.

Bun은 Node 기반 개발 툴체인 전체를 더 빠르고 단순하게 다시 묶으려는 시도임

Bun이 빠른것도 내부 구현이 다르고, 시작 속도와 실행 성능을 상당히 공격적으로 최적화했다. 또 TypeScript를 별도 설정 없이 바로 실행하는 경험도 큰 장점이다.

하지만 Bun이 다른 패키지 매니저들과 달리 더 넒은 역할을 하고, 특히 오래된 Node 생태계와 100% 동일하다고 보기는 어렵다. 그래서 아직은 완전히 안정하다고 할 수는 없다.

결론

잘 보고 어떤 것이 좋을지 판단해서 잘 선택해서 사용하자!

안정성과 보편성이 중요하면 npm,
효율과 실용성을 원하면 pnpm, → 제일 많이 사용
엄격성과 구조적 설계를 중시하면 Yarn,
속도와 통합 경험을 원하면 Bun

0개의 댓글