[번역] JavaScript 패키지 매니저 비교 - npm, Yarn 또는 pnpm?

dev_boku·2022년 9월 18일
54

Article 번역

목록 보기
5/5

원문 : https://doppelmutzi.github.io/packageManagers/

오늘날 패키지 매니저 분야에는 세 가지 주요 플레이어가 있습니다.

  1. npm
  2. Yarn — Yarn이 Yarn Classic (< v2) 또는 좀 더 최신 버전인 Yarn Berry(≥ v2)를 참조할 수 있단 걸 곧 알게 될 것입니다.
  3. 고성능(performant) npm (pnpm)

사실상, 모든 패키지 매니지의 기능은 거의 동일합니다. 그래서 설치 속도나 스토리지 사용량, 기존 워크플로와 결합되는 방식 등 기능 외적인 요구 사항을 기준으로 사용할 패키지 매니저를 결정하게 됩니다.

물론 각 패키지 매니저를 사용하는 방법은 다르지만 모두 중요한 컨셉은 공유합니다. 이러한 패키지 매니저로 다음을 수행할 수 있습니다.

  • 메타데이터 처리 및 쓰기
  • 모든 의존성을 일괄(Batch) 설치 또는 업데이트
  • 의존성 추가, 업데이트 및 제거
  • 스크립트 실행
  • 패키지 배포(publish)
  • 보안 감사(audit) 수행

패키지 매니저의 기능이 동일해도 내부적으로 다릅니다. 전통적으로 npm과 Yarn은 플랫 node_modules 폴더에 의존성을 설치했습니다. 그러나 이러한 의존성 해결 전략도 비판을 피할 수 없었습니다.

그래서 pnpm은 중첩된 node_modules 폴더에 의존성을 더욱 효율적으로 저장하기 위해 몇 가지 새로운 개념을 도입했습니다. Yarn Berry는 PnP(Plug'n'Play) 모드로 node_modules를 완전히 버리고 한 걸음 더 나아갑니다.

이 글에서는 적용할 수 있는 구현 옵션을 비교하면서 다음 사항을 다룰 것입니다.

자유롭게 건너뛰면서 여러분과 가장 관련 있는 내용을 읽으세요.

예제 프로젝트 사용 방법

저는 다양한 패키지 매니저의 고유한 개념을 보여주기 위해 예제 React app을 만들었습니다. 모든 패키지 매니저 종류에 해당하는 Git branch가 있습니다. 이것은 이 게시물의 아래 섹션에서 성능 표를 만드는 데 사용한 프로젝트이기도 합니다.

이 글의 주제에서는 애플리케이션 타입이 중요하지 않지만, 다양한 측면을 조명할 수 있는 중간 규모의 현실적인 프로젝트를 선택했습니다. 최근 사례로 Yarn Berry의 PnP 메커니즘은 이 프로젝트가 검토하는 데 도움이 되는 호환성 문제에 대해 열띤 토론을 일으켰습니다.

JavaScript 패키지 매니저 역사 개요

최초의 패키지 매니저는 2010년 1월에 출시된 npm이었습니다. 이는 오늘날 패키지 매니저가 작동하는 방식의 핵심 원칙을 확립했습니다.

npm이 10년 이상 사용되어 왔다면 다른 대안이 전혀 없는 이유는 무엇일까요? 이것이 나타난 주요 원인은 다음과 같습니다.

  • 다양한 node_modules 폴더 구조와 다양한 의존성 해결 알고리즘 (중첩 vs. 플랫, node_modules vs. PnP 모드)
  • 보안에 영향을 미치는 호이스팅에 대한 다양한 지원
  • 각각 성능에 영향을 미치는 다양한 lock 파일 형식
  • 디스크 공간 효율성에 영향을 미치는 디스크에 패키지를 저장하는 다양한 접근 방식
  • 대규모 모노레포의 유지 관리 가능성과 속도에 영향을 미치는 다중 패키지 프로젝트(일명 워크스페이스)에 대한 다양한 지원
  • 각각 DX에 영향을 미치는 새로운 도구 및 명령어에 대한 다양한 요구 사항
    • 이와 관련하여 플러그인 및 커뮤니티 도구를 통한 확장성에 대한 다양한 요구
  • 다양한 수준의 설정 가능성 및 유연성

npm이 주목을 받은 후 이러한 요구 사항이 어떻게 확인되었는지에 대해 살펴본 뒤 Yarn Classic이 이러한 요구 사항 중 일부를 해결한 방법을 살펴보겠습니다. 그리고 pnpm이 이러한 개념을 확장한 방법, Yarn Classic의 후계자인 Yarn Berry가 이러한 전통적인 개념 및 프로세스에 의해 설정된 틀을 깨려고 시도한 방법 등 여러가지에 대한 간략한 역사를 살펴보겠습니다.

npm, 선구자

npm은 패키지 매니저의 조상입니다. 실수로 많은 사람들이 npm을 "노드 패키지 매니저"의 약어라고 생각하지만 그렇지 않습니다. 그렇지만 Node.js 런타임과 함께 번들로 제공됩니다.

그때까지 프로젝트 의존성은 수동으로 다운로드 및 관리되었기 때문에 npm의 등장은 혁명이었습니다. 메타데이터 필드(예: devDependencies)가 포함된 package.json 파일, node_modules에 의존성 저장, 사용자 지정 스크립트, 공용 및 개인 패키지 레지스트리 등의 모든 개념은 npm에 의해 도입되었습니다.

2020년에는 GitHub가 npm을 인수하여 공식적으로 npm은 현재 Microsoft의 관리 하에 있습니다. 이 글을 쓰는 시점에서 가장 최근 메이저 버전은 2021년 10월에 출시된 v8입니다.

Yarn (v1 / Classic), 많은 혁신을 책임지다

2016년 10월 블로그 게시물에서 Facebook은 npm이 당시에 가지고 있던 일관성, 보안 및 성능 문제와 관련된 문제를 해결할 새로운 패키지 매니저를 개발하기 위해 Google 및 몇몇 다른 사람들과의 공동 협력을 발표했습니다. 그들은 대안으로 또 다른 자원 협상자(Yet Another Resource Negotiator)를 의미하는 Yarn으로 이름을 지었습니다.

Yarn의 아키텍처 설계는 npm이 수립한 많은 개념과 프로세스를 기반으로 했지만 Yarn은 초기 릴리스에서 패키지 매니저 환경에 큰 영향을 미쳤습니다. npm과 대조적으로 Yarn은 npm 초기 버전에서 가장 문제가 되었던 설치 프로세스의 속도를 높이기 위해 작업을 병렬화했습니다.

Yarn은 DX, 보안 및 성능에 대한 기준을 더 높게 설정했으며 아래를 포함한 많은 개념들도 만들어냈습니다.

  • 네이티브 모노레포 지원
  • 캐시-인식 설치
  • 오프라인 캐싱
  • Lock 파일

Yarn v1은 2020년에 유지 관리 모드(maintenance mode)에 들어갔습니다. 그 이후로 v1.x 라인은 레거시로 간주되어 Yarn Classic으로 이름이 변경되었습니다. 그 후속 제품인 Yarn v2 또는 Berry가 현재 활동적인 개발 브랜치입니다.

빠르고 디스크 효율성이 뛰어난 pnpm

pnpm의 버전 1은 Zoltan Kochan에 의해 2017년에 출시되었습니다. npm에 대한 드롭 인 대체(Drop-in replacement)이므로 npm 프로젝트가 있으면 바로 pnpm을 사용할 수 있습니다!

드롭 인 대체(Drop-in replacement)는 컴퓨터 과학 및 기타 분야 에서 사용되는 용어입니다. 다른 코드나 설정 변경 없이 하드웨어(또는 소프트웨어) 구성 요소를 다른 하드웨어의 (또는 소프트웨어) 구성 요소로 대체해도 부정적인 영향을 미치지 않는 기능을 나타냅니다. (참고)

pnpm 개발자가 본 npm 및 Yarn이 가진 주요 문제는 프로젝트 전체에서 사용되는 의존성의 중복된 저장소였습니다. Yarn Classic은 npm에 비해 속도 이점이 있었지만 의존성 해결에 대해 동일한 접근 방식을 사용했습니다. 이는 pnpm의 개발자에게 있어서는 안 되는 일이었습니다. npm 및 Yarn Classic은 호이스팅을 사용하여 node_modules를 플랫했습니다.

pnpm은 호이스팅 대신 의존성 해결 전략의 대안책으로 내용 주소화 저장소(Content-addressable storage)를 도입했습니다. 이 방법을 사용하면 홈 폴더(~/.pnpm-store/)의 전역 저장소에 패키지를 저장하는 중첩된 node_modules 폴더가 생성됩니다. 의존성의 모든 버전은 해당 폴더에 물리적으로 한 번만 저장되어 단일 정보 소스를 구성하고 상당한 디스크 공간을 절약합니다.

이것은 node_modules 레이아웃을 통해 달성되며, 심볼릭 링크를 사용하여 의존성의 중첩 구조를 생성합니다. 여기서 폴더 내의 모든 패키지의 모든 파일은 저장소에 대한 하드 링크입니다. 공식 문서의 다음 다이어그램은 이를 명확히 합니다.

pnpm의 영향은 2021년 보고서에서 확인할 수 있습니다. 경쟁업체는 내용 주소화 저장소의 혁신으로 인해 심볼릭 링크된 node_modules 구조 및 디스크 효율적인 패키지 관리와 같은 pnpm의 설치 개념을 채택하기를 원합니다.

Yarn (v2, Berry), Plug’n’Play로 바퀴를 재발명

Yarn 2는 2020년 1월에 출시되었으며 원래 Yarn에서 주요 업그레이드로 알려졌습니다. Yarn 팀은 본질적으로 새로운 코드 기반과 새로운 원칙을 가진 새로운 패키지 매니저라는 것을 더 명확하게 하기 위해 Yarn Berry라고 부르기 시작했습니다.

Yarn Berry의 주요 혁신은 node_modules를 수정하기 위한 전략으로 나온 Plug'n'Play(PnP) 접근 방식입니다. node_modules를 생성하는 대신 의존성 조회 테이블이 있는 .pnp.cjs 파일이 생성되며, 중첩된 폴더 구조가 아닌 단일 파일이기 때문에 보다 효율적으로 처리할 수 있습니다. 또한 모든 패키지는 node_modules 폴더보다 디스크 공간을 덜 차지하는 .yarn/cache/ 폴더 내에 zip 파일로 저장됩니다.

이 모든 변화들은 너무나 빨랐고 출시 후 많은 논란을 불러일으켰습니다. PnP의 주요 변경 사항으로 인해 메인테이너들은 기존 패키지와 호환되도록 업데이트해야 했습니다. 새로운 PnP 접근 방식이 기본적으로 사용되었으며 node_modules로 되돌리는 것이 처음에는 간단하지 않았습니다. 이로 인해 많은 저명한 개발자가 Yarn 2를 선택하지 않은 것에 대해 공개적으로 비판했습니다.

Yarn Berry 팀은 이후 후속 릴리스에서 많은 문제를 해결했습니다. PnP의 비호환성을 해결하기 위해 팀은 기본 작동 모드를 쉽게 변경할 수 있는 몇 가지 방법을 제공했습니다. node_modules 플러그인의 도움으로 기존의 node_modules 접근 방식을 사용하는 데 설정 한 줄이면 됐습니다.

또한 이 호환성 표에서 볼 수 있듯이 자바스크립트 생태계는 시간이 지남에 따라 PnP에 대한 지원을 점점 더 많이 제공했으며 일부 대규모 프로젝트에서는 Yarn Berry를 채택했습니다. 예제 프로젝트에서 데모 React 프로젝트로 PnP를 제대로 구현할 수도 있었습니다.

Yarn Berry는 출시된지 아직 오래되지 않았지만 이미 패키지 매니저 환경에 영향을 미치고 있습니다. pnpm은 2020년 말에 PnP 접근 방식을 채택했습니다.

설치 워크플로

먼저 모든 개발자의 로컬 및 CI/CD 시스템에 패키지 매니저를 설치해야 합니다.

npm

npm은 Node.js와 함께 제공되므로 추가 단계가 필요하지 않습니다. OS용 Node.js 설치 프로그램을 다운로드하는 것 외에도 소프트웨어 버전 관리를 위해 CLI 도구를 사용하는 것이 일반적입니다. Node와 관련해서는 Node Version Manager(nvm) 또는 Volta는 매우 편리한 유틸리티가 되었습니다.

Yarn Classic 그리고 Yarn Berry

Yarn1은 다양한 방법으로 인스톨 할 수 있습니다.예를 들어 $ npm i -g yarn을 사용하여 npm 패키지로 설치할 수 있습니다.

Yarn Classic에서 Yarn Berry로 마이그레이션하려면 다음과 같이 하는 것이 좋습니다.

  • Yarn Classic을 최신 1.x 버전으로 설치 또는 업데이트
  • yarn set version 명령어를 입력해서 최신 버전으로 업그레이드: $ yarn set version berry

그러나 권장하는 Yarn Berry 설치 방법은 Corepack을 사용하는 것입니다.

Corepack은 Yarn Berry의 사람들에 의해 만들어졌습니다. Corepack은 원래 package manager manager(pmm) 🤯라는 이름이었으며 LTS v16에서 노드와 병합되었습니다.

Corepack의 도움으로 Node에는 Yarn Classic, Yarn Berry 및 pnpm 바이너리가 shim으로 포함되어 있기 때문에 npm의 대체 패키지 매니저를 "별도로" 설치할 필요가 없습니다. 이러한 shim을 사용하면 사용자가 먼저 명시적으로 설치하지 않고 Node 배포를 복잡하게 만들지 않고도 Yarn 및 pnpm 명령을 실행할 수 있습니다.

Corepack은 Node.js ≥ v16.9.0이 사전 설치된 상태로 제공됩니다. 그러나 이전 Node 버전의 경우 $ npm install -g corepack을 사용하여 설치할 수 있습니다.

Corepack을 사용하기 전에 먼저 활성화하세요. 예제를 통해 Yarn Berry v3.1.1에서 활성화하는 방법을 알아보겠습니다.

# 먼저 옵트인해야 합니다.
$ corepack enable
# shim이 설치되었지만 구체적인 버전을 활성화해야 합니다.
$ corepack prepare yarn@3.1.1 --activate

pnpm

$ npm i -g pnpm을 사용하여 pnpm을 npm 패키지로 설치할 수 있습니다. 또 $ corepack prepare pnpm@6.24.2 --activate 명령어로 Corepack을 통해 pnpm을 설치할 수 있습니다.

프로젝트 구조

이 섹션에서는 다양한 패키지 매니저의 주요 특징을 한 눈에 볼 수 있습니다. 또한, 특정 패키지 매니저 설정 파일과 설치 단계에서 생성된 파일을 쉽게 확인할 수 있습니다.

모든 패키지 매니저는 모든 중요한 메타 정보를 프로젝트 매니페스트 파일 package.json에 저장합니다. 또한 루트 수준의 설정 파일을 사용하여 개인 레지스트리 또는 의존성 해결 방법을 설정할 수 있습니다.

설치 단계에서 의존성은 파일 구조(예: node_modules 내)에 저장되고 잠금 파일이 생성됩니다. 이 섹션에서는 워크스페이스 설정을 고려하지 않으므로 모든 예제에서는 의존성이 저장된 단일 위치만 보여줍니다.

npm

$ npm install 또는 더 짧게 $ npm i를 사용하면 package-lock.json 파일node_modules 폴더가 생성됩니다. 추가로 .npmrc 설정 파일을 루트 수준에 배치할 수 있습니다. lock 파일에 대한 자세한 내용은 다음 섹션을 참조하세요.

    .
    ├── node_modules/
    ├── .npmrc
    ├── package-lock.json
    └── package.json

Yarn Classic

$ yarn을 실행하면 yarn.lock 파일과 node_modules 폴더가 생성됩니다. .yarnrc 파일도 설정 옵션이 될 수 있습니다. Yarn Classic은 또한 .npmrc 파일 또한 호환합니다. 선택적으로 캐시 폴더(.yarn/cache/)와 현재 Yarn Classic 버전(.yarn/releases/)을 저장하는 위치도 사용될 수 있습니다. 이를 설정하는 다양한 방법은 설정 비교 섹션에서 확인해보겠습니다.

    .
    ├── .yarn/
    │   ├── cache/
    │   └── releases/
    │       └── yarn-1.22.17.cjs
    ├── node_modules/
    ├── .yarnrc
    ├── package.json
    └── yarn.lock

node_modules를 사용하는 Yarn Berry

설치 모드와 관계없이 Yarn Berry 프로젝트에서는 다른 패키지 매니저를 사용하는 프로젝트보다 더 많은 파일과 폴더를 처리해야 합니다. 일부는 선택 사항이고 일부는 필수 사항입니다.

Yarn Berry는 더 이상 .npmrc 또는 .yarnrc 파일을 호환하지 않습니다. 대신 .yarnrc.yml 설정 파일이 필요합니다. 생성된 node_modules 폴더가 있는 기존 워크플로의 경우 node_modules 또는 pnpm에서 영감을 받은 설치 변형을 사용하는 nodeLinker 설정을 제공해야 합니다.

    # .yarnrc.yml
    nodeLinker: node-modules # or pnpm

$ yarn을 실행하면 node_modules 폴더에 모든 의존성이 설치됩니다. 최신 버전에서는 Yarn Classic과 호환되지 않는 yarn.lock 파일이 생성됩니다. 또한 오프라인 설치에 사용되는 .yarn/cache/ 폴더가 생성됩니다. releases 폴더는 선택 사항이며 설정 비교 섹션에서 볼 수 있듯이 프로젝트에서 사용하는 Yarn Berry 버전을 저장합니다.

   .
    ├── .yarn/
    │   ├── cache/
    │   └── releases/
    │       └── yarn-3.1.1.cjs
    ├── node_modules/
    ├── .yarnrc.yml
    ├── package.json
    └── yarn.lock

PnP를 사용하는 Yarn Berry

strictloose PnP 모드 모두에서 $ yarn을 실행하면 .pnp.cjsyarn.lock 파일과 함께 .yarn/cache/.yarn/unplugged/가 생성됩니다. PnP strict가 기본 모드이고 loose의 경우 설정이 필요합니다.

    # .yarnrc.yml
    nodeLinker: pnp
    pnpMode: loose

PnP 프로젝트에서 .yarn/ 폴더에는 아마 releases/ 폴더 외에 IDE 지원을 제공하는 sdk/ 폴더가 포함될 것입니다. 사용하기에 따라 더 많은 폴더.yarn/의 일부로 들어갑니다.

    .
    ├── .yarn/
    │   ├── cache/
    │   ├── releases/
    │   │   └── yarn-3.1.1.cjs
    │   ├── sdk/
    │   └── unplugged/
    ├── .pnp.cjs
    ├── .pnp.loader.mjs
    ├── .yarnrc.yml
    ├── package.json
    └── yarn.lock

pnpm

pnpm 프로젝트의 초기 상태는 package.json 파일이 필요하기 때문에 npm 또는 Yarn Classic 프로젝트처럼 보입니다. 하지만, $ pnpm i를 사용하여 의존성을 설치한 후 node_modules 폴더가 생성되지만 내용 주소화 저장소 방식 때문에 구조가 완전히 다릅니다.

pnpm은 또한 자체 버전의 lock 파일인 pnp-lock.yml을 생성합니다. 선택적으로 .npmrc 파일을 사용하여 추가 설정을 제공할 수 있습니다.

    .
    ├── node_modules/
    │   └── .pnpm/
    ├── .npmrc
    ├── package.json
    └── pnpm-lock.yml

Lock 파일 및 의존성 저장소

이전 섹션에서 설명한 대로 모든 패키지 매니저는 lock 파일을 생성합니다.

lock 파일은 프로젝트에 대해 설치된 각 의존성의 버전을 정확히 저장하므로 보다 예측 가능하고 결정론적 설치가 가능합니다. 이는 의존성 버전이 거의 버전 범위(예: ≥ v1.2.5)로 선언되기 때문에 버전을 "고정"하지 않으면 실제로 설치된 버전과 다를 수 있기 때문에 필요합니다.

역주: 결정론적은 예측한 그대로 동작한다는 뜻으로 번역했습니다. 결정론적 알고리즘처럼 예측한 그대로 설치한다는 뜻입니다.
https://ko.wikipedia.org/wiki/%EA%B2%B0%EC%A0%95%EB%A1%A0%EC%A0%81_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98

lock 파일은 때때로 체크섬(checksum)을 저장하기도 합니다. 이에 대해서는 보안 섹션에서 더 자세히 다룰 것입니다.

lock 파일은 v5 (package-lock.json) 이후부터 npm 기능이었고, pnpm(pnpm-lock.yaml)은 첫날부터였습니다. 그리고 Yarn Berry에서 lock 파일은 새로운 YAML 형식(yarn.lock)입니다.

이전 섹션에서는 기존 방식처럼 의존성이 node_modules 폴더 구조에 설치되는 모습을 보았습니다. 이것은 npm, Yarn Classicpnpm이 모두 사용하는 방식이고, pnpm은 다른 패키지 매니저보다 더 효율적인 방식으로 동작합니다.

PnP 모드의 Yarn Berry는 다르게 작동합니다. node_modules 폴더 대신 의존성이 .yarn/cache/.pnp.cjs 파일의 조합으로 zip 파일로 저장됩니다.

모든 팀 구성원이 동일한 버전을 설치해서 "내 컴퓨터에서만 작동"하는 문제를 해결하기 때문에 이러한 lock 파일을 버전 제어 하에 두는 것이 가장 좋습니다.

CLI 명령어

다음 표는 npm, Yarn Classic, Yarn Berry 및 pnpm에서 사용할 수 있는 다양한 CLI 명령어의 선별된 세트를 비교합니다. 이것은 결코 완전한 목록은 아니지만 치트 시트를 구성합니다. 이 섹션에서는 워크스페이스 관련 명령어를 다루지 않습니다.

npm 및 pnpm은 특별히 많은 명령어 및 별칭(alias) 옵션을 제공합니다. 즉, 명령어는 다른 이름을 가질 수 있습니다. 즉, $ npm install$ npm add와 같습니다. 또한 많은 명령어 옵션에는 --save-dev 대신 -D와 같은 짧은 버전이 있습니다.

표에서는 모든 짧은 버전을 별칭(alias)으로 참조하겠습니다. 모든 패키지 매니저를 사용하여 여러 의존성을 공백으로 구분하여 추가, 업데이트 또는 제거할 수 있습니다(예: npm update react-dom). 명확하게 하기 위해 예제에서는 단일 의존성이 있는 사용법만 보여줍니다.

의존성 관리

이 표에서는 package.json에 지정된 모든 의존성을 설치하거나 업데이트하는 의존성 관리 명령어 또는 명령어에 여러 의존성을 지정하고 그것들을 설명합니다.

동작npmYarn ClassicYarn Berrypnpm
package.json의 의존성(deps) 설치npm install 별칭(alias): i, addyarn install 또는 yarnClassic 처럼pnpm install 별칭(alias): i
package.json의 의존성 업데이트 acc. semvernpm update 별칭(alias): up, upgradeyarn upgradeyarn semver up (plugin을 통해서)pnpm update 별칭(alias): up
package.json의 의존성을 최신으로 업데이트N/Ayarn upgrade latestyarn uppnpm update latest 별칭(alias): -L
의존성 업데이트 acc. semvernpm update reactyarn upgrade reactyarn semver up reactpnpm up react
의존성을 최신으로 업데이트npm update react@latestyarn upgrade react latestyarn up reactpnpm up -L react
대화형으로 의존성 업데이트N/Ayarn upgrade-interactiveyarn upgrade-interactive (plugin을 통해서)$ pnpm up --interactive 별칭(alias): -i
런타임 의존성 추가npm i reactyarn add reactClassic 처럼pnpm add react
개발 의존성 추가npm i -D babel별칭(alias): --save-devyarn add -D babel별칭(alias): --devClassic 처럼pnpm add -D babel별칭(alias): --save-exact
semver 없이 package.json에 의존성 추가npm i -E react별칭(alias): --save-exactyarn add -E react별칭(alias): --exactClassic 처럼pnpm add -E react 별칭(alias): --save-exact
의존성을 제거하고 package.json에서 제거npm uninstall react 별칭(alias): remove, rm, r, un, unlinkyarn remove reactClassic 처럼pnpm remove react 별칭(alias): rm, un, uninstall
package.json업데이트 없이 deps 제거npm uninstall nosaveN/AN/AN/A

Package 실행

다음 예제는 개발 기간 동안 유틸리티 도구를 구성하는 패키지를 관리하는 방법을 보여줍니다. 예를 들어 ntl과 같은 이진 파일을 사용하여 스크립트를 대화형으로 실행합니다. 표에 사용된 용어에 대해서도 간단하게 살펴보겠습니다.

  • 패키지: 의존성 또는 바이너리
  • 바이너리: node_modules/.bin/ 또는 .yarn/cache/(PnP)에서 실행되는 실행 가능한 유틸리티

Yarn Berry는 package.json에 지정했거나 보안상의 이유로 메타 필드에 노출된 바이너리만 실행할 수 있다는 점을 이해하는 것이 중요합니다. pnpm은 동일한 보안 동작을 제공합니다.

동작npmYarn ClassicYarn Berrypnpm
전역적으로 패키지 설치npm i -g ntl 별칭(alias): --globalyarn global add ntlN/A (global removed)pnpm add --global ntl
전역적으로 패키지 업데이트npm update -g ntlyarn global upgrade ntlN/Apnpm update --global ntl
전역적으로 패키지 삭제npm uninstall -g ntlyarn global remove ntlN/Apnpm remove --global ntl
터미널에서 바이너리 실행npm exec ntlyarn ntlyarn ntlpnpm ntl
스크립트로 바이너리 실행ntlntlntlntl
동적 패키지 실행npx ntlN.A.yarn dlx ntlpnpm dlx ntl

공통 명령어

이 표는 유용한 내장 명령어를 다룹니다. 공식 명령어가 없는 경우 npm 패키지 또는 Yarn Berry 플러그인을 통해 종종 타사 명령어를 사용할 수 있습니다.

npmYarn ClassicYarn Berrypnpm
publish packagenpm publishyarn publishyarn npm publishpnpm publish
설치된 의존성 나열npm ls 별칭(alias): list, la, llyarn listpnpm list 별칭(alias): ls
오래된 의존성 나열npm outdatedyarn outdatedyarn upgrade-interactivepnpm outdated
의존성 정보 출력npm explain ntl 별칭(alias): whyyarn why ntlClassic 처럼pnpm why ntl
프로젝트 초기화npm init -y npm init (대화형) 별칭(alias): --yesyarn init -y yarn init (대화형) 별칭(alias): --yesyarn initpnpm init -y pnpm init (interactive) 별칭(alias): --yes
라이센스 정보 출력N/A (via thirdparty package)yarn licenses listN/A (or via plugin, other plugin)N/A (서드파티 패키지를 통해)
패키지 매니저 버전 업데이트N/A (서드파티 도구 사용, e.g., nvm)with npm: yarn policies setversion 1.13.0with Corepack: yarn set version 3.1.1N/A (npm, Corepack 사용)
보안 감사(security audit) 수행npm audityarn audityarn npm auditpnpm audit

설정 파일

패키지 매니저 설정 작업은 package.json과 전용 설정 파일 모두에서 수행합니다. 설정 옵션의 예는 다음과 같습니다.

  • 사용하는 정확한 버전 정의
  • 특정 의존성 해결 전략 사용
  • 프라이빗 레지스트리에 대한 접근 설정
  • 패키지 매니저에게 모노레포 내에서 워크스페이스를 찾을 위치를 알림

npm

대부분의 설정은 전용 설정 파일(.npmrc)에서 이루어집니다.

npm의 workspaces 기능을 사용하려면 workspaces 메타데이터 필드를 사용하여 package.json에 설정을 추가하여 하위 프로젝트 또는 워크스페이스를 구성하는 폴더를 각각 찾을 위치를 npm에게 알려야 합니다.

{
  // ...
  "workspaces": ["hooks", "utils"]
}

모든 패키지 매니저는 퍼블릭 npm 레지스트리와 함께 즉시 작동합니다. 공유 라이브러리가 있는 회사 컨텍스트에서는 아마 공용 레지스트리에 게시하지 않고 재사용하기를 원할겁니다. 개인 레지스트리를 설정하려면 .npmrc 파일에서 이 작업을 수행할 수 있습니다.

 # .npmrc
@doppelmutzi:registry=https://gitlab.doppelmutzi.com/api/v4/projects/41/packages/npm/

npm에 대한 많은 설정 옵션이 있으며 문서에서 가장 잘 볼 수 있습니다.

Yarn Classic

package.json에서 Yarn 워크스페이스를 설정할 수 있습니다. npm과 유사하지만 워크스페이스는 프라이빗 패키지여야 합니다.

    {
      // ...
      "private": true,
      "workspaces": ["workspace-a", "workspace-b"]
    }

모든 선택사항인 설정은 .yarnrc 파일로 이동합니다. 일반적인 설정 옵션은 yarn-path를 사용함으로써 모든 팀 구성원이 특정 바이너리 버전을 사용하도록 강제합니다. yarn-path는 특정 Yarn 버전이 포함된 폴더(예: .yarn/releases/)로 연결됩니다. yarn policies 명령어로 Yarn Classic 버전을 설치할 수 있습니다.

Yarn Berry

Yarn Berry에서 워크스페이스를 설정하는 것package.json을 사용하여 Yarn Classic에서 수행하는 방식과 유사합니다. 대부분의 Yarn Berry 설정은 .yarnrc.yml에서 이루어지며 많은 설정 옵션을 사용할 수 있습니다. Yarn Classic 예제도 가능하지만 메타데이터 필드의 이름이 yarnPath로 변경되었습니다.

# .yarnrc.yml
yarnPath: .yarn/releases/yarn-3.1.1.cjs

Yarn Berry는 yarn plugin import를 사용하여 플러그인으로 확장할 수 있습니다. 이 명령은 .yarnrc.yml을 업데이트합니다.

# .yarnrc.yml
plugins:
  - path: .yarn/plugins/@yarnpkg/plugin-semver-up.cjs
    spec: "https://raw.githubusercontent.com/tophat/yarn-plugin-semver-up/master/bundles/%40yarnpkg/plugin-semver-up.js"

역사 섹션에 설명된 대로 비호환성으로 인해 PnP strict 모드에서 의존성에 문제가 있을 수 있습니다. 이러한 PnP 문제에 대한 일반적인 솔루션은 packageExtensions 설정 속성입니다. 예제 프로젝트로 다음 예제를 따라가겠습니다.

# .yarnrc.yml
packageExtensions:
  "styled-components@*":
    dependencies:
      react-is: "*"

pnpm

pnpm은 npm과 동일한 설정 메커니즘을 사용하므로 .npmrc 파일을 사용할 수 있습니다. 개인 레지스트리를 설정하는 것도 npm과 동일한 방식으로 작동합니다.

pnpm의 워크스페이스 기능을 사용하면 다중 패키지 프로젝트를 지원할 수 있습니다. 모노레포를 초기화하려면 pnpm-workspace.yaml 파일에서 패키지의 위치를 지정해야 합니다.

# pnpm-workspace.yaml
packages:
  - "packages/**"

모노레포 지원

모노레포가 뭔가요?

모노레포는 워크스페이스 또는 패키지라고 하는 여러 프로젝트를 보관하는 저장소(repository)입니다. 여러 저장소(repository)를 사용하는 대신 모든 것을 한 곳에 보관하는 프로젝트 구성 전략입니다.

물론 모노레포에서는 추가적으로 복잡한 부분들이 생깁니다. Yarn Classic에서 이 기능을 처음으로 활성화했지만 이제는 모든 주요 패키지 매니저가 워크스페이스 기능을 제공합니다. 이 섹션에서는 각기 다른 패키지 매니저로 워크스페이스를 구성하는 방법을 보여줍니다.

npm

npm 팀은 v7에서 오랫동안 기다려온 npm 워크스페이스 기능을 출시했습니다. 여기에는 루트 패키지 내에서 다중 패키지 프로젝트를 관리하는 데 도움이 되는 여러 CLI 명령어가 포함되어 있습니다. 대부분의 명령어는 작업 영역 관련 옵션과 함께 사용하여 특정 워크스페이스, 여러 워크스페이스 또는 모든 워크스페이스에 대해 실행해야 하는지 여부를 npm에 알릴 수 있습니다.

    # 모든 워크스페이스에 대한 모든 의존성 설치
    $ npm i --workspaces.
    # 하나의 패키지에 대해 실행
    $ npm run test --workspace=hooks
    # 여러 패키지에 대해 실행
    $ npm run test --workspace=hooks --workspace=utils
    # 모두 실행
    $ npm run test --workspaces
    # 테스트가 누락된 모든 패키지 무시
    $ npm run test --workspaces --if-present

다른 패키지 매니저와 달리 npm v8은 현재 고급 필터링이나 여러 워크스페이스 관련 명령어를 병렬로 실행하는 것을 지원하지 않습니다.

Yarn Classic

2017년 8월 Yarn 팀은 워크스페이스 기능 측면에서 최고 수준의 모노레포 지원을 발표했습니다. 이 시점 이전에는 Lerna와 같은 타사 소프트웨어가 포함된 다중 패키지 프로젝트에서만 패키지 매니저를 사용할 수 있었습니다. Yarn에 추가된 이 기능은 다른 패키지 매니저도 이러한 기능을 구현할 수 있는 길을 열었습니다.

관심이 있으시다면 Lerna를 사용하거나 사용하지 않고 Yarn Classic의 워크스페이스 기능을 사용하는 방법에 대해서도 이전에 썼습니다. 그러나 이 게시물은 Yarn Classic 워크스페이스 설정에서 의존성을 관리하는 데 도움이 되는 몇 가지 필수 명령어만 다룰 것입니다.

    # 모든 워크스페이스에 대한 모든 의존성 설치
    $ yarn
    # 의존성 트리 표시
    $ yarn workspaces info
    # 하나의 패키지에 대해서만 시작 명령을 실행
    $ yarn workspace awesome-package start
    # 패키지에 Webpack 추가
    $ yarn workspace awesome-package add -D webpack
    # 모든 패키지에 React 추가
    $ yarn add react -W

Yarn Berry

Yarn Berry는 Yarn Classic의 개념을 기반으로 구현되었기 때문에 처음부터 워크스페이스를 특징으로 합니다. Reddit 의견에서 Yarn Berry의 주요 개발자는 다음을 포함하여 워크스페이스 지향 기능에 대한 간략한 개요를 제공했습니다.

  • $ yarn add -–interactive: 패키지를 설치할 때 다른 워크스페이스의 버전을 재사용할 수 있습니다.
  • $ yarn up: 모든 워크스페이스에서 패키지를 업데이트합니다.
  • $ yarn workspaces focus: 단일 워크스페이스에 대해서만 의존성을 설치합니다.
  • $ yarn workspaces foreach: 모든 워크스페이스에서 명령을 실행합니다.

Yarn Berry는 package.json 파일의 dependencies 또는 devDependencies 필드에서 사용할 수 있는 프로토콜을 많이 사용합니다. 그 중 하나가 workspace:protocol입니다.

Yarn Classic의 워크스페이스와 달리 Yarn Berry는 의존성이 이 모노레포의 패키지 중 하나여야 한다고 명시적으로 정의합니다. 그렇지 않으면 Yarn Berry가 버전이 일치하지 않는 경우 원격 레지스트리에서 버전을 가져오려고 할 수 있습니다.

{
  // ...
  "dependencies": {
    "@doppelmutzi/hooks": "workspace:*",
    "http-server": "14.0.0"
    // ...
  }
}

pnpm

pnpm은 workspace: 프로토콜을 사용하여 Yarn Berry와 유사하게 모노레포 프로젝트를 용이하게 합니다. 많은 pnpm 명령은 모노레포 컨텍스트에서 특히 유용한 --recursive(-r) 또는 --filter와 같은 옵션을 허용합니다. 기본 필터링 명령어는 Lerna를 보완하거나 대체하는 데도 좋습니다.

# 모든 워크스페이스 정리
pnpm -r exec -- rm -rf node_modules && rm pnpm-lock.yaml
# @doppelmutzi 범위의 모든 워크스페이스에 대한 모든 테스트 실행
pnpm recursive run test --filter @doppelmutzi/

성능 및 디스크 공간 효율성

성능은 패키지 매니저를 선택하는 의사결정에서 중요한 요인이 됩니다. 이 섹션에서는 하나의 중소 규모 프로젝트를 기반으로 한 벤치마크를 보여줍니다. 다음은 샘플 프로젝트에 대한 몇 가지 참고 사항입니다.

  • 두 벤치마크 세트 모두 워크스페이스 기능을 사용하지 않음
  • 소규모 프로젝트는 33개의 의존성을 지정
  • 중형 프로젝트는 44개의 의존성을 지정

각 패키지 매니저 종류에 대해 한 번씩 세 가지 사용 사례(UC)에 대한 측정을 수행했습니다. 자세한 평가와 설명은 프로젝트 1(P1)프로젝트 2(P2)의 결과를 살펴보시기 바랍니다.

  • UC 1: cache/store 없음, lock 파일 없음, node_modules 또는 .pnp.cjs 없음
  • UC 2: cache/store 존재, lock 파일 없음, node_modules 또는 .pnp.cjs 없음
  • UC 3: cache/store 존재, lock 파일 존재, node_modules 또는 .pnp.cjs 없음

설치에 소요되는 시간을 측정하기 위해 gnomon 도구를 사용했습니다(예: $ yarn | gnomon). 또한 생성된 파일의 크기를 측정했습니다. 예: $ du -sh node_modules.

내 프로젝트에서 측정했을 때 모든 사용 사례와 두 프로젝트의 설치 속도 측면에서 Yarn Berry PnP strict가 승자였습니다.

Project 1 성능 결과

Methodnpmv8.1.2Yarn Classicv1.23.0pnpmv6.24.4Yarn Berry PnP loosev3.1.1Yarn Berry PnPv3.1.1Yarn Berry nmv3.1.1Yarn Berrypnpmv3.1.1
UC 186.63s108.89s43.58s31.77s30.13s56.64s60.91s
UC 241.54s65.49s26.43s12.46s12.66s46.36s40.74s
UC 323.59s40.35s20.32s1.61s1.36s28.72s31.89s
package-lock.json: 1.3M node_modules: 467Mnode_modules: 397Myarn.lock: 504Kpnpm-lock.yaml: 412Knode_modules: 319Myarn.lock: 540Kcache: 68Munplugged: 29M.pnp.cjs: 1.6Myarn.lock: 540Kcache: 68Munplugged: 29M.pnp.cjs: 1.5Mnode_modules: 395Myarn.lock: 540Kcache: 68Mnode_modules: 374Myarn.lock: 540Kcache: 68M

Project 2 성능 결과

Methodnpmv8.1.2Yarn Classic v1.23.0pnpmv6.24.4Berry PnP loosev3.1.1Berry PnPv3.1.1Berry nmv3.1.1Berrypnpmv3.1.1
UC 134.91s43.26s15.6s13.92s6.44s23.62s20.09s
UC 27.92s33.65s8.86s7.09s5.63s15.12s14.93s
UC 35.09s15.64s4.73s0.93s0.79s8.18s6.02s
package-lock.json: 684Knode_modules: 151Myarn.lock: 268Knode_modules: 159Mpnpm-lock.yaml: 212Knode_modules: 141M.pnp.cjs: 1.1Myarn.lock: 292K.yarn: 38M.pnp.cjs: 1.0Myarn.lock: 292K.yarn: 38Myarn.lock: 292Knode_modules: 164Mcache: 34Myarn.lock: 292Knode_modules: 156Mcache: 34M

다음은 Yarn Berry 팀pnpm의 공식 벤치마크입니다.

보안 기능

npm

npm은 나쁜 패키지를 다룰 때 너무 관대했고 많은 프로젝트에 직접적인 영향을 미치는 몇 가지 보안 취약점을 경험했습니다. 예를 들어 버전 5.7.0에서는 Linux OS에서 sudo npm 명령을 실행하면 시스템 파일의 소유권을 변경할 수 있게 되어 OS를 사용할 수 없게 되었습니다.

2018년에 또 다른 사건이 발생했으며 비트코인 도난과 관련이 있습니다. 기본적으로 인기 있는 Node.js 패키지인 EventStream은 버전 3.3.6에 악성 의존성을 추가했습니다. 이 악성 패키지에는 개발자 컴퓨터에서 비트코인을 훔치려는 암호화된 페이로드가 포함되어 있습니다.

이러한 문제를 해결하기 위해 최신 npm 버전은 package-lock.jsonSHA-512 암호화 알고리즘을 사용해서 설치하는 패키지의 무결성을 체크합니다.

전반적으로 npm은 특히 Yarn과 비교했을 때 더욱 명확해진 보안 격차를 줄이기 위해 더 많은 일을 했습니다.

Yarn

Yarn Classic과 Yarn Berry 모두 처음부터 yarn.lock에 저장된 체크섬으로 각 패키지의 무결성을 검증했습니다. Yarn은 또한 설치 중에 package.json에 선언되지 않은 악성 패키지를 검색하지 못하도록 합니다. 불일치가 발견되면 설치는 중단됩니다.

PnP 모드의 Yarn Berry는 전통적인 node_modules 접근 방식의 보안 문제를 겪지 않습니다. Yarn Classic과 달리 Yarn Berry는 명령어 실행의 보안을 향상시킵니다. package.json에서 명시적으로 선언한 의존성의 바이너리만 실행할 수 있습니다. 이 보안 기능은 다음에 설명할 pnpm과 유사합니다.

pnpm

pnpm은 또한 코드가 실행되기 전에 설치된 모든 패키지의 무결성을 확인하기 위해 체크섬을 사용합니다.

위에서 언급했듯이 npm과 Yarn Classic은 각각 호이스팅으로 인한 보안 문제가 있습니다. pnpm은 모델이 호이스팅을 사용하지 않기 때문에 이를 회피합니다. 대신, 불법 의존성 접근의 위험을 제거하는 중첩된 node_modules 폴더를 생성합니다. 이는 의존성이 package.json에 명시적으로 선언된 경우에만 다른 의존성에 의존성에 접근할 수 있음을 의미합니다.

호이스팅 알고리즘이 때때로 유령 의존성과 도플갱어를 유발할 수 있기 때문에 이것은 우리가 논의한 바와 같이 모노레포 설정에서 특히 중요합니다.

인기 프로젝트의 채택

오늘날 "엘리트 개발자"는 어떤 패키지 매니저를 사용하는지 알아보고자 많은 인기 있는 오픈 소스 프로젝트를 분석했습니다. 이러한 프로젝트가 적극적으로 유지 관리되고 최근에도 업데이트되는 것이 중요했습니다. 이런 부분은 패키지 매니저를 선택할 때 다른 관점을 제공할 수 있습니다.

npmYarn ClassicYarn Berrypnpm
SvelteReactJest (with nm)Vue 3
PreactAngularStorybook (with nm)Browserlist
Express.jsEmberBabel (with nm)Prisma
MeteorNext.jsRedux Toolkit (with nm)SvelteKit
Apollo ServerGatsby
Nuxt
Create React App
webpack-cli
Emotion

흥미롭게도 이 글을 쓰는 시점에서 이 오픈 소스 프로젝트 중 어느 것도 PnP 접근 방식을 사용하지 않습니다.

결론

패키지 매니저의 현재 상태는 훌륭합니다. 우리는 거의 모든 주요 패키지 매니저 사이에서 유사한 수준의 기능을 달성했습니다. 그러나 여전히 각각의 실제 내부 동작 방식은 상당히 다릅니다.

pnpm은 CLI 사용법이 비슷하기 때문에 처음에는 npm처럼 보이지만 의존성을 관리하는 방법은 많이 다릅니다. pnpm의 방법은 더 나은 성능과 최고의 디스크 공간 효율성으로 이어집니다. Yarn Classic은 여전히 인기가 있지만 레거시 소프트웨어로 간주되며 조만간 지원이 중단될 수 있습니다. Yarn Berry PnP는 이 구역의 신참이지만 패키지 매니지 환경을 다시 한 번 혁신할 수 있는 잠재력을 완전히 실현하지는 못했습니다.

수년에 걸쳐 많은 사용자들이 누가 어떤 패키지 매니저를 사용하는지에 대해 질문했고 전반적으로 사람들은 Yarn Berry PnP의 성숙도와 채택에 특히 관심이 있는 것 같습니다.

이 기사의 목적은 스스로 사용할 패키지 매니저를 결정할 수 있는 다양한 관점을 제공하는 것입니다. 특정 패키지 매니저를 권장하지 않는다는 점을 강조하고 싶습니다. 다양한 요구 사항 중요도에 따라 다르니 원하는 것을 선택하세요!

profile
Front-End Engineer

1개의 댓글

comment-user-thumbnail
2022년 9월 20일

좋은 글 번역 감사드립니다! 네이버D2에 올라온 포스팅(https://d2.naver.com/helloworld/0923884)도 함께 읽으니 더 좋네요!!

답글 달기