導入:なぜ Hoisting が重要なのか
大規模なプロジェクトでモノレポ構成を採用すると、複数のサブパッケージが共通のライブラリ(ReactやLodashなど)を依存関係に持つことが一般的です。もし各パッケージが個別に依存関係をインストールすると、ディスク容量が圧迫されるだけでなく、インストール時間が長大化し、最悪の場合は「複数のバージョンが混在して動かない」といった依存関係の不整合を引き起こします。これを解決するのが「Hoisting(巻き上げ)」という仕組みです。本記事では、この仕組みを正しく理解し、現場で安全に運用するためのポイントを解説します。
基礎知識:Hoisting とは何か
Hoisting とは、npm、yarn、pnpmなどのパッケージマネージャーが持つ機能で、サブパッケージが共通して必要とする依存関係を、ルート階層の node_modules にまとめて配置するプロセスを指します。
通常、node_modules はツリー構造になっていますが、Hoisting が働くと、重複するパッケージをルートに引き上げる(巻き上げる)ことで、フラットな構造に近い状態を作ります。これにより、同一ライブラリの重複インストールを避け、ビルドの効率化とディスク容量の削減を実現します。
実装と解決策
Yarn Workspaces を例に挙げると、設定は非常にシンプルです。ルートの package.json に workspaces を定義するだけで、パッケージマネージャーが自動的に依存関係の解析と Hoisting を行います。
設定例(root/package.json):
{
“private”: true,
“workspaces”: [
“packages/”
]
}
この設定により、各パッケージの node_modules にある重複したライブラリが、可能な限りルートの node_modules へ移動されます。
サンプルプログラム:依存関係の定義と確認
以下のコードは、モノレポ内のパッケージで共通のライブラリを定義する例です。
// packages/app-a/package.json
{
“name”: “@my-repo/app-a”,
“dependencies”: {
“lodash”: “^4.17.21”
}
}
// packages/app-b/package.json
{
“name”: “@my-repo/app-b”,
“dependencies”: {
“lodash”: “^4.17.21”
}
}
// 実行手順:
// 1. ルート階層でインストールを実行
// $ yarn install
// 2. node_modulesの状態を確認(ルートにlodashが巻き上げられていることを確認)
// $ ls node_modules/lodash
// 3. 依存関係のツリーを確認
// $ yarn list –pattern lodash
応用・注意点:現場で陥りやすい罠
Hoisting は非常に便利ですが、以下の点に注意が必要です。
1. ホイスティングの制限(幽霊依存関係)
Hoisting によってルートに巻き上げられたライブラリは、本来サブパッケージの package.json に記述されていないはずのライブラリであっても、サブパッケージから「require」や「import」できてしまうことがあります。これを「幽霊依存関係(Phantom Dependencies)」と呼びます。明示的に依存関係を定義していないライブラリをコード内で参照すると、将来的に Hoisting の挙動が変わった際にビルドエラーになります。常に利用するライブラリは必ず各パッケージの package.json に記述してください。
2. バージョンの不整合
異なるサブパッケージで、互換性のないバージョンのライブラリを必要としている場合、Hoisting は正しく行われません。この場合、片方はルートに、もう片方は各パッケージの node_modules に配置されます。これが原因で予期せぬ挙動が発生することがあるため、モノレポ内では可能な限り、共通ライブラリのバージョンを揃える運用(dependency-checkの導入など)が推奨されます。
3. pnpm の場合
pnpm はシンボリックリンクを活用した独自の Hoisting 制御を行っています。デフォルトでは Hoisting を制限することで、「幽霊依存関係」を排除する設計になっています。厳密な依存関係管理を求める場合は、pnpm の検討も非常に有効です。

コメント