Node でファイルを実行したら、import の行で次のエラーが出て止まることがあります。
SyntaxError: Cannot use import statement outside a module
直前に、次の警告が出ていることもあります。
Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(この警告の文言は Node のバージョンで多少変わります。要点は「ESM として読み込もうとした」ことです。)
原因は、そのファイルが CommonJS として扱われているのに、ES Module(ESM)の構文である import 文を使っていることです。直し方は「そのファイルを ESM にする」か「import をやめて require にする」の二択で、どちらを選ぶかを決めるために、まずなぜ今これが出ているのかを確認します。
なぜ「今どき」これが出るのか(自動検出との関係)
このエラーは以前より出にくくなりました。種別が決まっていない .js に import などの ESM 構文があると、Node が自動で ESM として実行します(自動検出)。既定で有効になったのはリリースライン別で、20 系は 20.19.0 以上、22 系は 22.7.0 以上です。21.x や 22.0〜22.6 には入っていません(自動検出なし)。そのため、対象の版なら素の .js に import を書いただけでも落ちずに動くことが増えました。
つまり、今このエラーを踏んでいるなら、次のどれかに当てはまっているはずです。
| 状況 | 扱い | import の可否 |
|---|---|---|
拡張子が .cjs | 常に CommonJS | 使えない(このエラー) |
package.json に "type": "commonjs" がある .js | CommonJS(明示) | 使えない(このエラー) |
自動検出が無い版の .js(20 系は 20.19 未満、22 系は 22.7 未満、21.x や 22.0〜22.6 など) | CommonJS | 使えない(このエラー) |
.mjs/"type": "module" の .js | ESM | 使える |
.cjs や "type": "commonjs" は「これは CommonJS だ」と明示しているので、自動検出は働きません。だから新しい Node でも確実にこのエラーになります。手元の新しい Node(素の .js)では動くのに、.cjs のファイルや古い Node の CI でだけ落ちる、という食い違いはこれが原因です。
再現(最小構成)
.cjs は常に CommonJS 扱いです。ここに import を書きます。
// index.cjs(.cjs は常に CommonJS)
import os from 'node:os';
console.log(os.platform());
node index.cjs を実行すると SyntaxError: Cannot use import statement outside a module が出て、終了コードは 1 になります。
解決1:そのファイルを ESM にする
import を使いたいなら、ファイルを ESM 扱いにします。いちばん簡単なのは拡張子を .mjs にすることです(1 ファイルの改名で済み、package.json を触りません)。
// index.mjs(.mjs は常に ESM。import がそのまま使える)
import os from 'node:os';
console.log(os.platform());
コードは同じで、拡張子を .cjs から .mjs に変えただけです。これで終了コード 0 で動きます。プロジェクト全体を ESM にするなら、.js のまま package.json に "type": "module" を足す方法もあります(ただし、その package.json 配下のサブフォルダも含めた .js がすべて ESM 扱いになる点に注意。既存の require ベースの .js があると、それらも一緒に落ちます)。
解決2:CommonJS のまま require にする
そのファイルを CommonJS のままにしたいなら、import 文をやめて require() を使います。
// index.cjs(CommonJS のまま。require で読み込む)
const os = require('node:os');
console.log(os.platform());
既存のコードベースが CommonJS で統一されている場合は、こちらのほうが影響が小さく済みます。
仕組み:import は ESM 専用の構文
import 文と export 文は ES Module の言語機能で、CommonJS のパーサは受け付けません。だから CommonJS 扱いのファイルに import があると、実行前の構文解析の段階で弾かれます(SyntaxError)。require() は CommonJS の機能なので、CommonJS のファイルではこちらを使います。
Node の自動検出は、この「種別が曖昧な .js」に限って、ESM 構文があれば ESM として読み直す仕組みです。.cjs や "type": "commonjs" のように種別が確定しているファイルには適用されません。
切り分け(うまくいかないとき)
- 素の
.jsでは動くのに.cjsで落ちる:想定どおりです。.cjsは明示的な CommonJS なので自動検出は適用されません。ESM にするなら.mjs、CommonJS を保つならrequireにします。 - CI でだけ落ちる/手元では動く:手元が Node 20.19 以上(自動検出あり)で、CI が古い Node のことがあります。両方の
node -vをそろえて確認します。 - 逆に、ESM のファイルで
requireを使って落ちた:症状が反対(ESM でrequireが無い)のケースです。対処は require is not defined in ES module scope を参照してください。 - 依存パッケージを
require()したらERR_REQUIRE_ESMになった:それは別のエラーです。require() of ES Module not supported(ERR_REQUIRE_ESM) を参照してください。