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. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = [GLOBAL_FOLDERS]
4. while I >= 0,
a. if PARTS[I] = "node_modules" CONTINUE
b. DIR = path join(PARTS[0 .. I] + "node_modules")
c. DIRS = DIRS + DIR
d. let I = I - 1
5. return DIRS
これは、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"例外を投げる