Skip to content

npm pnpm yarn

paulcjy edited this page Nov 6, 2024 · 1 revision

개요

모노레포를 구축하는 과정 중에 ‘pnpm으로 모노 레포를 배포하겠다.’ ‘npm으로 모노 레포를 배포하겠다.’ 같은 말이 나왔다.

하지만 이러한 패키지 매니저에 대해서 자세히 공부해 본 적은 없는 것 같아, 이번 기회에 알아보고 팀원들에게 공유하면 좋을 것 같아. 문서를 작성해보려고 한다.

본문

패키지 매니저(Package Manger)란?

패키지 매니저는 개발에 필요한 다양한 패키지를 설치하고 또 수정하고 업데이트 하는 등의 작업을 편리하게 도와주는 도구라고 한다.

그리고 현재 이러한 자바스크립트 관련 패키지 매니저로는 npm, pnpm, yarn 등을 주로 사용한다.

패키지 매니저가 나온 순서대로 본다면, npm(2010) → yarn(2016) → pnpm(2017)의 순서대로 패키지 매니저가 출시되었으며, 현재 사용자들은 이 3가지의 패키지 매니저에서 대부분의 자바스크립트 패키지를 찾아 설치하고 있다.

npm(Node Package Manger)

npm은 2010년에 가장 먼저 나온 자바스크립트 패키지 매니저 도구로, 위의 언급된 세 가지 패키지 매니저 도구 중 사람들이 가장 많이 사용하는 패키지 매니저 도구이다.

가장 먼저 나왔기에 패키지 매니저 도구 중 배포되고 있는 자바스크립트 패키지의 수가 압도적으로 많으며, 이로 인해 생태계가 엄청 풍부하다는 장점이 있다.

또한, npm의 경우에는 기본적으로 node.js에 내장되어 있기 때문에 추가적인 설치를 필요로 하지 않는다는 점 또한 장점이라고 할 수 있다.

npm의 이름을 볼 수 있듯이, npm을 통해 배포된 패키지들은 Node.js 환경에서 사용 가능한 모듈이라는 것을 의미한다.

npm을 통해서 패키지를 설치하고 싶을 때 터미널에 다음과 같은 명령어를 입력하면 설치할 수 있다.

# 패키지 설치
npm install <설치하고자 하는 패키지 이름>

이러한 명령어를 통해서 설치된 패키지는 package.json과 package-lock.json이라는 파일을 통해 관리되게 된다. 때문에, 협업할 때 누가 모듈을 설치하였고 이를 인지하지 못했더라도 이러한 정보가 package.json과 package-lock.json에 기록되어 있다면, npm install 명령어를 통해 다른 사람이 설치한 모듈이 어떠한 것인지 파악하지 않아도 설치된 패키지에 대한 동기화가 가능하다.

package.json과 package-lock.json이 맡는 역할을 자세하게 본다면 다음과 같다.

package.json

  • 역할: 프로젝트의 기본 메타데이터와 의존성을 정의하는 파일로, 패키지의 이름, 버전, 스크립트, 의존성 등을 포함하고 있다.
  • 내용:
    • dependenciesdevDependencies 필드에 패키지 이름과 버전 범위를 명시한다. 예를 들어 "express": "^4.17.1" 같은 방식으로 패키지 이름과 허용된 버전 범위를 지정한다.
    • 이는 패키지 설치 시 허용된 버전 범위 내의 최신 버전을 설치하도록 한다. (예: ^4.17.14.x.x 버전의 최신 버전을 설치)
  • 주요 기능: 프로젝트의 의존성을 정의하고 팀 간에 공유하기 위한 목적으로 사용된다.

package-lock.json

  • 역할: package.json에 정의된 의존성 버전을 구체적인 버전으로 "잠금" 처리하여, 언제 어디서든 동일한 의존성 트리를 재현할 수 있도록 한다.
  • 내용:
    • 프로젝트 내 모든 패키지와 하위 패키지의 정확한 버전출처가 기록되게 한다.
    • 각 패키지의 SHA-1 해시가 포함되어 있어, 정확히 동일한 버전과 내용의 패키지를 설치하도록 보장한다.
  • 주요 기능: 팀원들이나 CI 환경에서 패키지 설치 시 항상 동일한 버전을 설치하게 해줌으로써, 버전 충돌이나 예기치 않은 동작을 방지하게 한다.

사실 기존에 package-lock.json은 존재하지 않고 package.json만 존재하였다. 하지만 npm은 모든 패키지를 한 곳에서 관리하는 중앙 집중식 형태이며, 이렇게 중앙 집중식으로 관리되었기에 2015년, 2022년에 관련 보안 문제가 터진 적이 있었다.(오픈소스로 관리되었던 npm 패키지에 악성 코드가 들어가 있었지만 이를 검증하지 못해 다른 사람들이 악성코드를 인지하지 못하고 패키지를 설치하여 피해를 본 사건 등)

그래서 이러한 사태를 방지하기 위해 2017년 5월부터 package-lock.json을 도입하여 npm을 통해서 설치되는 패키지에 대한 무결성을 확실하게 체크하여 안정성을 높이고 추가적으로 npm audit 명령어를 제공하여 보안 취약점이 있는 패키지를 식별하고 이에 대한 보고서를 만들어주는 기능을 추가하였다.

하지만 이러한 보안 문제를 해결하더라도 Npm 패키지는 다음과 같은 단점이 추가적으로 존재한다.

  • npm install을 통해 여러 패키지를 한 번에 설치하려 할 때 순차적으로 패키치를 하나하나씩 설치하기 때문에 속도가 느리다.

    다행히 최신 npm에서는 병렬 설치 기능이 도입되었다. 따라서 npm install이나 npm ci를 통해 병렬적으로 패키지를 설치하게 된다.(npm ci 명령어는 주로 CI/CD 환경에서 사용한다.)

    다양한 npm 패키지 설치 방식(Chat GPT)

    • npm install:
      • 일반적으로 package.json에 정의된 모든 의존성을 설치합니다. 이 과정에서 package-lock.json도 참고하지만, package.json의 범위를 기반으로 최신 버전을 결정하기 때문에 추가적인 계산과 네트워크 요청이 발생합니다.
      • npm v5 이후부터는 병렬 설치 기능이 적용되어 이전 버전보다 설치 속도가 개선되었습니다.
    • npm ci:
      • ciContinuous Integration을 위한 명령어로, npm install보다 더 빠르고 일관된 설치 환경을 제공합니다.
      • npm ci의 특징:
        • node_modules 폴더를 삭제한 후 package-lock.json에 명시된 정확한 버전으로 모든 의존성을 다시 설치합니다. 이 과정은 package.json을 보지 않고 package-lock.json만을 기반으로 하기 때문에 속도가 npm install보다 빠릅니다. 또한, node_modules 폴더를 완전히 삭제하고 설치하기 때문에 기존 의존성 관계를 고려하지 않아도 되므로 설치 과정이 간소화됩니다.
        • package.json이 변경되면 설치가 실패하고, 반드시 package-lock.json이 있어야 동작합니다. 이는 package-lock.json이 없을 경우 예기치 않은 의존성 문제가 발생할 수 있음을 의미합니다.
        • 일반적인 npm install보다 설치 속도가 훨씬 빠르며, CI/CD 환경이나 대규모 프로젝트에서 안정적이고 일관된 의존성 관리를 위해 사용됩니다.
    • npm install --prefer-offline:
      • 이 옵션은 캐시된 패키지를 우선적으로 사용하여 네트워크 요청을 줄이고 설치 속도를 높입니다.
      • 특히 네트워크가 느린 환경에서 유용하며, 이전에 설치한 패키지가 캐시에 있는 경우 빠르게 설치할 수 있습니다.
    • npm install --no-audit:
      • 패키지 설치 시 보안 감사(audit)를 비활성화하여 설치 속도를 높입니다.
      • npm은 기본적으로 설치 시 보안 감사 정보를 확인하는데, 이 옵션을 사용하면 빠른 설치가 필요한 경우 시간을 절약할 수 있습니다.
  • npm install을 통해 설치된 패키지 파일들은 node_module이라는 폴더에 저장되고 관리되는데 이러한 패키지 파일들은 압축되지 않은 형태로 node_module에 존재하기 때문에 크기가 매우 커진다.

  • 유령 의존성 현상이 발생한다.

    image 만약 패키지가 기존의 왼쪽에 있는 형태로 의존성 트리를 가지고 있다면, A 패키지를 두 번 설치하게 될 수도 있다.

    이러한 문제로 인해서 npm은 왼쪽의 의존성 트리를 오른쪽과 같이 바꾸게 되는데 문제는 이러한 과정에서 최적화를 위해 의존성 트리의 레벨이 달라져 다른 쪽에서 의존성 관계를 가지고 있지 않은 다른 패키지를 가져올 수 있게 된다.

    예를 들어 B(1.0) 패키지를 기준으로 봤을 때 왼쪽에 있는 형태로 설치가 된다면, B(1.0) 패키지는 A와 C만이 불러올 수 있고 package-1은 불러올 수 없게 된다.

    하지만 만약 의존성 트리가 최적화를 위해 오른쪽처럼 바뀐 상태로 설치가 된다면, package-1은 B(1.0) 패키지를 불러올 수 있게 된다.

    이렇게 기존 패키지에서 불러올 수 없는 패키지가 불러와질 수 있도록 바뀌는 현상을 유령 의존성 현상이라고 부른다.

    다행히도 최근에 npm은 이러한 현상이 발생하지 않는데, 그 이유는 package-lock.json이 이러한 현상 또한 방지해주는 역할을 하기 때문이다.

  • 의존성을 탐색하는 과정이 매우 성능이 느린 방식의 연산으로 탐색을 진행하게 된다.

    만약 우리가 require('lodash')를 통해서 설치된 lodash를 탐색한다고 해보자. 그렇다면, 바로 프로젝트 폴더에 존재하는 node_module 폴더에 설치되어 있는 lodash 패키지를 가져오는 것이 아니라 전역으로 설치되어 있는 node_module로 부터 그 외의 다른 공간에 있는 node_module 폴더까지 탐색하는 과정을 거쳐서 lodash 모듈을 찾아서 가져오게 된다.

    이 과정에서 무수히 많은 File I/O가 발생하게 되고 이는 전반적으로 프로그램 실행을 느리게 만든다.

    하지만 최근 npm에서는 이러한 방식에 대해서 성능 개선이 이루어졌다고 한다.

  • 모노레포 방식의 패키지 관리를 지원하지 않음.(그러나 최신 버전에서는 제공한다.)

yarn(Yet Another Resource Negotiator)

메타에서 만든 패키지 관리자 도구인 yarn은 2016년에 출시되었으며 npm의 단점들을 극복하기 위해서 나온 패키지 관리 도구이다.(물론 최신 버전에서는 npm의 문제가 어느 정도 해결되었지만, yarn이 2016년에 출시되었다는 점을 기억하자.)

yarn은 기존에 나왔을 때 npm에서 문제가 있었던 부분을 해결하며 나왔다. 대표적인 특징은 다음과 같다.

  • 패키지 병렬 설치
  • 패키지 데이터 캐싱을 통한 빠른 패키지 설치 속도 보장
  • 인터넷이 연결되지 않았어도 기존에 패키지를 설치한 적이 있다면, 오프라인에서 자체적으로 기존에 설치한 패키지를 설치
  • 모노레포(여러 개의 모듈을 한 저장소에 묶어서 관리하는 방식)에 대한 관리 기능 제공 ⇒ 워크스페이스 기능
  • 유령 의존성 문제 해결(yarn.lcok 파일과 node_module 안에 node_module을 생성하고 거기에 패키지를 설치하면서 문제 해결)
  • 기존 npm 레지스트리를 활용해서 패키지 설치 가능

하지만 현재 yarn은 2020년을 마지막으로 유지보수를 종료하였고, 대신 기존 yarn을 업그레이드 한 yarn berry가 출시되었다.

yarn berry의 주요 특징은 다음과 같다.(Claude)

Plug'n'Play (PnP)

javascript
Copy
// .pnp.cjs 파일을 통한 의존성 관리
require('./.pnp.cjs').setup();
const package = require('some-package');
  • node_modules 디렉토리 제거
  • 의존성을 ZIP 아카이브로 저장하여 공간 효율성 향상
  • 프로젝트 시작 시간 대폭 단축

Zero-Installs

plaintext
Copy
.yarn/
├── cache/        # 의존성 캐시 (.zip 파일들)
├── releases/     # Yarn 릴리즈
└── .pnp.cjs     # 의존성 매핑

  • 의존성을 버전 관리 시스템에 포함
  • clone 후 즉시 실행 가능
  • 별도의 install 과정 불필요

향상된 워크스페이스 기능

yaml
Copy
# package.json
workspaces:
  packages:
    - "packages/*"
  focuses:
    - "frontend"
    - "backend"
  • 더 강력한 모노레포 지원
  • 선택적 워크스페이스 설치
  • 워크스페이스 간 의존성 관리 개선

타입스크립트 지원 강화

typescript
Copy
// 자동 PATH 해결
import { something } from 'package';
  • PnP와 타입스크립트 완벽 통합
  • 자동 타입 정의 해결
  • IDE 지원 개선

의존성 검사 강화

json
Copy
{
  "packageExtensions": {
    "react-scripts@*": {
      "peerDependencies": {
        "eslint-config-react-app": "*"
      }
    }
  }
}
  • 더 엄격한 peer dependency 검사
  • 누락된 의존성 자동 감지
  • 패치 시스템으로 문제 해결

캐싱 시스템 개선

  • 글로벌 캐시 대신 프로젝트별 캐시
  • 더 빠른 설치 속도
  • 캐시 무결성 보장

확장 기능

javascript
Copy
// plugin 예시
module.exports = {
  name: '@yarn/plugin-example',
  factory: require => {
    const { BaseCommand } = require('@yarnpkg/cli');
    class ExampleCommand extends BaseCommand {
// 플러그인 구현
    }
    return {
      commands: [ExampleCommand],
    };
  },
};
  • 플러그인 시스템 도입
  • 커스텀 명령어 추가 가능
  • 기능 확장 용이

보안 강화

  • 더 강력한 체크섬 검증
  • 의존성 제한 기능
  • 안전한 스크립트 실행

성능 최적화

  • 설치 속도 개선
  • 메모리 사용량 감소
  • 디스크 공간 효율화

에러 처리 개선

bash
Copy
➤ YN0000: │ SyntaxError: Unexpected token '.'
➤ YN0000: │     at /project/index.js:1:1
  • 더 명확한 에러 메시지
  • 문제 해결 가이드 제공
  • 디버깅 용이성 향상

pnpm(Performant Node Package Manager)

pnpm 역시 npm의 패키지 설치 속도가 느린 문제를 해결하기 위해서 나온 패키지 관리 도구이다.(이번에도 말하지만 최신 npm에서는 문제가 어느 정도 해결됐다. pnpm이 2017년에 나왔다는 점을 기억하자.)

pnpm은 C언어의 포인터와 같이 패키지의 위치를 심볼릭 링크하는 방식을 통해 의존성이 중복으로 설치되는 현상을 방지한다.

image

위 사진처럼 전역적인 content-addressable store를 통해 비휘발성 메모리에 설치된 패키지와 코드를 연결하게 되고, 이러한 방식을 통해 패키지가 한 번만 설치될 수 있도록 보장한다. 만약 똑같은 패키지가 설치되려고 하면, content-addressable store에 공간을 참조해서, 만약 해당 패키지가 이미 설치되어 있다면 설치를 두 번 진행하지 않고 넘어가는 것이다.

이를 통해 디스크 공간을 절약하며, 추가적으로 병렬 설치 또한 지원하여 전반적인 패키지 설치 속도를 향상한다.

또한 pnpm도 yarn과 마찬가지고 모노레포 방식에 대한 패키지 관리를 지원한다.

하지만 국내에서는 pnpm을 통해 패키지를 관리하는 법에 관한 레퍼런스 문서가 많이 존재하지 않는다는 단점이 있다.

참고 자료

패키지 매니저 비교 - npm, yarn, pnpm

왜 기업들은 Yarn Berry를 많이 사용할까? - (1) (feat : npm)](https://html-jc.tistory.com/676)

[패키지 매니저] npm, yarn, pnpm, yarn-berry

동기 | pnpm