宣伝: YouTubeで初心者向けの配信をしています。よかったら覗いていってね😉
あなたは普段、何気なく npm install
を使っていることでしょう。
しかし、 npm install
が何をしているのか、実は誤解している人も多いと思います。
記事のタイトルは釣りではないので、どんな時に npm install
は問題を起こすのか、説明できない人は以下を読み進めてください。これは多くの開発者が無意識に無視している、とても重要な事項だと思っています。
なお、npm 4.x系以下の方は本記事の対象ではありません。
追記: 強めに書きすぎて誤解を招く部分があったので何度か修正しています。
npm
はパッケージマネージャです。パッケージマネージャは、開発に必要なパッケージ(ライブラリとか、プラグインとか色々)を管理するためのツールです。全ての人が、全く同じ開発環境を再現するためにとても重要な役割を果たしています。
開発を始める時、 npm init
コマンドによって package.json
が追加されます。ここに、使用しているパッケージの名前とバージョンに関する情報が書かれていることは、おそらくこの記事を読んでいる人はだいたい知っているのではないでしょうか。
新しく、 xxx
というパッケージを追加するとき、 npm install xxx --save
を実行すると、 package.json
の dependencies
が更新されます。 これと同時に、 package-lock.json
というファイルが追加されます。今回の話の主役はこのファイルなのですが、まずは package.json
に書かれている内容の曖昧さについて知ってもらうために説明を続けます。
xxx
の最新バージョンが 1.0.0
の場合、package.json
の dependencies
には、次のような行が追加されます。
"xxx": "^1.0.0"
これが意味するところは、 1.x.x
に該当する最新のバージョンという意味になります。
ですので、次にこの package.json
を使って npm install
するまでに xxx
のバージョン 1.0.1
が公開されていた場合、次に npm install
を行う人の開発環境には 1.0.1
の xxx
がインストールされることになります。ここまでは、ご存知の方が多いと思います。
では、どのタイミングでも同じ 1.0.0
の xxx
をインストールできるようにするにはどうすればいいと思いますか?
dependencies
のバージョン固定package.json
の dependencies
を、 "xxx": "1.0.0"
にすれば、確かに xxx
のバージョンは 1.0.0
になりそうですね。
ですが、これがまず大きな誤解の1つです。なぜなら、 xxx
の 1.0.0
の package.json
には、 次のような dependencies
が書かれている可能性があるからです。
"yyy": "^1.0.0"
勘がいい人ならわかると思いますが、 xxx
の 1.0.0
を npm install
しようとした時に、 yyy
の新しいバージョンが公開されていた場合どうなるでしょうか。あなたは同じ xxx
の 1.0.0
をインストールしたと思うかもしれませんが、 yyy
の品質によっては全く別の、バグや脆弱性が含まれたバージョン 1.1.0
が公開されているかもしれません。
(ちなみにこれは実例がありますし、よくテストされていないパッケージだと起こる可能性が十分にあります。また、npmの制約によって公開から72時間以上たった場合基本的に削除/unpublishができなくなるため、バグとして全世界から参照され続ける可能性もあります。)
これで npm install
と package.json
だけでは、全ての人が全く同じ開発環境を再現することが不可能だということをご理解いただけたと思います。
ちなみに、難しいことを考えずに同じ開発環境を再現しようと思った場合、特に何も考えなくても、パッケージがインストールされている node_modules
ディレクトリをリポジトリにpushしておけば、別に新たな参加者が来た時にも追加で手順など必要ない状況を作ることも可能です。が、プロジェクトの規模が拡大するにつれ、 node_modules
ディレクトリはheavyになります。(以下僕が好きな画像です 引用元)
こんなに重たいものをリポジトリにpushするなんて正気の沙汰ではないわけですが、先に書いた package-lock.json
というのが、基本的にはこの node_modules
ディレクトリを完全再現することが可能なので、 node_modules
をpushする代わりに package-lock.json
をpushするわけです。
ですので、 package-lock.json
は同じ環境を再現するために必要で、だからリポジトリにpushしたほうがよい<strong>というところまでは</strong>みなさんなんとなくご存知だと思います。
npm install
は package-lock.json
を上書きするよし、 package-lock.json
があるから安心だ。 npm install
をすれば全く同じ環境が再現するぞ!と思っている人はそうならない場合に関して考慮する必要があります。実際、 package-lock.json
が存在しても npm install
はそれを上書きする場合があります。つまり<strong>同じ開発環境を構築したい時に、 npm install
は使えない</strong>可能性があります。
可能性、というのは、npmのバージョンによる微妙な npm install
の挙動の違いや、様々なユースケース、人為的なミスが存在するため同じ開発環境を構築することができない可能性について指しています。仮に誰かがpackage.json
だけ書き換えてしまった、 package-lock.json
の commit
や push
を忘れた、コンフリクトの解決が適切でなかったなどの場合で容易に起こります。
記事のタイトルの答えはもう出てしまいましたが、まだ問題が解決していないので読み進めてみてください。
package-lock.json
からインストールするもうお分かりだと思いますが、全く同じ環境を再現しようとした場合、 package-lock.json
から node_modules
ディレクトリを構築する必要がありますが、 npm install
はそれをやってくれません(注: 絶対ではないので強めに書いています)。 <strong>npm ci
</strong>を使いましょう。
詳細に関しては冒頭に添えた参考資料を参照してほしいのですが、 npm ci
は
node_modules
ディレクトリの削除package-lock.json
と package.json
の整合性のチェックpackage-lock.json
から node_modules
を再現という動作をします。また、 npm install
のように勝手に package-lock.json
を更新することはなく、なんども同じ node_modules
を再現することができます。これでようやく、全く同じ環境を再現することができるようになりました。
これらのことを踏まえると、新しいプロジェクトに参加する場合でも package-lock.json
が正しくコミットされていれば、 npm install
ではなく npm ci
を使った方が良いです。 npm ci
が失敗した場合、上の事柄を正しくチームに共有してあげてください。
余談ですが一般的に ci
というと Continuous Integration
を指しますが、動作的には Clean Install
の略ともとれそうです。(これは想像です)
ドキュメントにもある通り npm ci
は package-lock.json
を更新しませんし、個別にパッケージをインストールすることもできません。
ですのでバージョンをアップデートしたい場合には、 package-lock.json
を更新することができる npm install
や npm update
などを使います。また、パッケージを追加したい時も今まで通り npm install zzz --save
などとすれば大丈夫です。ただしこの時、 package-lock.json
が更新されたら必ず変更点や動作に問題がないかの確認と、リポジトリへのpush、チームメンバーへの npm ci
の実施を伝えるのがベストです。
ちなみに、とても想像力の豊かな人だと、この npm install
によって依存パッケージの依存パッケージ...がバグを含んだ更新をしていた場合、どのように対処すればいいのか気になった方もいるのではないでしょうか。
実は npm
にはこの機能はありません。(あったら教えてください
が、 yarn
にはあります。 package.json
に resolutions
というフィールドを追加し、そこにパッケージのバージョンを指定することで依存ライブラリの依存ライブラリ()のバージョンを指定することが可能です。 参考までに
ちなみにセキュリティ的な観点で言いますと、これらに関して理解が浅く、対策がされていないプロジェクトの場合、そのプロジェクトが使っていそうなライブラリをソースコードから特定し、そのライブラリに悪意のあるコードを埋め込みパッチバージョンリリースなどすると次回のリリースで汚染される可能性がありそうですね。
ぜひ現在の開発フローを見直してみてください。では!