Skip to main content

npm install시 —force와 —legacy-peer-deps은 왜 사용할까?

이 글에서 다루는 내용

이 글에서는 npm과 같은 패키지 관리 도구에서 의존성 충돌 문제를 해결하기 위해 사용되는 --force--legacy-peer-deps 옵션의 차이점과 사용 사례를 다룹니다. 특히, 레거시 프로젝트에서 의존성 충돌로 인해 발생하는 문제를 어떻게 우회하거나 해결할 수 있는지에 대한 구체적인 방법을 살펴봅니다.

이를 위해 가상의 프로젝트를 만들어 의존성 충돌 상황을 재현하고, 각 옵션을 적용했을 때의 결과와 동작 방식을 비교합니다. 또한, 옵션 사용 시의 주의사항과 함께, 다른 패키지 관리 도구인 pnpm과 yarn에서 유사한 상황을 처리하는 방법도 설명합니다.




—force와 —legacy-peer-deps는 언제 사용해야 할까?

레거시 프로젝트에서 작업을 해야 할 때 의존성 충돌 문제를 자주 만나게 됩니다. 상시 프로젝트 마이그레이션은 현실적으로 어렵고, 당장 수정 배포는 나가야 하는 상황에서 우회 방법을 찾게 됩니다.

—force—legacy-peer-deps는 npm, pnpm, yarn 등 패키지 관리 도구에서 의존성 문제를 해결하거나 우회하기 위해 사용되는 플래그입니다.


—force 옵션에 대해

모든 peer dependencies 충돌을 무시하고 강제로 패키지를 설치합니다.

언제 필요할까?

  • 테스트를 위해 로컬에서 빠르게 설치할 필요가 있을 때
  • 중요한 작업 중 의존성 문제가 blocker일 경우 설치를 강제로 이행해야 할 경우
  • 패키지간 의존성 버전이 맞지 않지만 어쨌든 설치하려 시도하는 경우

이 점 주의하세요

  • 설치 이후 런타임 오류가 계속해서 발생할 가능성이 높습니다.
  • 종속성 트리가 깨지거나 예기치 못한 충돌 발생 우려가 있습니다.

설치가 성공하더라도 peer dependency conflict 경고가 출력되며 패키지 작동 여부를 반드시 테스트해야 합니다.


—legacy-peer-deps 옵션에 대해

npm v7 이상에서 도입된 엄격한 peer dependencies 검증을 비활성화 합니다. npm v6 이전 방식처럼 peer dependencies 충돌을 무시하고 설치합니다.

언제 필요할까?

  • 레거시 패키지 등 오래된 프로젝트가 특정 버전에 의존하는 경우
  • 새로운 패키지와 기존 프로젝트가 충돌하는 상황에서 기존 규칙대로 설치하고 진행하고 싶은 경우

이 점 주의하세요

  • 종속성 트리가 예상대로 설치되지 않을 경우가 높아요.
  • 향후 업데이트 과정에서 다시 충돌 문제가 발생할 가능성이 있어요.

옵션동작 방식사용 상황
--force모든 충돌을 무시하고 최신 버전 설치테스트 환경, 당장 실행이 필요할 때
--legacy-peer-depsnpm 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


에러 메시지 살펴보기

  1. npm ERR! code ERESOLVE
    1. ERESOLVE는 npm이 의존성 트리를 해결하지 못했다는 뜻입니다.
  2. Found: lodash@3.10.1
    1. 현재 프로젝트의 node_modules에 lodash@3.10.1이 설치되어 있습니다.
  3. Could not resolve dependency: peer lodash@"^4.0.0" from fake-dependency@1.0.0
    1. fake-dependency@1.0.0이 peerDependencies로 lodash@"^4.0.0"을 요구하고 있지만, 현재 프로젝트에 설치된 버전은 lodash@3.10.1이므로 충돌이 발생합니다.

—force 옵션으로 강제하기

아래는 npm install 명령을 실행하면서 --force 옵션을 사용해 강제로 종속성 문제를 해결한 상황입니다.

$ npm install ../fake-dependency/fake-dependency-1.0.0.tgz --force

출력되는 메시지를 요약하면 아래와 같습니다.

  1. --force로 인해 종속성 충돌을 무시하고 설치가 완료되었습니다.
  2. lodash@3.10.1이 설치된 상태에서 fake-dependency가 요구하는 lodash@^4.0.0은 무시되었습니다.
  3. 이로 인해 런타임에서 fake-dependency가 올바르게 동작하지 않을 가능성이 있습니다.
  4. 1 critical severity vulnerability는 추가적인 보안 문제가 존재하며 이를 수정하려면 업데이트가 필요합니다.

의존성 충돌과는 별개로 소스 코드는 잘 동작합니다.


npm audit fix —force로 취약성 강제 수정하기

프롬프트에서 audit 명령으로 패키지 의존성 문제와 보안 취약점을 자동 수정하라고 제안합니다. 해당 옵션을 실행하면 어떻게 동작하는지 살펴봅니다.

  • 기존 lodash@3.10.1에서 lodash@4.17.21로 업데이트되었습니다.
  • lodash 패키지를 업데이트하면서 SemVer(Semantic Versioning)의 주요 버전(Major Version)이 변경되었다는 경고가 나타납니다.
  • SemVer에서 Major Version 변경은 기존 API와 호환되지 않을 가능성이 있어, Breaking Changes가 포함될 수 있습니다.

현재의 가상 프로젝트와 달리 실제 프로젝트에선 lodash의 Breaking Change로 인해 동작 오류가 발생할 가능성이 높습니다. 따라서 버전 업데이트 후 모든 기능을 테스트하여 변경된 API나 동작을 확인해야 합니다.




—legacy-peer-deps 사용해보기

이번엔 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

  • —force와 —legacy-peer-deps 모두 지원

pnpm

  • 엄격한 종속성 관리를 특징으로 하지만 peer dependency 불일치는 경고로 표시합니다.
  • —force를 사용할 수 있지만 —legacy-peer-deps는 지원하지 않습니다.
  • 대신 —shamefully-hoist 또는 —no-peer-deps 옵션을 통해 peer dependency 문제를 우회할 수 있습니다.

yarn

  • yarn v2 이상에서는 —legacy-peer-deps를 지원하지 않습니다.
  • pnpm과 마찬가지로 설치 중단 대신 경고로 표시합니다.
  • —force와 유사한 플래그는 있지만, 엄격한 dependency 관리 철학으로 잘 사용되지 않는다고 하네요.
  • resolution 필드를 활용하거나 yarn.lock을 수동으로 수정하는 방법도 있습니다.

가장 이상적인 시나리오는 최신화, 버전 검증, 엄격한 의존성 관리 정책 등이 있겠으나 현실적으로 어려울때가 많으니 적어도 어떤 trade-off가 있을지 알고 사용하는게 좋겠다는 생각입니다.




동작 차이 검증하기

1) yarn v1.x

npm 대신 yarn을 사용하여 fake-dependency를 설치했을때 incorrect peer dependency 경고만 표시될 뿐 그대로 설치됩니다.

yarn.lock 파일을 살펴보아도 설치는 완료된 것으로 보입니다.

이는 yarn v1.x의 특성에 기인합니다.

  • peer dependencies가 충돌해도 검증 없이 설치를 진행합니다.
  • 충돌이 있는 패키지는 경고만 표시하고 설치를 계속합니다.
  • 이는 npm v7의 엄격한 peer dependencies 검증과 대조됩니다.
  • 충돌 시 호환성 문제가 발생할 수 있으나, yarn v1.x는 이를 강제로 막지 않습니다.



2) pnpm v8.x

pnpm 패키지 매니저도 peer dependencies 이슈에 대해 경고만 할 뿐 설치를 막진 않습니다.

pnpm의 Peer Dependencies 처리 방식을 살펴보면,

  • pnpm은 엄격한 의존성 관리와 독립된 node_modules 구조를 사용하는 것이 특징입니다.
  • 그러나, peer dependencies 충돌은 경고만 출력하고 설치는 진행합니다.

이 동작은 아래와 같은 이유로 설계되었다고 합니다.

  1. 중단 없이 설치: 프로젝트를 멈추지 않고 설치를 진행해 개발자가 직접 충돌을 해결할 수 있도록 유도.
  2. 최대한 독립적 구조 유지: 다른 패키지의 영향을 최소화하면서도 의존성을 설치.

만약 .npmrc 또는 실행시 플래그로 --strict-peer-dependencies를 활성화하면 충돌을 그냥 넘어가지 않습니다.

# npmrc
strict-peer-dependencies = true

이 옵션을 사용하면 좀 더 엄격하게 종속성을 관리할 수 있겠습니다.