
다들 처음 프론트 개발 시작할 때 아래 명령어를 작성해봤을겁니다
npm install
npm start
근데 저도 항상 그냥 쓰라고 해서 썼는데 갑자기 이것들이 대체 뭘까..라는 생각이 들며 아래와 같은 의문이 들었다.
패키지 매니저를 가장 쉽게 말하면, 외부 라이브러리를 설치하고 관리해주는 도구다.
좀 더 정확하게는 “내 코드가 참조하는 외부 패키지들을, 정확한 버전으로, 문제없이 가져와서, 어디서든 똑같이 실행되도록 만드는 것”이다.
우리는 흔히 아래와 같이 코드를 쓴다.
import React from "react";
import axios from "axios";
이 코드는 컴퓨터 입장에서 모호한 부분이 많다.
react가 정확히 어떤 버전인지 알 수 없다. React 18.2.0인지, 18.3.x인지, 혹은 다른 버전인지 코드만 보고 알 수 없다. 그리고 React가 내부적으로 어떤 라이브러리를 필요로 하는지도 개발자가 일일히 기억하지 않는다.
→ 이런 문제를 정리해주는 도구가 바로 패키지 매니저이다.
만약 패키지 매니저가 없이 React 프로젝트를 만든다고 생각해보자.
우리는 React만 설치한다고 생각하지만 실제로는 React 자체뿐만 아니라 React가 의존하는 수많은 파일과 라이브러리들이 함께 필요할 수 있다. 여기에 Typescript, ESLint, 각종 라이브러리까지 더하면 프로젝트에서 필요한 패키지는 수백 개가 된다.
이거를 사람이 직접 관리…?
라이브러리를 일일이 사이트에서 찾아서 다운로드해야 하고,
각 라이브러리의 버전이 맞는지 확인해야 하고,
연관되어서 필요한 라이브러리들도 또 찾아서 설치해야 하고,
팀원이 같은 프로젝트를 실행했을 때 나와 똑같은 버전을 써야하므로 기록도 해야한다.
그렇기때문에 패키지 매니저가 꼭 필요하다
패키지 매니저를 이해하려면 먼저 package.json과 lock 파일의 역할을 알아야 한다.
package.json은 쉽게 말해서 이 프로젝트가 어떤 패키지를 필요로 하는지 적어둔 곳이다.
{
"dependencies": {
"react": "^18.2.0",
"axios": "^1.7.0"
}
}
이걸로 “이 프로젝트에서는 React와 axios가 필요하구나”를 알 수 있다.
여기서 ^18.2.0같은 표기는 정확히 18.2.0만 쓰겠다는 뜻이 아니라, 그 범위를 만족하는 다른 버전도 허용한다는 의미다.
문제는 이 상태로 두면 설치 시점마다 조금씩 다른 버전이 들어올 수 있다.
어제 설치한 사람과 오늘 설치한 사람이 서로 다른 하위 버전을 받을 수 있음
그러면 “왜 내 컴에서는 되는데 님 컴에서는 안됨” 시전을 하게 되는거입니다.
그래서 lock 파일이 있는거다
이 파일들은 실제로 어떤 버전이 선택되었는지 정확하게 고정해서 기록하는 파일이다.
즉, package.json이 “이 정도 범위의 패키지가 필요하다”라고 작성해두면, lock 파일은 “실제로 정확히 이 버전들로 설치했다”라는 설치 기록과 같다.
이 lock 파일로 팀원 모두가 동일한 버전을 설치할 수 있다.
패키지 매니저의 동작은 도구마다 조금씩 다르지만, 큰 흐름은 비슷하다.
보통 세 단계로 이해하면 쉽다.
프로젝트에 적혀 있는 의존성 목록을 보고, 실제로 어떤 버전을 써야 할지 계산한다.
예를 들어 react: ^18.2.0이라고 적혀 있다면, 패키지 매니저는 이 범위를 만족하는 실제 버전을 고른다.
그리고 React가 내부적으로 또 어떤 의존성을 가지는지도 계속 따라 내려가면서 함께 결정한다.
이때는 단순히 “React 설치”보다는 React와 그 하위 의존성까지 포함한 전체 의존성을 계산하는 것이다.
이때, 같은 package.json이라도 시간이 지나면 설치되는 결과가 달라질 수 있기 때문에 계산된 결과를 lock 파일에 남겨둔다.
Resolultion이 끝나면 이제 무엇을 설치할지 정해지므로, 인터넷의 패키지 저장소에서 실제 파일을 가져오는 단계다.
대부분의 자바스크립트 패키지는 npm 레지스트리에서 받아온다.
npm은 두 가지 의미로 쓰임
npm Registry: 패키지들이 저장된 거대한 창고
npm CLI: 그 창고에서 패키지를 가져오는 도구
yarn, pnpm, bun을 써도 대부분 같은 npm 레지스트리에서 패키지를 받아온다. 차이는 어떻게 받아오고, 어떻게 연결하냐에 있음
이 단계가 각 패키지 매니저의 차이가 가장 크다.
Fetch 단계에서 패키지를 받아왔다고 끝이 아니다. 이제 개발자가 코드에서 import React from “react” 라고 썼을 때, 런타임이나 번들러가 실제 파일을 찾아갈 수 있어야 한다.
패키지 매니저는 받아온 패키지를 사용 가능하게 배치하고 연결하는 작업까지 해준다.
그리고 패키지 매니저들은 이 Link 방식이 다르다.
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은 npm의 사용 방식과 호환성을 크게 해치지 않으면서, 속도와 디스크 효율을 개선하려는 방향에서 등장했다.
pnpm도 겉보기에는 node_modules를 사용한다. 그래서 npm과 크게 다르지 않아 보일 수 있지만, 중요한 차이가 있다.
바로바로 같은 패키지를 프로젝트마다 복사해서 저장하지 않는다는 것!!
npm은 프로젝트 A와 B가 둘 다 React 18.2.0을 사용하면, 각각의 프로젝트 안에 React 파일들이 따로 생긴다.
반면 pnpm은 이 패키지를 글로벌 저장소 같은 한 곳에 보관하고, 프로젝트에서는 그 위치를 링크로 연결한다.
이 방식 덕분에 디스크 사용량이 크게 줄고 설치 속도도 빨라진다.
또 pnpm은 의존성 관리가 더 엄격한 편이다. 직접 선언하지 않은 패키지를 우연히 참조하는 일을 줄여주기 때문에, 프로젝트 구조가 더 깔끔해지고 예측 가능해진다.
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은 단순한 패키지 매니저가 아니라 자바스크립트 런타임이면서, 그 안에 패키지 매니저 기능까지 포함한 올인원 도구라고 볼 수 있다.
기존 Node.js 기반 개발에서는 보통 역할이 나뉜다.
자바스크립트 실행 → Node.js
패키지 관리 → npm이나 yarn
테스트 → Jest , vitest등
번들링 → Vite나 Wepback 같은 도구가 함
반면 Bun은 이런 여러 역할을 하나의 실행 파일 안에 통합하려는 방향이다.
Bun은 Node 기반 개발 툴체인 전체를 더 빠르고 단순하게 다시 묶으려는 시도임
Bun이 빠른것도 내부 구현이 다르고, 시작 속도와 실행 성능을 상당히 공격적으로 최적화했다. 또 TypeScript를 별도 설정 없이 바로 실행하는 경험도 큰 장점이다.
하지만 Bun이 다른 패키지 매니저들과 달리 더 넒은 역할을 하고, 특히 오래된 Node 생태계와 100% 동일하다고 보기는 어렵다. 그래서 아직은 완전히 안정하다고 할 수는 없다.
잘 보고 어떤 것이 좋을지 판단해서 잘 선택해서 사용하자!
안정성과 보편성이 중요하면 npm,
효율과 실용성을 원하면 pnpm, → 제일 많이 사용
엄격성과 구조적 설계를 중시하면 Yarn,
속도와 통합 경험을 원하면 Bun