Node.jsデザインパターンを読んで勉強中なので
そちらの備忘録として投稿していきます。
まず初めにNode.jsがどうやって異なるバージョンの同一モジュールを管理し、
「依存地獄(dependency hell)」を回避しているかを整理します。
Node.jsの依存解決アルゴリズム
Node.jsがモジュールを読み込む時に
指定されたモジュール名をもとにモジュールをコアモジュール、もしくはローカルファイル
から探すために使用されるアルゴリズムは
以下の三段階に分かれている。
-
コアモジュール
まずは指定されたモジュール名がNodeのコアモジュールかどうか調べる -
ファイルモジュール
コアモジュールに見つからなかった場合、ローカルファイルシステムを探す。
モジュール名が「/」で始まる場合は絶対パスとして、「./」、「../」で始まる場合は
requireを呼び出しているファイ ルからの相対パスとして解釈される。 -
パッケージモジュール
モジュール名の開始文字列が「/」、「./」、「../」のいずれでもない場合は、
requireを呼び出しているファイルと同じディレクトリの下にあるnode_modulesディレクトリの中を探す。
それでも見つからない、もしくはnode_modulesディレクトリがなかった場合
さらに親のディレクトリを探しにいき、上へ上へ探索していき
ローカルファイルシステムのルートに到達するまで探す。
ファイルモジュールとパッケージモジュールのロード時のルール
ファイルモジュールとパッケージモジュールのロード時には
上記のアルゴリズム以外にルールがある。
- 指定されたモジュール名と同じ名前のファイルがあれば
(なければ拡張子.jsもしくは.jsonを補完して確認)
そのファイルをロードする。 - 指定されたモジュール名と同じ名前のディレクトリがあれば、その配下にpackage.jsonファイルがないか調べる。
存在すれば、ファイル中のmainプロパティで指定されたファイルをロードする。 - 指定されたモジュール名ディレクトリ配下にindex.jsというファイルがあればロードする。
整理
myApp/ ├── foo.js └─┬ node_modules `` ``├── depA `` `` |`` `` ``└── index.js `` `` ├── depB `` `` │`` `` ``├── bar.js `` `` │`` `` ``└── node_modules `` `` │`` `` `` `` `` `` `` └── depA `` `` │`` `` `` `` `` `` `` `` `` `` `` └── index.js `` `` └── depC `` `` `` `` `` ``├── foobar.js `` `` `` `` `` ``└── node_modules `` `` `` `` `` `` `` `` `` `` └── depA `` `` `` `` `` `` `` `` `` `` `` `` `` `` └── index.js
上記のようなディレクトリ構成として
各ファイルからモジュールdepAをロードしてみる。
-
/myApp/foo.jsからrequire('depA')を呼び出した場合
/myApp/node_modules/depA/index.jsをロードする -
/myApp/node_modules/depB/bar.jsからrequire('depA')を呼び出した場合
/myApp/node_modules/depB/node_modules/depA/index.jsをロードする -
/myApp/node_modules/depC/foobar.jsからrequire('depA')を呼び出した場合
/myApp/node_modules/depC/node_modules/depA/index.jsをロードする
まとめ
依存解決アルゴリズムにより、Nodeは複雑な依存関係も解決でき、
ひいては大規模なアプリケーションにおいて、
バージョン間の衝突なく何百、何千といった依存パッケージをもつことが可能になる
参考書籍
Node.jsデザインパターン 第2版