기록의 습관화
article thumbnail

Node Package Manager

지난주 수요일에 AWS FE 세미나를 다녀왔습니다.

Next.js와 StoryBook과 관련된 1시간 30분 간의 난상토론이었는데

토론이 막바지에 다다를 때쯤 Package Manager에 관련된 이야기가 나왔습니다.

yarn berry에 관련된 이야기를 하다가 다른 분께서

 

Phantom Dependency를 해결해주긴 하지만 Git에 yarn berry 모듈을 모두 올리면 다운로드하는데 모두 리소스다. 최대로 Git에서 지원해 주는 용량도 한계가 존재한다. 그래서 pnpm을 더 많이 쓴다.

 

현재 제가 가장 많이 쓰고 있는 것은 yarn v1입니다.

 

다른 패키지 매니저도 다 사용을 해보았지만

 

yarn berry : git에 용량 제한이 걸림

pnpm : react native 패키지의 경우 설치가 제대로 되지 않음

npm : 설치가 너무 느린감이 있음

 

과 같은 확실하지 않은 이유에서 사용을 하고 있던 거 같네요 🥲

 

그래서 이번 기회에 기존의 npm과 yarn v1이 가지고 있던 문제점이 무엇이며

어떤 점을 개선하려고 다른 패키지 매니저가 있는 건지에 대한 특징들을 비교해 보도록 할게요.

기본적인 특징들은 다음과 같습니다.

 

pnpm에서 제공하는 벤치마킹

NPM

아마 개발자라면 한 번쯤은 모두 들어봤을 패키지 매니저입니다.

장점

1. 널리 사용됨

  • npm은 가장 널리 사용되는 JavaScript 패키지 매니저로, 대규모 공개 패키지 레포지토리를 가지고 있습니다.

2. 통합성

  • Node.js와 함께 자동으로 설치되어, 별도의 설치 과정이 필요 없습니다.

3. 간편한 사용법 

  • 명령어 구조가 간단하고 직관적이어서 쉽게 배울 수 있습니다.

단점

1. 비효율적인 의존성 검색

 

개발을 하게 되면 필요에 의해 많은 라이브러리를 설치해서 사용하게 됩니다.

NPM의 경우 라이브러리가 존재하는지 찾기 위해 현재 나의 경로로부터 상위까지 이동하며

이 라이브러리가 있는지에 대해서 탐색을 계속 진행하게 됩니다.

 

만약 axios를 사용한다고 가정을 해보겠습니다.

 

> node   
Welcome to Node.js v18.18.2.
Type ".help" for more information.
> require.resolve.paths('axios')
[
  '/Users/jangdonghyeon/Desktop/npm/npm/swagger-type-generator/repl/node_modules',
  '/Users/jangdonghyeon/Desktop/npm/npm/swagger-type-generator/node_modules',
  '/Users/jangdonghyeon/Desktop/npm/npm/node_modules',
  '/Users/jangdonghyeon/Desktop/npm/node_modules',
  '/Users/jangdonghyeon/Desktop/node_modules',
  '/Users/jangdonghyeon/node_modules',
  '/Users/node_modules',
  '/node_modules',
  '/Users/jangdonghyeon/.node_modules',
  '/Users/jangdonghyeon/.node_libraries',
  '/Users/jangdonghyeon/.nvm/versions/node/v18.18.2/lib/node',
  '/Users/jangdonghyeon/.node_modules',
  '/Users/jangdonghyeon/.node_libraries',
  '/Users/jangdonghyeon/.nvm/versions/node/v18.18.2/lib/node'
]

 

위와 같이 현재 디렉터리부터 nvm까지 정말 많은 의존성을 탐색하러 돌아다니는 모습을 확인할 수 있습니다.

만약 Global과 패키지가 없다는 경우를 가정하게 되면 readdir, stat과 같은 I/O 호출이 반복되고 실패도 하기도 하니 정말 비효율적일 수 있겠죠.

 

런타임 시에도 노드 확인은 모든 단일 필수 파일을 확인하기 위해 많은 시간을 할애하기 때문에

readdirnode가 애플리케이션을 부팅하는데 많은 시간을 할애하는 이유입니다.

이 문제는 node_modules 폴더의 디자인 자체가 패키지 중복을 적절히 제거할 수 없게 되어 있기 때문입니다.

 

  • 트리 레이아웃(hoisting)을 최적화하기 위해 알고리즘을 제공할 수 있지만 모든 문제점들을 해결할 수는 없습니다.
  • 디스크 사용량이 필요한 것보다 높을 뿐만 아니라 일부 메모리가 여러 번 인스턴스화되는 것이 원인입니다.

 

2. Phantom Dependency

 

흔히 우리가 유령 의존성이라고 알고 있는 문제입니다.

NPM의 경우 기본적으로 패키지 의존성을 트리구조로 관리합니다.

 

 

하나의 패키지만 설치한다면 괜찮겠지만 A라는 프로젝트에서 사용하는 패키지와 B라는 패키지에서 사용되는 패키지가 중복으로 설치되면 정말 비효율적 이겠죠?

 

이를 해결하기 위해 Hoisting 기법을 사용합니다.

 

위 그림과 같이 중복 설치를 방지하기 위해 왼쪽의 트리구조를 오른쪽과 같이 의존성 트리를 바꾸게 됩니다. 이렇게 하여서 원래는 바로 사용할 수 없었던 B(1.0)과 같은 패키지를 사용할 수 있게 됩니다. 하지만 이걸 모르는 상황에서 내가 설치한 적이 없는 패키지가 설치가 되어있고 다른 패키지를 삭제했을 때 갑자기 사라지게 되면 혼란이 오겠죠?

이와 같은 현상을 유령 의존성이라 칭하게 됩니다.

  1. 보안 문제
  • npm의 경우 많이 알고 있는 npm 패키지를 다운로드하여서 사용하게 됩니다.

npm | Home

 

npm | Home

Bring the best of open source to you, your team, and your company Relied upon by more than 17 million developers worldwide, npm is committed to making JavaScript development elegant, productive, and safe. The free npm Registry has become the center of Java

www.npmjs.com

 

하지만 누군가 의도적으로 패키지 내부에 악성 코드를 심어두었다고 가정을 해보겠습니다.

저희가 실제로 사용하려는 패키지 내부에는 Peer Dependency Module이라고 불리는 녀석들이 있습니다.

이들을 사용하고 싶지 않은 저희의 의도와는 관련 없이 이 녀석들을 설치하게 되는데 만약 이 녀석들 안에 보안 문제가 있는 라이브러리가 존재하게 되면 꼼짝없이 당하게 되는 거죠.

 

이러한 문제를 해결하기 위해 npm 측에서는 npm audit를 지원하여 이 보안 문제를 해결할 수 있습니다.

사실 이러한 문제는 npm 뿐만이 아닌 yarn, pnpm 모든 패키지 매니저가 가지고 있는 문제점 중 하나이기 때문에

패키지 매니저들 각각 이 보안 문제를 다른 방식으로 해결하곤 합니다.

 

Yarn Classic

yarn v1으로 불리는 녀석입니다.

저희가 흔히

npm i -g yarn

을 하고 yarn을 사용하게 되면 사용되는 친구입니다.

장점

  1. Offline Mode
  • Yarn은 한 번 설치된 패키지를 로컬 캐시에 저장합니다. 이는 인터넷 연결이 없는 상태에서도 이미 캐시 된 패키지를 재설치할 수 있게 해 주어, 연결 문제가 있거나 오프라인 상태에서도 개발을 계속할 수 있도록 지원합니다.
  1. Deterministic
  • Yarn은 yarn.lock 파일을 사용하여 프로젝트에 설치된 패키지의 정확한 버전을 기록합니다. 이는 동일한 yarn.lock 파일을 가진 모든 시스템에서 동일한 종속성이 동일한 방식으로 설치됨을 보장합니다. 이로 인해 다양한 개발 환경에서도 일관성 있는 환경을 유지할 수 있습니다.
  1. Network Performance
  • Yarn은 네트워크 요청의 효율성을 극대화하기 위해 요청을 효과적으로 대기열에 넣고 관리합니다. 이는 네트워크 워터폴(waterfall, 여러 단계에 걸쳐 순차적으로 진행되는 네트워크 요청) 현상을 방지하고, 전반적인 설치 속도를 향상합니다.
  1. Network Resilience
  • Yarn은 네트워크 요청 중 하나가 실패하더라도 전체 설치 프로세스가 중단되지 않도록 설계되었습니다. 실패한 요청은 자동으로 재시도되며, 이는 네트워크 상태의 불안정성에도 불구하고 패키지 설치의 안정성을 향상합니다.
  1. Flat Mode
  • Yarn은 Flat Mode를 통해 서로 일치하지 않는 종속성 버전을 하나의 버전으로 통합하여 해결할 수 있습니다. 이는 프로젝트 내의 중복된 패키지 인스턴스를 줄이고, 디스크 공간의 효율성을 향상시킵니다.
  1. Secure
  • Yarn은 설치된 패키지의 무결성을 검증하기 위해 체크섬을 사용합니다. 이는 코드 실행 전에 패키지가 변경되지 않았음을 확인함으로써 보안을 강화합니다.

단점

  1. node_modules 폴더의 크기
  • Yarn Classic은 여전히 node_modules 폴더를 사용합니다. 이 폴더는 종종 크고 복잡해질 수 있으며, 많은 디스크 공간을 차지할 수 있습니다.
  1. 호환성 문제
  • 일부 npm 패키지와의 호환성 문제가 발생할 수 있습니다. 특히, 패키지 버전 관리 방식이 npm과 다를 때 이러한 문제가 나타날 수 있습니다.

Yarn Berry

흔히 yarn v2라고 불리는 친구입니다.

기존의 yarn v1이 가지는 단점들을 해결하고 pnp라는

yarn set version berry

장점

1. TypeScript의 지원

  • Yarn Berry의 경우 Typescript로 작성되어 있어서 타입 체크를 지원합니다.

2. Plug n Play

  • Node의 혁신적인 설치 전략 중에 하나입니다.
  • CommonJS 워크 플로를 기반으로 하는 흥미로운 특성을 제공합니다.
  • 빠른 의존성 검색
    • . yarn/cache에 수평적으로 존재합니다. require()에 소요되는 시간이 크게 단축됩니다.
    • 모든 패키지에 대한 접근 시간 → O(1)이 됩니다.
  • 빠른 설치
    • 압축 파일 단위로 설치됨 → 의존성을 구성하는 파일의 수가 절대적으로 감소합니다.
  • 유령 의존성 방지
    • 호이스팅을 사용하지 않기 때문에 의도하지 않은 의존성 발생 하지 않습니다.

3. zerio-install

  • 기존의 node_modules를 사용하는 것이 아닌 바이너리 아카이브 압축을 이용해서 yarn cache를 제공합니다. 이렇게 하면 한 번의 pull을 통해 모두 동일한 의존성을 설치 없이 사용할 수 있습니다.

단점

  1. Plug'n'Play (PnP)의 호환성 문제
  • Yarn Berry의 PnP 기능은 일부 기존 도구나 IDE와 완벽하게 호환되지 않을 수 있습니다. 특히, node_modules 폴더를 예상하는 도구들과의 호환 문제가 발생할 수 있습니다.
  • 실제로 프로젝트에 적용하게 되면 pnp를 지원하지 않는 라이브러리들이 존재하여 의도적으로 의존성을 따로 설치해줘야 하는 부분이 존재합니다.

pnpm

장점

 

1. 디스크 공간 절약

  1. 디스크 공간 절약
  • 패키지를 하드 링크와 심볼릭 링크를 통해 관리하여 디스크 공간을 효율적으로 사용합니다.

2. 빠른 속도

  • Yarn과 비슷하게 빠른 설치 속도를 제공합니다. 실제로 모든 패키지 매니저와 비교하여도 빠른 속도를 자랑합니다.

3. 좋은 메모리 관리

  • 노드 모듈의 중복을 최소화하여 메모리 사용을 줄입니다.

단점

  1. 마이그레이션의 복잡성
  • pnpm은 독특한 방식으로 node_modules 구조를 생성합니다. 이 때문에 기존 도구나 시스템과의 호환성 문제가 발생할 수 있으며, 특히 기존 npm이나 Yarn 기반 프로젝트에서 pnpm으로 전환할 때 이슈가 될 수 있습니다.
  1. 상대적으로 적은 사용률
  • pnpm은 npm이나 Yarn에 비해 상대적으로 덜 알려져 있으며, 커뮤니티와 사용자 기반이 작습니다. 이로 인해 정보나 지원을 찾기가 더 어려울 수 있습니다.

용량

패키지를 설치하게 되면 자동으로 node_modules 디렉토리가 생깁니다.

비교의 예시를 위해서 단순히 6MB 정도의 프로젝트를 각각 다른 패키지 매니저를 통해 의존성을 설치했을 때 얼마 정도까지 용량이 늘어나는지 체크해 보도록 하겠습니다.

 

 

확인해 보면

yarn > pnpm > npm > yarn berry 순으로 용량에 차이가 있는 걸 확인할 수 있습니다.

어쩐지 지금까지 진행했던 많은 프로젝트에서 yarn을 사용하고 있는데 디스크 용량이 부족해지는 이유가 여기 있었네요 😂

물론 위에서 소개했던 각각의 장단점들을 살펴보아 요번 프로젝트에서 어떤 기능이 필요하고 어떤 패키지 매니저를 사용할지는 비교 분석을 해보아야 할 것 같습니다.

마치면서

지금까지 이렇게 각각의 패키지 매니저의 특징과 phantom dependency가 가지는 문제점을 알아보았습니다.

그런데 이렇게 정리를 하고 나서도 막상 다음 프로젝트를 선택해야 한다 했을 때 어떤 패키지 매니저를 사용해야 할지 고민이네요...

사실 프로젝트에서 관리하는 라이브러리들의 의존성들의 개수와 깊이가 깊지 않아서 CI, CD나 의존성을 설치할 때 그렇게 크게 

문제점을 느끼지 못했던 게 아니었던 게 아닐까 싶습니다. 

그래도 확실하게 npm과 classic yarn은 다음 프로젝트에서는 사용하지 않지 않을까 싶네요 🫠

profile

기록의 습관화

@ww8007

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!