ミニマムなパッケージマネージャを作ってみました。実装はGoでおこなっています。パッケージマネージャの仕組みについて少しばかり学べたので記録として残しておこうと思います。
参考元
動機は以下のリポジトリ・記事で、挙動としてカバーしているスコープも同じです。
過程で学べたこと
詳細な解説はこちらの記事が分かりやすいため、割愛します。ここでは私がパッケージマネージャの自作を通して勉強になったことをつらつらと書いていきます。
Lockfileの役割
Lockfileとは npm
における package.lock.json
や、 yarn
における yarn.lock
です。install
の際にはこのLockfileの情報を元にインストールが行われ、パッケージの依存関係解決後に最新情報をLockfileに記録しておきます。
Lockfileの役割としては大きく以下です。
- パッケージ全体の依存ツリーを正確に表現することで、開発者、デプロイ、CI等の間で常に同じバージョンのパッケージを利用できる
- node_modules のその時の状態をバージョン管理(e.g. git) できる
- パッケージインストール時に、マニフェストの取得やバージョンの解決をスキップできる
セマンティックバージョニング
セマンティックバージョニングはソフトウェアのバージョンを表現する際によく用いられるルールです。Major.Minor.Patch
という形で表記され、上から順に以下のような意味合いを持ちます。
多くのnpmパッケージでは、^(キャレット)
や ~(チルダ)
がバージョンの前に付与されており、これによって、後方互換性を保ちつつ一定のレンジ内で最新のバージョンをインストールすることが可能となります。
再帰的な依存関係解決
各パッケージの依存関係はネストするため、再帰的な依存関係の解決とバージョンのコンフリクト解消が必要です。 例えば、以下のようなパッケージを例として考えてみます。
{ "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がパッケージを探索するアルゴリズムに基づくものらしいです。
まとめ
考慮不足なところも多いですが、最低限の動きだけでも実現できて嬉しいです。
普段何げなく利用しているパッケージマネージャですが、install
1つとっても考えることが多く勉強になりました。