🌊

【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・記事

https://developer.mozilla.org/ja/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/Introduction

ChatGPT解説

https://link-and-motivation.hatenablog.com/entry/2022/09/28/161218

Discussion