How to solve canvas crash in Vitest with threads and jsdom
27 August 2022software development
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.
Fix the canvas error by disabling threads
Note that if threads are disabled, you have to cleanup 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
},
});
vitest.config.js
import { cleanup } from '@testing-library/vue'; // Or whatever framework you use
import { afterEach } from 'vitest';
afterEach(cleanup);
test-setup.ts
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,
},
});
wallaby.config.js
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 browserlike 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.