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 にする」の二択で、どちらを選ぶかを決めるために、まずなぜ今これが出ているのかを確認します。

なぜ「今どき」これが出るのか(自動検出との関係)

このエラーは以前より出にくくなりました。種別が決まっていない .jsimport などの ESM 構文があると、Node が自動で ESM として実行します(自動検出)。既定で有効になったのはリリースライン別で、20 系は 20.19.0 以上、22 系は 22.7.0 以上です。21.x や 22.0〜22.6 には入っていません(自動検出なし)。そのため、対象の版なら素の .jsimport を書いただけでも落ちずに動くことが増えました。

つまり、今このエラーを踏んでいるなら、次のどれかに当てはまっているはずです。

状況扱い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