Skip to main content

npm Shrinkwrap再考:Package-LockとYarn.Lockでnpm依存関係を固定する

著者
Headshot of Assaf Hefetz

Assaf Hefetz

2018年1月10日

0 分で読めます

2016年、依存関係の管理が世界中で大きな話題になりました。無名の開発者が「left-pad」という小さなNode.jsパッケージを公開停止したことで、このパッケージに依存する何千ものプロジェクトが壊れ、世界最大級のWebサイトもいくつかダウンしたのです。npm, Inc.は事態を収束させるため、パッケージの復元に乗り出しました。

この出来事は、依存関係がいかに重要かを示しています。依存関係が存在しなかったり、想定と異なっていたりすれば、大きな問題につながることがあります。

ShrinkWrapはもう不要:Nodeの依存関係をスマートに固定する方法

依存関係をロック、つまり「ピン留め」することは、RubyやPythonをはじめとするエコシステムで広く採用されているベストプラクティスです。パッケージとその依存関係のバージョンを固定し、プロジェクトをデプロイするたびに各依存関係の同じバージョンがインストールされるようにすることで、インストールの信頼性と予測可能性を高めます。

Node.jsでは、最近まで依存関係のロックはあまり普及していませんでした。その解決策がnpm Shrinkwrapでしたが、いくつかの問題がありました。たとえば、依存関係に関する機密情報を含むshrinkwrap.jsonファイルが、パッケージの公開時に含まれてしまう可能性があります。また、Shrinkwrapを使うと新しい依存関係の追加が難しくなります。さらに、HTTPS URLを使用しない場合、Shrinkwrapにはリモートコード実行攻撃に対して脆弱になるというセキュリティリスクもあります。

最近、npmコミュニティはnpm 5に組み込まれた、より新しくスマートな解決策package-lock.jsonを歓迎しました。Yarnを使っている人は、すでにNodeの依存関係をロックする機能が組み込まれたyarn.lockを利用できました。この2つの解決策については、後ほど説明します。

その前に、ロックするべきでしょうか?

依存関係をロック(ピン留め)するメリットとデメリット

多くの実務者や、米国政府によるソフトウェア製造者向けガイド「出荷前に」は、プロジェクトのすべての依存関係をピン留めするよう推奨しています。その理由は次のとおりです。

  • 破壊的変更 - 依存関係の新しいバージョンには、コードでサポートされていない変更や、特定の実装と互換性のない変更が含まれる場合があります。

  • バグや問題 - 新しいバージョンで、パッケージ開発者が見落とした問題が発生することがあります。アップグレードのたびにコード内のすべての依存関係をテストすることはできないため、まずは自分の環境で動作確認済みのバージョンを使うのが得策です。

  • 予測可能性 - アジャイル開発と継続的デリバリーは、予測可能な方法でソフトウェアを自動デプロイできることを前提としています。一貫性のない動作や予測できない動作を引き起こす可能性のあるパッケージのアップグレードは、この予測可能性に反します。

一方、パッケージをピン留めすることにはデメリットもあります。

  • セキュリティ - Snykはオープンソースの脆弱性をスキャンするツールを提供しているため、依存関係に潜む危険性をよく認識しています。データによると、少なくともNode.jsコミュニティでは、プロジェクトの76%が既知の脆弱性を含むパッケージを使用しています。依存関係をロック/ピン留めすると、脆弱性も一緒に固定されます。セキュリティ上の問題が発見され、パッケージの作成者が修正を公開しても、古く脆弱なバージョンを使い続けることになります。

  • サードパーティモジュール - 他のプロジェクトの依存関係となるサードパーティモジュールの作成者である場合、依存関係をピン留めすると、ユーザーは同じバージョンを使い続けることになります。たとえばPythonのパッケージングに関するドキュメントでは、これは制限が厳しすぎるため、適切な方法とは見なされていません。この点を指摘してくれたDustin Ingramに感謝します。

セキュリティ上の問題は重大ですが、予測可能なインストールとデプロイのメリットに、多くの開発者は抗えないでしょう。私たちの推奨は、依存関係をロックしつつ、ビルドプロセスに脆弱性スキャンを追加することです。

セキュリティテストと監視を導入しておけば、依存関係が古くなったり、脆弱性が発見されたりしたときに把握でき、その時点で更新できます。理想的には、欠陥や脆弱性から最大限保護するため、新しいバージョンが公開されたらすぐにプロジェクト内のすべてのオープンソースコンポーネントを更新するべきです。既知の脆弱性が発見された時点で少なくともアップグレードするのが、次善の策として妥当でしょう。

package-lockで依存関係をロックする

package-lock.jsonはnpm 5の新機能で、過去のインストールで生成された依存関係ツリーを正確に記述します。これにより、依存関係が途中で更新されても、その後のインストールで同一の依存関係ツリーを生成できます。

npm 5はpackage-lockファイルを自動的に作成します。このファイルはソース管理にチェックインしてください。ソース管理へのチェックインは面倒なこともありますが、大きなメリットが2つあります。

  • package-lockをコミットすると、セキュリティレイヤーをもう1つ追加できます。SHA整合性値を使って、インストールするものが想定どおりであることを確認できます。

  • package-lockをコミットすると、npmがインストール済みパッケージのメタデータを解決する必要がなくなるため、インストールが速くなります。

package-lock.jsonの形式はnpm-shrinkwrap.jsonと同じです。npm-shrinkwrap.jsonが存在する場合、package-lock.jsonは完全に無視されます。

package-lock.jsonファイルに含まれる変数をいくつか紹介します。

  • name - package-lock.jsonでは特定のパッケージのロックを定義します。ここでパッケージ名を指定します。

  • version - 固定するバージョンです。

  • lockfileVersion - package-lockファイルのバージョン。初期値は1です。

  • packageIntegrity - package.jsonから生成される整合性値です。ssriなどのモジュールで生成できます。

  • preserveSymlinks - 環境変数NODE_PRESERVE_SYMLINKSを指定してインストールしたかどうかを示します。

  • dependencies - パッケージ名と依存関係オブジェクトの対応付けです。依存関係オブジェクトには次のプロパティがあります。

    • version - このパッケージを取得する際に使用できる、一意の識別子です。パッケージソースごとのバージョンの指定方法については、package-lockのドキュメントを参照してください。

    • bundled - 依存関係がバンドルされているかどうかを示します。バンドルされている場合は、個別の依存関係としてインストールするのではなく、親モジュールから抽出するかどうかを示します。

    • dev - この依存関係が開発用の依存関係、またはその推移的依存関係かどうかを示します。

    • その他のプロパティには、integrity、resolved、optional、dependencies(この依存関係の下位依存関係)があります。

詳しくはpackage-lock.jsonのドキュメントをご覧ください。また、npm 5でのロックファイルに関するベストプラクティスについては、Jiří Pospíšilの投稿もご覧ください。

yarn.lockで依存関係をロックする

YarnにはRubyと同様に、依存関係をロックする機能が組み込まれています。複数のマシンで一貫したインストールを実現するには、各依存関係のどのバージョンがインストールされたかを正確に把握する必要があります。

Yarnは自動生成されるyarn.lockファイルを提供し、これはプロジェクトのルートに配置されます。npmのpackage-lockと同様に、yarn.lockもソース管理にチェックインすることを想定しています。

ファイルの例を以下に示します(yarn.Lockのドキュメントより)。

       # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 
       # yarn lockfile v1 
       package-1@^1.0.0: 
           version "1.0.3" 
           resolved "https://registry.npmjs.org/package-1/-/package-1-1.0.3.tgz#a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" 
       package-2@^2.0.0: 
           version "2.0.1" 
           resolved "https://registry.npmjs.org/package-2/-/package-2-2.0.1.tgz#a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" 
           dependencies: 
               package-4 "^4.0.0" 
       ...

依存関係を追加、アップグレード、または削除すると、Yarnはyarn.lockファイルを自動的に更新します。ファイルを直接編集しないでください。Yarnは最上位のyarn.lockファイルのみを参照し、依存関係内のyarn.lockファイルは無視します。最上位のファイルには、依存関係ツリー内のすべてのパッケージのバージョンをロックするために必要な情報が含まれています。

詳しくはyarn.lockのドキュメントをご覧ください。また、Yarnとyarn.lock機能について解説したPluralsightの詳細なガイドもご覧ください。

まとめ

依存関係は非常に重要です。予期しない事態を避けるため、またパッケージをインストールするたびにネストされた依存関係のバージョンが変わる可能性があるため、多くのフロントエンド開発者は依存関係をロックまたはピン留めしています。npmユーザーはこれまで取り残されていましたが、npm 5の新機能package-lockによって、そうした状況は変わりました。

現在、npmでパッケージをロックする便利な方法は少なくとも2つあります。よりスマートな解決策としてShrinkwrapに代わるnpm 5のpackage-lock.jsonファイルと、yarn.lockとして知られるYarn組み込みの依存関係ロック機能です。

私たちからのアドバイスは、依存関係をロックしつつ、その管理も忘れないことです。Snykのようなツールを使って、依存関係の脆弱性を継続的に監視するプロセスを整えましょう。また、定期的に依存関係ツリーを見直し、古くなった依存関係を積極的に更新することで、欠陥や将来発見される脆弱性から最大限保護できます。