🎼

.gitディレクトリの中でVitestを実行すると、エラーが発生する話

に公開

新しくWebアプリを作るためにまずはテストを実行できる環境を作ろうと、Vitestを使った環境構築をしていました。しかし、 Error: Cannot find module '/@vite/env' というエラーが発生し、どうにもこうにも解消できません。AIに任せたところ、一生あれこれ試行錯誤していて解決しそうな雰囲気がまったくありませんでした。

Vitestのバージョン3系を使うとこのエラーが発生するものの、2系を使えば問題ないことはわかりました。とはいえ気持ち悪いのでどうにかならないか調べ、原因がわかったので同様の問題に悩む方のために記事にします。

結論を言うと作業ディレクトリのパスに.gitが含まれている場合に問題が発生することがわかりました。最近使い始めたphantomのデフォルト設定で、ワークツリーの展開先が.git/phantom/worktreesディレクトリ以下のため、この問題に遭遇しました。

ディレクトリ構成としては、このような状態になります。

  PROJECT_ROOT/
  ├── .git/
  │   └── phantom/
  │       └── worktrees/ ← ワークツリーの作業ディレクトリはここの下に展開
  │           └── feature-branch1/ ← ここで Vitest を実行するとエラーが発生
  │           └── feature-branch2/
  └── (通常のプロジェクトファイル置き場)

具体的な問題の内容と原因について説明します。

調査のためのプロジェクト構成

今回、問題調査のために最小構成になるようなプロジェクトを作って試していました。Svelteのドキュメントを参考に作っています。

https://svelte.dev/docs/svelte/testing

最終的に以下のような構成でテストをしました。

package.json
{
  "name": "frontend",
  "private": true,
  "version": "0.0.1",
  "type": "module",
  "scripts": {
    "test": "vitest"
  },
  "devDependencies": {
    "@sveltejs/kit": "^2.16.0",
    "@testing-library/jest-dom": "^6.6.3",
    "jsdom": "^26.1.0",
    "svelte": "^5.0.0",
    "typescript": "^5.0.0",
    "vitest": "3.2.4"
  }
}
multiplier.svelte.ts
export function multiplier(getCount: () => number, k: number) {
  return {
    get value() {
      return getCount() * k;
    },
  };
}
multiplier.svelte.test.js
import { flushSync } from "svelte";
import { expect, test } from "vitest";
import { multiplier } from "./multiplier.svelte.ts";

test("Multiplier", () => {
  let count = 0;
  let double = multiplier(() => count, 2);

  expect(double.value).toEqual(0);

  count = 5;

  expect(double.value).toEqual(10);
});
vite.config.ts
/// <reference types="vitest" />
import { defineConfig } from "vitest/config";
import { sveltekit } from "@sveltejs/kit/vite";

export default defineConfig({
  plugins: [sveltekit()],
  test: {
    environment: "jsdom",
    globals: true,
  },
});

テスト実行 - 正常系

npm installしてからテストを実行すると、期待通りテストはパスします。

$ npm run test

> frontend@0.0.1 test
> vitest

src/app.html does not exist

 DEV  v3.2.4 /Users/takayama/Document/vitest-sample

 ✓ multiplier.svelte.test.js (1 test) 1ms
   ✓ Multiplier 1ms

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  08:20:31
   Duration  756ms (transform 185ms, setup 0ms, collect 282ms, tests 1ms, environment 288ms, prepare 40ms)

 PASS  Waiting for file changes...
       press h to show help, press q to quit

テスト実行 - エラー

しかし構成をそのままに、.gitディレクトリの中に入れてからテストをすると、以下のエラーが発生します。

$ npm run test

> frontend@0.0.1 test
> vitest

src/app.html does not exist

 DEV  v3.2.4 /Users/takayama/Document/check/.git/vitest-sample

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Errors ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Unhandled Error ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Error: Cannot find module '/@vite/env'
 ❯ VitestExecutor._fetchModule node_modules/vite-node/dist/client.mjs:247:19
 ❯ process.processTicksAndRejections node:internal/process/task_queues:105:5
 ❯ VitestExecutor.directRequest node_modules/vite-node/dist/client.mjs:268:44
 ❯ VitestExecutor.cachedRequest node_modules/vite-node/dist/client.mjs:189:11
 ❯ VitestExecutor.executeId node_modules/vite-node/dist/client.mjs:166:10
 ❯ createVitestExecutor node_modules/vitest/dist/chunks/execute.B7h3T_Hc.js:469:2
 ❯ startVitestExecutor node_modules/vitest/dist/chunks/execute.B7h3T_Hc.js:531:9
 ❯ startViteNode node_modules/vitest/dist/chunks/base.DfmxU-tU.js:10:14
 ❯ runBaseTests node_modules/vitest/dist/chunks/base.DfmxU-tU.js:24:30

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯


 Test Files   (1)
      Tests  no tests
     Errors  1 error
   Start at  08:19:14
   Duration  143ms (transform 0ms, setup 0ms, collect 0ms, tests 0ms, environment 0ms, prepare 0ms)

 FAIL  Tests failed. Watching for file changes...
       press h to show help, press q to quit

今回用意した最小構成とは別に、src/routes...などのディレクトリ構成にしている場合に、/@vite/envとは違う別のエラーが発生する場合があります。いずれも、必要なファイルが見つからないという、同じ原因によるエラーです。

 FAIL  src/routes/page.test.ts [ src/routes/page.test.ts ]
Error: Cannot find module '/Users/takayama/Document/check/.git/phantom/worktrees/feature-branch/frontend/src/routes/page.test.ts'
Caused by: Error: Failed to load url /Users/takayama/Document/check/.git/phantom/worktrees/feature-branch/frontend/src/routes/page.test.ts (resolved id: /Users/takayama/Document/check/.git/phantom/worktrees/feature-branch/frontend/src/routes/page.test.ts). Does the file exist?
 ❯ loadAndTransform ../node_modules/vite/dist/node/chunks/dep-DBxKXgDP.js:35725:17

エラーが発生する原因

セキュリティ上の理由から、**/.git/**にマッチするファイルは読み込まれないというデフォルト設定がありました。

https://github.com/vitejs/vite/blob/main/docs/config/server-options.md#serverfsdeny

以下に引用します。

server.fs.deny

  • Type: string[]
  • Default: ['.env', '.env.*', '*.{crt,pem}', '**/.git/**']

Blocklist for sensitive files being restricted to be served by Vite dev server. This will have higher priority than server.fs.allow. picomatch patterns are supported.

コードだと以下の箇所です。

https://github.com/vitejs/vite/blob/d2c81f7c13030c08becd8a768182074eedb87333/packages/vite/src/node/server/index.ts#L1097

この設定の影響で、.gitディレクトリの中にあるファイルが適切に読み込まれていないという状況が発生していたようです。

.gitディレクトリの中で実行するとエラーになることは早い段階でわかったのですが、なぜそれが発生するのか理由を突き止めるのに手間取ってしまいました。具体的には、node_modulesディレクトリの中を.gitという文字列で漁って編集しながらテストするというなんとも泥臭い方法を取りました。

解決方法

原因がわかったので、解決方法もわかります。.gitディレクトリの中で作業をしない これに尽きます。phantomの場合は、phantom.config.jsonでワークツリーのパスを変更できます。

https://github.com/aku11i/phantom/blob/main/README.ja.md#phantomの仕組み

もう一つは設定の上書きです。server.fs.denyから、**/.git/**を削除してしまえばエラーは発生しなくなります。が、本番環境で利用しない方がいいと思われるのであくまで一時的な対処として考えたほうが良さそうです。自己責任でお願いします。

vite.config.ts
/// <reference types="vitest" />
import { defineConfig } from "vitest/config";
import { sveltekit } from "@sveltejs/kit/vite";

export default defineConfig({
  plugins: [sveltekit()],
  test: {
    environment: "jsdom",
    globals: true,
  },
  server: {
    fs: {
      strict: true,
      deny: [".env", ".env.*", "*.{crt,pem}"],
    },
  },
});
ARMテックブログ

Discussion