1 | /*
|
2 | * Copyright 2022 The Backstage Authors
|
3 | *
|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | * you may not use this file except in compliance with the License.
|
6 | * You may obtain a copy of the License at
|
7 | *
|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
9 | *
|
10 | * Unless required by applicable law or agreed to in writing, software
|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | * See the License for the specific language governing permissions and
|
14 | * limitations under the License.
|
15 | */
|
16 |
|
17 | const fs = require('fs');
|
18 | const { default: JestRuntime } = require('jest-runtime');
|
19 |
|
20 | const fileTransformCache = new Map();
|
21 | const scriptTransformCache = new Map();
|
22 |
|
23 | let runtimeGeneration = 0;
|
24 |
|
25 | module.exports = class CachingJestRuntime extends JestRuntime {
|
26 | // Each Jest run creates a new runtime, including when rerunning tests in
|
27 | // watch mode. This keeps track of whether we've switched runtime instance.
|
28 | __runtimeGeneration = runtimeGeneration++;
|
29 |
|
30 | transformFile(filename, options) {
|
31 | const entry = fileTransformCache.get(filename);
|
32 | if (entry) {
|
33 | // Only check modification time if it's from a different runtime generation
|
34 | if (entry.generation === this.__runtimeGeneration) {
|
35 | return entry.code;
|
36 | }
|
37 |
|
38 | // Keep track of the modification time of files so that we can properly
|
39 | // reprocess them in watch mode.
|
40 | const { mtimeMs } = fs.statSync(filename);
|
41 | if (mtimeMs > entry.mtimeMs) {
|
42 | const code = super.transformFile(filename, options);
|
43 | fileTransformCache.set(filename, {
|
44 | code,
|
45 | mtimeMs,
|
46 | generation: this.__runtimeGeneration,
|
47 | });
|
48 | return code;
|
49 | }
|
50 |
|
51 | fileTransformCache.set(filename, {
|
52 | ...entry,
|
53 | generation: this.__runtimeGeneration,
|
54 | });
|
55 | return entry.code;
|
56 | }
|
57 |
|
58 | const code = super.transformFile(filename, options);
|
59 | fileTransformCache.set(filename, {
|
60 | code,
|
61 | mtimeMs: fs.statSync(filename).mtimeMs,
|
62 | generation: this.__runtimeGeneration,
|
63 | });
|
64 | return code;
|
65 | }
|
66 |
|
67 | // This may or may not be a good idea. Theoretically I don't know why this would impact
|
68 | // test correctness and flakiness, but it seems like it may introduce flakiness and strange failures.
|
69 | // It does seem to speed up test execution by a fair amount though.
|
70 | createScriptFromCode(scriptSource, filename) {
|
71 | let script = scriptTransformCache.get(scriptSource);
|
72 | if (!script) {
|
73 | script = super.createScriptFromCode(scriptSource, filename);
|
74 | // Tried to store the script object in a WeakRef here. It starts out at
|
75 | // about 90% hit rate, but eventually drops all the way to 20%, and overall
|
76 | // it seemed to increase memory usage by 20% or so.
|
77 | scriptTransformCache.set(scriptSource, script);
|
78 | }
|
79 | return script;
|
80 | }
|
81 | };
|