[ETC] Yarn Berry

2023년 02월 08일8분 소요
포스트 배너

YarnNPM과 같은 Node Package Manager입니다. Yarn의 1 버전을 Yarn Classic이라고 하고 Yarn의 2 버전 이상을 Yarn Berry라고 합니다.

Yarn Classic

Yarn의 1 버전을 Yarn Classic 이라고 합니다. Yarn Classic 저장소에 프로젝트 설명을 보면, 1.x 버전의 유지보수를 중단하고 새로운 기능이나 버그 픽스는 Yarn Berry에서 이루어 진다고 명시되어 있습니다.

동결된 Yarn Classic

Yarn Classic의 문제점

Yarn ClassicNPM과 동일하게 node_modules 디렉토리를 이용하여 의존성을 관리합니다. node_modules를 이용한 의존성 관리는 아래와 같은 3가지 문제점이 있습니다.

의존성 탐색 알고리즘의 비효율

패키지를 찾기 위해서 아래 그림과 같이 node_modules 디렉토리를 탐색합니다. 이 때 node_modules 디렉토리를 읽기 위해 메모리 I/O를 반복하게 됩니다.

node_modules 검색 과정

큰 저장 공간과 설치 시간

node_modules 디렉토리에는 사용되는 모든 의존성 패키지들이 설치되어 매우 큰 공간을 차지하게 됩니다. 또한 node_modules 디렉토리 구조를 만들기 위해 많은 메모리 I/O가 필요합니다.

유령 의존성(phantom dependency)

Yarn Classicnode_modules의 의존성 탐색, 저장 공간과 설치 시간의 비효율을 줄이기 위해 호이스팅(Hoisting)을 사용합니다. 호이스팅은 아래 그림과 같이 의존성 트리를 만듭니다.

yarn 호이스팅

호이스팅이 되면서 Package-1에서는 설치하지 않았던 B (1.0) 의존성이 설치되어 B (1.0)import하여 사용할 수 있는 유령 의존성이 생기게 됩니다. 이런 유령 의존성은 A (1.0)C (1.0) 의존성이 제거 되면 함께 제거되고, 또 A (1.0)이 업데이트 되어 B (1.0)를 사용하지 않게 된다면 자동으로 제거되어 어떤 사이드 이팩트가 발생 할 지 예상하기 어렵게 만듭니다.

Yarn Berry

Yarn의 2 버전 이상을 Yarn Berry 이라고 합니다. Yarn Berry에는 PnP와 Zero Install 두가지 중요한 개념이 있습니다.

PnP(Plug'n'Play)

PnP는 Plug And Play의 줄임말로 해석하면 '꼽기만 하면 사용할 수 있다.'로 해석할 수 있습니다. Yarn Berry는 성능 개선을 위해 node_modules를 읽는 느린 메모리 I/O 대신 .yarn/cache에 종속 패키지들을 zip 형태로 저장하고, .pnp.cjs 파일에 의존성 패키지의 의존성 정보를 저장하여 의존성 정보를 알 수 있게 만들었습니다.

.yarn/cache.pnp.cjs
  • .yarn/cache: 디렉토리 하위에 의존성 패키지들을 zip 형태로 저장하고 있습니다.
  • .pnp.cjs: 어떠한 패키지가 어떠한 패키지에 의존성이 있는지 저장하고 있는 파일입니다. 예를 들어 A 패키지를 실행해야 한다면, .pnp.cjs 파일에서 A 패키지의 의존성 정보를 읽어와서 A 패키지의 의존성 패키지를 .yarn/cache에서 찾아 A 패키지를 실행합니다.

Yarn Berry는 PnP 방식을 사용하여 아래의 3가지 개선점을 만들었습니다.

의존성 탐색 시간의 단축

Yarn Classic은 패키지의 node_modules에 의존성을 저장하고, 또 그 의존성 패키지의 node_modules 밑에 의존성을 저장하는 방식으로 수직적으로 의존성을 관리하였다면, Yarn Berry.yarn/cache에 모든 의존성을 담아 수평적으로 의존성을 관리합니다. 수평적 의존성 관리는 모든 패키지의 접근 시간을 O(1)로 만들어 require 등으로 패키지를 가져오는데 사용되는 시간을 단축했습니다.

의존성 파일 크기 감소

Yarn Berry.yarn/cache에 압축 파일 단위로 의존성을 관리하여 파일의 수와 크기를 감소시켰습니다.

유령 의존성 해결

Yarn Berry는 호이스팅을 사용하지 않아 유령 의존성 사용으로 발생하는 사이드 이팩트를 막았습니다.

Zero Install

Yarn Berry를 좀 더 PnP스럽게 사용하려면 Zero Install을 사용하면 됩니다. Zero Install이란 단어 그대로 설치 없이(yarn install 명령어 없이) 바로 프로젝트를 실행할 수 있도록 하는 설정을 이야기합니다.

.yarn/cache에 저장되어 있는 의존성 패키지와 .pnp.cjs에 저장되어 있는 패키지 의존성 정보를 버전 관리 프로그램(Git 등..)이 관리할 수 있도록 해서 구현할 수 있습니다. 버전 관리 프로그램으로 Git을 사용한다면, Zero Install을 사용할 때와 사용하지 않을 때 .gitignore 파일을 아래 코드와 같이 설정하면 됩니다.

# Zero Install 사용할 경우
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Zero Install 사용하지 않을 경우
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

Zero Install을 사용하면 아래 2가지 장점이 있습니다.

CI 시간 감소

프로젝트마다 다르지만, 보통의 경우 아래와 같은 과정을 거쳐 서비스가 배포됩니다.

git clone <project repository>
yarn install
yarn build

Zero Install을 사용할 경우 의존성을 설치할 필요가 없어서, yarn install 명령어 없이 git clone 만으로 프로젝트가 바로 빌드 가능한 상태가 되어 의존성을 설치하는 시간을 절약할 수 있습니다.

브랜치 변경시 바로 프로젝트 실행 가능

버전 관리 프로그램을 통해 의존성 패키지가 관리되면, 브랜치를 변경할 경우 해당 브랜치에 이미 필요한 의존성 패키지가 존재하여 추가로 yarn install 명령어 실행 없이 프로젝트를 실행할 수 있습니다.

Yarn Berry 설정

.yarnrc.yml 파일에서 Yarn Berry 설정을 할 수 있습니다. .yarnrc.yml 파일에 작성 할 수 있는 몇가지 Yarn Berry 설정을 살펴도록 하겠습니다. 자세한 .yarnrc.yml 설정 방법은 공식 문서를 참고 부탁드립니다.

nodeLinker

nodeLinker: "pnp" | "pnpm" | "node-modules"

패키지를 설치하는데 사용하는 링커를 선택할 수 있는 옵션입니다. 기본 값은 pnp입니다. node-modules로 설정할 경우 Yarn Classic이나 NPM처럼 node_modules 디렉토리에서 의존성을 관리하게 됩니다.

pnpMode

pnpMode: "strict" | "loose"

strict가 기본 값입니다. Yarn Berry의 PnP는 호이스팅을 사용하지 않아서 package.jsondependencies에 나열 된 의존성만 설치됩니다. 이런 동작은 유령 의존성을 없앨 수 있지만, 사용중인 패키지가 유령 의존성을 사용하고 있다면 문제가 될 수 있습니다. 이런 경우 loose를 사용하여 문제를 해결 할 수 있습니다.

pnpMode: "loose"를 사용할 경우 node-modules 호이스터를 사용해서 호이스팅 되는 패키지 목록을 폴백 풀(fallback pool)로 만들어 .pnp.cjs에 저장합니다. 의존성 패키지를 찾지 못할 경우 폴백 풀에서 패키지를 찾아 실행하게 됩니다.

packageExtensions

webpack@*:
  dependencies:
    lodash: "^4.15.0"
  peerDependencies:
    webpack-cli: "*"
  peerDependenciesMeta:
    webpack-cli:
      optional: true

일부 패키지는 의존성 정보가 잘못되었거나 누락이 있을 수 있습니다. Yarn Berry의 PnP는 의존성 관리에 엄격한 편인데, 사용하는 패키지에 의존성 정보가 잘못되어 있다면 해당 패키지는 사용할 수 없습니다. 의존성 정보가 잘못 된 패키지를 사용하기 위해서 packageExtensions을 사용하여 의존성 정보를 확장할 수 있습니다.

위의 코드는 모든 버전에 해당하는 webpackdependenciesloash@^4.15.0, peerDependencieswebpack-cli@*, peerDependenciesMetawebpack-clioptional을 확장한 코드입니다.

enableScripts

enableScripts: true | false

enableScripts의 기본 값은 true입니다.

clone 된 프로젝트에 .yarn/cache.pnp.cjs 파일이 모두 존재하지만 yarn install 후에야 프로젝트를 실행할 수 있는 반쪽 자리 Zero Install이라면, .yarn/unplugged 디렉토리를 의심해 봐야 합니다.

Yarn Berry는 의존성 패키지를 모두 zip으로 관리하기 때문에 의존성 패키지는 모두 읽기 전용으로 동작할 수 밖에 없습니다. 하지만 의존성 패키지가 쓰여지거나(파일 쓰기) 실행(쉘 스크립트 실행 등..) 되어야 한다면, zip 파일은 압축 해제되어 .yarn/unplugged 디렉토리에 저장됩니다.

yarn install 과정 중에 의존성 패키지에 사후 설치(postinstall) 스크립트가 있다면 해당 패키지는 압축 해제 되어 .yarn/unplugged 디렉토리에 저장됩니다. 이런 이유로 Zero Install 설정을 했지만 yarn install을 해야 프로젝트가 실행되는 경우가 발생하게 됩니다.

yarn install 없이 바로 프로젝트를 실행하기 위해서는 enableScripts: false로 설정해야 합니다. enableScripts: false는 사후 설치 스크립트를 막아 .yarn/unplugged 디렉토리를 생성하지 않도록 만듭니다.

참고
이전 포스트
[ETC] package.json의 scripts
다음 포스트
[ETC] Monorepo - 개념
© 2024 Beomy. All rights reserved.