🚨

ts-jestでcannot be compiled under '--isolatedModules'と出た時の対処法

2021/11/19に公開

ts-jestisolateModules のエラーでハマったのでメモ。

起こったこと

npx create-next-app --typescriptで Next.js のプロジェクトを作り、その後 ts-jest の README の通りテストのセットアップを行い、Jest の動作確認のために以下テストを作成しました。

sample.spec.ts
describe("membersPosts", () => {
  test('sample', () => {
    expect(1 + 2).toBe(3)
  })
})

1 + 2 は 3 になるという Jest の動作を確認するためだけの自明なテストです。
しかし、絶対通るだろうと思って実行したら型エラーで落ちました。

src/builder/__tests__/sample.spec.ts:1:1 - error TS1208: 'membersPosts.spec.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.

当時の状況・自分の焦り用は以下 YouTube で全世界に公開されています😇

YouTubeのvideoIDが不正ですhttps://youtu.be/lI8CjHspGFU?t=1892

原因

まず Error の直接的な原因は、create-next-appで作られたtsconfig.jsonisolateModules: trueが設定されていることです。

tsconfig.json
{
  "compilerOptions": {
    // ...
    "isolatedModules": true,
  },
}

isolateModules フラグは単一ファイルのトランスパイルで正しく型解釈できない特定のコードを書いた場合にコンパイルエラーを出すフラグです。
デフォルトはfalseとなっています。

フラグをtrueにしている場合にコンパイルエラーとなるコードは以下のようなものがあります。

  • 型のみの再エクスポートファイル
  • export も import もしていないファイル
  • const enum メンバーへの参照をしているファイル

https://www.typescriptlang.org/tsconfig#isolatedModules

対処法

対処法としては以下のようなものがあります。

Bad 👎

最初に、微妙な対処法としては Error メッセージのAdd an import, export, or an empty 'export {}' statement to make it a moduleというコメントの通り、テストファイルにexport {}を追記するというものです。

describe("membersPosts", () => {
  test('sample', () => {
    expect(1 + 2).toBe(3)
  })
})

+ export {}

これでこのファイルは export を含むのでモジュールとして解釈され isolateModules のエラーは回避できます。
実際に前述の動画の配信では、暫定的にこちらで対処しました。ですが、実質不要な export{} がテストファイルに含まれるのは非常に微妙ですよね。。

Good 👍

テストでは不要な isolateModules のフラグが ON になっていることが原因なので、あのテストを通すための正しい対処としては、テスト上では isolateModulesfalse に戻すことです。
それはテスト用の tsconfig.json を作成し、ts-jestに読み込ませることで実現できます。

まず最初にテスト用の tsconfig.test.json を作成します。初期作成されたtsconfig.jsonを継承して、isolateModulesのフラグのみを false で上書きしています。

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "isolatedModules": false
  }
}

そしてこれをテストで読み込まれるようにjest.config.jsを修正します。

jest.config.js
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  globals: {
    "ts-jest": {
      tsconfig: './tsconfig.test.json'
    }
  }
};

これでテストではtsconfig.test.jsonが読み込まれるようになるので、isolateModulesのエラーは回避できます。

他にもテストのディレクトリが決まっていて、そこにテストファイルを配置していくというルールがあるのならば、テスト用のtsconfig.jsonをそのディレクトリに配置することでも回避可能です。

__tests__/
├── tsconfig.json
└── sample.spec.ts

Very Good 🌟

実は、そもそもの本質的な回避方法は何もしないことです。

以下の Stack Over Flow のコメントの通り本来テストは別のモジュールを import して、そのモジュールをテストするはずなので、実は通常のテストファイルは自動的にisolateModulesのエラーを回避できます。

You do not have any import statements in your code. This basically means you are not testing anything outside of the test file.
If you test something that is not in the test code (and therefore import something), the test file will become a module and the error will go away 🌹

https://stackoverflow.com/questions/57860261/isolatedmodules-error-on-jest-test-with-create-react-app-and-typescript

なので実際のプロダクト開発では、Good で上げた方法は対処する必要がありません。
Jest のテストを早まってしまったのが間違いでした。

おわりに

以上、cannot be compiled under '--isolatedModules' の対処法でした。配信でエラーでると焦りますね。良い勉強になりました。

Discussion