Node で .js を実行したら、次のエラーで落ちることがあります。
ReferenceError: __dirname is not defined in ES module scope
__filename を使っていれば、同じ形で __filename is not defined in ES module scope になります。原因は同じで、そのファイルが ES Module(ESM)として扱われているのに、CommonJS 専用の __dirname / __filename を使っていることです。コードは変えていないのに急に出たなら、直前に package.json へ "type": "module" を足したか、ファイルを .mjs にしたケースがほとんどです(import を書いた素の .js が、新しい Node の自動検出で ESM 扱いになる場合もあります。後述の表を参照)。
なぜ「急に」出るのか(ESM 扱いになる条件)
同じ __dirname のコードでも、そのファイルが CommonJS 扱いか ESM 扱いかで結果が変わります。次のどれかに当てはまるとファイルは ESM になり、__dirname が使えなくなります。
| 条件 | ファイルの扱い |
|---|---|
近い package.json に "type": "module" がある .js | ESM |
拡張子が .mjs | ESM(常に) |
種別未確定の .js に import/export などの ESM 構文がある(Node 20.19 以上/22.7 以上の自動検出) | ESM |
上記に当てはまらない .js / .cjs | CommonJS(__dirname が使える) |
3行目に注意してください。type: "module" も .mjs も付けていない素の .js でも、import 文を書いていると新しい Node(20.19 以上/22.7 以上)の自動検出で ESM 扱いになり、そこで __dirname を使うと落ちます(__dirname 単体で ESM 構文が無ければ CommonJS のままです)。手元の古い版では出ないのに、新しい版や CI でだけ出る、という形になります。詳しくは Cannot use import statement outside a module の自動検出の項を参照してください。
ESM スコープには、CommonJS が用意していた __dirname / __filename / require / module.exports / exports が存在しません。だから「TypeScript や別プロジェクトの設定に合わせて type: "module" を入れた」「拡張子を .mjs にした」タイミングで、それまで動いていた __dirname が一斉に落ちます。手元の古い設定では出ず、type: "module" を入れた環境でだけ出る、という形になります。
再現(最小構成)
package.json を ESM 指定にします。
{
"type": "module"
}
同じフォルダの .js で __dirname を使います。
// index.js(type:module 下なので ESM 扱い)
import { join } from 'node:path';
const here = __dirname;
console.log(join(here, 'data.json'));
node index.js を実行すると ReferenceError: __dirname is not defined in ES module scope が出て、終了コードは 1 になります。__dirname を参照した行でそのまま落ちるので、data.json の有無は関係ありません。
解決1:import.meta.url から復元する(どの版でも動く)
ESM には、実行中ファイルの URL を指す import.meta.url があります。これを fileURLToPath でパスへ変換し、path.dirname でディレクトリを取れば、__dirname と同じものを自分で作れます。
// index.js(ESM のまま。__dirname を import.meta.url から復元)
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
console.log(join(__dirname, 'data.json'));
__filename が必要なら const __filename = fileURLToPath(import.meta.url); です。import.meta.url は ESM ならどの Node のバージョンでも使えるので、この方法は環境を選びません。CI やサーバーの Node 版が分からないときは、まずこれを使うのが安全です。
解決2:import.meta.dirname を使う(Node 20.11 / 21.2 以上)
新しい Node には、__dirname / __filename の置き換えがそのまま用意されています。
// index.js(Node 20.11 / 21.2 以上なら、これだけでよい)
import { join } from 'node:path';
console.log(join(import.meta.dirname, 'data.json'));
import.meta.dirname と import.meta.filename は Node v21.2.0 で追加され、LTS の v20.11.0 にバックポートされました。ローカルの file: で実行される ESM で使えます。1 行で済むので、対象の Node 版を 20.11 以上に固定できるなら、こちらが最短です。なお「試験的(experimental)」の扱いが外れたのは Node 22.16 / 24.0 以降です。試験的 API を避けたい方針なら、実行はできても解決1(fileURLToPath)に寄せておくと安全です。
版境界の落とし穴(手元では動くのに CI で落ちる)
import.meta.dirname は古い版では例外ではなく undefined になります。そのため、手元の新しい Node(例:22 系)ではそのまま動くのに、CI やデプロイ先が古い版(20.10 以前、18 系など)だと、
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined
のように、__dirname is not defined とは別のエラーに化けて落ちます。join(undefined, 'data.json') を実行してしまうためです。最近の解説記事をコピーしてこの症状に当たったら、対象環境の Node 版を確認してください。版を 20.11 以上に固定できないなら、解決1(fileURLToPath 版)にすれば全バージョンで同じ結果になります。
仕組み:ESM スコープに CommonJS の変数は無い
__dirname / __filename は言語機能ではなく、CommonJS がモジュールを実行するときに関数の引数として渡していた変数です(require / module / exports も同じ)。ESM はこの仕組みを使わないため、これらの変数はそもそも定義されません。だから ESM 扱いのファイルで __dirname を書くと「未定義の変数を参照した」= ReferenceError になります。
代わりに ESM は、実行中モジュールの情報を import.meta から渡します。import.meta.url はどの版でも、import.meta.dirname / import.meta.filename は新しい版で使える、というのがこのエラーの全体像です。
切り分け(うまくいかないとき)
__dirnameと一緒にrequire is not definedやmodule is not definedも出る:どれも同じ原因(ESM 扱い)です。requireの側の対処は require is not defined in ES module scope を参照してください。- 本当は ESM にしたくなかった:
package.jsonの"type": "module"を外すか、そのファイルだけ.cjsにリネームすれば CommonJS 扱いに戻り、__dirnameがそのまま使えます。逆に ESM に寄せる場合、type: "module"はそのpackage.json配下のサブフォルダも含めた.jsすべてを ESM にする点に注意してください。 import.meta.dirnameにしたらundefinedになった/別のエラーが出た:上の「版境界の落とし穴」の通り、対象の Node が 20.11 未満です。解決1のfileURLToPath版に切り替えてください。- そもそも
import文の側でCannot use import statement outside a moduleが出る:症状が反対(CJS 扱いなのに ESM 構文を書いた)のケースです。対処は Cannot use import statement outside a module を参照してください。