Error [error TS2882: Cannot find module or type declarations for side-effect import]

error TS2882 の直し方(TypeScript 6 で副作用 import の未検査が既定で止まる)

FIX SUMMARY verified
Applies when
node@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436TypeScript 6.0+ (noUncheckedSideEffectImports default changed from false to true)tsc after upgrading TypeScript to 6.0 or later, on a side-effect import (import "./globals.css") whose target has no type declarations

Verified: reproduced in node@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436, then the error TS2882: Cannot find module or type declarations for side-effect import signature was gone after the fix (exit 0).

CSS を読み込むだけの 1 行が、TypeScript を 6 に上げた tsc で止まります。

src/index.ts(1,8): error TS2882: Cannot find module or type declarations for side-effect import of './globals.css'.

エディタの日本語表示は次の形で報告されています(当プロジェクトで実測したのは上の英語出力で、こちらは日本語圏の報告からの引用です)。

'./globals.css' の副作用インポートに対するモジュールまたは型宣言が見つかりません。ts(2882)

TypeScript 6.0 で noUncheckedSideEffectImports の既定が false から true になりました。 5.9 までは、解決できない副作用 import(import "./globals.css" のように何も受け取らない import)は黙って無視されていました。6.0 からは型宣言が無いとエラーになります。

最小の修正は、tsconfig.json の include が拾う場所にアンビエント宣言を 1 つ置くことです。

// src/globals.d.ts
declare module "*.css";

ただしこの *.css は、多くの場合に足りません。 照合されるのは import に書いた文字列のほうで、解決先のファイル名ではないからです(後述)。

なぜ TypeScript 6 で出るのか:noUncheckedSideEffectImports の既定が反転した

同じソース・同じ tsconfig.json のまま、TypeScript の版だけを変えると境界が出ます。

// src/index.ts
import "./globals.css";

export const title = "errfix";
TypeScript結果終了コード
5.9.3通る0
6.0.3error TS28822
7.0.2同じメッセージ1

7.0 も 6.0 の既定を継承します。メッセージは同一で、終了コードだけが 6.0 の 2 から 7.0 で 1 に変わります。

副作用 import は、名前を 1 つも受け取りません。だから型としては何も要らないように見えますが、TypeScript から見ると「そのモジュールが何なのか分からないまま実行だけさせている」状態です。noUncheckedSideEffectImports は、その状態を検査するかどうかの設定で、5.6 で追加されて 6.0 で既定が入れ替わりました。

このエラーは 2 つの別々の原因から出ます。 対象のファイルが存在しない場合も、存在するが型宣言が無い場合も、同じ文字列になります。

$ rm src/globals.css
$ npx tsc --noEmit
src/index.ts(1,8): error TS2882: Cannot find module or type declarations for side-effect import of './globals.css'.

メッセージの module or type declarations がそのまま両方を指しています。宣言を足す前に、まずパスが実在するかを確かめてください。

宣言を足したのに消えないとき:原因は 2 つある

declare module "*.css"; を書いたのにメッセージが 1 文字も変わらない、という形が 2 通りあります。症状が同じなので、両方を見てください。

原因 A:import に書いた文字列が .css で終わっていない

ワイルドカードが照合するのは import に書いた specifier です。解決先のファイル名ではありません。だから specifier がライブラリのサブパスで、その先が CSS に解決される形——import "somelib/styles" のように specifier 自体が .css で終わらない形——では、*.css は一致しません。

その形を最小の合成パッケージで作って測ると、こうなります(照合が specifier に対して行われることの再現であって、特定のライブラリの現況ではありません)。

// node_modules/fakelib/package.json
{ "name": "fakelib", "exports": { "./styles": "./styles.css" } }
import "fakelib/styles";
置いた宣言結果
なしerror TS2882 ... of 'fakelib/styles'
declare module "*.css";同じエラーのまま
declare module "fakelib/styles";通る
declare module "fakelib/*";通る

fakelib/styles は .css で終わらないので、*.css のパターンに当たりません。宣言を足したのにメッセージが 1 文字も変わらないのはこの形です。specifier をそのまま書くか、declare module "fakelib/*"; のようにライブラリ側に合わせたパターンにしてください。

エラー文の of '...' に入っている文字列が、照合の対象です。 そこを見て宣言のパターンを決めてください。

なお、CSS を配るライブラリでこの形が報告されたことはありますが(vuetifyjs/vuetify#22766 など)、上流が型宣言を同梱すれば宣言は要らなくなります。実際、[email protected] の ./styles と @payloadcms/[email protected] の ./css はどちらも exports に types を持っており、この 2 つは現在そのままコンパイルが通ります。ライブラリ名で覚えるのではなく、エラー文に出ている specifier で判断してください。

原因 B:.d.ts がプログラムに入っていない

宣言ファイルは、tsconfig.json がプログラムに含めた場合にだけ読まれます。include: ["src"] だけを書いた構成では、src の外にあるファイルは入りません。同じ内容の declare module "*.css"; でも、置き場所だけで結果が変わります。

置き場所(include: ["src"] のとき)結果
src/globals.d.ts通る
./globals.d.ts(ルート直下)error TS2882 のまま

include 以外にも、files に直接並べる・別のファイルから /// <reference path="..." /> で参照する・types に型パッケージとして載せる、といった経路でプログラムに入ります。「src の中に置く」が唯一の正解ではありません——確かめるのは置き場所ではなく、実際にプログラムへ入っているかです。npx tsc --showConfig の files に、その .d.ts が出ているかを見てください。

直し方:宣言を足すか、検査を止めるか

宣言を足す(既定の検査を保ったまま通す)。

// src/globals.d.ts — include の内側に置く
declare module "*.css";
declare module "fakelib/styles";

検査そのものを止める(5.9 までの挙動へ戻す)。

{
  "compilerOptions": {
    "noUncheckedSideEffectImports": false
  }
}

どちらも同じソースで終了コード 0 になることを実測しています。差はこの先の取りこぼしです。false に戻すと、いま直したい CSS の import だけでなく、解決できない副作用 import すべてが 5.9 までと同じく黙殺されます。TypeScript はこのオプションを追加したとき、綴り誤りの副作用 import が見逃されることを動機として挙げています(当プロジェクトでは綴り誤りの側を実測していません)。移行を止めないための当座として使い、宣言を並べる形へ移す前提で入れてください。

Vite が用意する vite/client の型は *.css を宣言しているので、テンプレートが置く src/vite-env.d.ts の /// <reference types="vite/client" /> が残っている構成では、この宣言がすでにある状態になります(当プロジェクトでは Vite 構成を実測していません)。

切り分け

  • 宣言を足したのにメッセージが変わらない:.d.ts の置き場所(include の内側か)と、パターンが specifier に一致しているか(.css で終わらない specifier に *.css を当てていないか)の 2 つを見てください。上の 2 節がその形です。
  • tsc は通るのにエディタだけ赤い:エディタとコマンドラインが別の TypeScript か、別の tsconfig.json を使っています。まず両方の版と、エディタがどの tsconfig.json を選んでいるかを確かめてください(当プロジェクトではエディタ側を実測していません)。
  • error TS2591: Cannot find name 'process' も一緒に出ている:同じ TypeScript 6.0 の既定変更の別件です。compilerOptions.types の既定が [] になったほうで、原因も修正も別です。@types/node を入れてあるのに process が見つからない場合 を見てください。
  • Option '...' has been removed が出ている:削除された compiler option の話で、こちらとは別の境界です。TypeScript が削除した compiler option を見てください。
  • CI の終了コードで分岐している:6.0 は 2、7.0 は 1 を返します。値ではなく成否で分岐してください。

検証環境

  • node@sha256:d649c27d...(node:22-bookworm-slim)、[email protected]、ネットワーク有り
  • 再現:import "./globals.css" を持つ src/index.ts で npx tsc --noEmit が error TS2882: Cannot find module or type declarations for side-effect import を出して終了コード 2
  • 修正:src/globals.d.ts(declare module "*.css";)というファイルを 1 つ足すだけで終了コード 0

再現から修正までは errfix の検証ハーネスが機械的に確認しています。版の対照(5.9.3 が終了コード 0、6.0.3 が 2、7.0.2 が 1)、.d.ts を include の外に置くと直らないこと、対象ファイルを削除しても同じメッセージになること、noUncheckedSideEffectImports: false が終了コード 0 に戻すこと、そしてサブパスが CSS に解決される specifier では declare module "*.css" が効かず、specifier に一致する宣言だけが通ることは、いずれも同じイメージで実測しています。

[email protected] の ./styles と @payloadcms/[email protected] の ./css が現在そのまま通ることも、同じイメージで実測しています(どちらも exports に types を持ちます)。

Vite・Next.js の実構成、エディタとコマンドラインで設定が食い違う場合、noUncheckedSideEffectImports: false にしたときの綴り誤りの扱いは実測していません。 エディタの日本語表示は日本語圏の報告からの引用です。

検証(machine-verified)

この修正は node@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436 のバージョン固定コンテナ内で再現し、修正後に error TS2882: Cannot find module or type declarations for side-effect import のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/typescript-6-ts2882-side-effect-import
● reproduce error TS2882: Cannot find module or type declarations for side-effect import present ✓
● apply fix exit 0
● re-run error TS2882: Cannot find module or type declarations for side-effect import gone ✓
PASS verified · node@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436 · signature gone

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