Error [Cannot use import statement outside a module]

Cannot use import statement outside a module の直し方(Node.js)

FIX SUMMARY verified
Applies when
node:20-alpineimport in CommonJS

Verified: reproduced in node:20-alpine, then the Cannot use import statement outside a module signature was gone after the fix (exit 0).

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" がある .jsCommonJS(明示)使えない(このエラー)
自動検出が無い版の .js(20 系は 20.19 未満、22 系は 22.7 未満、21.x や 22.0〜22.6 など)CommonJS使えない(このエラー)
.mjs/"type": "module" の .jsESM使える

.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) を参照してください。

検証(machine-verified)

この修正は node:20-alpine のバージョン固定コンテナ内で再現し、修正後に Cannot use import statement outside a module のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/cannot-use-import-outside-module
● reproduce Cannot use import statement outside a module present ✓
● apply fix exit 0
● re-run Cannot use import statement outside a module gone ✓
PASS verified · node:20-alpine · signature gone

確認したのは上のイメージの中だけです。別の環境で直らなかった、記述が違う、という場合は 報告してください(対象と検証イメージは件名・本文に入ります)。