【JavaScript】'MODULE_NOT_FOUND' の意味と解決法
はじめに
JavaScriptでアルゴリズムを実装したときに、VS Codeで出会ったエラーの意味と実際に解決した方法をまとめました。
メッセージ内容
node reverseInorderTraversal.js
node:internal/modules/cjs/loader:1146
throw err;
^
Error: Cannot find module '/Users/mavo/project/algorithm-solutions/binary-tree/problems/12_reverseInorderTraversal/js/tests/reverseInorderTraversal.js'
at Module._resolveFilename (node:internal/modules/cjs/loader:1143:15)
at Module._load (node:internal/modules/cjs/loader:984:27)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
at node:internal/main/run_main_module:28:49 {
code: 'MODULE_NOT_FOUND',
requireStack: []
}
Node.js v20.12.2
結論
実行ファイルがそもそも間違っていたことです。
// 正
node reverseInorderTraversalTest.js
// 誤
node reverseInorderTraversal.js
原因
指定したファイルを特定できなかったことが原因です。
Node.js 実行の流れ
1. Node を起動してエントリファイルを指定
node reverseInorderTraversal.js
を実行すると、
Node.js(以下、Nodeと表記) はまず reverseInorderTraversal.jsを「エントリモジュール」として読み込もうとします。
Node が reverseInorderTraversal.js を "エントリモジュール" としてロードするということは、そのファイルが 最初に読み込まれ、実行プロセスの入口(entry point)になり、そこから他の require / import をたどって、必要なファイルを順次ロードしていく、という流れを意味します。
2. エントリファイルの読み込み
Node は指定されたファイルのパスを解決し、ファイルをディスクから読み取ります。
CommonJS の場合、ファイルの中身は内部的に次のような関数でラップされます。
(function (exports, require, module, __filename, __dirname) { /* 実装したコード */ })
そのラップ関数を V8 にコンパイルして実行します。
3. require()(または import)が呼ばれる
エントリ内で require('...') が実行されると、Node は Module._resolveFilename を呼んで モジュール識別子(引数)をファイルシステム上の実ファイル名に変換しようとします。
require() は、ただ単に文字列(モジュール名やパス)を受け取ります。
const reverseInorderTraversal = require('../src/reverseInorderTraversal.js');
この例で言うと、../src/reverseInorderTraversal.js
は、この時点では単なる文字列に過ぎないということです。
Node はこれをモジュール識別子と呼びます。
この時点では、まだ「実際のファイルの場所」は決まっていません。
ここから Node.js は「この識別子はどこのファイルを指しているのか?」を解決します
4. モジュール解決アルゴリズム(_resolveFilename の仕事)
内部的には Module._resolveFilename という関数が呼ばれ、「識別子 → 実際のファイルパス」の変換を行ないます。
require('識別子')
│
├─ (絶対パス) → そのまま利用
│
├─ (相対パス) → 呼び出し元からの相対位置で解決
│
└─ (非相対パス) → 組み込み → node_modules探索
│
└─ 見つからない → MODULE_NOT_FOUND
4-1. 絶対パスだった場合
/
で始まる場合は、すでに絶対パスとみなし、そのパスをそのまま使います。
拡張子 .js
, .json
, .node
が省略されていれば順番に補完して探します。
(例)/path/to/foo
→ /path/to/foo.js
→ /path/to/foo.json
→ /path/to/foo.node
4-2. 相対パスだった場合
./
や ../
で始まる場合は、呼び出し元ファイルの場所(__dirname)からの相対位置として解決します。
4-3. 非相対モジュール名だった場合
-
非相対モジュール名(express など)なら node_modules を探索して上位ディレクトリへ順に上がりながら探す。
-
解決時に拡張子を補完(
.js
,.json
,.node
の順)したり、ディレクトリなら package.json の main を見て、それでもなければ index.js を試します。 -
組み込みモジュール(fs, path, http など)かどうか確認し、あればそれを使います。
なければ node_modules ディレクトリを探索 -
呼び出し元のディレクトリから上に向かって、node_modules を順に探します。
-
パッケージがディレクトリの場合、package.json の "main" を見て入口ファイルを決定します。
5. 見つからなければ MODULE_NOT_FOUND を投げる
Module._resolveFilename がファイルを特定できなければ例外(今回の Error: Cannot find module '…')を投げます。
見つかったら Module._load → キャッシュ → コンパイル → 実行
見つかれば、そのモジュールを読み込み(初回はコンパイルしてラップ関数を実行)、module.exports をキャッシュして返します。
まとめ
今回のケースでは、単に指定するファイルが間違っていたので、モジュールが見つからず処理ができなかったということでした。
初歩的なミスではあるものの、プログラムが動かないと大変です。
今回、内部でざっくりどんな処理が行われているのかを掴んだ上で、どんなエラーになぜつながっているのかを一部理解できたと思います。
エラーが出たら、まずはファイル名・パス・カレントディレクトリを確認するようにして、気をつけましょう。
最後までお読みいただき、ありがとうございました。
参考URL・記事
ChatGPT解説
Discussion