DEV Community

Cover image for Improving Vitest Performance
The Jared Wilcurt
The Jared Wilcurt

Posted on • Updated on

Improving Vitest Performance

The following is a summary of the Vitest GitHub issue "Vitest runs tests 3x slower than Jest", which if printed would require about 24 pages. So I've summed up all the ideas and advice I found there into a more concise list of things you can try if you notice Vitest being slow. Please note, many performance improvements were merged into Vitest since that issue was created so not all recommendations on this page are still relevant, the goal is just to put them in one spot in a quick, concise, easy to try list.

If you'd like to see some real world benchmarks on a 5.5 year old production app, read my follow up post and what happened when I tried to apply some of these following techniques.

Summary

Things within your control to improve performance:

  • Upgrade Node.js. Newer Node versions are faster.
  • Switching from jsdom to happy-dom is faster in most cases, though will require tweaking/adjusting some tests. (some note it is 3x faster to import per test)
  • Remove or mock heavy dependencies (rather than importing them).
  • Rather than import a dependency from a "barrel file", import it from a pre-built file, or directly from the actual file. Example:
    • Instead of import { OneIcon } from 'thousands-of-icons'; do import OneIcon from 'thousands-of-icons/icons/OneIcon.vue';. This way you don't have to wait for 2,000+ icon components to be loaded and transformed on every test.
  • Read up on deps.experimentalOptimizer and how to adjust your include/exclude/entries settings for better performance in tests and how your dependencies are bundled during test runs.
  • Instead of inlining a dependency, alias it to point to a pre-built/CJS version. Example:
    environment: 'happydom',
   -deps: {
   -  inline: ['element-plus']
   -}
   +alias: [
   +  {
   +    find: /^element-plus$/,
   +    replacement: resolve(__dirname, 'node_modules', 'element-plus/dist/index.full.mjs')
   +  }
   +]
Enter fullscreen mode Exit fullscreen mode
  • You can try --no-threads CLI argument, depending on your tests this may be much slower or slightly faster. Also some tests may need to be tweaked to work with this. It will use child_process instead of worker threads.
  • The --single-thread CLI may also make things much slower or maybe slightly faster depending on the codebase.
  • Set css: false to skip CSS imports in tests (If you are using --browser then maybe you don't want this, but also --browser is slower too because... it involves a real browser)
  • Set useAtomics: true to synchronize threads, might improve performance in some repos, but will cause Node to crash in older Node versions.
  • Increase concurrent: 5 to a higher value to run more tests simultaneously (maxConcurrency).
  • Similarly you can increase minThreads: 8 to set the minimum number of CPU threads that will be used to run tests (also see: maxThreads)
  • If you opted in to using --sequence.shuffle stop using it (or override with --sequence.shuffle=false). Running tests in a random order is slower because Vitest can cache and sort tests running the slowest ones first which is faster overall.
  • In some cases the test reporter can be the bottleneck, if you aren't particularly attached to the one you are using you could try a different one to see if performance improves. Example:
    • vitest --reporter=tap
  • If you have any style files that contain .module in the file name (ex: sidebar.module.sass). Follow these instructions
  • Turning isolation off (--no-isolate or --isolate=false) is 3-8x faster, but will likely break your existing tests, requiring them to be re-written. Also it will cause issues in watch mode. Generally not a good idea.
  • Adjust the slowTestThreshold to be bigger. If it doesn't report it as being slow, then who's to say it isn't fast? Ride that placebo.

Information:

  • You can use the benchmark feature to evaluate where time is spent during test running. Either when running a specific test() (as bench()), or by running --bench CLI or using benchmark: {} in the test: {} config. This can help with debugging slow tests.
  • There is a --ui module graph that may be useful when debugging performance.
  • Vitest inherits settings from your dev and build Vite config. Some settings will give you better performance once you build your app, but worse performance when running tests, so you need to override these settings in the test config. (I think, this isn't super clear in the docs).

The future

  • Vitest relies on the vm module. It is being ran in a way that is much slower than how Jest does it. If Vitest were to switch to Jest's approach it would introduce all the bugs Jest has into Vitest. So this will likely never happen as Vitest values correctness over performance (be happy that this is the case). However, once vm has proper native ESM support, it will be considerably faster (this is a major bottleneck for Vitest).
  • If ShadowRealms are ever added to EcmaScript (and implemented into V8/Node) they'll allow for a different approach to isolating code that would be faster without the downsides of sharing global.

Be sure to look at the follow up post for real world benchmarks on a 5 year old frontend web app.


Photo Credits:

"Man running along seashore during golden hour" by Swapnil Dwivedi

Top comments (2)

Collapse
 
schliengeranais profile image
Anaïs Schlienger

Thank you for this article! Super useful.
Is minThreads: 8 compatible with --no-threads? (as I understand it, min-threads is linked to CPU threads, where --no-threads refers to the child process used)

Collapse
 
joels profile image
Joel Sullivan • Edited

This is super helpful! Using this and the tips on performance in this short note, I was able to accelerate Vitest by ~4x for my 800 tests, making it faster than Jest. All I had to do was add the following to my Vitest config:

    // Speed things up a bit -- these help but probably won't be needed someday
    maxConcurrency: 20,
    pool: 'vmThreads',
    poolOptions: {
      threads: {
        singleThread: true,
      }
    },
    isolate: false, // only safe with the poolOptions above
    css: false,
    deps: {
      optimizer:{
        web: {
          enabled: true,
        }
      }
    },
  },

Enter fullscreen mode Exit fullscreen mode