최근 pnpm workspace 기능을 이용한 모노레포 환경에 대해 공부하던 중 pnpm workspace 패키지의 peer dependency 해결(resolve)방식이 특이하다는 것을 발견했습니다.
그것은 npm 레지스트리에 업로드된 패키지와 로컬에 설치된 workspace 패키지가 peer dependency를 resolve하는 방식이 다르다는 점이었는데 각 패키지에서 그러한 차이가 발생하는 이유와 peer dependency resolve 방식을 일치시키는 방법을 공유하려고 합니다.
peer dependency는 주로 호스트 패키지와 함께 사용되어야 하는 플러그인 패키지에서 해당 플러그인과 호환되는 호스트 패키지의 버전을 명시할 때 사용됩니다.
예를 들어, Airbnb 자바스크립트 스타일 가이드에서 제공하는 ESLint 공유 설정인 eslint-config-airbnb-base 패키지는 ESLint(호스트 패키지)와 함께 사용되어야 하기 때문에 아래와 같이 peerDependencies
필드에 호환되는 ESLint 패키지의 버전을 명시합니다. (코드)
// eslint-config-airbnb-base의 package.json
{
"name": "eslint-config-airbnb-base",
"peerDependencies": {
"eslint": "^7.32.0 || ^8.2.0",
},
}
그렇다면 dependencies
필드 대신 peerDependencies
필드에 ESLint 패키지 의존성을 명시하는 이유는 무엇일까요?
{
"name": "eslint-config-airbnb-base",
"dependencies": {
"eslint": "^7.32.0 || ^8.2.0",
},
}
만약 위와 같이 dependencies
필드에 ESLint 패키지 의존성을 추가했다면 ESLint v7.32.0을 사용하고 있는 애플리케이션에서 의존성 트리는 아래와 같이 생성될 것입니다.
├── [email protected]
└─┬ [email protected] // eslint-config-airbnb-base 최신 버전
└── [email protected] // eslint Major 버전 8 기준 최신 버전
불필요하게 eslint
패키지가 중복 설치될 뿐만 아니라 애플리케이션이 사용하는 ESLint의 버전과 eslint-config-airbnb-base
패키지가 사용하는 ESLint의 버전이 다르기 때문에 버그 추적도 굉장히 어려워질 것입니다.
반면, peerDependencies
필드에 추가한 의존성들은 상위 의존성 트리에서 resolve되기 때문에 애플리케이션에 설치된 의존성과 동일한 의존성을 사용하게 되어 위와 같은 문제들을 해결합니다.
위에 설명한 것처럼 peerDependencies
필드에 추가한 의존성들은 상위 의존성 트리에서 resolve되기 때문에 부모 패키지에 설치된 의존성을 그대로 사용합니다.
예를 들어, 아래와 같이 react 17 또는 18 버전을 peer dependency로 가지는 shared-ui
패키지가 있다고 가정해봅시다.
{
"name": "shared-ui",
"peerDependencies": {
"react": "^18.0.0 || ^17.0.0"
},
"devDependencies": {
"react": "17.0.0" // 개발 과정에서는 이전 버전과의 호환성을 검증하기 위해 가장 오래된 버전 설치
}
}
shared-ui
패키지는 react를 peer dependency로 가지기 때문에 react 17 버전이 설치된 애플리케이션에서 shared-ui
패키지는 react 17 버전을 사용하고 react 18 버전이 설치된 애플리케이션에서 shared-ui
패키지는 react 18 버전을 사용해야 합니다.
이러한 요구사항을 구현하기 위해 pnpm은 동일한 버전의 shared-ui
복사본을 루트 node_modules 하위 두 개의 다른 디렉토리에 각각 설치합니다. (더 정확히 말하자면 루트 node_modules 하위의 .pnpm 디렉토리(virtual store)에 peer dependency 버전마다 의존성 집합을 만들고 의존성 집합마다 별도의 shared-ui
복사본을 가집니다. (https://pnpm.io/how-peers-are-resolved))
즉, react 17 버전이 설치된 애플리케이션과 react 18 버전이 설치된 애플리케이션이 shared-ui
패키지에 대해 별도의 의존성 집합을 가지기 때문에 shared-ui
패키지는 애플리케이션에서 사용하는 react 버전에 맞게 react를 resolve 할 수 있는 것입니다.