Node.jsのrequireがどのようにモジュールを探すか、そのプロセスを説明した下記公式ドキュメントの和訳です。
内容に誤りがあればお教えください。
Node.jsのモジュール解決プロセス
require(X) をパス Y にあるモジュールで実行したとき、
- もし、 X がコアモジュールなら、
- a. コアモジュールを返す
- b. 終了
- もし、 X が "/" で始まるなら、
- a. Y のパスをファイルシステムルートにセットしなおす
- もし、 X が "./"、"/"、"../"のどれかで始まるなら、
- a. LOAD_AS_FILE(Y + X)
- b. LOAD_AS_DIRECTORY(Y + X)
- c. 例外"not found"を投げる
- LOAD_SELF_REFERENCE(X, dirname(Y))
- LOAD_NODE_MODULES(X, dirname(Y))
- 例外"not found"を投げる
LOAD_AS_FILE(X)
- もし、 X がファイルなら、 X をそのファイル拡張子の形式としてロードする。 終了
- もし、 X.js がファイルなら、 X.js をJavaScriptテキストとしてロードする。 終了
- もし、 X.json がファイルなら、 X.json をパースしてJavaScriptオブジェクトにする。 終了
- もし、 X.node がファイルなら、 X.node をバイナリアドオンとしてロードする。 終了
LOAD_INDEX(X)
- もし、 X/index.js がファイルなら、 X/index.js をJavaScriptテキストとしてロードする。 終了
- もし、 X/index.json がファイルなら、 X/index.json をパースしてJavaScriptオブジェクトにする。 終了
- もし、 X/index.node がファイルなら、 X/index.node をバイナリアドオンとしてロードする。 終了
LOAD_AS_DIRECTORY(X)
- もし、 X/package.json がファイルなら、
- a. X/package.json をパースして、
mainフィールドを探す。 - b. もし、
mainがfalsyな値なら、2に進む。 - c.
let M = X + main - d. LOAD_AS_FILE(M)
- e. LOAD_INDEX(M)
- f. LOAD_INDEX(X) 非推奨
- g. 例外"not found"を投げる
- a. X/package.json をパースして、
- LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START)
- let DIRS = NODE_MODULES_PATHS(START)
- for each DIR in DIRS:
- a. LOAD_PACKAGE_EXPORTS(DIR, X)
- b. LOAD_AS_FILE(DIR/X)
- c. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1.letPARTS=pathsplit(START)2.letI=countofPARTS-13.letDIRS=[GLOBAL_FOLDERS]4.whileI>=0,a.ifPARTS[I]="node_modules"CONTINUEb.DIR=pathjoin(PARTS[0..I]+"node_modules")c.DIRS=DIRS+DIRd.letI=I-15.returnDIRSこれは、NODE_MODULES_PATHS('/root/a/b/c')を与えたときに、下記のリストを返す処理です。
['$HOME/.node_modules','$HOME/.node_libraries','$PREFIX/lib/node','/root/a/b/c/node_modules','/root/a/b/node_modules','/root/a/node_modules','/root/node_modules','/node_modules',]LOAD_SELF_REFERENCE(X, START)
- STARTに最も近いパッケージのスコープを探す。
- もし、スコープが見つからなければ、return。
- もし、
package.jsonに"exports"がなければ、return。 - もし、
package.jsonの name が X で始まらないなら、"not found"例外を投げる。 - それ以外の場合は、このパッケージに相対的な X の残りの部分を、
package.jsonのnameでLOAD_NODE_MODULESを介してロードされたかのようにロードする。
LOAD_PACKAGE_EXPORTS(DIR, X)
このプロセスは右記の規格に対応するものです→jkrems/proposal-pkg-exports: Proposal for Bare Module Specifier Resolution in node.js
- X を名前とサブパスの組み合わせとしての解釈を試みる。その名前は @scopeにスラッシュ(
/)で始まるサブパスが続く形式を想定する。 - もし、 X このパターンにマッチしない、もしくは、DIR/名前/package.jsonがファイルでないなら、return。
- DIR/name/package.json をパースして、 "exports" フィールドを探す。
- もし、 "exports" が null か undefined なら、return。
- もし、 "exports" が object での場合、"." 始まりのキーがありつつ "." 始まりでないキーもあるなら、"invalid config"例外を投げる。
- もし、 "exports" が string または "." 始まりのキーが1つもない object なら、それの値を "." として扱う。
- もし、 サブパスが "." で "exports" が "." エントリーを持たないなら、return
- "exports" の中からサブパスで始まる最も長いキーを探す。
- もし、キーが見つからないなら、 "not found" 例外を投げる。
let RESOLVED = fileURLToPath(PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name), exports[key], subpath.slice(key.length), ["node", "require"]))(これはESMリゾルバーで定義されたもの)- もしキーが "/" で終わるなら:
- a. LOAD_AS_FILE(RESOLVED)
- b. LOAD_AS_DIRECTORY(RESOLVED)
- それ以外の場合は、
- a. もし RESOLVED がファイルなら、そのファイル拡張子フォーマットでロードする。 終了
- "not found"例外を投げる