이 글에서는 npm과 같은 패키지 관리 도구에서 의존성 충돌 문제를 해결하기 위해 사용되는 --force
와 --legacy-peer-deps
옵션의 차이점과 사용 사례를 다룹니다. 특히, 레거시 프로젝트에서 의존성 충돌로 인해 발생하는 문제를 어떻게 우회하거나 해결할 수 있는지에 대한 구체적인 방법을 살펴봅니다.
이를 위해 가상의 프로젝트를 만들어 의존성 충돌 상황을 재현하고, 각 옵션을 적용했을 때의 결과와 동작 방식을 비교합니다. 또한, 옵션 사용 시의 주의사항과 함께, 다른 패키지 관리 도구인 pnpm과 yarn에서 유사한 상황을 처리하는 방법도 설명합니다.
레거시 프로젝트에서 작업을 해야 할 때 의존성 충돌 문제를 자주 만나게 됩니다. 상시 프로젝트 마이그레이션은 현실적으로 어렵고, 당장 수정 배포는 나가야 하는 상황에서 우회 방법을 찾게 됩니다.
—force와 —legacy-peer-deps는 npm, pnpm, yarn 등 패키지 관리 도구에서 의존성 문제를 해결하거나 우회하기 위해 사용되는 플래그입니다.
모든 peer dependencies 충돌을 무시하고 강제로 패키지를 설치합니다.
설치가 성공하더라도 peer dependency conflict 경고가 출력되며 패키지 작동 여부를 반드시 테스트해야 합니다.
npm v7 이상에서 도입된 엄격한 peer dependencies 검증을 비활성화 합니다. npm v6 이전 방식처럼 peer dependencies 충돌을 무시하고 설치합니다.
옵션 | 동작 방식 | 사용 상황 |
---|---|---|
--force | 모든 충돌을 무시하고 최신 버전 설치 | 테스트 환경, 당장 실행이 필요할 때 |
--legacy-peer-deps | npm v6처럼 peer dependencies 검증 비활성화 | 레거시 프로젝트 |
가상 프로젝트로 dependency conflict 상황을 재현하여 의존성 충돌 우회 옵션에 대해 이해해봅니다.
재현하는 상황을 정확히 표현하자면 업스트림 디펜던시 컨플릭트(Upstream Dependency Conflict)라고 합니다.
.
├── fake-dependency
│ ├── fake-dependency-1.0.0.tgz
│ ├── index.js
│ └── package.json
│
└── npm-install-demo
├── node_modules
├── package-lock.json
├── package.json
└── src
npm-install-demo, fake-dependency 두 개의 가상 프로젝트를 만듭니다.
{
"name": "npm-install-demo",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"dependencies": {
"lodash": "^3.10.1"
}
}
{
"name": "fake-dependency",
"version": "1.0.0",
"description": "A fake library to simulate lodash dependency conflict",
"main": "index.js",
"peerDependencies": {
"lodash": "^4.0.0"
},
"dependencies": {},
"devDependencies": {}
}
npm-install-demo 프로젝트는 lodash@^3.10.1 버전에 의존하고 있는 반면, fake-dependency는 peerDependencies로 lodash@^4.0.0 버전에 의존하고 있습니다.
만약 npm-install-demo 프로젝트에서 fake-dependency를 설치하면 어떻게 될까요?
pack 명령을 사용하여 tgz 파일을 미리 만들고 npm-install-demo에서 file 경로로 의존성을 가져오도록 설치해보겠습니다.
$ npm install ../fake-dependency/fake-dependency-1.0.0.tgz
아래는 npm install 명령을 실행하면서 --force 옵션을 사용해 강제로 종속성 문제를 해결한 상황입니다.
$ npm install ../fake-dependency/fake-dependency-1.0.0.tgz --force
출력되는 메시지를 요약하면 아래와 같습니다.
의존성 충돌과는 별개로 소스 코드는 잘 동작합니다.
프롬프트에서 audit 명령으로 패키지 의존성 문제와 보안 취약점을 자동 수정하라고 제안합니다.
해당 옵션을 실행하면 어떻게 동작하는지 살펴봅니다.
현재의 가상 프로젝트와 달리 실제 프로젝트에선 lodash의 Breaking Change로 인해 동작 오류가 발생할 가능성이 높습니다. 따라서 버전 업데이트 후 모든 기능을 테스트하여 변경된 API나 동작을 확인해야 합니다.
이번엔 legacy peer deps 옵션을 사용해서 강제 설치해봅니다.
$ npm install ../fake-dependency/fake-dependency-1.0.0.tgz --legacy-peer-deps
—force 옵션을 사용하여 설치했을때와 마찬가지로 종속성 충돌에 대해 설명하고 있습니다.
lock파일 내부에서도 peerDependency로 4.0.0 이상이 요구되는 반면 참조되는 종속성은 3.10.1으로 어긋남을 알 수 있습니다.
단순한 프로젝트라서 육안으로 —force와 —legacy-peer-deps의 동작 차이는 보이지 않지만, 종속성 충돌에 대한 우회 동작은 동일한 것으로 보입니다.
npm, pnpm, yarn에서 각 옵션은 조금씩 차이가 있습니다.
npm
pnpm
yarn
가장 이상적인 시나리오는 최신화, 버전 검증, 엄격한 의존성 관리 정책 등이 있겠으나 현실적으로 어려울때가 많으니 적어도 어떤 trade-off가 있을지 알고 사용하는게 좋겠다는 생각입니다.
npm 대신 yarn을 사용하여 fake-dependency를 설치했을때 incorrect peer dependency 경고만 표시될 뿐 그대로 설치됩니다.
yarn.lock 파일을 살펴보아도 설치는 완료된 것으로 보입니다.
이는 yarn v1.x의 특성에 기인합니다.
pnpm 패키지 매니저도 peer dependencies 이슈에 대해 경고만 할 뿐 설치를 막진 않습니다.
pnpm의 Peer Dependencies 처리 방식을 살펴보면,
이 동작은 아래와 같은 이유로 설계되었다고 합니다.
만약 .npmrc 또는 실행시 플래그로 --strict-peer-dependencies를 활성화하면 충돌을 그냥 넘어가지 않습니다.
# npmrc
strict-peer-dependencies = true
이 옵션을 사용하면 좀 더 엄격하게 종속성을 관리할 수 있겠습니다.