はじめに
前回、npmのinstall-strategyを切り替えてnode_modulesの中身を比べる記事を書きました。
そのとき、linkedを指定したのにpackage.jsonに書いていないdebugが読み込めてしまい、調べてみるとホームフォルダのnode_modulesから読み込まれていた、ということがわかりました。
requireは、見つかるまで親フォルダのnode_modulesを順にたどって探すためです。
読み込み元はrequire.resolve()で調べましたが、わかるのは最終的に見つかった場所だけでした。
あとから、require.resolve.paths()を使うと探しに行くフォルダの一覧まで表示できると知ったので、実験用プロジェクトで実際に出してみました。
環境
| 項目 | バージョン |
|---|---|
| OS | macOS 26.6.2 |
| Node.js | v24.18.1 |
実験用のプロジェクトは前回のnpm-install-strategy-labをそのまま使っています。
require.resolve.paths() とは
名前が似ているので、前回使ったrequire.resolve()と並べて整理しておきます。
Node.jsのドキュメントでは、それぞれ次のように説明されています。
| メソッド | 返すもの |
|---|---|
require.resolve(request) |
実際に読み込まれるファイルのパス(モジュールの読み込みはしない) |
require.resolve.paths(request) |
requestを探すときに見に行くパスの配列 |
require.resolve()は「最終的にどこで見つかったか」、require.resolve.paths()は「どこを探しに行くか」を教えてくれる、という違いです。
なお、require.resolve()は見つからないとMODULE_NOT_FOUNDのエラーになります。
また、fsやhttpのような組み込みモジュールを渡すと、require.resolve.paths()は配列ではなくnullを返すとされています。
試してみる
プロジェクトの中で debug の探索先を出してみる
$ node -e "console.log(require.resolve.paths('debug'))"
- 実行結果
➜ npm-install-strategy-lab node -e "console.log(require.resolve.paths('debug'))"
[
'/Users/user_name/Test/test/npm-install-strategy-lab/node_modules',
'/Users/user_name/Test/test/node_modules',
'/Users/user_name/Test/node_modules',
'/Users/user_name/node_modules',
'/Users/node_modules',
'/node_modules',
'/Users/user_name/.node_modules',
'/Users/user_name/.node_libraries',
'/Users/user_name/.anyenv/envs/nodenv/versions/24.18.1/lib/node'
]
プロジェクトのnode_modulesから始まって、1階層ずつ親フォルダに上がりながら、ルートの/node_modulesまで6つ並んでいました。
前回ハマったホームフォルダのnode_modulesは、上から4番目に入っています。
前回は探す順番を手で書きましたが、require.resolve.paths()を使えば、その順番をそのまま確認できました。
その下には、~/.node_modules、~/.node_libraries、Node.jsのインストール先のlib/nodeという、node_modulesとは名前の違うパスが3つ続いていました。
ドキュメントによると、これらはnode_modulesをたどっても見つからなかったときに探されるフォルダとされています。
このときdebugが本当にどこにもないのかを、同じ場所でrequire.resolve()を使って確かめてみました。
$ node -e "console.log(require.resolve('debug'))"
- 実行結果
➜ npm-install-strategy-lab node -e "console.log(require.resolve('debug'))"
node:internal/modules/cjs/loader:1520
throw err;
^
Error: Cannot find module 'debug'
Require stack:
- /Users/user_name/Test/test/npm-install-strategy-lab/[eval]
(スタックトレースは省略)
code: 'MODULE_NOT_FOUND',
requireStack: [ '/Users/user_name/Test/test/npm-install-strategy-lab/[eval]' ]
}
Node.js v24.18.1
require.resolve()はMODULE_NOT_FOUNDのエラーになりました。
一覧のどこにもdebugがなかったということですが、require.resolve.paths()のほうはエラーにならず、探しに行く一覧をそのまま返していました。
ファイルの場所で一覧が変わるか
node -eはカレントフォルダを基準にしているようだったので、サブフォルダにファイルを作って実行してみました。
$ mkdir sub
$ echo 'console.log(require.resolve.paths("debug"));' > sub/paths.js
$ node sub/paths.js
- 実行結果
➜ npm-install-strategy-lab node sub/paths.js
[
'/Users/user_name/Test/test/npm-install-strategy-lab/sub/node_modules',
'/Users/user_name/Test/test/npm-install-strategy-lab/node_modules',
'/Users/user_name/Test/test/node_modules',
'/Users/user_name/Test/node_modules',
'/Users/user_name/node_modules',
'/Users/node_modules',
'/node_modules',
'/Users/user_name/.node_modules',
'/Users/user_name/.node_libraries',
'/Users/user_name/.anyenv/envs/nodenv/versions/24.18.1/lib/node'
]
実行したフォルダは同じnpm-install-strategy-labなのに、今度はsub/node_modulesから始まり、ルートまでが7つに増えました。
require.resolve.paths()を書いたファイルがあるsubフォルダを基準に、そこから親フォルダへたどっているようです。
node -eのときはファイルがないので、カレントフォルダが基準になっていたのだと思います。
組み込みモジュールと相対パスを渡してみる
$ node -e "console.log(require.resolve.paths('fs'))"
$ node -e "console.log(require.resolve.paths('./foo'))"
- 実行結果
➜ npm-install-strategy-lab node -e "console.log(require.resolve.paths('fs'))"
null
➜ npm-install-strategy-lab node -e "console.log(require.resolve.paths('./foo'))"
[ '/Users/user_name/Test/test/npm-install-strategy-lab' ]
fsを渡すと、ドキュメントのとおりnullが返ってきました。
組み込みモジュールはNode.jsに最初から入っているので、node_modulesを探しに行く必要がないということだと思います。
./fooのように相対パスで渡すと、返ってきたのはカレントフォルダ1つだけで、node_modulesは1つも出てきませんでした。
./で始まる場合は、node_modulesをたどらず、書いた場所から直接探しているようです。
debugのようにパッケージ名だけで書いたときとは、探しに行く場所がまったく違うことがわかりました。
わかったこと
前回は、linkedにしたのにdebugが読み込めてしまい、require.resolve()で読み込み元を調べてからホームフォルダのnode_modulesに気づきました。
require.resolve.paths()の一覧には、そのホームフォルダのnode_modulesが上から4番目にはっきり出ていました。
最初にこの一覧を見ていれば、npm-install-strategy-labのフォルダより上にあるnode_modulesまで探しに行っていることにすぐ気づけたのかなと感じました。
また、node -eとsub/paths.jsでは、同じフォルダで実行したのに、一覧の1番目に出てくるnode_modulesが違いました。
どのフォルダから探し始めるかは、コマンドを実行した場所ではなく、requireを書いたファイルがある場所で決まるようです。
同じプロジェクトの中でもファイルの置き場所によって探しに行くフォルダが変わるので、調べるときは問題のファイルと同じ場所で確かめるのがよさそうです。
一方で、組み込みモジュールのfsや./fooのような相対パスでは、node_modulesは1つも出てきませんでした。
今回試した中では、親フォルダのnode_modulesまでたどったのはdebugのようにパッケージ名で書いたときだけだったので、前回のように思わぬ場所から読み込まれないかは、パッケージ名でrequireしたときに気をつければよさそうです。
まとめ
前回の記事では、怪しいときはrequire.resolve()で読み込み元を確かめるのが確実だと書きました。
ただ、require.resolve()でわかるのは見つかった場所だけで、そこに至るまでにどこを探していたのかは、自分で想像するしかありませんでした。
今回require.resolve.paths()を使ってみて、探しに行く候補をNode.js自身に一覧で出してもらえることがわかりました。
前回は頭の中で組み立てて手で書いた探索順を、コマンド1行でそのまま確認できるのはいいなと感じました。
今後、モジュールが思わぬ場所から読み込まれていると感じたときは、require.resolve()で読み込み元を見るのと合わせてrequire.resolve.paths()で探しに行く候補も確かめてみようと思います。
参考
- npmのinstall-strategyを4種類切り替えてnode_modulesの中身を比べてみた - Qiita
- Modules: CommonJS modules - require.resolve(request[, options]) | Node.js
- Modules: CommonJS modules - require.resolve.paths(request) | Node.js
- Modules: CommonJS modules - Loading from node_modules folders | Node.js
- Modules: CommonJS modules - Loading from the global folders | Node.js