npm vs pnpm

npm과 pnpm의 차이는 dependency tree에 있다. npm의 node_modules는 flattened, 즉 평탄한 의존성 트리를 만드는 반면 pnpm의 node_modules는 nested dependency tree를 만든다.

npm3 (출처)

dependency hoisting

npm2는 중첩된 형태로 의존성을 관리하였다면 npm3부턴 중첩 의존성 트리로부터 발생할 수 있는 복잡도를 해소하기 위해 평탄한 형태로 node_modules에 패키지를 저장한다. 즉, 특정 패키지의 의존성들이 해당 패키지의 하위로 들어가는 것이 아니라 루트 디렉토리로 끌어올려져 설치되며, 이를 의존성 호이스팅dependency hoisting이라 한다.

예를 들어 패키지 B에 의존성을 가진 패키지 A를 설치한다 하자. npm install 명령어를 통해 npm은 node_modules 폴더 내에 A와 B 모듈을 설치하게 된다.

npm v2는 왼쪽 그림과 같이 중첩된 형태로 패키지를 설치하겠지만, npm v3는 오른쪽 그림과 같이 B는 루트로 호이스팅되어 평탄한 형태로 설치한다.

Untitled

왜 flat한 의존성 트리를 설치할까(참고)?

사실 npm v2의 nested 구조를 사용하면 패키지 각각의 의존성 버전들이 충돌할 경우는 거의 없다. 그럼에도 불구하고 npm v3가 flat 구조로 변경한 이유는 불필요한 의존성 중복 설치를 막기 위해서다. 만약 패키지 A와 B 모두 같은 버전의 패키지 C를 필요로 한다면, npm v2에서는 다음과 같이 같은 버전의 패키지를 중복하여 두 번 설치하여야 한다.

node_modules
`-- A
    `-- node_modules
				`-- [email protected]
`-- B
    `-- node_modules
				`-- [email protected]

npm2에서도 dedupe 명령어를 통해서 중복된 패키지를

이에 반해 v3에서는 C 역시 top level에서 flat하게 설치되므로 한 번만 설치되면 된다. 같은 패키지를 중복해서 설치할 필요가 없으므로 효율적으로 패키지를 관리할 수 있다.

node_modules
`-- A
`-- B
`-- [email protected]

의존성 호이스팅 예시

Untitled

glob-parent라는 패키지를 직접 설치하는 곳은 없지만, node_modules를 보면 루트 위치에 호이스팅되어 설치돼 있다.

의존성 호이스팅의 단점(참고)

하지만 의존성 호이스팅은 유령 의존성 phantom dependency을 발생시킬 가능성이 있다. 모든 의존성이 평탄하게 설치되었으므로, 특정 패키지 내에서 원래라면 import할 수도 없고 필요하지도 않은 다른 의존성을 몰래 import할 수 있다.

이는 npm뿐만 아니라 yarn classic에서도 해당하는 문제라 pnpm이 가장 주목한 이슈이기도 하다. 만약 npm2와 같이 중첩된 의존성 트리를 유지하면서도 효과적으로 의존성을 관리할 수 있다면 어떨까?

pnpm(참고)

pnpm은

이를 content-addressable store 방식이라 부른다.

Untitled

content-addressable store 방식에서의 node_modules의 구조

Untitled