Error [Cannot find module 'node:fs']

Cannot find module 'node:fs':node: プレフィックスが古い Node で通らないときの直し方

FIX SUMMARY verified
Applies when
node:14.17.6-alpinerequire of node: prefixed builtin on Node < 14.18

Verified: reproduced in node:14.17.6-alpine, then the Cannot find module 'node:fs' signature was gone after the fix (exit 0).

新しめのコードをそのまま古い 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: を付けた組み込みモジュールの requireNode 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: プレフィックスは、fspath のような組み込みモジュールを「これは 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_modulesfs という名前の紛らわしいパッケージがあっても確実に組み込みを指せる、という利点があります。これを使い続けたいなら、実行する Node(CI・本番の Docker ベースイメージ含む)を 14.18 以降、できれば 16 以降に揃えます。DockerfileFROM 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: を付けても解決されません(付けるのは fspathcrypto などの組み込みだけ)。
  • ESM(import)で似た症状が出たimportrequire より早くプレフィックス対応が入っています。それでも古い版(12.20 未満など)では通らないので、その場合も対処は上と同じ(プレフィックスを外す/Node を上げる)です。ESM 化に伴う他のエラーが同時に出ているなら require is not defined in ES module scope も参照してください。

検証(machine-verified)

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

verify — run-case.mjs
$ node run-case.mjs node/node-protocol-not-found
● reproduce Cannot find module 'node:fs' present ✓
● apply fix exit 0
● re-run Cannot find module 'node:fs' gone ✓
PASS verified · node:14.17.6-alpine · signature gone