본문 바로가기
Search/Package Manager

Yarn Berry

by egas 2021. 8. 11.

Yarn Berry 너는 무엇이야?

“Berry” is the codename for the Yarn 2 release line.

yarn Berryyarn 패키지 매니저의 2번째 새로운 버전이다. 2018년 9월 yarn의 RFC 저장소에 해당 PR로부터 시작했다. Javascript로 만들어져 있던 yarn을 이번에 새로운 버전 berry에서는 Typescript로 만들었다. 따라서, berry의 경우 yarn 레파지토리와는 독립적으로 개발되고 있다. Yarn v1의 주요 개발자인 Maël Nison님이 만드셨고, 2020년 1월 25일부터 정식 버전(v2)이 출시되었다.

이에 따라 기존의 웹사이트 주소도 옮겨졌다.

 

 

yarn은 v1로 major가 1점대 버전이며, berry는 v2로 major가 2점대 버전이다. 현재 v3까지 나왔다. yarn Github 페이지에서 berry 레파지토리가 링크되어있다. yarn은 더 이상 유지보수가 이루어지지 않는다. fronzen 되었다.

 

node_modules

기존에 npm이나 yarn v1을 써봤던 사람들이라면 node_modules 폴더를 열어보았을 때, 한숨부터 나온다는 말이 무슨 말인지 알 것이다. npm은 파일 시스템을 이용해서 의존성을 관리한다. 이렇게 관리했을 때 의존성 검색은 비효율적으로 동작한다. 세부 정보는 다음 링크를 참고하자.

또한, NPM은 패키지를 찾지 못하면 상위 디렉터리의 node_modules 폴더를 계속 검색하는 특성 때문에, 어떤 의존성을 찾을 수 있는지는 해당 패키지의 상위 디렉터리 환경에 따라 달라질 수 있다. 환경에 따라 달라지게 될 경우 해당 상황을 재현하기 힘들다. 매우 드물겠지만, 이런 가능성이 존재한다는 점만으로도 문제 될 소지가 있다.

 

NPM에서 구성하는 node_modules 디렉토리 구조는 매우 큰 공간을 차지한다. 위에 사진이 괜히 나온것이 아니다. 간단한 라이브러리를 다운로드하여도 node_modules에는 무수히 많은 파일들이 설치된다. 또한, 이 많은 파일들의 의존성을 검사하기란 매우 어려운 일이다. Yarn v1이나 NPM은 기본적인 의존성 트리의 유효성까지만 검증하고, 각 패키지의 내용이 올바른지는 확인하지 않는다.

유령 의존성 (Phantom Dependency)

npm 및 yarn v1에서는 공간을 아끼기 위해, 오른쪽 사진과 같이 중복되는 의존성들에 대해서 끌어올리기(Hoisting) 기법을 사용한다.

결과적으로 package-1은 B(1.0)을 require/import 할 수 있게 된다. 직접적인 의존성이 없음에도 라이브러리를 require/import 할 수 있는 현상을 유령 의존성(Phantom Dependency)이라고 한다. 이때, 우리는 package.json에 명시하지 않은 라이브러리를 사용할 수 있게 된다.

 

유령 의존성이 문제가 되는 이유는  package.json에 명시되지 않는 라이브러리를 사용할 수 있게 되기 때문에, package.json에서 Dependency를 삭제/업데이트할 때 어떤 사이드 이펙트가 발생할지 몰라서 쉽게 라이브러리가 필요 없음에도 삭제를 못하는 경우가 발생할 수 있다.

 

Yarn Berry는 유령 의존성(Phantom Dependency) 문제를 Plug’n’Play 전략을 이용하여 해결한다.

 

Yarn Berry은 이전 버전과 무엇 다른가?

yarn berry는 pnp라는 개념을 도입하여 아래와 같은 내용들을 해결해준다.

 

  • Node.js의 기본적인 모듈 처리 메커니즘으로 인한 속도 저하
    • Node.js는 어떤 모듈을 사용하려고 하면, 그 모듈을 찾기 위해 계속 거슬러 올라가서 node_modules 폴더를 찾는 방식을 사용한다. 해당 과정에서 시스템 함수를 호출함에 따라서 시간이 걸리며, 이는 런타임에 시작 속도 저하를 유발한다.
  • Yarn의 설치 메커니즘으로 인한 속도 저하
    • Yarn 은 패키지를 설치할 때, 원격 저장소에서 패키지를 찾아 오프라인 미러에 저장하고 오프라인 미러에 있는 패키지를 캐시에 푼다. 그다음 캐시에 있는 패키지를 node_modules로 복사하는 방식을 사용한다. 이 과정에서 무거운 I/O 연산으로 인한 속도 저하가 발생한다.
  • node_modules 자체가 실용적으로 설계되지 않았음
    • node_modules에 설치되는 패키지들의 중복제거(dedupe)를 하지 못한다. 최적화를 통해서 호이 스팅을 하기도 하지만 유령 의존성(Phantom Dependency)의 문제를 유발했다. 개발을 할 때는 동작을 했지만 프로덕션에서 의존성에 명시되어 있지 않기 때문에 깨질 수 있는 문제가 발생한다.

 

Berry 는 .pnp.js 파일을 생성해서 위와 같은 문제를 해결한다. .pnp.js 파일에는 다음과 같은 정보들이 담겨있다.

  • 의존성 트리에서 사용할 수 있는 패키지
  • 패키지들의 연결 관계
  • 디스크의 저장된 위치

또한, node_modules를 생성하지 않고 기본 캐시 폴더인 yarn/cache 에 패키지를 zip 파일로 저장해서 이 경로를 .pnp.js 에 명시하는 방식을 사용한다. 이제 우리는 .pnp.js를 활용하여 기존 방식보다 훨씬 빠르게 애플리케이션의 패키지들을 설치 및 실행할 수 있다. 또한, 모든 패키지들이 명시되어있기 때문에 각 패키지들도 최상단으로 끌어올려져서 완전한 플랫 한 구조를 갖는다. (세부 내용은 다음 링크 참고)

 

Zero-Installs의 부분으로서 repository에 .pnp.js를 commit 할 수 있다. (yarn install을 할 필요가 없다.)

 

사용법

로컬 생성에 사용할 전역 Yarn 바이너리를 먼저 설치하자. 전역 Yarn은 "Classic" yarn (v1)이다. Yarn 1에 대해 구성된 프로젝트가 갑자기 2.x 구성 형식으로 마이그레이션 하는 대신, berry를 사용할 프로젝트에 대해서만 설정을 해줄 것이다.

 

npm install -g yarn

 

다음 명령어로 설치가 잘 되었는지 확인해보자.

 

yarn --version

 

프로젝트 폴더로 이동한다.

 

cd ~/path/to/project

 

다음을 실행해서 berry로 설정해주자. (.gitignore 에 필요한 파일들에 대한 세부 정보는 다음 링크 참고) 명령어를 실행하면 해당 폴더에서 yarn v1을 yarn berry로 업그레이드시켜준다.

 

yarn set version berry

 

(최신 버전으로 업데이트하려면 아래 명령어를 실행하자.)

yarn set version latest

 

기존 파일들을 수정하자.

  • .npmrc 파일을 .yarnrc.yml 으로 수정 (관련 옵션)
  • package.lock.json 파일이 존재한다면 제거
  • node_modules 폴더 제거
  • package.json 파일에 작성된 eslintConfig는 더 이상 사용되지 않는다. .eslintrc.json 파일로 빼자.

 

PnP 방식을 사용하기를 원하지 않을 경우 .yarnrc.yml 파일에 다음 내용을 추가해주자. 추가를 하면 zip 아카이브로 관리되는 것이 아닌 기존의 node_modules 방식으로 관리된다.

// .yarnrc.yml
nodeLinker: node-modules

 

아래 명령어로 설치를 해주자.

 

yarn install

또는

yarn

아래 폴더를 보면 node_modules가 없는 것을 볼 수 있다.

  • yarn.lock: YAML기반 yarn의 lockfile
  • .pnp.js: Plug’n’Play module resolver, dependencies들을 찾을 수 있는 정보가 기록된다. 디스크 I/O 없이 어떤 패키지가 어떤 라이브러리에 의존하는지, 각 라이브러리는 어디에 위치하는지를 바로 알 수 있다.
  • .yarn/cache: 압축된 dependencies들이 위치한 곳

아래는 .pnp.js에 기입되어있는 react-dom의 정보이다.

// .pnp.js
      ["react-dom", [
        ["npm:17.0.2", {
          "packageLocation": "./.yarn/cache/react-dom-npm-17.0.2-f551215af1-1c1eaa3bca.zip/node_modules/react-dom/",
          "packageDependencies": [
            ["react-dom", "npm:17.0.2"]
          ],
          "linkType": "SOFT",
        }],
        ["virtual:572974bfd16fba63746e564b946fcd0d5481e3958a2d86b2339f8f9624fc9f4abd3898c4444c731638d6001d3f88ca414f2c05308d6789925602f170e2d31abc#npm:17.0.2", {
          "packageLocation": "./.yarn/__virtual__/react-dom-virtual-5bc113ba13/0/cache/react-dom-npm-17.0.2-f551215af1-1c1eaa3bca.zip/node_modules/react-dom/",
          "packageDependencies": [
            ["react-dom", "virtual:572974bfd16fba63746e564b946fcd0d5481e3958a2d86b2339f8f9624fc9f4abd3898c4444c731638d6001d3f88ca414f2c05308d6789925602f170e2d31abc#npm:17.0.2"],
            ["@types/react", null],
            ["loose-envify", "npm:1.4.0"],
            ["object-assign", "npm:4.1.1"],
            ["react", "npm:17.0.2"],
            ["scheduler", "npm:0.20.2"]
          ],
          "packagePeers": [
            "@types/react",
            "react"
          ],
          "linkType": "HARD",
        }]
      ]],

패키지들은 .yarn/cache에서 zip 아카이브로 관리된다.

다음은 아래 package.json을 기반으로 yarn v1과 yarn v3(berry)에서 yarn install 이후 용량을 측정해보았다.

// package.json
{
  "name": "test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "react-scripts": "^4.0.3"
  },
  "scripts": {
    "start": "react-scripts start"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "packageManager": "yarn@3.0.0",
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

 

yarn v1
yarn berry v2

.yarn 파일을 보라.. 192 밖에 되질 않는다.

 

마지막으로 .gitignore 파일에 다음 중 하나를 추가해주자. (Which files should be gitignored?)

# Zero-Install을 사용하겠다면?
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Zero-Install을 사용하지 않겠다면?
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*

React를 Yarn berry로 실행해보자.

아래 파이들을 작성해준다.

// package.json
{
  "name": "test",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "react-scripts": "^4.0.3"
  },
  "scripts": {
    "start": "react-scripts start"
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "packageManager": "yarn@3.0.0",
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

 

// public/index.html
<div id="root"></div>

 

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

 

// src/App.js
import React from 'react';

export default function App() {
  return <p>Hello yarn berry :)</p>;
}

 

yarn start로 react를 실행해주자.

yarn start

그러면 아래와 같이 node_modules 폴더 아래 캐시들이 생성된다.

 

코드는 아래 Github를 참고하자.

 

GitHub - hochan222/yarn-berry-react-example: yarn-berry-react-example

yarn-berry-react-example. Contribute to hochan222/yarn-berry-react-example development by creating an account on GitHub.

github.com

Zero-Install

Yarn Berry에서 의존성을 버전 관리에 포함하는 것을 Zero-Install라고 한다.

 

yarn berry의 장점은 의존성을 압축 파일로 관리하기 때문에 의존성의 용량이 적다. 또 프로젝트가 거대해질 경우 각 의존성은 하나의 Zip 파일로만 표현되기 때문에 의존성을 구성하는 파일의 숫자가 NPM에 비해 적다.

 

이처럼 용량과 파일의 숫자가 적기 때문에 Yarn Berry를 사용하면 의존성을 Git으로 관리할 수 있고, git에 기록으로 남아있기 때문에 yarn install을 실행하지 않아도 프로그램을 실행할 수 있게 된다. 

Compatibility Table

yarn berry(v2)를 적용해보고자 한다면, 아래 호환성 표를 확인하자.

 

Plug'n'Play

An overview of Plug'n'Play, a powerful and innovative installation strategy for Node.

yarnpkg.com

 

Other Feature

yarn berry는 앞에 소개한 기능 외에도 여러 기능들이 있다. 자세한 내용은 다음 링크를 참고하자.

TL;DR

https://github.com/timqian/star-history

star-history로 Github stars에 대한 graph를 그려보았다. npm은 npm/npm에서 npm/cli로 레파지토리를 옮겨서 npm/npm에 대한 star는 정체되었다. 그럼에도 불구하고 yarn이 최근에 압도적으로 성장하고 있다는 사실을 알 수 있다.

 

아직 yarn berry는 초창기 버전에 속하지만, 위에서 언급한 문제들을 해결하는 것만으로도 충분한 가치가 있다고 생각한다.

728x90

'Search > Package Manager' 카테고리의 다른 글

Javascript Package Manager  (0) 2021.08.11

댓글