Yarn berry (Plug n Play)

4 minute read

서론

회사 다른팀 상품에 우리팀 상품이 들어가게되면서, 그 팀에서 사용하고 있던 패키지 매니저인 yarn berry를 따라야 했었다. 그 전에는 pnpm을 사용했었는데, yarn berry로 마이그레이션을 하면서 yarn berry에 대해 조사한 내용을 기록한다.

Yarn이란?

요즘 많이 쓰이는 자바스크립트 패키지 매니저(관리자)에는 npm, pnpm, Yarn(Yet Another Resource Negotiator) 이 있다.

패키지 매니저는 프로젝트에 필요한 패키지를 설치/삭제/갱신 해주는 관리 도구다. 제일 많이 쓰이고, 노드를 깔면 설치되는 디폴트 패키지 매니저는 npm 이다. 그렇다면 pnpm과 yarn과 같은 다른 패키지 매니저는 왜 쓰는걸까? pnpm은 관련해서 작성한 블로그글이 있다.

yarn1(classic)은 기존 npm이 가지고 있던 일관성, 보안, 성능 문제를 해결하기 위해 페이스북과 구글 개발자들이 만들었다. yarn1은 npm 설치 프로세스의 속도를 높이기 위해서 작업을 병렬화 하였다. 이외에도 ‘native 모노레포 지원’, ‘cache-aware 설치’, ‘오프라인 캐싱’, ‘lock files’ 와 같은 개념을 도입했다고 한다.1 이후 yarn2(berry)가 2020년에 출시되었다. 이 페이지에서는 yarn berry(v2)만 가지고 있는 기능에 대해 알아본다.

(yarn classic은 yarn 1.x 버전을, yarn berry는 yarn 버전 2.0이상부터를 의미한다).

기존 NPM 문제점

yarn berry가 기존 NPM 문제점을 해결하기위해 도입한 기능들이 있다. 각 기능을 설명하기 앞서 기존 NPM의 문제점을 설명한다. 2

  • 비효율적인 의존성 검색
    npm은 한 라이브러리를 찾기 위해 순회를 하는데, 계속 상위 디렉토리의 node_modules폴더를 탐색한다. 바로 패키지를 찾지 못하면, 계속 느린 I/O호출(readdir,stat)이 반복된다.
  • 환경에 따라 달라지는 동작
    NPM은 패키지를 찾지 못하면, 상위 디렉토리 node_modules 폴더를 계속 검색한다. 해당 패키지의 상위 디렉토리 환경에 따라 어떤 의존성 패키지를 찾을지 달라진다. 다른 버전의 의존성을 불러오게 될 수도 있다고 한다.
  • 비효율적인 설치
    NPM의 node_modules 디렉토리 구조는 큰 공간을 차지한다. 용량뿐만 아니라, node_modules 디렉토리 구조를 만들기 위해 많은 I/O 작업이 필요하다. 한 패키지를 설치하는데, 그 패키지가 다른 패키지의 특정 버전에 의존하는 경우가 많다. 이럴때에는 설치가 유효한지 검증해야하는데, node_modules폴더가 복잡하기 때문에 설치 유효성과 설치 후 잘 설치가 되어있는지 검증하는데 많은 시간이 소요된다. 검증하려면 많은 I/O호출이 필요하고, 이는 설치 시간을 늘리는 요인이 된다. 그렇기 때문에, Yarn v1이나 NPM은 기본적인 의존성 트리의 유효성만 검증하고, 각 패키지 내용이 유효한지는 검증하지 않는다고 한다.
  • 유령 의존성 Phantom Dependency

    패키지1 -> a -> b
    패키지1 -> c -> a -> b

    위처럼 패키지1이 패키지 a,c에 의존하고 있고, a는 b를 의존(a->b), c는 b를 의존하고 있는 a를 또 의존하고있다고 가정한다 (c->a->b). 중복적으로 c,d를 설치하지 않기 위해, NPM과 Yarn1은 끌어올리기(hoisting)기법을 사용한다고 한다. 2 끌어올려지면, dependency tree는

    패키지1 -> a
    패키지1 -> b
    패키지1 -> c

    형태로 변경되는데, 이렇게 되면 패키지1에서 기존에 불러올 수 없었던 b를 불러올 수 있게 된다. 끌어올리기에 따라 직접 의존하지 않던 라이브러리를 require()불러올 수 있는 현상을 유령 의존성이라고 한다. package.json에 명시하지 않은 라이브러리를 “유령”처럼 조용히 사용할 수 있게 되고, 다른 의존성을 package.json에서 제거했을 때 조용히 사라지기도 한다. 의존성 관리를 어렵게 만든다.

기능/특징

Plug’n’Play (PnP)

yarn berry는 node_modules 디렉토리에 패키지를 저장하지 않는 Plug’n’Play 기능을 제공한다. yarn berry의 Plug’n’Play 기능은 기존 NPM이 가지고 있던 비효율적인 의존성 검색, 환경에 따라 달라지는 동작, 비효율적인 설치, 유령 의존성과 같은 문제를 해결하기 위해 나왔다.

yarn berry는 node_modules폴더를 생성하지 않는다. 대신에, .yarn/cache 폴더에 의존성의 정보를 저장하고, .pnp.cjs파일에 의존성을 찾을 수 있는(lookup) 정보를 기록한다. .pnp.cjs파일안 의존성 lookup 정보엔 의존성 패키지의 이름, 버전, 경로가 있다. .pnp.cjs는 중첩된 폴더 구조가 아닌 단일 파일이기 때문에, 디스크 I/O없이 어떤 패키지가 어떤 라이브러리에 의존하고 있는지, 각 라이브러리의 위치를 알 수 있다. 2 쉽게 말해서, 더 효율적으로 의존성을 찾을 수 있다는 뜻이다.

.yarn/cache에는 모든 의존성 패키지가 zip 압축파일로 저장되어있다. 예를 들어, axios ^1.4.0버전은 .yarn/cache/axios-npm-1.4.0-4d7ce8ca3e-7fb6a4313b.zip파일로 저장되어있다. zip 파일이기 때문에 기존 node_modules폴더보다 디스크 공간을 적게 차지한다. yarn berry는 .pnp.cjs파일을 통해 동적으로 zip 내용을 참조한다.

PnP 기능은 릴리즈 이후 논란이 컸다고 한다. 초기 릴리즈 때 pnp방식이 default 방식으로 설정되어있었는데 아마 기존에 사용하던 개발자들이 매우 혼란스러웠을거라 예상한다.. PnP 비호환성을 해결하고 논란을 잠재우기 위해 yarn berry팀은 차후 한 줄의 코드로 node_modules(classic 방식)로 관리할 수 있는 옵션을 줬다.

이후 2020년말에 pnpm도 PnP 방식을 지원하기 시작했다고 한다.

사용법

package.json

NPM에서 했던 방식과 동일하게 필요한 의존성 패키지의 메타 정보를 기록한다.

특정 yarn 버전을 사용하기 원하면 아래처럼 옵셔널로 packageManager필드를 추가해도 된다. 이 프로젝트에서 사용할 package manager를 정의하는 필드이다.


{
  "name": "hello-world",
  "version": "0.1.0",
  ...
  "dependencies": {
    "axios": "^1.4.0",
    ...
  },
  "devDependencies": {
    ...
  },
  "packageManager": "yarn@3.3.1"
}

yarn berry 설치

# yarn berry 설치
npm install -g yarn@2

# yarn berry 버전으로 설정. 특정 숫자 버전을 원하면 yarn set version 3.3.0 이런식으로 입력
yarn set version berry 

# 기존 node_modules 삭제
rm -rf node_modules

# 의존성 패키지 설치
yarn install

yarn berry 실행

Yarn berry는 Node.js가 제공하는 require()문을 덮어쓰어, 효율적으로 패키지를 찾을 수 있게 한다. PnP 기능을 사용하려면, node명령어 대신 yarn node명령어를 사용해야 한다.

package.json안 scripts도 yarn 으로 스크립트를 실행하여 PnP로 의존성을 불러오게 한다. 예를 들어, 기존에 npm run dev 했다면, yarn dev로 실행한다.

yarn.lock

기존 npm의 package.lock파일을 git 저장소에 올리는 것처럼, yarn의 yarn.lock파일도 git 저장소에 올린다.

zero install

Yarn PnP기능을 사용하면 의존성 패키지를 zip압축파일로 관리한다. 따라서 의존성 용량 크기가 작다. 각 의존성은 하나의 zip파일이기 때문에 파일 개수도 적다. 적은 용량과 파일 수로 이 .yarn/cache/폴더안 의존성 패키지를 Git으로 관리할 수 있다.

의존성 파일을 버전관리에 포함하는 것을 zero-install이라고 한다.

zero-install을 사용하게되면 새로운 환경에 git clone받아도 의존성 패키지를 다운로드 받을 필요가 없다. 또한, CI에서 의존성 설치하는 시간을 절약할 수 있다.

yarn config (.yarnrc.yml )

pnp를 활성화시키려면 .yarnrc.yml파일안 nodeLinker: "pnp"를 추가한다.

nodeLinker: "pnp"
yarnPath: .yarn/releases/yarn-3.3.1.cjs
pnpMode: loose  # strict

References

Leave a comment