akfm.dev

Jest高速化の魔法のレシピ

September 25th, 2020

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はその辺に問題意識持ってたりするんですかね?

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