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は複雑な依存関係も解決でき、
ひいては大規模なアプリケーションにおいて、
バージョン間の衝突なく何百、何千といった依存パッケージをもつことが可能になる