etc
package manager
패키지 매니저 공부 배경
위투디 프로젝트에서 사용했던 기술을 바탕으로 2개의 프로젝트를 진행해보려고 한다. 2개의 프로젝트 모두 웹에서 디자인 할 수 있는 canvas api를 사용하기 때문에 두 프로젝트가 컴포넌트를 공유하여 사용할 수 있도록 제작하려 한다. 물론 버튼이나, 레이아웃 등의 기타 컴포넌트도 공통적으로 관리하여 개발 생산성을 높일 것이다.
이런 공통적인 기능을 어떻게 유지할까 고민해봤다.
- 패키지로 만들어서 관리하는 방식.
- 모노레포 방식.
1번인 패키지로 만들어서 관리하는 방식은 공통적인 기능(컴포넌트)이 변경이 되면 패키지를 다시 빌드해줘야 한다. 이렇게 되면 패키지 버전이 달라지게 되고 2개의 프로젝트 모두 패키지를 업데이트 해줘야한다. 개발을 하면서 공통적인 기능의 수정이 빈번하게 일어날 것 같은데 이 과정이 매우 번거롭다고 생각했다.
2번인 모노레포 방식을 사용하기로 결심했다
빅테크 기업들의 모노레포 도입기를 보니까 yarn berry의 workspaces 기능을 많이 사용하였다. 나는 npm을 사용했기에 이번 기회에 패키지 매니저에 대해서 공부해야겠다고 생각했다.
yarn classic
yarn은 기존에 npm이 있었음에도 불구하고 왜 개발되었을까?
- 병렬화를 통해서 다운로드 속도를 개선한다.
- lock 파일을 자동으로 생성(npm의 경우 과거에는 자동으로 생성을 지원하지 않았으나 현재는 지원함.)
- package.json에는 버전의 정확한 버전이 아니라, 버전의 범위가 명시되어 있다. 패키지의 버그가 수정되어 버전이 올라갔을 때, 범위로 명시 되었기에 매번 패키지의 릴리즈에 대해서 추적하지 않아도 된다.
- package-lock.json에는 의존성 트리에 대한 정보가 담겨있으며 node_modules의 트리나 package.json이 수정되었을 때 자동으로 생성된다. 의존성 트리에 대한 정보가 있음으로써 npm install 했을 때 동일한 환경을 구성하도록 도와준다.
- 의존성 트리 알고리즘 변경
- 의존성 트리가 덜 변경되도록 함. npm의 경우 다운로드 순서에 따라서 의존성 트리가 변경되는 경우가 있음.
- 캐시 사용.
yarn은 현재 2020년 부터 유지보수 단계이다. 1.x 버전은 모두 레거시로 간주한다. 장기적으로 프로젝트를 진행한다면 yarn berry를 사용하는 편이 좋아보인다.
pnpm
2017년에 제작되었으며 npm이 보유한 문제점을 해결한 대체품으로 npm만 있으면 사용 가능하다.
pnpm은 2가지 문제를 해결한다.
- 패키지 중복 다운로드 문제
- 유령 의존성 문제 해결
1. 패키지 중복 다운로드 문제
만일 100개의 프로젝트가 동일한 dependency를 사용하고 있다고 가정하면, 모든 프로젝트에 해당 dependency를 다운로드 해주어야 한다. pnpm 개발자들은 이러한 행위가 불필요하다고 생각했다.
pnpm을 사용하면 의존성이 content-addressable에 저장이 된다. 모든 파일은 디스크 상에서 단일 위치에 저장되고, 패키지가 설치될 때 그 파일들은 단일 위치에서 하드 링크되고 추가적인 디스크 공간을 소비하지 않는다. 중복된 패키지 설치를 줄이기 때문에 많은 디스크 공간을 세이브 할 수 있고 새롭게 패키지를 구성할 때 필요한 다운로드 수를 줄여 빠른 다운로드가 가능하다.
pnpm은 프로젝트 수가 많아질수록 그 위력이 강력해지는 것 같다.
2. 유령 의존성 문제 해결
npm과 yarn classic의 경우 의존성을 위해 다운로드 되는 패키지의 중복 저장을 막기위해 평탄화 작업을 한다. 평탄화 작업이란 중복되는 패키지를 위로 끌어올리는 작업을 의미한다.
만일 A가 B라는 패키지에 의존을 하고있고 C라는 패키지가 A라는 패키지에 의존하고 있을 때, 평탄화 작업이 일어나지 않는다면 B 패키지의 경우 중복 저장된다. 평탄화 작업을 통해서 중복 저장은 피했지만, 유령 의존성이라는 문제가 생기게 된다. package.json에 명시되어 있지 않은 B라는 패키지에 바로 접근할 수 있게 되는 것이다.
pnpm은 symlink를 사용하여 프로젝트의 직접적인 의존성 만을 모듈 디렉토리의 루트로 추가하여 이러한 문제를 해결한다.
프로젝트의 의존성 정보를 symlink로 정확하게 dependencies의 중첩된 구조를 생성한다. symlink를 이용한 방식이 가능한 이유는 단일 출처에 저장되어, 끌어올리는 평탄화 작업이 필요없기 때문이다.
A 패키지의 의존성을 가지는 B 패키지를 설치한다고 가정해보자.
node_modules 폴더 아래 .pnpm이라는 폴더가 있다. 해당 폴더에는 A 패키지와 B 패키지가 담기게 되고, B 패키지에는 필요한 A 패키지에 대한 심볼릭 링크가 된다. 그 다음으로는 직접 의존성이 처리된다. B 패키지는 node_modules에 심볼릭 링크된다. node_modules는 B라는 패키지 정보만 보유하고 있기 때문에 유령 의존성 문제가 발생하지 않는다.
yarn berry
yarn berry는 yarn classic의 업그레이드 버전이다.
yarn berry는 node_modules를 아에 없애버리고자 하고 있다. node_modules를 왜 없애버릴려고 했을까?
비효율적인 의존성 검색
node_modules는 복잡한 폴더 구조이다. 그리고 npm은 파일을 찾기위해 node의 파일 시스템을 사용한다.
필요한 파일을 찾기위해 상위 폴더 순회를 반복한다. 하지만 yarn berry의 경우 의존성을 관리하기위해 .pnp.cjs라는 파일을 사용하는데 이는 중첩된 폴더 구조가 아닌 단일 파일로 디스크 I/O없이 빠르게 의존성 검색이 가능하다.
무거운 node_modules
기존에는 간단한 프로젝트를 하려고 해도 수백 메가바이트에 달하는 node_modules를 설치해야했다.
하지만 yarn은 의존성을 .yarn/cache 폴더 아래 zip 파일로 관리하여 디스크 공간을 많이 세이브 할 수 있고, 의존성 패키지 다운로드 속도를 대폭 줄일 수 있다.
또한 zip 파일로 관리가 되기에 의존성을 구성하는 파일의 수가 많지 않으므로 변경 사항을 감지하거나 의존성을 삭제하는 작업이 매우 빠르다.
의존성 패키지의 용량이 작아지면서 Zero-Install이라는 개념이 생겼다. Zero-Install이란 프로젝트를 클론했을 때, npm install과 같은 명령어를 사용하여 필요한 패키지를 다운로드 할 필요가 없다는 의미이다.
어떻게 가능한 것일까?
프로젝트를 처음 구성했을 때 가장 먼저 하는 일은 git 저장소를 생성하고 .gitignore에 /node_modules를 추가하는 것이였다.
그 이유는 node_modules가 무겁고 환경에 따라서 동작이 달라질 수 있기 때문이다.
하지만 yarn berry의 경우 zip 파일로 용량이 상대적으로 적고, .pnp.cjs 파일을 이용하여 의존성이 관리되기 때문에 외부 환경에 영향을 받지 않는다. 따라서 의존성 파일을 git을 사용하여 버전관리를 해도 좋다. 의존성 파일을 코드 저장소에 포함시킴으로써 우리는 프로젝트를 클론하기만 하면 패키지 install없이 시작할 수 있다.
어떤 패키지 매니저를 사용할 것인가?
나는 yarn berry를 사용하여 프로젝트를 진행하기로 결정하였다.
그 이유는 3가지이다.
- 엄격한 의존성 관리가 가능하다.
- 내가 진행하는 프로젝트의 수는 그렇게 많지 않다. pnpm을 사용하여 패키지 중복 다운로드 문제를 해결함으로써 크게 디스크 공간이 세이브 되지 않는다. (현재 디스크 공간도 여유롭다.)
- Zero-Install이 가능하며 의존성 파일의 크기가 작아짐으로써 CI 시간을 절약할 수 있다.