logoMiyauchi

Speed ​​up TypeScript with Jest

Introduction

With the advent of esbuild, the front-end world has become more demanding of speed in the development environment. The rise of vite can be said to be the best of all.

esbuild and swc are written by the fast Go and Rust, and more often omit Typescript type checking. Type checking for tsc is usually done in IDEs and workflows, so by stripping them off, you're specializing in converting to JavaScript as a pure compiler.

Now, when testing Typescript code, it's often ts-jest or babel-jest as a transformer. However, these can slow down the test.

This time, I will show you how to speed up the execution of Jest and realize a fast test.

Conclusion

I will show you about the method first.

yarn add -D jest @swc/jest

{ "transform": { "^.+\\.(t|j)sx?$": "@swc/jest" }}

jest.config.json

Use @swc/jest as the transformer.

Performance comparison

Let's see how much the performance has improved due to the speedup.

Since the performance differs depending on the execution environment, I will compare the results relative to each other.

CommonJS + Javascript

Try the pattern that seems to be the fastest in theory. CommonJS-style JavaScript may be the fastest, as it shouldn't need to be transpiled. (I'm sorry if I made a mistake 🙏)

I'm not interested in the contents of the function, so prepare a suitable function and test it.

exports.add = (a, b) => a + b

index.js

const { add } = require('../src') describe('add', () => { it('should return 2 when it gives 1,1', () => { const result = add(1,1) expect(result).toBe(2) })})

test/index.spec.js

module.exports = { testEnvironment: "node", roots: ["<rootDir>/test/"]};

jest.config.js

Disable the cache and average about 10 times. It is not a strict measurement, but this time it is a speed comparison of each, so I think that a relative comparison can be made by matching the conditions.

for i in {0..9}; do yarn jest --no-cache ; done

Result:

Transformer

Mean(s)

None(CommonJS + JavaScript)

0.512

I will consider this as a standard.

ESM + TypeScript

A pattern that describes TypeScript in ES module format. This is the pattern most of the time you use TypeScript.

export const add = (a: number, b: number): number => a + b

index.ts

import { add } from '../src/' describe('add', () => { it('should return 2 when it gives 1,1', () => { const result = add(1,1) expect(result).toBe(2) })})

test/index.spec.ts

Use ts-jest for transformers

module.exports = { ..., transform: { '^.+\\.tsx?$': 'ts-jest', }};

jest.config.js

Result:

Transformer

Mean(s)

ts-jest

1.660

None(CommonJS + JavaScript)

0.512

It takes about 3 times longer than CommonJS + JavaScript. I want to do something about this.

Use esbuild for transformers

esbuild is a fast bundler written in Go. By default, Bundler has built-in support for parsing TypeScript syntax and discarding type annotations.

yarn add -D esbuild-jest esbuild

module.exports = { ..., transform: { '^.+\\.tsx?$': 'esbuild-jest' }};

jest.config.js

Result:

Transformer

Mean(s)

esbuild-jest

0.373

ts-jest

1.660

None(CommonJS + JavaScript)

0.512

It's amazing speed. Surprisingly faster than CommonJS + JavaScript.

Use swc for transformers

swc is an ultra-fast compiler written in rust. It seems that Deno is also used for deno lint and deno doc.

yarn add -D @swc/jest

module.exports = { ..., transform: { '^.+\\.tsx?$': ['@swc/jest'], }};

jest.config.js

Result:

Transformer

Mean(s)

@swc/jest

0.351

esbuild-jest

0.373

ts-jest

1.660

None(CommonJS + JavaScript

0.512

You can see that both esbuild and swc can be transpiled at incredible speeds. It is not possible to compare the speed of both with this result alone, but it seems that swc is slightly more advantageous when examined. However, esbuild-jest has the advantage that you can optionally change the following items:

interface Options { jsxFactory?: string jsxFragment?: string sourcemap?: boolean | 'inline' | 'external' loaders?: { [ext: string]: Loader } target?: string format?: string}

Also, for VSCode, an extension for jest is provided. This will automatically run the test in the background if there is a change in the test target, of course this test will also be faster.

If the test is successful, it will nark: white_check_mark: to the target code. Since the test ends immediately, it is a good development experience to mark it immediately.

Stop to use jest.config.ts

The above result used jest.config.js as the jest config file. However, the .ts format configuration file does not improve performance much even if the transformer is changed.

// @swc/jest + jest.config.jsTest Suites: 1 passed, 1 totalTests: 1 passed, 1 totalSnapshots: 0 totalTime: 0.385 sRan all test suites.✨ Done in 1.24s. // @swc/jest + jest.config.tsTest Suites: 1 passed, 1 totalTests: 1 passed, 1 totalSnapshots: 0 totalTime: 0.389 sRan all test suites.✨ Done in 2.93s.

Just changing the file format to .ts has taken more than twice as long.

This is because jest is requesting ts-node from the transpiling of jest.config.ts. Therefore, if possible, you should write the configuration file in json format as jest.config.json, or write the configuration file in jest.config.js format.

Cons

As mentioned at the beginning, esbuild and swc omit type checking and enjoy speed. Therefore, the following code cannot detect compilation errors during testing.

export const add = (a: number, b: string): number => a + b

In this case, the annotations are inappropriate, but when compiled into JavaScript, it works and passes the test.

That said, most IDEs should display the error visually, and you can prevent it by adding tsc to your workflow.