1. npm의 패키지 관리 방법
만약 express가 debug라는 모듈에 의존한다면 node_modules는 어떻게 생겼을까?
→ npm@3 이전에는 A였다면, npm@3부터는 B의 방식으로 변경되었다.
A의 문제점
- deep dependency trees를 형성하게 된다 → directory paths가 너무 길어질 수 있다.
- 패키지들이 서로 다른 dependencies에 필요할 경우 패키지의 복제본이 여러 개 존재하게 된다.
- 일부 패키지는 복제본이 있을 경우 오류가 발생할 수 있다.
그래서 npm@3부터는 ‘flattening’을 통해 node_modules의 구조가 B처럼 변경되었다.
B(flattened dependency trees)의 문제점
- 모듈이 스스로가 의존하지 않는 패키지에 접근할 수 있게 되었다.
- 의존성 트리를 flattening 하는 과정은 다소 복잡하다.
- 경우에 따라 패키지가 복제될 수 있다.
- 아래 그림은 A v1.0이 B v1.0에 의존하며, A가 패키지 중 가장 먼저 설치된 상황이다. 이후 B v2.0에 의존하는 C v1.0과 D v.1.0이 각각 설치된다면, 이미 top-level에 B v1.0이 설치되어 있으므로, nested dependency로 각각 설치된다.
→ 설치 순서에 따라 node_modules 내 폴더 구조가 달라진다.
2. pnpm의 패키지 관리 방법
A가 가진 문제점을 npm에서는 flattening을 통해 해결했다면, pnpm에서는 다른 방법으로 해결했다.
- node_modules 폴더 아래 모든 패키지는 각자의 dependencies를 갖는다.
- 하지만 pnpm은 symlinks를 통해 dependences를 flat하게 유지한다.
symlink란?
symlink란?
- 특정 파일이나 디렉터리에 대한 참조를 포함하는 특별한 파일. Windows 운영체제에서 바로 가기와 같은 기능을 한다.
예시
foo가 bar에 의존할 때, npm에서는 아래와 같이 구성된 구조가
node_modules
├─ foo
| ├─ index.js
| └─ package.json
└─ bar
├─ index.js
└─ package.json
pnpm에서는 아래와 같이 구성된다.
-> - a symlink (or junction on Windows)
node_modules
├─ foo -> .registry.npmjs.org/foo/1.0.0/node_modules/foo
└─ .pnpm
└─ .registry.npmjs.org
├─ foo/1.0.0/node_modules
| ├─ bar -> ../../bar/2.0.0/node_modules/bar
| └─ foo
| ├─ index.js
| └─ package.json
└─ bar/2.0.0/node_modules
└─ bar
├─ index.js
└─ package.json
require(’foo’)
를 하면 node_modules/foo/index.js
가 아닌 node_modules/.registry.npmjs.org/foo/1.0.0/node_modules/foo/index.js
의 파일을 실행한다.
- 만약 bar이 또 패키지 c에 의존한다면? foo가 bar을 의존하는 구조처럼 상위
bar/2.0.0/node_modules
안에 c에 대한 symlink가 형성될 것이고, c는 .registry.npmjs.org 아래 형성될 것이다.
→ deep dependency tree를 형성하지 않는다.
bar
은 node_modules 디렉터리 바로 아래에 있지 않기 때문에 코드에서 접근할 수 없다.
장점
- 복제본이 존재하지 않는다.
- 의존하지 않는 패키지에 대해 접근할 수 없다 → 더 안정적이고, 예측 가능하다.
3. pnpm과 모노레포
모노레포의 개념
- Monolithic Repository: 두 페이지를 구분 없이 섞어서 구성하고 참조해 독립이라는 느낌이 안 드는 경우
- MonoRepo: 독립된 각 프로젝트를 하나의 레포지토리에 묶는 방식 ‘독립된’ 이라는 개념(모듈화)이 핵심 서로 패키지처럼 참조
장점
- 기능을 모듈로 분리함으로써 캡슐화를 이룰 수 있다.
- 소프트웨어 확장이 쉬워진다.
- 테스트가 쉬워진다.
- PolyRepo에 비해 각 패키지를 넘나드는 작업을 하기에 편리하다. 서로 참조하기가 쉽다.
- Unified Versioning: 버전 관리를 한 번에 할 수 있다.
- 외부 디펜던시 관리: 외부 디펜던시의 버전을 맞추기 용이해진다.
pnpm의 workspace 프로토콜
workspace란?
- 하나의 레포지토리 안에 다수의 프로젝트가 존재하여 서로에 대한 의존성을 형성하고 있는 구조.
-
pnpm은 이용 가능한 패키지가 선언된 버전의 범위와 일치하는 경우에 workspace 내의 패키지를 참조한다.
- e.g.) bar이 "foo": "^1.0.0" 에 의존하고 있고, foo@1.0.0 이 워크스페이스에 있다면 foo@1.0.0 는 bar와 연결(linked)된다. 하지만 "foo": "2.0.0" 에 의존하고 있다면 새로 설치된다.
-
workspace:* workspace에 있는 버전의 패키지를 사용한다.
- e.g.) b가 패키지 a에 의존한다면?
b의 package.json에 다음과 같이 명시해주면 된다.
{
...
"dependencies": {
"a": "workspace:*",
}
}
참고자료
Why should we use pnpm?
The Case for pnpm Over npm or Yarn
Dev: MonoRepo 개념 알아보기
Managing a full-stack, multipackage monorepo using pnpm - LogRocket Blog
Workspace | pnpm