Error [does not provide an export named 'styleText']

does not provide an export named 'styleText' の直し方(util.styleText が import できない)

FIX SUMMARY verified
Applies when
node:20.11.1-bookwormutil.styleText imported on a Node older than 20.12.0 / 21.7.0 (minor-version boundary inside an LTS line)

Verified: reproduced in node:20.11.1-bookworm, then the does not provide an export named 'styleText' signature was gone after the fix (exit 0).

Node だけで色付きのログを出せる util.styleText を使おうとすると、次のエラーで止まることがあります。

SyntaxError: The requested module 'node:util' does not provide an export named 'styleText'
    at ModuleJob._instantiate (node:internal/modules/esm/module_job:123:21)

node:util は Node に組み込みのモジュールなので、インストール漏れでもパスの打ち間違いでもありません。実行している Node が古く、その版の node:utilstyleText がまだ無い、それだけです。

やっかいなのは、境界がメジャーバージョンではなくマイナーバージョンにあることです。「Node 20 だから使えるはず」では判定できません。

まず node -v をマイナーまで読む

styleText が入った版は次のとおりです。

Nodeimport { styleText } from 'node:util'
v18.20.8SyntaxError
v20.11.1SyntaxError
v20.12.0通る
v21.6.2SyntaxError
v21.7.0通る
v22 / v24通る

境界は 20.12.021.7.0 です。Node 18 には最後まで入りませんでした。

node -vv20.11.1v18.x を返したなら、原因はこれで確定します。手元では通るのに CI やコンテナで落ちるときも、たいていは同じ理由で、そちらの Node だけが 20.12.0 より古いという形です。

なお、v21.6.2 は v20.12.0 より版番号が大きいのに styleText を持ちません。この API は先に開発版の 21 系(21.7.0)へ入り、そのあと LTS の 20 系(20.12.0)へ取り込まれたためです。同じ 20.12.0 / 21.7.0 で入った API は他にもあり、crypto.hash がそうです。この「版番号の逆転」そのものについては TypeError: crypto.hash is not a function の直し方 で詳しく扱っているので、そちらを参照してください。

styleText の場合、Node を上げて import が通ったあとに、まだ 2 回止まります

直し方:Node を 22 以降に上げる

コードは 1 文字も変えません。同じソースのまま、実行する Node を上げれば通ります。

上げ先は 22 以降にしてください。20.12.0 で止めないでください。 理由は 3 つあり、順に後述します(保守期限・配列フォーマット・CI ログの色)。まず手順です。

手元の Node(nvm):

nvm install 22
nvm use 22
node -v   # v22.x が出れば入れ替わっている

Docker:

# Before
FROM node:20.11-alpine
# After
FROM node:22-alpine

GitHub Actions:

- uses: actions/setup-node@v4
  with:
    node-version: '22'   # '20.11' のようなピン留めが原因になっていることがある

node-version: 20 のように系だけを書いた場合、入るのは 20 系の最終版です。これは 20.12.0 より新しいので、import は通ります。20 系はすでに保守終了していて、この指定を放置してもこれ以上は上がりません。

だから、このエラーが出ているなら、20.11 のようにマイナーまで固定しているか、ベースイメージや OS のパッケージが古い 20.x を配っているかのどちらかです。手元と CI で node -v の出力を並べると、どちらが古いかがすぐ分かります。

ただし、import が通ることと styleText が期待どおり動くことは別です。20 系は最後の版まで上げても、配列で指定した色だけは非 TTY でも消えません(後述)。上げ先を 22 以降にしてほしいのは、そのためでもあります。

「styleText が使える版」は 1 つに決まらない

styleText の版境界は 1 本ではなく、3 本あります。しかも別々の版に散らばっています。ここが「Node 20.12 以上が必要」という一行の答えでは足りない理由です。

何が変わるか20 系22 系
import { styleText } が通る20.12.0 から最初から
配列フォーマット styleText(['bold','green'], s) が通る20.13.0 から最初から
非 TTY(パイプ・CI ログ)で色が抑制される20.18.0 から(文字列指定のみ。配列指定は 20 系では最後まで抑制されない)22.8.0 から(文字列指定)/22.15.0 から(配列指定も)

そのうえで、Node 20 は 2026-04-30 に保守期間が終了しています(Node 公式のリリーススケジュール)。いま 20.12.0 へ上げても、サポートの切れた系列の中で 1 つマイナーを進めるだけで、下の 2 つの落とし穴も残ったままです。22(2027-04-30 まで)か 24(2028-04-30 まで)へ移ってください。

上げたのに、今度は TypeError で落ちる

Node を 20.12.0 に上げた読者が次に踏むのがこれです。

import { styleText } from 'node:util';

console.log(styleText(['bold', 'green'], 'PASS'));
TypeError: The argument 'format' must be one of: 'reset', 'bold', 'dim', ... Received [ 'bold', 'green' ]

複数のスタイルを配列でまとめて渡す書き方は、styleText が入った 20.12.0 の時点では使えません。 20 系では 20.13.0 から通るようになりました(22 系は最初から通ります)。ネット上のサンプルは配列指定で書かれていることが多いので、「styleText は存在するのに書き方が通らない」という形でもう一度止まります。

エラーの種類が SyntaxError から TypeError に変わるため、同じ原因(Node が古い)だとは見えません。20.12.0 だけを目標にして上げると、ここでもう一往復することになります。

色が出ない、あるいは CI ログにエスケープが残る

styleText は途中から、出力先が端末でないときは色を付けないよう変わりました。パイプ・リダイレクト・CI のログはどれも端末ではないので、この変更がそのまま見え方の差になります。

  • 20.17.0 まで/22.7.0 まで:端末でなくてもエスケープシーケンスを出します。node app.mjs > out.log とすると、ログに制御文字が残ります。
  • 20.18.0 から/22.8.0 から:端末でなければ素のテキストになります。「手元では色が付くのに CI のログでは付かない」のはこの変更によるもので、故障ではありません。

ただし、この抑制が効くのは文字列指定のときだけです。配列指定は 20 系の最後まで抑制されません。

次の表は**すべてパイプ先(=端末ではない)**での挙動です。NO_COLOR の行は、端末で実行した場合に色が消えるかどうかを表します。

実行条件v20.12.0v20.20.2(20 系の最後)v22.23.1 / v24.18.0
文字列指定・パイプ先色が出る抑制される抑制される
文字列指定・端末で NO_COLOR=1抑制されない抑制される抑制される
配列指定・パイプ先TypeError抑制されない抑制される
配列指定・端末で NO_COLOR=1TypeError抑制されない抑制される

つまり Node 20 系には、配列指定で付いた色を止める手段がありません。 端末でなくても、NO_COLOR=1 / NODE_DISABLE_COLORS=1 / FORCE_COLOR=0 のどれを立てても、エスケープシーケンスが出ます(20 系の最後の版で 4 通りとも確認しました)。20 系はすでに保守が終わっているので、これが最終形です。「文字列指定の行は CI ログがきれいなのに、配列指定にした行だけ制御文字が混ざる」という食い違いは、これで説明が付きます。

逆に CI でも色付きのログを見たい場合は、第 3 引数で端末の検査を切れます。

styleText('green', 'PASS', { validateStream: false });

これは 20.18.0 / 22.8.0 以降で意図どおりに働きます(それ以前はもともと抑制されないので、指定してもしなくても色が出ます)。なお { validateStream: true } は既定値なので、渡しても結果は変わりません

書き方で変わるエラー文字列

同じ「Node が古い」でも、styleText の取り出し方によってメッセージが変わります。SyntaxError で検索して見つからないときは、自分が見ているのが下のどれかを確かめてください。

書き方古い Node で出るものいつ落ちるか
import { styleText } from 'node:util'SyntaxError: The requested module 'node:util' does not provide an export named 'styleText'実行前(モジュールの解決時)
const { styleText } = require('node:util')TypeError: styleText is not a function呼び出した行
import util from 'node:util'util.styleText(...)TypeError: util.styleText is not a function呼び出した行

ESM の名前付き import だけが、実行前に落ちます。 import の解決は本体の評価より前に行われるので、styleText を呼ぶ行に一度も到達しなくても、そのファイルは読み込まれた時点で SyntaxError になります。console.log を仕込んでも何も出ないのはこのためで、コードのどこが悪いかを探しても見つかりません。

一方 require とデフォルト import は、モジュールの取得までは成功します。styleTextundefined のまま関数として呼ばれ、その行で TypeError になります。

切り分け

  • node -v は 20.12.0 以上なのに SyntaxError が出る → その node と、実際にスクリプトを走らせている node が別物である可能性があります。process.execPath をログに出すと、どのバイナリで動いているかが分かります(node -e "console.log(process.execPath, process.version)")。エディタの実行ボタン、npm run 経由、Docker の中、CI のジョブでそれぞれ違う Node が使われていることがあります。
  • node:util ではなく util と書いているimport { styleText } from 'util' でも同じエラーになります。原因も対処も同じで、接頭辞の有無は関係ありません。
  • does not provide an export named は出るが、名前が styleText ではない → 別の API の話です。ただし読み方は同じで、その API が入った Node の版を調べ、実行している Node と比べるという手順は変わりません。
  • TypeScript で型エラーになる → 実行時とは別の話です。@types/node が古いと、Node 側に styleText があっても型定義に無いため、コンパイル時に落ちます。@types/node を上げてください。
  • ERR_UNKNOWN_BUILTIN_MODULECannot find module 'node:util' → これは node: 接頭辞そのものを解釈できない、さらに古い Node(14 以前)です。styleText 以前の問題なので、Node の入れ替えが先になります。

検証環境

  • node:20.11.1-bookworm、ネットワーク有り。同じイメージに nodejs.org から Node 20.12.0 の公式ビルドを展開し、2 つの Node を同居させています
  • 再現:node reproduce/app.mjs(イメージの Node 20.11.1)で does not provide an export named 'styleText' が出て終了コード 1
  • 修正:同一のソースを展開した Node 20.12.0 で実行し、終了コード 0・シグネチャ消滅。reproduce/app.mjsfix/app.mjs に差分はありません(変えたのは実行する Node だけです)
  • 本文の 3 つの表(追加の版・配列フォーマット・非 TTY での抑制)は、v18.20.8 / v20.11.1 / v20.12.0 / v20.13.0 / v20.17.0 / v20.18.0 / v20.20.2 / v21.6.2 / v21.7.0 / v22.0.0 / v22.7.0 / v22.8.0 / v22.14.0 / v22.15.0 / v22.17.0 / v22.18.0 / v22.23.1 / v23.11.1 / v24.18.0 の公式イメージで個別に実行して確認しました(出力はすべてパイプ経由=非 TTY)。配列指定の抑制が 22 系のどの版で入ったかは、22.14.0(抑制されない)と 22.15.0(抑制される)の間まで絞り込みました
  • Node 各系列の保守期限は、Node 公式のリリーススケジュールによります

nvm / Volta / actions/setup-node / Dockerfile での実際の入れ替え手順は、機構が同じ(実行する Node の版が境界)ことを根拠にした対処であり、それぞれの経路での再現は通していません。

検証(machine-verified)

この修正は node:20.11.1-bookworm のバージョン固定コンテナ内で再現し、修正後に does not provide an export named 'styleText' のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/util-styletext-not-exported
● reproduce does not provide an export named 'styleText' present ✓
● apply fix exit 0
● re-run does not provide an export named 'styleText' gone ✓
PASS verified · node:20.11.1-bookworm · signature gone