[번역] yarn a new package manager for javascript

세니·2025년 3월 12일
0
post-thumbnail

요즘 회사에서 패키지 관리자를 npm에서 다른 관리자로 바꾸려고 이것저것 찾아보는 중 발견한 글이다. 무려 약 10년전에 페이스북에서 발행한 글이지만 나름 읽어볼만 하다.

서론

자바스크립트 커뮤니티에서는 엔지니어들이 기본 컴포넌트, 라이브러리, 또는 프레임워크를 매번 다시 작성하지 않기 위해 수십만 개의 코드를 공유합니다. 각 코드 조각은 다른 코드에 의존할 수 있으며, 이러한 의존성은 패키지 매니저에 의해 관리됩니다. 가장 인기 있는 자바스크립트 패키지 매니저는 npm 클라이언트로, npm 레지스트리에서 30만 개 이상의 패키지에 접근할 수 있습니다. 500만 명 이상의 엔지니어들이 npm 레지스트리를 사용하며, 매달 최대 50억 번의 다운로드가 이루어집니다.

페이스북에서는 수년 동안 npm 클라이언트를 성공적으로 사용해왔지만, 코드베이스의 규모와 엔지니어 수가 증가하면서 일관성, 보안, 성능 문제에 직면하게 되었습니다. 각 문제를 하나하나 해결하려고 시도한 후, 의존성을 보다 안정적으로 관리할 수 있는 새로운 솔루션을 구축하기로 결정했습니다. 그 결과물이 바로 Yarn으로, 빠르고 신뢰할 수 있으며 안전한 npm 클라이언트의 대안입니다.

저희는 Exponent, Google, Tilde와의 협력을 통해 Yarn의 오픈 소스 릴리스를 발표하게 되어 기쁩니다. Yarn을 사용하면 엔지니어들은 여전히 npm 레지스트리에 접근할 수 있으면서도, 패키지를 보다 빠르게 설치하고 머신 간 또는 안전한 오프라인 환경에서 일관되게 의존성을 관리할 수 있습니다. Yarn은 공유 코드를 사용할 때 엔지니어들이 자신감을 가지고 빠르게 작업할 수 있도록 하여, 새로운 제품과 기능 개발에 집중할 수 있게 합니다.


페이스북에서의 자바스크립트 패키지 관리의 진화

패키지 매니저가 존재하기 전에는 자바스크립트 엔지니어들이 프로젝트에 직접 저장하거나 CDN을 통해 제공되는 소수의 의존성에 의존하는 것이 일반적이었습니다. 첫 번째 주요 자바스크립트 패키지 매니저인 npm은 Node.js가 도입된 직후에 만들어졌으며, 곧 세계에서 가장 인기 있는 패키지 매니저 중 하나가 되었습니다. 수천 개의 새로운 오픈 소스 프로젝트가 생성되었고, 엔지니어들은 그 어느 때보다 많은 코드를 공유하게 되었습니다.

React와 같은 페이스북의 많은 프로젝트들이 npm 레지스트리의 코드에 의존하고 있습니다. 하지만 내부적으로 확장해 나가면서, 서로 다른 머신과 사용자들 사이에서 의존성을 설치할 때 일관성 문제가 발생했고, 의존성을 가져오는 데 걸리는 시간이 길어졌으며, npm 클라이언트가 일부 의존성의 코드를 자동으로 실행하는 방식에 대해 보안상의 우려가 있었습니다. 이러한 문제들을 해결하기 위한 방안을 시도했지만, 종종 새로운 문제들을 야기했습니다.


npm 클라이언트 확장 시도

초기에는 권장되는 모범 사례에 따라 package.json 파일만 저장하고 엔지니어들에게 수동으로 npm install을 실행하도록 요청했습니다. 이는 엔지니어들에게는 괜찮았지만, 보안과 신뢰성 문제로 인해 인터넷과 차단된 샌드박스 환경이 필요한 연속 통합(CI) 환경에서는 작동하지 않았습니다.

다음으로 시도한 해결책은 node_modules 전체를 리포지토리에 포함 시키는 것이었습니다. 이것은 작동했지만, 몇몇 간단한 작업들을 매우 어렵게 만들었습니다. 예를 들어, babel의 마이너 버전을 업데이트하면 80만 줄의 커밋이 생성되어 적용하기 어려웠으며, 잘못된 UTF-8 바이트 시퀀스, Windows 줄바꿈, 압축되지 않은 이미지 등에 대해 lint 규칙이 발생했습니다. node_modules의 변경 사항을 병합하는 데 엔지니어들은 종종 하루 종일 걸리기도 했습니다. 또한 소스 컨트롤 팀은 체크인된 node_modules 폴더가 엄청난 양의 메타데이터를 포함하고 있다고 지적했습니다. 현재 React Native의 package.json은 68개의 의존성만 나열하지만, npm install을 실행한 후의 node_modules 디렉터리에는 121,358개의 파일이 포함되어 있습니다.

페이스북의 엔지니어 수와 설치해야 할 코드의 양에 맞춰 npm 클라이언트를 확장하기 위한 마지막 시도를 했습니다. 전체 node_modules 폴더를 압축하여 내부 CDN에 업로드함으로써, 엔지니어들과 연속 통합 시스템 모두가 파일을 일관되게 다운로드하고 압축을 해제할 수 있도록 했습니다. 이로 인해 수십만 개의 파일을 소스 컨트롤에서 제거할 수 있었지만, 엔지니어들이 새로운 코드를 가져올 뿐만 아니라 빌드하기 위해서도 인터넷 접근이 필요하게 되었습니다.

또한 의존성 버전을 고정하기 위해 사용한 npm의 shrinkwrap 기능 문제도 해결해야 했습니다. Shrinkwrap 파일은 기본적으로 생성되지 않으며, 엔지니어들이 이를 생성하는 것을 잊으면 동기화가 깨지기 때문에, 우리는 shrinkwrap 파일의 내용이 node_modules의 내용과 일치하는지 검증하는 도구를 작성했습니다. 그러나 이 파일들은 정렬되지 않은 키를 가진 거대한 JSON 덩어리여서, 변경 사항이 거대하고 검토하기 어려운 커밋을 생성하게 됩니다. 이를 완화하기 위해 모든 항목을 정렬하는 추가 스크립트를 추가해야 했습니다.

마지막으로, npm을 사용하여 단일 의존성을 업데이트하면, 의미론적 버전 관리(semantic versioning) 규칙에 따라 관련 없는 다른 많은 의존성들도 업데이트됩니다. 이로 인해 모든 변경 사항이 예상보다 훨씬 커지며, node_modules를 커밋하거나 CDN에 업로드하는 등의 작업이 엔지니어들에게 이상적이지 않은 프로세스가 되었습니다.


새 클라이언트 구축

npm 클라이언트 주위에 인프라를 계속 구축하기보다, 문제를 보다 총체적으로 바라보기로 결정했습니다. 대신 우리가 겪고 있는 핵심 문제들을 해결할 수 있는 새로운 클라이언트를 구축한다면 어떨까요? 런던 사무소의 Sebastian McKenzie가 이 아이디어를 해킹하기 시작했고, 우리는 그 잠재력에 빠르게 흥분하게 되었습니다.

작업을 진행하면서 업계 전반의 엔지니어들과 이야기를 나누었고, 그들 역시 비슷한 문제에 직면해 있었으며 단일 문제를 해결하는 데 집중한 유사한 해결책들을 시도해왔음을 알게 되었습니다. 커뮤니티가 직면한 전체 문제에 대해 협력함으로써 모든 사람에게 작동하는 솔루션을 개발할 수 있다는 것이 분명해졌습니다. Exponent, Google, Tilde의 엔지니어들의 도움을 받아 우리는 Yarn 클라이언트를 구축하고, 페이스북 외의 다양한 사용 사례와 모든 주요 JS 프레임워크에서 그 성능을 테스트 및 검증했습니다. 오늘, 우리는 이를 커뮤니티와 공유하게 되어 기쁩니다.


Yarn 소개

Yarn은 기존의 npm 클라이언트나 다른 패키지 매니저의 워크플로를 대체하면서도 npm 레지스트리와 호환되는 새로운 패키지 매니저입니다. 기존 워크플로와 동일한 기능 세트를 제공하면서도 더 빠르고, 안전하며, 신뢰성 있게 작동합니다.

모든 패키지 매니저의 기본 기능은 글로벌 레지스트리에서 특정 용도로 사용되는 코드 조각, 즉 패키지를 엔지니어의 로컬 환경에 설치하는 것입니다. 각 패키지는 다른 패키지에 의존할 수도, 아닐 수도 있습니다. 일반적인 프로젝트는 의존성 트리 내에 수십, 수백, 심지어 수천 개의 패키지를 포함할 수 있습니다.

이러한 의존성은 의미론적 버전 관리(semver)에 따라 버전이 관리되고 설치됩니다. Semver는 새로운 버전에서 API를 깨뜨리거나, 새로운 기능을 추가하거나, 버그를 수정하는 등의 변경 유형을 반영하는 버전 관리 체계를 정의합니다. 하지만 semver는 패키지 개발자들이 실수를 하지 않는다는 전제에 의존하기 때문에, 의존성이 고정되지 않으면 파괴적인 변경이나 새로운 버그가 설치된 의존성에 포함될 수 있습니다.


아키텍처

Node 생태계에서는 의존성이 프로젝트 내의 node_modules 디렉터리에 배치됩니다. 그러나 중복된 의존성이 병합되면서 실제 의존성 트리와 파일 구조가 달라질 수 있습니다. npm 클라이언트는 node_modules 디렉터리에 의존성을 결정론적으로 설치하지 않습니다. 이는 의존성이 설치되는 순서에 따라 한 사람과 다른 사람의 node_modules 디렉터리 구조가 다를 수 있음을 의미합니다. 이러한 차이로 인해 “내 컴퓨터에서는 작동하는데”라는 버그가 발생하여 해결하는 데 오랜 시간이 걸릴 수 있습니다.

Yarn은 잠금 파일(lockfiles)과 결정적이며 신뢰할 수 있는 설치 알고리즘을 사용하여 버전 관리 및 결정론성 문제를 해결합니다. 이러한 잠금 파일은 설치된 의존성을 특정 버전으로 고정하며, 모든 머신에서 동일한 ****node_modules 파일 구조가 생성되도록 보장합니다. 작성된 잠금 파일은 정렬된 키를 가진 간결한 형식을 사용하여 변경 사항이 최소화되고 검토가 간단하도록 합니다.

설치 과정은 세 단계로 나뉩니다.

  • Resolution: Yarn은 레지스트리에 요청을 보내고 각 의존성을 재귀적으로 조회하면서 의존성 해결을 시작합니다.
  • Fetching: 다음으로, Yarn은 글로벌 캐시 디렉터리에서 필요한 패키지가 이미 다운로드되어 있는지 확인합니다. 다운로드되어 있지 않다면, Yarn은 해당 패키지의 tarball을 가져와 글로벌 캐시에 저장합니다. 이를 통해 오프라인 상태에서도 작동하며, 의존성을 한 번 이상 다운로드할 필요가 없게 됩니다. 또한, 전체 오프라인 설치를 위해 의존성을 tarball 형태로 소스 컨트롤에 포함시킬 수도 있습니다.
  • Linking: 마지막으로, Yarn은 글로벌 캐시에서 필요한 모든 파일을 로컬 node_modules 디렉터리로 복사하여 모든 것을 연결합니다.

이러한 단계들을 명확하게 분리하고 결정적인 결과를 도출함으로써, Yarn은 작업을 병렬로 수행할 수 있어 자원 활용을 극대화하고 설치 과정을 더욱 빠르게 만듭니다. 일부 페이스북 프로젝트에서는 Yarn을 사용하여 설치 시간을 수 분에서 단 몇 초로 단 한 차원 줄였습니다. 또한 Yarn은 뮤텍스를 사용하여 여러 CLI 인스턴스가 충돌하거나 서로 오염되지 않도록 보장합니다.

이 전체 프로세스에서 Yarn은 패키지 설치에 대해 엄격한 보증을 적용합니다. 어떤 패키지에 대해 어떤 라이프 사이클 스크립트가 실행되는지 제어할 수 있습니다. 패키지 checksum도 잠금 파일에 저장되어 매번 동일한 패키지를 받을 수 있도록 보장합니다.


특징

설치를 훨씬 빠르고 신뢰할 수 있게 만드는 것 외에도, Yarn은 의존성 관리 워크플로를 더욱 간소화하기 위한 추가 기능들을 제공합니다.

  • npm 및 bower 워크플로와의 호환성: 두 워크플로를 모두 지원하며, 레지스트리를 혼합하여 사용할 수 있습니다.
  • 라이선스 관리: 설치된 모듈의 라이선스를 제한할 수 있고, 라이선스 정보를 출력할 수 있는 기능을 제공합니다.
  • 안정적인 공개 JS API: 빌드 도구에서 활용할 수 있도록 로깅이 추상화된 안정적인 API를 제공합니다.
  • 깔끔한 CLI 출력: 가독성이 높고 최소화된, 깔끔한 CLI 출력을 제공합니다.

실제 운영 환경에서의 Yarn

페이스북에서는 이미 생산 환경에서 Yarn을 사용하고 있으며, 매우 잘 작동하고 있습니다. Yarn은 많은 자바스크립트 프로젝트의 의존성 및 패키지 관리를 담당하고 있습니다. 각 마이그레이션을 통해 엔지니어들이 오프라인으로 빌드할 수 있도록 지원하고, 워크플로를 가속화했습니다. 다양한 조건 하에서 Yarn과 npm의 React Native 설치 시간 비교 결과를 여기에서 확인할 수 있습니다.

Getting started

npm install -g yarn
yarn

Yarn CLI는 개발 워크플로에서 npm을 대체하며 기존 명령어와 동일한 명령어 또는 새로운 유사 명령어를 제공합니다

  • npm install → yarn 인수를 제공하지 않을 경우 yarn 명령어는 package.json 파일을 읽고 npm 레지스트리에서 패키지를 가져온 후 node_modules 폴더를 채웁니다. 이는 npm install 과 동일합니다.
  • npm install --save <name> → yarn add <name> 우리는 npm install <name> 의 보이지 않는 의존성을 제거하고 명령어를 분리했습니다. yarn add <name> 을 실행하는 것은 npm install --save <name> 과 동일합니다.
profile
세니는 무엇을 하고 있을까

0개의 댓글