npm

npm(Node Package Manager)는 자바스크립트 런타임 환경(Node.js)의 기본 패키지 매니저이다.

크게 위의 두 가지 구성 요소가 있다.

Node.js 환경 내에서는 웬만한 어떤 패키지 매니저를 사용해도 결국 npm에 업로드된 패키지를 다운로드하여 관리하게 된다. 여기서는 패키지 매니저로서의 npm에 집중한다.

용어 정리(참조)

npm2

npm2는 dependency hell의 발생을 막기 위해 중첩된 의존성 트리를 사용하여 의존성을 관리한다.

dependency hell

다음과 같이 A, B, C 총 3개의 모듈이 있다. 이 때 A는 1.0 버전의 B를 요구하고 C는 2.0 버전의 B를 요구한다.

Untitled

이제 A와 C를 설치해 보자.

Untitled

A와 C 모두 모듈 B를 요구하므로 패키지 매니저는 B 역시 설치하여야 한다. 하지만 A와 C가 원하는 B의 버전이 서로 다르기 때문에 버전 충돌이 일어나게 된다.

이와 같이 의존성이 꼬이는 현상을 dependency hell이라 한다. 지금은 단순히 3개의 패키지만을 설치했지만, 프로젝트 규모가 커지면 커질수록 의존성 지옥은 복잡해지게 된다.

Untitled

nested dependency tree

npm2는 이런 dependency hell을 막기 위해 중첩된 형태로 의존성 트리를 구성한다.

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

Untitled

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할 수 있다.