Blog

How to solve canvas crash in Vitest with threads and jsdom

27 Aug, 2022
Xebia Background Header Wave

Vitest is the new Jest killer. It is fast, thanks to the stuff that also drives Vite, like esbuild.

The main reason why I prefer Vitest is that it can reuse your Vite config (React or Vue without Vite is a no-go if you ask me) but moreover, we are finally redeemed by those old-fashioned slow Webpack builds that target commonJS. You write ESM (with Typescript of course) and Vitest runs ESM without transforming ⚡️👍🏻.

Both Jest and Vite run tests in parallel using Worker threads. However, somehow I got this error after I migrated from Jest to Vitest

Module did not self-register: '~/my-project/node_modules/canvas/build/Release/canvas.node'

This error seems to be related to jsdom, the Node library I use to simulate the browser environment.
If you have the canvas package installed, jsdom automatically loads it, so that the Canvas API is also available in your test. However, there is an issue in this package so that it could not run inside a worker thread. canvas is a peer dependency of jsdom, so when you use npm >=7, this will always be installed in your project. I have no clue (yet) why this error doesn’t happen in Jest, but you can ‘fix’ it by disabling threads.
Vitest logo

Fix the canvas error by disabling threads

Note that if threads are disabled, you have to clean up yourself in the test-setup.ts script.

export default defineConfig({
  test: {
    environment: 'jsdom', // assume you use jsdom
    setupFiles: 'test-setup.ts',
    threads: false, // disable worker threads so that canvas runs in main thread
  }
});
import { cleanup } from '@testing-library/vue'; // Or whatever framework you use
import { afterEach } from 'vitest';

afterEach(cleanup);

If you use WallabyJS, you also have to set the workers to 1, else the tests will be flaky.

module.exports = () => ({
  workers: {
    initial: 1,
    regular: 1
  }
});

Alternative solution: no canvas

Having threads is a major benefit, this is one of the things that make Vite so fast. So is there a way that we can still use threads?

Well, if you don’t use the canvas package, we maybe can get rid of it so that jsdom doesn’t load it.

Your first option is to not use jsdom. Try happy-dom for example.

Second, you could run rm -rf node_modules/canvas as postinstall script or just before running tests. Or always run npm install --legacy-peer-deps so that the pre-7 behavior of not installing peer deps automatically is triggered. I know this is dirty (and error-prone) but it works.

Somehow I managed to remove all references to canvas from package-lock.json so that I won’t get installed the next time you run npm install (or one of your team members).

Check with npm ls canvas if canvas is not in your package-lock.json anymore.

Conclusion

Vitest is awesome, but when you want threads (yes!) and you use jsdom to run browser-like tests in Node, you might run into the nasty "Module did not self-register" error triggered by the canvas package that is not able to run in a worker thread.

If you do not really depend on the canvas package, you have to remove it from package-lock.json so that it will not be installed into node_modules (via jsdom‘s peer deps).

It this is not possible, you have to run the tests in single-thread mode by setting threads: 1 in the Vitest config file.

Frank van Wijk
Frank is a senior frontend consultant focusing on quality and maintainability.
Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts