Skip to content

Commit 42f78d4

Browse files
authored
perf(jest-runtime): load chalk only once per worker (#10864)
1 parent 5ba0cc9 commit 42f78d4

File tree

12 files changed

+93
-4
lines changed

12 files changed

+93
-4
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
/website/translated_docs
2828
/website/i18n/*
2929

30+
/benchmarks/*/node_modules/
31+
3032
/reports/*
3133

3234
coverage

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484

8585
### Performance
8686

87+
- `[jest-runtime]` Load `chalk` only once per worker ([#10864](https://github.com/facebook/jest/pull/10864))
88+
8789
## 26.6.3
8890

8991
### Fixes
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[1-9]*.test.js
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
it('is fast', () => {});
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
First, run `./prepare.sh` to generate the benchmark files. On Windows, use a Bash (WSL, Git, Cygwin …) to do this, but you can use CMD for the actual benchmark run if the CMD environment is what you want to benchmark for.
2+
3+
To run the benchmark, use a benchmarking tool such as [hyperfine](https://github.com/sharkdp/hyperfine):
4+
5+
```sh
6+
hyperfine -w 3 -m 10 ../../jest /tmp/other-jest-clone-to-compare-against/jest
7+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"jest": {
3+
"testEnvironment": "node"
4+
}
5+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env sh
2+
for i in {1..499}; do
3+
cp 0.test.js $i.test.js
4+
done
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# This file is generated by running "yarn install" inside your project.
2+
# Manual changes might be lost - proceed with caution!
3+
4+
__metadata:
5+
version: 4
6+
7+
"root-workspace-0b6124@workspace:.":
8+
version: 0.0.0-use.local
9+
resolution: "root-workspace-0b6124@workspace:."
10+
languageName: unknown
11+
linkType: soft

packages/jest-runtime/src/__tests__/runtime_internal_module.test.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ describe('Runtime', () => {
3232
runtime.requireModule(modulePath);
3333
}).toThrow(new Error('preprocessor must not run.'));
3434
});
35-
3635
it('loads internal modules without applying transforms', async () => {
3736
const runtime = await createRuntime(__filename, {
3837
transform: {'\\.js$': './test_preprocessor'},
@@ -56,7 +55,6 @@ describe('Runtime', () => {
5655
const exports = runtime.requireModule(modulePath);
5756
expect(exports).toEqual({foo: 'foo'});
5857
});
59-
6058
it('loads internal JSON modules without applying transforms', async () => {
6159
const runtime = await createRuntime(__filename, {
6260
transform: {'\\.json$': './test_json_preprocessor'},
@@ -68,5 +66,29 @@ describe('Runtime', () => {
6866
const exports = runtime.requireInternalModule(modulePath);
6967
expect(exports).toEqual({foo: 'bar'});
7068
});
69+
70+
const OPTIMIZED_MODULE_EXAMPLE = 'chalk';
71+
it('loads modules normally even if on the optimization list', () =>
72+
createRuntime(__filename).then(runtime => {
73+
const modulePath = path.resolve(
74+
path.dirname(runtime.__mockRootPath),
75+
'require-by-name.js',
76+
);
77+
const requireByName = runtime.requireModule(modulePath);
78+
expect(requireByName(OPTIMIZED_MODULE_EXAMPLE)).not.toBe(
79+
require(OPTIMIZED_MODULE_EXAMPLE),
80+
);
81+
}));
82+
it('loads internal modules from outside if on the optimization list', () =>
83+
createRuntime(__filename).then(runtime => {
84+
const modulePath = path.resolve(
85+
path.dirname(runtime.__mockRootPath),
86+
'require-by-name.js',
87+
);
88+
const requireByName = runtime.requireInternalModule(modulePath);
89+
expect(requireByName(OPTIMIZED_MODULE_EXAMPLE)).toBe(
90+
require(OPTIMIZED_MODULE_EXAMPLE),
91+
);
92+
}));
7193
});
7294
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
module.exports = moduleName => require(moduleName);

packages/jest-runtime/src/index.ts

+17
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,17 @@ const defaultTransformOptions: InternalModuleOptions = {
9595
type InitialModule = Omit<Module, 'require' | 'parent' | 'paths'>;
9696
type ModuleRegistry = Map<string, InitialModule | Module>;
9797

98+
// These are modules that we know
99+
// * are safe to require from the outside (not stateful, not prone to errors passing in instances from different realms), and
100+
// * take sufficiently long to require to warrant an optimization.
101+
// When required from the outside, they use the worker's require cache and are thus
102+
// only loaded once per worker, not once per test file.
103+
// Use /benchmarks/test-file-overhead to measure the impact.
104+
// Note that this only applies when they are required in an internal context;
105+
// users who require one of these modules in their tests will still get the module from inside the VM.
106+
// Prefer listing a module here only if it is impractical to use the jest-resolve-outside-vm-option where it is required,
107+
// e.g. because there are many require sites spread across the dependency graph.
108+
const INTERNAL_MODULE_REQUIRE_OUTSIDE_OPTIMIZED_MODULES = new Set(['chalk']);
98109
const JEST_RESOLVE_OUTSIDE_VM_OPTION = Symbol.for(
99110
'jest-resolve-outside-vm-option',
100111
);
@@ -660,6 +671,12 @@ export default class Runtime {
660671

661672
requireInternalModule<T = unknown>(from: Config.Path, to?: string): T {
662673
if (to) {
674+
const require = (
675+
nativeModule.createRequire ?? nativeModule.createRequireFromPath
676+
)(from);
677+
if (INTERNAL_MODULE_REQUIRE_OUTSIDE_OPTIMIZED_MODULES.has(to)) {
678+
return require(to);
679+
}
663680
const outsideJestVmPath = decodePossibleOutsideJestVmPath(to);
664681
if (outsideJestVmPath) {
665682
return require(outsideJestVmPath);

scripts/checkCopyrightHeaders.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,15 @@ const GENERIC_IGNORED_PATTERNS = [
9999
const CUSTOM_IGNORED_PATTERNS = [
100100
'\\.(example|map)$',
101101
'^examples/.*',
102-
'^flow-typed/.*',
103102
'^packages/expect/src/jasmineUtils\\.ts$',
104103
'^packages/jest-config/src/vendor/jsonlint\\.js$',
105104
'^packages/jest-diff/src/cleanupSemantic\\.ts$',
106105
'^packages/jest-haste-map/src/watchers/common\\.js$',
107106
'^packages/jest-haste-map/src/watchers/NodeWatcher\\.js$',
108107
'^packages/jest-haste-map/src/watchers/RecrawlWarning\\.js$',
109108
'^website/static/css/code-block-buttons\\.css$',
110-
'^website/static/js/code-block-buttons\\.js',
109+
'^website/static/js/code-block-buttons\\.js$',
110+
'^benchmarks/test-file-overhead/prepare\\.sh$',
111111
].map(createRegExp);
112112

113113
const IGNORED_PATTERNS = [

0 commit comments

Comments
 (0)