Jest高速化の魔法のレシピ

この記事はArchiveされているため、情報が更新されていない可能性があります。

Introduction

ある日ふと思ったけど、JS のトランスパイルって遅いですよね。 特に Jest。 watch し忘れて作業してた時にふと回しなおすと遅くてしょうがない。 まぁ watch してればまだマシですが、それでもちょっと修正しただけでも待たなきゃいけないし、地味にストレスを感じてました。

ということで今回は Jest の高速化をしてみました。

結果

プロジェクトによりけりなので参考程度ですが、プロジェクトによっては3 倍以上高速化した物もありました。 3 倍です。 もう全然ストレスが違います。

以前は「まぁテスト早くしたってそんな変わんなくね?」程度にしか思ってなかったんですが、高速化経験しちゃうともう戻れないですね。 ちなみに ts-jest は普段から私は使わないようにしてたので、ts-jest 使ってる人はもっと早くなる場合もあるかもです。

高速化戦略

では具体的にどういった部分がボトルネックになっていたのか、何をしたのかみていきたいと思います。

ts-jest をやめる

まず ts-jest を使っているならやめましょう。 ちょっとこれは自分で試してないんですが、Typescript の型チェックは重いです。 大抵の IDE は ts-jest なしでもある程度型チェックしてくれるので、いっそやめちゃっても大丈夫な場合が多いし、そもそも普通にプロダクションコードと一緒に tsc チェックかけてる場合も多いはず。

まずやめれるかどうか確認+検討しましょう。

並列処理テストをやめる or コア数を制限する

Jest はデフォルトでは PC のコア数 - 1、watch 中はコア数の半分のスレッドを立ててテストを実行します。 テストファイル数や PC にもかなりよると思いますが、これを適切に制限してあげることでテストが高速化したりします。 特に CI だったり、テストファイル自体がそこまで多くない場合は 50%前後改善したりしてる事例も多いようです。 実際僕も試した中でこれくらい改善した物もありました。

コア数の制限には--maxWorkers--runInBandを指定します。

// 直列テスト
jest --runInBand
// スレッド数2
jest --maxWorkers=2

まずは**--runInBand**を指定して、速度がどうなるかみることをお勧めします。

babel でトランスパイルするのをやめよう

ts-jest は使わないようにしましょうと書きましたが、ts-jest を使わず babel-jest を使って安心してる方も多いはず。 しかし、babel も遅い場合があります。

これはプロダクションコードとテストコードで異なるトランスパイラを使用することになるので論点はありますが、ユニバーサルな JS とか書いてると実行環境がブラウザとサーバーだったりとそもそもトランスパイラどころか実行環境からしていろいろ出てくるので テストのトランスパイラ依存で困ることってそんなにないだろう(というかそこの厳密性よか速くしたい)と思い切って babel をやめるのも手だと思います。

では babel 使わずそのままテストとかプロダクションコードが Jest で動くかと言われると、Node のバージョンや書いてる babel の preset にもよってきちゃうので難しいので、代わりにesbuildswcを使いましょう!

esbuild や swc はそれぞれ Go と Rust で書かれているため、babel に比べてトランスパイルが高速です。 これらを用いて Jest のトランスパイルをすることで実行時間を短縮することが可能です。 これももちろんプロジェクトによりけりですが、私が試したケースでは30~50%程度実行速度が改善しました。

それぞれ Jest との疎通用にesbuild-jest@swc/jestといったライブラリもあって導入は簡単で、jest.config.js を少しいじれば OK です。

jest.config.js

module.exports = {
  transform: {
    '^.+\\.tsx?$': 'esbuild-jest',
    // swcの場合は以下
    // '^.+\\.tsx?$': '@swc/jest',
  },
}

※esbuild,babel,swc をブランチごとに比較検証したリポジトリ https://github.com/AkifumiSato/esbuild-jest-measure-speed

ただ esbuild-jest は target の指定ができないのが微妙なので、多少自分で似たようなライブラリを作る必要があるかもしれません。 その点、swc は導入簡単だし.swcrc 置くだけなのでお勧めです。

※決して Rust が好きだから swc を推してるわけではありません。

【追記】 やっぱり esbuild で target 指定して使いたいなと思い、esbuild-jest-transformという esbuild の build に option 渡せるライブラリを公開しました。 同じことでお困りの方は是非。

まとめ

人間欲が出る物で、Jest 流行り始めた時は「これは便利だ、実行時間なんて気にしない!」なんて思っていたのに、やっぱり高速化を体験してしまうと戻れませんね。

テストだけでなく、プロダクションコードも最近 swc 使い始めてかなり感動してるんですが、bundler とか lint とかも高速化できないかなぁと思う今日この頃・・・ 知らないだけであるのだろうか。 Deno や Rome はその辺に問題意識持ってたりするんですかね?

その辺も高速化できたら、また記事書こうと思います。