자바스크립트 모듈과 패키지 매니저: Yarn, Pnpm, Npm 무엇이 다를까?
JS 개발을 하다 보면 수많은 외부 모듈을 사용하게 된다.
대표적인 두 개의 모듈 시스템을 비교하면 다음과 같다.
모듈 방식 | 주요 환경 | 문법 예시 | 동작 방식 |
CommonJS (CJS) |
Node.js | const fs = require('fs') | 동기적 로딩: 코드를 실행하면서 바로 모듈을 읽음 (require 호출 시 파일 읽기) |
ES Module (ESM) |
브라우저, 최신 Node.js |
import fs from 'fs' | 정적 분석 기반 로딩: 실행 전 import 경로를 미리 분석해서 모듈을 불러옴 (트리 쉐이킹 가능) |
외부 모듈을 사용하는 방식은 점차 ESM으로 바뀌고 있지만 모든 모듈은 결국 node_modules 폴더에 설치된다.
따라서 어느 패키지 매니저가 어떻게 이 디렉터리를 구성하느냐가 성능과 안정성에 큰 영향을 준다.
외부 모듈을 사용하는 예시와 동작 원리
왜 외부 라이브러리는 브라우저 기본 기능이 아니고 따로 설치해야 할까?
브라우저는 최소한의 표준만 제공한다. document, window, fetch, localStorage 와 같은 웹 표준 API만 내장하고 있다.
이건 HTML, CSS, JS 등 모든 웹사이트가 공통적으로 사용하는 최소한의 기능이기 때문이다.
오늘날 개발을 편리하게 만들어주는 기능인 axios, react와 같은 기능은 브라우저가 만든 게 아닌, 개발자 커뮤니티가 만든 라이브러리다.
따라서 개발자가 작업 환경에서 npm install 과 같은 명령어를 입력하여 가져다 써야한다.
import axios from 'axios';
axios.get('/api/users').then((res) => console.log(res.data));
여기서 npm은 수십만 개의 JS 모듈이 등록된 공개 저장소다.
만약 브라우저가 npm에 있는 모듈을 다 내장하게 된다면 용량도 커지고 보안 이슈도 생길 것이다.
또, 각 프로젝트마다 필요한 기능이 다르므로 매우 비효율적이다.
패키지 매니저와 번들러가 모듈을 로드하는 방식
NPM - 전통적인 방식
Node.js의 기본 패키지 매니저.
package-lock.json으로 의존성 버전을 고정하고, v7부터는 workspaces도 지원된다.
예시로 axios 라이브러리를 직접 node_modules/axios/ 에 복사한다. 브라우저 번들러는 해당 경로를 그대로 읽는다.
설치도 단순하고 구조도 익숙하지만, 설치 속도나 디스크 효율성 면에서 아쉬움이 있다.
Yarn Classic(v1) / Yarn Berry(v2+) - 병렬 설치와 안정성 개선
Facebook에서 만든 대체 패키지 매니저로, 병렬 설치와 안정성을 중점적으로 개선했다.
yarn.lock으로 정확한 버전 고정과, Yarn Workspaces를 통해 멀티 패키지 환경을 지원한다.
Yarn Berry는 기존 Yarn(v1)과 달리 node_modules를 기본적으로 생성하지 않는다.
대신 Plug'n'Play(Pnp) 시스템을 도입하여, 패키지를 .yarn/cache 폴더에 저장한 후 가상 파일 시스템을 통해 로딩한다.
이로 인해 설치 속도가 빨라지고, 의존성 충돌 가능성이 줄며, 프로젝트 구조가 깔끔해진다.
PNPM - 성능에 초점을 맞춘 패키지 매니저
Performant NPM이라는 이름 그대로 설치 속도가 빠르고 디스크 사용량이 적다는 장점이 있다.
모든 의존성을 각 프로젝트의 node_modules 폴더에 복사하지 않는다.
중앙 저장소(store)에 패키지를 한 번만 설치하고, 각 프로젝트에는 하드 링크를 걸어 연결한다.
즉, 같은 패키지를 여러 프로젝트에서 사용하더라도, 중복 다운로드 및 저장이 발생하지 않는다.
이는 디스크 공간 절약 및 설치 속도를 크게 향상시킨다.
패키지 매니저 선택 과정과 사용 경험
Yarn Berry + PNPM
현재 메인으로 하고 있는 프로젝트에서 프론트엔드는 Yarn Berry(v4+)를, 백엔드는 Pnpm을 사용하고 있습니다.
프론트엔드는 디자인 시스템과 실제 서비스 앱이 하나의 모노레포로 구성되어 있어요.
디자인 시스템에서 만든 컴포넌트를 앱에서 그대로 불러와 사용하는 구조입니다.
이때 yarn workspace를 사용하면 서로 다른 패키지 간 의존성을 별도 설치 없이 바로 연결해줄 수 있어요.
Yarn Berry의 Plug'n'Play 기능 덕분에, 누락된 의존성을 사전에 막아주는 정적 의존성 체크가 가능한 점도 좋았어요.
예를 들어, 디자인 시스템에서 styled-components를 사용했는데 앱에서 사용하지 않아 생기는 숨겨진 충돌 같은 걸 쉽게 예방할 수 있었습니다.
또한, Storybook을 사용해 디자인 시스템을 테스트하고 문서화하고 있는데,
공식 문서나 세팅 예제가 대부분 Yarn 기반이라 처음 구성할 때 시행착오를 줄일 수 있었습니다.
반면, 백엔드는 의존성이 크고 디스크 효율과 속도가 중요한 구조이므로
하드 링크 기반으로 설치하고 의존성 충돌을 엄격히 관리할 수 있는 Pnpm을 사용하게 되었습니다.
Yarn Berry - Zero-Install을 가능하게 하려면
이론적으로 Yarn Berry는 .yarn/cache 에 모든 모듈을 zip 파일로 저장하고, .pnp.cjs 파일을 통해 node_modules 없이도 모듈을 해석하는 Plug'n'Play(PnP) 시스템을 제공한다.
따라서, 자동화된 빌드/테스트 환경에서 별도 설치 과정(yarn install)이 필요하지 않다.
하지만 실제로 Zero-Install을 적용하려면 일부 파일이 Git에 커밋되어야 한다.
나 또한 설정의 문제로 매번 yarn install 명령어를 실행해서 의존성을 맞춰줘야만 했는데.. 아래 과정을 거쳐 해결할 수 있었다!
문제가 되었던 설정은?
.yarnrc.yml 파일에서 nodeLinker: pnp로 올바르게 설정하여 PnP를 활성화 시켜주었지만
.gitignore 파일에서 주석 처리로 인해 캐시 zip 파일들이 Git에 커밋되지 않는 것이 원인이었다.
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.pnp.*
따라서 .yarn/cache 과 .pnp.cjs를 커밋 대상에 포함시켜주었다.
그리고 다음 명령어를 실행해주었다. `yarn config set enableGlobalCache false`
.yarn/*
!.yarn/cache // Git 커밋 대상에 포함
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.pnp.*
!.pnp.cjs // Git 커밋 대상에 포함
이렇게 설정하면 .yarn/cache에 저장된 zip 파일과 .pnp.cjs가 Git에 커밋되어,
CI 환경에서도 yarn install 없이 지원 가능한 정말 Zero-Install이 구현된다!
패키지 매니저는 단순히 라이브러리를 설치하는 도구가 아니라, 개발 환경을 구성하는 핵심 도구다.
따라서, 프로젝트 특성에 맞는 도구를 전략적으로 선택하고 그 이유를 깊이 고민해봐야 한다!
'기술 스택 > JavaScript' 카테고리의 다른 글
[자바스크립트 모듈 시스템] CJS에서 ESM까지 (0) | 2025.04.03 |
---|---|
브라우저의 렌더링 과정 (0) | 2025.03.19 |
SPA란 (1) | 2024.01.04 |
[nodeJS] 백엔드와 브라우저 간 상호작용 (세션, 브라우저, 쿠키) (0) | 2023.03.31 |