🚘

npm installでのCould not resolve dependencyエラーと--legacy-peer-depsについて

2022/12/24に公開

はじめに

npm installを実行したときに以下のような依存解決エラーが出ることがあります。このエラーは--legacy-peer-depsオプションを付与することで解消できるのですが、エラーの具体的な意味や本オプションの内容をちゃんと理解してこなかったので、すこし調べてみました。

npm ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR! 
npm ERR! While resolving: react-alert@7.0.3
npm ERR! Found: react@18.2.0
npm ERR! node_modules/react
npm ERR!   react@"^18.2.0" from the root project
npm ERR!   peerOptional react@"^16.8.0 || ^17.0.0 || ^18.0.0" from @apollo/client@3.7.2
npm ERR!   node_modules/@apollo/client
npm ERR!     @apollo/client@"^3.7.2" from the root project
npm ERR!   25 more (@emotion/react, @emotion/styled, ...)
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.1 || ^17" from react-alert@7.0.3
npm ERR! node_modules/react-alert
npm ERR!   react-alert@"^7.0.3" from the root project
npm ERR! 
npm ERR! Conflicting peer dependency: react@17.0.2
npm ERR! node_modules/react
npm ERR!   peer react@"^16.8.1 || ^17" from react-alert@7.0.3
npm ERR!   node_modules/react-alert
npm ERR!     react-alert@"^7.0.3" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

Could not resolve dependencyエラーとは

まずはこのエラーの内容を確認します。"Could not resolve dependency"というエラーメッセージから分かるように、ライブラリの依存関係を解決できなかった、つまりライブラリ同士でなんらかの不整合が発生している、というのが本エラーの概要です。

具体的にみてみる

npm ERR! node_modules/react
npm ERR!   react@"^18.2.0" from the root project
npm ERR!   peerOptional react@"^16.8.0 || ^17.0.0 || ^18.0.0" from @apollo/client@3.7.2

ここに注目すると、reactライブラリの依存解決に失敗しているようです。
プロジェクトルートではreact@^18.2.0を指定しているところ、@apollo/client@3.7.2のpeerOptionalではreact@"^16.8.0 || ^17.0.0 || ^18.0.0"が指定されている、と言っていますね。

peerOptionalってなに?という疑問は後回しにして、@以降のバージョン指定の部分を見ていきましょう。

バージョン指定を確認する

バージョンはmainバージョン.minorバージョン.patchバージョンという形式で表されますが、エラーメッセージでは先頭に^(キャレット)がついています。これは、ゼロ以外の一番左の数値が同じバージョンを許容するという印です。

具体的には以下のようになります。

  • ^1.0.0: 1.0.0以上、2.0.0未満
  • ^0.1.1: 0.1.1以上、0.2.0未満

他に~(チルダ)を利用した指定方法もありますが、ここでは割愛します。
では上記を踏まえて再度エラーを確認してみましょう。

  • react@"^18.2.0": 18.2.0以上19.0.0未満
  • react@"^16.8.0 || ^17.0.0 || ^18.0.0": 16.8.0以上19.0.0未満

この部分の依存関係が矛盾しているのかなと思っていたのですが、18.2.0などは両方の指定を満たすことができます。つまりエラーの原因は他にあるということですね。

npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.8.1 || ^17" from react-alert@7.0.3

エラーメッセージをみると、さきほどと似たエラーがあります。ここではreact-alert@7.0.3のpeerはreact@"^16.8.1 || ^17"である、と書かれています。
react@"^16.8.1 || ^17": 16.8.1以上18.0.0未満です。ここまでで指定されたReactの依存バージョンをまとめると以下のようになります。

  • プロジェクトルート: 18.2.0以上19.0.0未満
  • @apollo/client@3.7.216.8.0以上19.0.0未満
  • react-alert@7.0.316.8.1以上18.0.0未満

これらをすべて同時に満たすバージョンは存在しないため、依存解決ができずにエラーが表示されたんですね。

dependencyとpeerDependency

エラーの内容は分かりましたが、エラーメッセージに登場したpeerOptionalとかpeerとはなんでしょうか。これらはいずれもpeerDependencyというもので、dependencyとはちょっと違う概念です。

dependencyとは

まずdependencyとは、そのNPMモジュールが動作するのに必要なモジュールやライブラリです。たとえばアプリケーションのUI構築にReactを利用するとき、npm install reactを実行しますが、これはアプリケーションのdependencyにreactを追加することと同義です。Reactがなければそのアプリケーションは動かないため、そのアプリケーションはReactに対してdependencyをもっています。

peerDependencyとは

peerDependencyは、そのNPMモジュールが一緒に利用することを想定しているモジュールやライブラリです。たとえば車とタイヤを考えると、タイヤは車にとりつけて使われる前提で作られています。このとき、タイヤにとってのpeerDependencyは車になります。車がなくてもタイヤとしては存在できるけど、想定される使い方をするには車にとりつけてください、というスタンスです。

以上を踏まえてもういちどエラーにおけるReactの依存関係をまとめると以下になります。

  • プロジェクトルート: 18.2.0以上19.0.0未満が必ず必要
  • @apollo/client@3.7.216.8.0以上19.0.0未満で動作する前提
  • react-alert@7.0.316.8.1以上18.0.0未満で動作する前提

--legacy-peer-depsとは

依存解決エラーを解消するひとつの手段として--legacy-peer-depsオプションがありますが、これはpeerDependencyの依存解決をせずにインストールを続行せよというオプションです。注意したいのは、これがNPMに「追加でなにかをさせる」のではなく「処理(=peerDependencyの解決)をスキップさせる」オプションであるという点です。

この一見分かりづらい挙動にはnpmの歴史的背景が関係しています。
v6以前は実行していなかったpeerDependencyの解決処理がv7で追加され、デフォルトで有効になりました。つまり--legacy-peer-depsオプションはv7以降のnpmにおいて、v6と同じ動作をするように矯正するものです。

v6以前ではpeerDependencyの依存解決はしないため、バージョンの矛盾が発生せず(というよりチェックされず)npm installが正常終了する、という挙動になります。

結局、--legacy-peer-depsは使っていいの?

エラーメッセージにも"accept an incorrect (and potentially broken) dependency resolution."とある通り、バージョンの不整合をノーチェックで通しているだけなので、目先のエラーは消えても実際にはバージョン差分による不具合などが発生する可能性があります。

基本的には利用するライブラリのバージョンを変えたり別ライブラリを検討したりして整合性を合わせる方法を探し、どうしようもない場合に--legacy-peer-depsを使うというのが良さそうです。

任意のライブラリのpeerDependencyを調べるには以下コマンドを利用するようです。
npm info name-of-module peerDependencies

参考:
https://stackoverflow.com/questions/66239691/what-does-npm-install-legacy-peer-deps-do-exactly-when-is-it-recommended-wh

Discussion