question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Slow startup time in monorepos (single test/project)

See original GitHub issue

All of this is done on OSX with 3,5 GHz Dual-Core Intel Core i7 and 1 jest worker.

Problem

We have a monorepo with currently around 26 packages. We currently run with yarn (1) workspaces. So the code base is not exactly small, but once you work on it you mostly work on one of those packages at a time. The problem is that even when just running a single test, it takes about ~10 seconds for the tests to finish. This is mostly startup time because jest reports the test itself running in ~100ms.

We would like to get this time down to allow for a better developer experience. The fact that all tests together take almost ten minutes doesn’t bother us that much, but running a single test should ideally finish in less than a second.

We hope that someone here can help us or at least point us in the right direction.

jest config

module.exports = {
  testRunner: 'jest-circus/runner',
  transform: {
    '^.+\\.(t|j)sx?$': 'babel-jest',
  },
  transformIgnorePatterns: ['<rootDir>/node_modules/'],
  moduleNameMapper: {
    '//': 'Here are 10 modules mapped',
  },
  clearMocks: true,
  roots: ['<rootDir>'],
  snapshotSerializers: ['jest-date-serializer'],
  testURL: 'http://www.test.com',
  moduleFileExtensions: ['tsx', 'ts', 'js', 'json'],
  testEnvironment: 'jest-environment-jsdom-sixteen',
  setupFilesAfterEnv: [
    'jest-canvas-mock',
    'jest-localstorage-mock',
    '<rootDir>/setup/index.ts',
    '<rootDir>/setup/errorCatcher.ts',
  ],
  reporters: ['default', '<rootDir>/setup/consoleErrorReporter.js'],
  rootDir: '<rootDir>/../../../',
};

consoleErrorReporter gathers information about errors written to the console.

So in order to allow a custom config for each project the final config is dynamically built:

const fs = require('fs');
const { rootDir, ...baseConfig } = require('./setup/baseJestConfig');
const packages = []; // we have a function which returns all packages

const projects = packages.map(({ location, title }) => {
  try {
    // use config if provided by a package
    fs.accessSync(`${location}/jest.config.js`);
    return `<rootDir>/${location}`;
  } catch (e) {
    // otherwise just use the base config
    return {
      ...baseConfig,
      displayName: title,
      testRegex: `${location}/.*(Test|.test)\\.(t|j)sx?$`,
    };
  }
});

module.exports = {
  ...baseConfig,
  roots: ['<rootDir>'],
  projects,
};

In the end projects would be an array of 26 configs, which mostly look the same.

What we tried so far

Different transpiler

I first thought transpilation might be a bottleneck. I tried swc and esbuild. To my surprise, it made no difference.

Define just the config for the package you are using

We initially filtered for configs we need for a run, but then found out about --selectedProjects. Both approaches sped up startup time by a factor of three on my machine. My colleague (with slightly better hardware) could observe around 50% speedup, regardless of the total amount of tests that he ran.

How we tried to debug

Get some times

Hacked timings in jest-runtime/build/index.js like

console.time(module.filename);
compiledFunction.call(
  module.exports,
  module, // module object
  module.exports, // module exports
  module.require, // require implementation
  module.path, // __dirname
  module.filename, // __filename
  this._environment.global, // global object
  ...lastArgs.filter(notEmpty)
);
console.timeEnd(module.filename);

Most files take less than a ms, longest took around 600ms. I can see this pilling up for 7-8k files when done in sync.

Profiled the node process

We are not very familiar with how to read and interpret these reports. Here is an excerpt from it, I cut off lines and just left the top 5 for each:

Statistical profiling result from isolate-0x10469d000-91221-v8.log, (20504 ticks, 27 unaccounted, 0 excluded).

 [Shared libraries]:
   ticks  total  nonlib   name
    300    1.5%          /usr/lib/system/libsystem_platform.dylib
     65    0.3%          /usr/lib/system/libsystem_pthread.dylib
     47    0.2%          /usr/lib/system/libsystem_kernel.dylib
     25    0.1%          /usr/lib/system/libsystem_malloc.dylib
      1    0.0%          /usr/lib/system/libdispatch.dylib

 [JavaScript]:
   ticks  total  nonlib   name
    116    0.6%    0.6%  RegExp: /\.git/|/\.hg/
     53    0.3%    0.3%  LazyCompile: *_ignore /Users/****/node_modules/jest-haste-map/build/index.js:1191:10
     30    0.1%    0.1%  LazyCompile: *<anonymous> /****/node_modules/jest-haste-map/build/crawlers/node.js:254:15
     22    0.1%    0.1%  RegExp: .*\/locales\/.*en\.json$
     14    0.1%    0.1%  LazyCompile: *resolve path.js:973:10

 [C++]:
   ticks  total  nonlib   name
   8465   41.3%   42.2%  T __kernelrpc_thread_policy_set
   3148   15.4%   15.7%  T __ZN2v88internal19ScriptStreamingDataC2ENSt3__110unique_ptrINS_14ScriptCompiler20ExternalSourceStreamENS2_14default_deleteIS5_EEEENS4_14StreamedSource8EncodingE
   2265   11.0%   11.3%  T node::SyncProcessRunner::Spawn(v8::FunctionCallbackInfo<v8::Value> const&)
   1135    5.5%    5.7%  T __kernelrpc_mach_vm_purgable_control_trap
    596    2.9%    3.0%  t node::fs::Read(v8::FunctionCallbackInfo<v8::Value> const&)

 [Summary]:
   ticks  total  nonlib   name
    439    2.1%    2.2%  JavaScript
  19600   95.6%   97.7%  C++
    487    2.4%    2.4%  GC
    438    2.1%          Shared libraries
     27    0.1%          Unaccounted

 [C++ entry points]:
   ticks    cpp   total   name
   4961   47.8%   24.2%  T __ZN2v88internal21Builtin_HandleApiCallEiPmPNS0_7IsolateE
   3711   35.8%   18.1%  T __ZN2v88internal19ScriptStreamingDataC2ENSt3__110unique_ptrINS_14ScriptCompiler20ExternalSourceStreamENS2_14default_deleteIS5_EEEENS4_14StreamedSource8EncodingE
    933    9.0%    4.6%  T __kernelrpc_mach_vm_purgable_control_trap
    130    1.3%    0.6%  T __ZN2v88internal30Builtin_ErrorCaptureStackTraceEiPmPNS0_7IsolateE
    129    1.2%    0.6%  T _open$NOCANCEL

Interestingly the CPU profiler in node shows a lot (~4s) of “nothing” in between starting the script and executing jest: 98929895-25c4ed80-24dc-11eb-9df6-3cfa500f194f

Also the jestAdapter takes 8s before a tests starts and a total of 12s for the entire run.

Test Suites: 5 passed, 5 total
Tests:       26 passed, 26 total

As far as I can tell the “nothing” time is spent with reading files. onStreamRead and program, zoomed in: image

Issues that might be related

https://github.com/facebook/jest/issues/10301 https://github.com/facebook/jest/issues/9554

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:6
  • Comments:26

github_iconTop GitHub Comments

10reactions
joshribakoff-smcommented, Jul 22, 2022

@mingshenggan as described in my above comment(s) it’s because jest recursively follows all import statements during startup. So by importing 1 MUI icon via the barrel export, it forces jest to crawl 1000s of file(s) also imported by that barrel export.

Certainly reducing the amount of imports will help, but assuming your project actually needs to import part(s) of larger libraries, it doesn’t solve the root issue within jest

4reactions
gor918commented, May 19, 2022

const jest: { globals: { "ts-jest": { "isolatedModules": true } }, }

Testing was sped up with this configuration

Read more comments on GitHub >

github_iconTop Results From Across the Web

TestProject running slow - Site Feedback
Hi guys! Have you encountered now the TestProject is running slow like click any tab (Projects/Monitor/Reports) can't load the page or can't ...
Read more >
Reducing testing and building time in Monorepos
The first thing that we want to do is to only run tests that are affected by the current files. How do we...
Read more >
Mono Repo vs Micro Repo – Micro Repo wins in a landslide
A new dev can be productive in an hour with a micro repo. With a mono repo, they might not even have the...
Read more >
jest console log after tests are done - You.com | The AI Search ...
facebook/jestSlow startup time in monorepos (single test/project)#10833. Created about 2 years ago. 21. All of this is done on OSX with 3,5 GHz...
Read more >
Refreshing output directory after the build might be very slow
After each build "Refresh files" process is started and it takes much time (the vast majority of times I restart CLion after several...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found