diff --git a/CHANGELOG.md b/CHANGELOG.md index e0777206f22d..cecb1ed5096b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[jest-cli, jest-config, @jest/core, jest-haste-map, @jest/reporters, jest-runner, jest-runtime, @jest/types]` Add `workerThreads` configuration option to allow using [worker threads](https://nodejs.org/dist/latest/docs/api/worker_threads.html) for parallelization ([#13939](https://github.com/facebook/jest/pull/13939)) - `[jest-worker]` Add `start` method to worker farms ([#13937](https://github.com/facebook/jest/pull/13937)) ### Fixes diff --git a/docs/CLI.md b/docs/CLI.md index 699076e2e85d..83b75a32afbd 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -506,3 +506,13 @@ In most CI environments, this is automatically handled for you. ### `--watchman` Whether to use [`watchman`](https://facebook.github.io/watchman/) for file crawling. Defaults to `true`. Disable using `--no-watchman`. + +### `--workerThreads` + +Whether to use [worker threads](https://nodejs.org/dist/latest/docs/api/worker_threads.html) for parallelization. [Child processes](https://nodejs.org/dist/latest/docs/api/child_process.html) are used by default. + +:::caution + +This is **experimental feature**. See the [`workerThreads` configuration option](Configuration.md#workerthreads) for more details. + +::: diff --git a/docs/Configuration.md b/docs/Configuration.md index eebddd813855..d560e4029b4f 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -2375,3 +2375,17 @@ This option allows comments in `package.json`. Include the comment text as the v } } ``` + +### `workerThreads` + +Default: `false` + +Whether to use [worker threads](https://nodejs.org/dist/latest/docs/api/worker_threads.html) for parallelization. [Child processes](https://nodejs.org/dist/latest/docs/api/child_process.html) are used by default. + +Using worker threads may help to improve [performance](https://github.com/nodejs/node/discussions/44264). + +:::caution + +This is **experimental feature**. Keep in mind that the worker threads use structured clone instead of `JSON.stringify()` to serialize messages. This means that built-in JavaScript objects as `BigInt`, `Map` or `Set` will get serialized properly. However extra properties set on `Error`, `Map` or `Set` will not be passed on through the serialization step. For more details see the article on [structured clone](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm). + +::: diff --git a/e2e/__tests__/__snapshots__/showConfig.test.ts.snap b/e2e/__tests__/__snapshots__/showConfig.test.ts.snap index 79a0e38b919b..2d44f820d845 100644 --- a/e2e/__tests__/__snapshots__/showConfig.test.ts.snap +++ b/e2e/__tests__/__snapshots__/showConfig.test.ts.snap @@ -138,7 +138,8 @@ exports[`--showConfig outputs config info and exits 1`] = ` "useStderr": false, "watch": false, "watchAll": false, - "watchman": true + "watchman": true, + "workerThreads": false }, "version": "[version]" }" diff --git a/packages/jest-cli/src/args.ts b/packages/jest-cli/src/args.ts index c33f5e57310f..7a863ec270d1 100644 --- a/packages/jest-cli/src/args.ts +++ b/packages/jest-cli/src/args.ts @@ -709,4 +709,10 @@ export const options: {[key: string]: Options} = { '--no-watchman.', type: 'boolean', }, + workerThreads: { + description: + 'Whether to use worker threads for parallelization. Child processes ' + + 'are used by default.', + type: 'boolean', + }, }; diff --git a/packages/jest-config/src/Defaults.ts b/packages/jest-config/src/Defaults.ts index ac08c902e511..f55769e61a42 100644 --- a/packages/jest-config/src/Defaults.ts +++ b/packages/jest-config/src/Defaults.ts @@ -88,6 +88,7 @@ const defaultOptions: Config.DefaultOptions = { watch: false, watchPathIgnorePatterns: [], watchman: true, + workerThreads: false, }; export default defaultOptions; diff --git a/packages/jest-config/src/ValidConfig.ts b/packages/jest-config/src/ValidConfig.ts index 275bf1c22459..998fac69a68a 100644 --- a/packages/jest-config/src/ValidConfig.ts +++ b/packages/jest-config/src/ValidConfig.ts @@ -186,6 +186,7 @@ export const initialOptions: Config.InitialOptions = { ], watchman: true, workerIdleMemoryLimit: multipleValidOptions(0.2, '50%'), + workerThreads: true, }; export const initialProjectOptions: Config.InitialProjectOptions = { diff --git a/packages/jest-config/src/index.ts b/packages/jest-config/src/index.ts index cc133355bf09..0214acdf77f3 100644 --- a/packages/jest-config/src/index.ts +++ b/packages/jest-config/src/index.ts @@ -140,6 +140,7 @@ const groupOptions = ( watchPlugins: options.watchPlugins, watchman: options.watchman, workerIdleMemoryLimit: options.workerIdleMemoryLimit, + workerThreads: options.workerThreads, }), projectConfig: Object.freeze({ automock: options.automock, diff --git a/packages/jest-config/src/normalize.ts b/packages/jest-config/src/normalize.ts index f271b737a2b5..af482ff2979a 100644 --- a/packages/jest-config/src/normalize.ts +++ b/packages/jest-config/src/normalize.ts @@ -936,6 +936,7 @@ export default async function normalize( case 'watch': case 'watchAll': case 'watchman': + case 'workerThreads': value = oldOptions[key]; break; case 'workerIdleMemoryLimit': diff --git a/packages/jest-core/src/cli/index.ts b/packages/jest-core/src/cli/index.ts index e2509a164b9c..40c9f4c39c9a 100644 --- a/packages/jest-core/src/cli/index.ts +++ b/packages/jest-core/src/cli/index.ts @@ -157,6 +157,7 @@ const buildContextsAndHasteMaps = async ( resetCache: !config.cache, watch: globalConfig.watch || globalConfig.watchAll, watchman: globalConfig.watchman, + workerThreads: globalConfig.workerThreads, }); hasteMapInstances[index] = hasteMapInstance; return createContext(config, await hasteMapInstance.build()); diff --git a/packages/jest-haste-map/src/index.ts b/packages/jest-haste-map/src/index.ts index 82ec7fbda446..bc65149445bf 100644 --- a/packages/jest-haste-map/src/index.ts +++ b/packages/jest-haste-map/src/index.ts @@ -79,6 +79,7 @@ type Options = { throwOnModuleCollision?: boolean; useWatchman?: boolean; watch?: boolean; + workerThreads?: boolean; }; type InternalOptions = { @@ -103,6 +104,7 @@ type InternalOptions = { throwOnModuleCollision: boolean; useWatchman: boolean; watch: boolean; + workerThreads?: boolean; }; type Watcher = { @@ -267,6 +269,7 @@ class HasteMap extends EventEmitter implements IHasteMap { throwOnModuleCollision: !!options.throwOnModuleCollision, useWatchman: options.useWatchman ?? true, watch: !!options.watch, + workerThreads: options.workerThreads, }; this._console = options.console || globalThis.console; @@ -748,6 +751,7 @@ class HasteMap extends EventEmitter implements IHasteMap { this._worker = {getSha1, worker}; } else { this._worker = new Worker(require.resolve('./worker'), { + enableWorkerThreads: this._options.workerThreads, exposedMethods: ['getSha1', 'worker'], forkOptions: {serialization: 'json'}, maxRetries: 3, diff --git a/packages/jest-reporters/src/CoverageReporter.ts b/packages/jest-reporters/src/CoverageReporter.ts index 86b9722cd0a8..84aefc822803 100644 --- a/packages/jest-reporters/src/CoverageReporter.ts +++ b/packages/jest-reporters/src/CoverageReporter.ts @@ -147,6 +147,7 @@ export default class CoverageReporter extends BaseReporter { worker = require('./CoverageWorker'); } else { worker = new Worker(require.resolve('./CoverageWorker'), { + enableWorkerThreads: this._globalConfig.workerThreads, exposedMethods: ['worker'], forkOptions: {serialization: 'json'}, maxRetries: 2, diff --git a/packages/jest-runner/src/index.ts b/packages/jest-runner/src/index.ts index 3388c836f282..9d0d8234f75f 100644 --- a/packages/jest-runner/src/index.ts +++ b/packages/jest-runner/src/index.ts @@ -105,6 +105,7 @@ export default class TestRunner extends EmittingTestRunner { } const worker = new Worker(require.resolve('./testWorker'), { + enableWorkerThreads: this._globalConfig.workerThreads, exposedMethods: ['worker'], forkOptions: {serialization: 'json', stdio: 'pipe'}, // The workerIdleMemoryLimit should've been converted to a number during diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index ce567bde1730..bea33f915138 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -79,6 +79,7 @@ type HasteMapOptions = { resetCache: boolean; watch?: boolean; watchman: boolean; + workerThreads?: boolean; }; interface InternalModuleOptions extends Required { @@ -370,6 +371,7 @@ export default class Runtime { throwOnModuleCollision: config.haste.throwOnModuleCollision, useWatchman: options?.watchman, watch: options?.watch, + workerThreads: options?.workerThreads, }); } diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index dff27f7080a3..ca8164c3458c 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -206,6 +206,7 @@ export type DefaultOptions = { watch: boolean; watchPathIgnorePatterns: Array; watchman: boolean; + workerThreads: boolean; }; export type DisplayName = { @@ -326,6 +327,7 @@ export type InitialOptions = Partial<{ watchman: boolean; watchPlugins: Array]>; workerIdleMemoryLimit: number | string; + workerThreads: boolean; }>; export type SnapshotUpdateState = 'all' | 'new' | 'none'; @@ -419,6 +421,8 @@ export type GlobalConfig = { config: Record; }> | null; workerIdleMemoryLimit?: number; + // TODO: make non-optional in Jest 30 + workerThreads?: boolean; }; export type ProjectConfig = { @@ -574,5 +578,6 @@ export type Argv = Arguments< watchman: boolean; watchPathIgnorePatterns: Array; workerIdleMemoryLimit: number | string; + workerThreads: boolean; }> >;