3歩進んで2歩下がる

Software Engineer

ミニマムなパッケージマネージャを作ってみる

ミニマムなパッケージマネージャを作ってみました。実装はGoでおこなっています。パッケージマネージャの仕組みについて少しばかり学べたので記録として残しておこうと思います。

github.com

参考元

動機は以下のリポジトリ・記事で、挙動としてカバーしているスコープも同じです。

github.com zenn.dev

過程で学べたこと

詳細な解説はこちらの記事が分かりやすいため、割愛します。ここでは私がパッケージマネージャの自作を通して勉強になったことをつらつらと書いていきます。

zenn.dev

Lockfileの役割

Lockfileとは npm における package.lock.json や、 yarn における yarn.lock です。install の際にはこのLockfileの情報を元にインストールが行われ、パッケージの依存関係解決後に最新情報をLockfileに記録しておきます。

Lockfileの役割としては大きく以下です。

  • パッケージ全体の依存ツリーを正確に表現することで、開発者、デプロイ、CI等の間で常に同じバージョンのパッケージを利用できる
  • node_modules のその時の状態をバージョン管理(e.g. git) できる
  • パッケージインストール時に、マニフェストの取得やバージョンの解決をスキップできる

qiita.com

セマンティックバージョニング

セマンティックバージョニングはソフトウェアのバージョンを表現する際によく用いられるルールです。Major.Minor.Patch という形で表記され、上から順に以下のような意味合いを持ちます。

多くのnpmパッケージでは、^(キャレット)~(チルダ) がバージョンの前に付与されており、これによって、後方互換性を保ちつつ一定のレンジ内で最新のバージョンをインストールすることが可能となります。

qiita.com docs.npmjs.com

再帰的な依存関係解決

各パッケージの依存関係はネストするため、再帰的な依存関係の解決とバージョンのコンフリクト解消が必要です。 例えば、以下のようなパッケージを例として考えてみます。

{
  "name": "dev",
  "version": "1.0.0",
  "dependencies": {
    "eslint-scope": "^5.0.0",
    "estraverse": "^3.0.0"
  }
}

この場合、依存解決の過程で estraverse が3バージョン必要となり、それぞれのメジャーバージョンが異なるため衝突してしまいます。

  • ^3.0.0 (直下)
  • ^4.1.1 (eslint-scope@^5.0.0 -> estraverse@4.1.1)
  • ^5.2.0 (eslint-scope@^5.0.0 -> esrecurse@4.1.0 -> estraverse@^5.2.0)

本家の npm install 実行時、これらのパッケージは最終的に以下のように node_modules 配下へ展開されます。

node_modules/eslint-scope/node_modules/estraverse
node_modules/esrecurse/node_modules/estraverse
node_modules/estraverse

つまり、ルートで指定されたパッケージのバージョンはそのまま node_modules 直下へ、それ以外の各パッケージが依存しているバージョンはそれぞれのパッケージ配下へさらに node_modules ディレクトリが掘られていきます。

これはどうやらパッケージのインポート時にNode.jsがパッケージを探索するアルゴリズムに基づくものらしいです。

maku77.github.io

まとめ

考慮不足なところも多いですが、最低限の動きだけでも実現できて嬉しいです。 普段何げなく利用しているパッケージマネージャですが、install 1つとっても考えることが多く勉強になりました。