できない.dev

Vitest で「Cannot find module」とパスエイリアス(@/)が解決できない

tsconfig の paths は型解決用で、Vitest(Vite)の実行時には効かない。
テストで @/ が解決できずに落ちる(Vitest 4.1 の表示は Cannot find package)のは resolve.alias 未設定が主因。
alias を定義するか vite-tsconfig-paths を入れる。

公開: 更新:

症状できない · exit 1
npx vitest run
…
⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯
 FAIL  src/foo.test.ts [ src/foo.test.ts ]
Error: Cannot find package '@/foo' imported from /tmp/tmp.KOavIPFlrK/src/foo.test.ts
解決後できた · exit 0
npx vitest run
…
 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  03:52:03
   Duration  110ms (transform 18ms, setup 0ms, import 26ms, tests 2ms, environment 0ms)

2026-10-10 に node:20(Node.js 20.20.2 / npm 10.8.2) で実際に打って取った出力。検証環境の詳細

要約

テストを実行すると Error: Cannot find package '@/foo' imported from … のように落ちる(Vitest 4.1.11 で確認)のは、@/ のようなパスエイリアスが Vitest の実行時に解決できていないため。tsconfig.json の paths はあくまで TypeScript の型解決・エディタ補完用で、実際にモジュールを束ねる Vite/Vitest は resolve.alias を見る(alias | Vitest(新しいタブで開く))。
二つは別物なので、tsconfig だけ書いても実行時には効かない。
alias を Vite 側にも定義するか、tsconfig の paths を同期するプラグインを入れる。

実行例

tsconfig の paths だけを設定した状態で npx vitest run を実行すると Cannot find package '@/foo' で落ち、終了コードは 1 になった。resolve.alias を足すと同じコマンドが通って終了コードは 0 になり、resolve.tsconfigPaths: true でも同じ結果になる。vite-tsconfig-paths も動くが、プラグインを外して resolve.tsconfigPaths に置き換えるよう促す案内が出た。

$ cat tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "baseUrl": ".",
    "paths": { "@/*": ["./src/*"] }
  }
}
$ npx vitest run
 RUN  v4.1.11 /tmp/tmp.KOavIPFlrK
 
 ❯ src/foo.test.ts (0 test)
 
⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯
 
 FAIL  src/foo.test.ts [ src/foo.test.ts ]
Error: Cannot find package '@/foo' imported from /tmp/tmp.KOavIPFlrK/src/foo.test.ts
 ❯ src/foo.test.ts:2:1
      1| import { expect, test } from "vitest";
      2| import { foo } from "@/foo";
       | ^
      3|
      4| test("foo", () => {
 
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
 
 Test Files  1 failed (1)
      Tests  no tests
   Start at  03:52:03
   Duration  102ms (transform 12ms, setup 0ms, import 0ms, tests 0ms, environment 0ms)
$ echo $?
1
$ cat vitest.config.ts
import { defineConfig } from "vitest/config";
import { fileURLToPath } from "node:url";
 
export default defineConfig({
  resolve: {
    alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) },
  },
});
$ npx vitest run
 RUN  v4.1.11 /tmp/tmp.KOavIPFlrK
 
 ✓ src/foo.test.ts (1 test) 2ms
 
 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  03:52:03
   Duration  110ms (transform 18ms, setup 0ms, import 26ms, tests 2ms, environment 0ms)
$ echo $?
0
$ cat vitest.config.ts
import { defineConfig } from "vitest/config";
 
export default defineConfig({ resolve: { tsconfigPaths: true } });
$ npx vitest run
 RUN  v4.1.11 /tmp/tmp.KOavIPFlrK
 
 ✓ src/foo.test.ts (1 test) 2ms
 
 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  03:52:04
   Duration  102ms (transform 14ms, setup 0ms, import 21ms, tests 2ms, environment 0ms)
$ echo $?
0
$ cat vitest.config.ts
import { defineConfig } from "vitest/config";
import tsconfigPaths from "vite-tsconfig-paths";
 
export default defineConfig({ plugins: [tsconfigPaths()] });
$ npx vitest run
The plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the resolve.tsconfigPaths option. You can remove the plugin and set resolve.tsconfigPaths: true in your Vite config instead.
 
 RUN  v4.1.11 /tmp/tmp.KOavIPFlrK
 
 ✓ src/foo.test.ts (1 test) 1ms
 
 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  03:52:06
   Duration  97ms (transform 14ms, setup 0ms, import 21ms, tests 1ms, environment 0ms)
$ echo $?
0

— 2026-10-10 時点の出力

検証環境Node.js 20.20.2 / npm 10.8.2 / Python 3.11.2 / 2026-10-10 検証(ほか 2 件)
検証日
実行環境
node:20 Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5
  • Vitest 4.1.11

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

解決策

1. resolve.alias を絶対パスで定義する

vitest.config.ts(または vite.config.ts)のトップレベルに置く(Shared Options | Vite(新しいタブで開く))。

import { defineConfig } from "vitest/config";
import { fileURLToPath } from "node:url";
 
export default defineConfig({
  resolve: {
    alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) },
  },
});

2. tsconfig の paths を同期する

エイリアスを tsconfig 一本で管理したいなら、Vite 8 以降は resolve.tsconfigPaths: true で tsconfig の paths を Vite 側へ反映する(Shared Options | Vite(新しいタブで開く))。

import { defineConfig } from "vitest/config";
 
export default defineConfig({ resolve: { tsconfigPaths: true } });

Vite 7 以前はプラグインで反映する。
Vite 8 でこのプラグインを入れると、テストは通るが「resolve.tsconfigPaths: true に置き換えられる」旨の警告が出る。

import tsconfigPaths from "vite-tsconfig-paths";
 
export default defineConfig({ plugins: [tsconfigPaths()] });

3. include を確認する

tsconfig.json の include にテストフォルダが含まれているかも確認する。
含まれないと型解決とテスト解決の食い違いが起きやすい。

よくある原因

  1. 設定の二重管理漏れ: tsconfig の paths は書いたが、resolve.alias を書いていない。
  2. 相対パス: alias の値を相対パスで書いており、基準ディレクトリがずれて解決できない。
  3. 設定ファイル分離: vite.config と vitest.config が別で、テスト側に alias が渡っていない。
  4. require には効かない: alias は import 文のみ対象で、require() は解決しない。

この手順で直った?