Node で .js を実行したら、次のエラーで落ちることがあります。
ReferenceError: require is not defined in ES module scope, you can use import instead
Node は続けて、原因のヒントも出します。
This file is being treated as an ES module because it has a '.js' file extension and '.../package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
つまり、そのファイルが ES Module(ESM)として扱われているのに、CommonJS の require() を使っているのが原因です。コードは変えていないのに急に出たなら、よくあるのは直前に package.json へ "type": "module" を足したか、ファイルを .mjs にしたケースです(新しい Node では、後述の自動検出で ESM 扱いになることもあります)。
なぜ「急に」出るのか(ESM 扱いになる条件)
同じ require() のコードでも、そのファイルが CommonJS 扱いか ESM 扱いかで結果が変わります。次のどれかに当てはまるとファイルは ESM になり、require が使えなくなります。
| 条件 | ファイルの扱い |
|---|---|
近い package.json に "type": "module" がある .js | ESM |
拡張子が .mjs | ESM(常に) |
種別未確定の .js に import/export などの ESM 構文がある(Node 20.19 以上/22.7 以上の自動検出) | ESM |
上記に当てはまらない .js / .cjs | CommonJS(require が使える) |
3行目に注意してください。import 文と require() が混在した .js を新しい Node で実行すると、自動検出でファイル全体が ESM 扱いになり、require の側が落ちます(type: "module" も .mjs も無いのに出るのはこのパターンです。詳細は Cannot use import statement outside a module の自動検出の項)。
ESM スコープには、CommonJS が用意していた require / module.exports / exports / __dirname / __filename が存在しません。だから「TypeScript や別プロジェクトの設定に合わせて type: "module" を入れた」「拡張子を .mjs にした」タイミングで、それまで動いていた require() が一斉に落ちます。手元の古い設定では出ず、type: "module" を入れた環境でだけ出る、という形になります。
再現(最小構成)
package.json を ESM 指定にします。
{
"type": "module"
}
同じフォルダの .js で require() を使います。
// index.js(type:module 下なので ESM 扱い)
const os = require('os');
console.log(os.platform());
node index.js を実行すると ReferenceError: require is not defined in ES module scope が出て、終了コードは 1 になります。
解決1:require を import に置き換える(基本)
ESM ではファイル先頭の import 文でモジュールを読み込みます。組み込みモジュールは node: プレフィックスを付けると、同名のユーザーパッケージに解決を奪われず、必ず組み込みモジュールへ解決されます。
// index.js(ESM のまま。require を import 文に置き換えた)
import os from 'node:os';
console.log(os.platform());
これで終了コード 0 で動きます。自分のモジュールも import { foo } from './foo.js'; のように、拡張子まで書いて読み込みます。
解決2:どうしても require を残したいとき(createRequire)
CommonJS 専用のパッケージや、require('./data.json') のような読み込みを残したい場合は、node:module の createRequire で ESM の中に require を作れます。
// index.js(ESM のまま require を復活させる)
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const os = require('os');
console.log(os.platform());
import.meta.url を基準に解決するので、相対パスの require('./x') もそのまま使えます。ただしこれは移行の橋渡しです。恒久的には import に統一したほうが、依存関係を静的にたどれ、バンドラや型チェックの解析対象になります。
仕組み:ESM スコープに CommonJS の変数は無い
require は言語機能ではなく、CommonJS がモジュールを実行するときに関数の引数として渡していた変数です(module / exports / __dirname / __filename も同じ)。ESM はこの仕組みを使わないため、これらの変数はそもそも定義されません。だから ESM 扱いのファイルで require を書くと「未定義の変数を参照した」= ReferenceError になります。
import は ESM の言語機能なので、ESM のファイルではこちらを使います。
切り分け(うまくいかないとき)
requireと一緒に__dirname is not definedやmodule is not definedも出る:どれも同じ原因(ESM 扱い)です。__dirnameの代わりは、ローカルのfile:ESM ならimport.meta.dirname(Node 20.11 / 21.2 以上)、それ以前やfile:以外ではimport.meta.urlからfileURLToPathで求めます。- 本当は ESM にしたくなかった:
package.jsonの"type": "module"を外すか、そのファイルだけ.cjsにリネームすれば CommonJS 扱いに戻り、requireがそのまま使えます。逆に ESM に寄せる場合、type: "module"はそのpackage.json配下のサブフォルダも含めた.jsすべてを ESM にする点に注意してください。 importに変えたら別のエラーになった:CommonJS パッケージから名前付き import しようとすると失敗することがあります(既定エクスポートしか無いため)。対処は does not provide an export named / Named export not found を参照してください。- 逆に、CommonJS 側から ESM を
require()して落ちた:症状が反対(CJS のまま ESM を読みたい)のケースです。対処は require() of ES Module not supported(ERR_REQUIRE_ESM) を参照してください。