新しめのコードをそのまま古い Node で動かしたら、組み込みモジュールのはずなのに次のエラーで落ちることがあります。
Error: Cannot find module 'node:fs'
Require stack:
- /app/index.js
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:889:15)
at Function.Module._load (internal/modules/cjs/loader.js:745:27)
at Module.require (internal/modules/cjs/loader.js:961:19)
原因は、require('node:fs') の node: プレフィックス(組み込みモジュールであることを明示する書き方)を、その版の require がまだ解決できないことです。node: を付けた組み込みモジュールの require は Node 14.18.0 / 16.0.0 以降で使えます。それより前の版では node:fs を「node:fs という名前のパッケージ」だと思って探し、node_modules にも見つからず Cannot find module 'node:fs' になります。fs 自体はどの版にもあるので、原因はプレフィックスだけです。
これは典型的な版境界です。開発マシンは新しい Node、CI や本番の Docker ベースイメージが古い Node、という食い違いで「手元では動くのに CI でだけ Cannot find module 'node:fs'」という形で出ます。
なぜ node: を付けると古い版で落ちるのか
node: プレフィックスは、fs や path のような組み込みモジュールを「これは Node の組み込みであって、node_modules の同名パッケージではない」と曖昧さなく指定するための書き方です。プレフィックス対応は Node の途中の版で追加されたので、対応した版でないと require がプレフィックスを理解できません。
同じ node:fs でも、require(CommonJS)と import(ESM)で使えるようになった版が違うのが、この問題で引っかかりやすい点です。
| 書き方 | プレフィックスに対応した版 | それより前の版での結果 |
|---|---|---|
require('node:fs')(CommonJS) | 14.18.0 / 16.0.0 以降 | Error: Cannot find module 'node:fs' |
import ... from 'node:fs'(ESM) | 12.20 / 14.13.1 以降 | 同様に解決不可 |
ESM の import は先にプレフィックス対応が入ったため、require だけがまだ古い、という版(たとえば 14.13〜14.17)が存在します。「ESM では node: が通るのに require では通らない」という食い違いは、この版差が理由です。
再現(最小構成)
package.json を置かない(=.js は CommonJS 扱い)ディレクトリに、次のファイルを置きます。
// index.js(CommonJS。node: プレフィックス付きで require)
const fs = require('node:fs');
console.log('readFileSync is', typeof fs.readFileSync);
これを Node 14.17 以前で node index.js すると Error: Cannot find module 'node:fs' が出て、終了コードは 1 になります。Node 14.18 以降なら readFileSync is function と表示されます。同じコード・同じマシンでも、Node の版だけで結果が変わります。
解決
やりたいことによって直し方が2つあります。
1. node: プレフィックスを外す(古い版でも動かしたいとき)
古い Node もサポート対象なら、プレフィックスを外します。プレフィックス無しの組み込みモジュール名(fs のようにその版に存在するもの)は、どの版でも解決できます。
// index.js(プレフィックスを外した)
const fs = require('fs');
console.log('readFileSync is', typeof fs.readFileSync);
2. Node を上げる(node: を使い続けたいとき)
node: プレフィックスには、node_modules に fs という名前の紛らわしいパッケージがあっても確実に組み込みを指せる、という利点があります。これを使い続けたいなら、実行する Node(CI・本番の Docker ベースイメージ含む)を 14.18 以降、できれば 16 以降に揃えます。Dockerfile の FROM node:14 のような古い固定や、CI の Node バージョン設定を見直してください。Node 14 はすでに EOL(サポート終了)なので上げること自体は望ましいですが、メジャー版をまたぐときは他の依存パッケージが新しい版に対応しているかを併せて確認してください。
どちらを選ぶかは「古い Node を support 対象に残すか」で決めます。残さないなら 2、残すなら 1 です。
切り分け(似ているが原因が違うもの)
ERR_UNKNOWN_BUILTIN_MODULE: No such built-in module: node:testが出た:これはプレフィックスの問題ではなく、その組み込みモジュール自体がその版にまだ無いときのエラーです。たとえばnode:test(組み込みテストランナー)は Node 18/16.17 以降で追加されたので、それより前の版ではnode:が解決できても本体が存在せず、このエラーになります。対処はプレフィックス外しではなく、Node を上げることです(16 系に残すなら 16.17 以降、そうでなければ 18 以降)。Cannot find module 'node:foo'でfooが組み込みではない:node:は組み込みモジュール専用のプレフィックスです。npm パッケージにnode:を付けても解決されません(付けるのはfs・path・cryptoなどの組み込みだけ)。- ESM(
import)で似た症状が出た:importはrequireより早くプレフィックス対応が入っています。それでも古い版(12.20 未満など)では通らないので、その場合も対処は上と同じ(プレフィックスを外す/Node を上げる)です。ESM 化に伴う他のエラーが同時に出ているなら require is not defined in ES module scope も参照してください。