Skip to content

Commit 68f446b

Browse files
authoredDec 10, 2020
perf: reuse jest file system cache for isolatedModules: false (#2189)
Jest 27 passes `cacheFS` from runtime to transformer. Since TypeScript `LanguageService` performs some file reads, we should use and update that cache so that Jest won't attempt to reread those files, improves performance.
1 parent 3c18b32 commit 68f446b

10 files changed

+12465
-149
lines changed
 

‎README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ It supports all features of TypeScript including type-checking. [Read more about
2121
| We are not doing semantic versioning and `23.10` is a re-write, run `npm i -D ts-jest@"<23.10.0"` to go back to the previous version |
2222
|---|
2323

24-
[<img src="./docs/assets/img/documentation.png" align="left" height="24"> View the online documentation (usage & technical)](https://kulshekhar.github.io/ts-jest)
24+
[<img src="./docs/static/img/documentation.png" align="left" height="24"> View the online documentation (usage & technical)](https://kulshekhar.github.io/ts-jest)
2525

26-
[<img src="./docs/assets/img/slack.png" align="left" height="24"> Ask for some help in the `ts-jest` community of Slack](https://bit.ly/3bRHFPQ)
26+
[<img src="./docs/static/img/slack.png" align="left" height="24"> Ask for some help in the `ts-jest` community of Slack](https://bit.ly/3bRHFPQ)
2727

28-
[<img src="./docs/assets/img/troubleshooting.png" align="left" height="24"> Before reporting any issue, be sure to check the troubleshooting page](TROUBLESHOOTING.md)
28+
[<img src="./docs/static/img/troubleshooting.png" align="left" height="24"> Before reporting any issue, be sure to check the troubleshooting page](TROUBLESHOOTING.md)
2929

30-
[<img src="./docs/assets/img/pull-request.png" align="left" height="24"> We're looking for collaborators! Want to help improve `ts-jest`?](https://github.com/kulshekhar/ts-jest/issues/223)
30+
[<img src="./docs/static/img/pull-request.png" align="left" height="24"> We're looking for collaborators! Want to help improve `ts-jest`?](https://github.com/kulshekhar/ts-jest/issues/223)
3131

3232
---
3333

‎e2e/__tests__/__snapshots__/logger.test.ts.snap

-12
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,12 @@ Array [
2222
"[level:20] file caching disabled",
2323
"[level:20] created language service",
2424
"[level:20] computing cache key for <cwd>/Hello.spec.ts",
25-
"[level:20] getting resolved modules from TypeScript API for <cwd>/Hello.spec.ts",
26-
"[level:20] updateMemoryCache: update memory cache for language service",
2725
"[level:20] processing <cwd>/Hello.spec.ts",
2826
"[level:20] getCompiledOutput(): compiling using language service",
2927
"[level:20] updateMemoryCache: update memory cache for language service",
3028
"[level:20] visitSourceFileNode(): hoisting",
3129
"[level:20] getCompiledOutput(): computing diagnostics using language service",
3230
"[level:20] computing cache key for <cwd>/Hello.ts",
33-
"[level:20] getting resolved modules from TypeScript API for <cwd>/Hello.ts",
34-
"[level:20] updateMemoryCache: update memory cache for language service",
3531
"[level:20] processing <cwd>/Hello.ts",
3632
"[level:20] getCompiledOutput(): compiling using language service",
3733
"[level:20] updateMemoryCache: update memory cache for language service",
@@ -66,17 +62,13 @@ Array [
6662
"[level:20] file caching disabled",
6763
"[level:20] created language service",
6864
"[level:20] computing cache key for <cwd>/Hello.spec.ts",
69-
"[level:20] getting resolved modules from TypeScript API for <cwd>/Hello.spec.ts",
70-
"[level:20] updateMemoryCache: update memory cache for language service",
7165
"[level:20] processing <cwd>/Hello.spec.ts",
7266
"[level:20] getCompiledOutput(): compiling using language service",
7367
"[level:20] updateMemoryCache: update memory cache for language service",
7468
"[level:20] visitSourceFileNode(): hoisting",
7569
"[level:20] getCompiledOutput(): computing diagnostics using language service",
7670
"[level:20] calling babel-jest processor",
7771
"[level:20] computing cache key for <cwd>/Hello.ts",
78-
"[level:20] getting resolved modules from TypeScript API for <cwd>/Hello.ts",
79-
"[level:20] updateMemoryCache: update memory cache for language service",
8072
"[level:20] processing <cwd>/Hello.ts",
8173
"[level:20] getCompiledOutput(): compiling using language service",
8274
"[level:20] updateMemoryCache: update memory cache for language service",
@@ -113,17 +105,13 @@ Array [
113105
"[level:20] file caching disabled",
114106
"[level:20] created language service",
115107
"[level:20] computing cache key for <cwd>/Hello.spec.ts",
116-
"[level:20] getting resolved modules from TypeScript API for <cwd>/Hello.spec.ts",
117-
"[level:20] updateMemoryCache: update memory cache for language service",
118108
"[level:20] processing <cwd>/Hello.spec.ts",
119109
"[level:20] getCompiledOutput(): compiling using language service",
120110
"[level:20] updateMemoryCache: update memory cache for language service",
121111
"[level:20] visitSourceFileNode(): hoisting",
122112
"[level:20] getCompiledOutput(): computing diagnostics using language service",
123113
"[level:20] calling babel-jest processor",
124114
"[level:20] computing cache key for <cwd>/Hello.ts",
125-
"[level:20] getting resolved modules from TypeScript API for <cwd>/Hello.ts",
126-
"[level:20] updateMemoryCache: update memory cache for language service",
127115
"[level:20] processing <cwd>/Hello.ts",
128116
"[level:20] getCompiledOutput(): compiling using language service",
129117
"[level:20] updateMemoryCache: update memory cache for language service",

‎package-lock.json

+12,261-22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/__helpers__/fakers.ts

+14-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Logger } from 'bs-logger'
33
import { resolve } from 'path'
44

55
import { ConfigSet } from '../config/config-set'
6-
import type { TsJestGlobalOptions } from '../types'
6+
import type { StringMap, TsJestGlobalOptions } from '../types'
77
import type { ImportReasons } from '../utils/messages'
88
import { TsJestCompiler } from '../compiler/ts-jest-compiler'
99

@@ -59,15 +59,18 @@ export function createConfigSet({
5959
}
6060

6161
// not really unit-testing here, but it's hard to mock all those values :-D
62-
export function makeCompiler({
63-
jestConfig,
64-
tsJestConfig,
65-
parentConfig,
66-
}: {
67-
jestConfig?: Partial<Config.ProjectConfig>
68-
tsJestConfig?: TsJestGlobalOptions
69-
parentConfig?: TsJestGlobalOptions
70-
} = {}): TsJestCompiler {
62+
export function makeCompiler(
63+
{
64+
jestConfig,
65+
tsJestConfig,
66+
parentConfig,
67+
}: {
68+
jestConfig?: Partial<Config.ProjectConfig>
69+
tsJestConfig?: TsJestGlobalOptions
70+
parentConfig?: TsJestGlobalOptions
71+
} = {},
72+
jestCacheFS: StringMap = new Map<string, string>(),
73+
): TsJestCompiler {
7174
tsJestConfig = { ...tsJestConfig }
7275
tsJestConfig.diagnostics = {
7376
...(tsJestConfig.diagnostics as any),
@@ -82,5 +85,5 @@ export function makeCompiler({
8285
}
8386
const cs = createConfigSet({ jestConfig, tsJestConfig, parentConfig, resolve: null })
8487

85-
return new TsJestCompiler(cs)
88+
return new TsJestCompiler(cs, jestCacheFS)
8689
}

‎src/compiler/ts-compiler.spec.ts

+119-33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { readFileSync } from 'fs'
22
import { LogLevels } from 'bs-logger'
3+
import { join } from 'path'
34

45
import { TS_JEST_OUT_DIR } from '../config/config-set'
56
import { makeCompiler } from '../__helpers__/fakers'
@@ -163,6 +164,7 @@ const t: string = f(5)
163164

164165
describe('isolatedModule false', () => {
165166
const baseTsJestConfig = { tsconfig: require.resolve('../../tsconfig.spec.json') }
167+
const jestCacheFS = new Map<string, string>()
166168

167169
beforeEach(() => {
168170
logTarget.clear()
@@ -171,21 +173,28 @@ const t: string = f(5)
171173
describe('allowJs option', () => {
172174
const fileName = 'test-allow-js.js'
173175
const source = 'export default 42'
176+
jestCacheFS.set(fileName, source)
174177

175178
it('should compile js file for allowJs true with outDir', () => {
176-
const compiler = makeCompiler({
177-
tsJestConfig: { tsconfig: { allowJs: true, outDir: '$$foo$$' } },
178-
})
179+
const compiler = makeCompiler(
180+
{
181+
tsJestConfig: { tsconfig: { allowJs: true, outDir: '$$foo$$' } },
182+
},
183+
jestCacheFS,
184+
)
179185

180186
const compiled = compiler.getCompiledOutput(source, fileName)
181187

182188
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
183189
})
184190

185191
it('should compile js file for allowJs true without outDir', () => {
186-
const compiler = makeCompiler({
187-
tsJestConfig: { tsconfig: { allowJs: true } },
188-
})
192+
const compiler = makeCompiler(
193+
{
194+
tsJestConfig: { tsconfig: { allowJs: true } },
195+
},
196+
jestCacheFS,
197+
)
189198
const compiled = compiler.getCompiledOutput(source, fileName)
190199

191200
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
@@ -199,29 +208,36 @@ const t: string = f(5)
199208
return <>Test</>
200209
}
201210
`
211+
jestCacheFS.set(fileName, source)
202212

203213
it('should compile tsx file for jsx preserve', () => {
204-
const compiler = makeCompiler({
205-
tsJestConfig: {
206-
tsconfig: {
207-
jsx: 'preserve' as any,
214+
const compiler = makeCompiler(
215+
{
216+
tsJestConfig: {
217+
tsconfig: {
218+
jsx: 'preserve' as any,
219+
},
208220
},
209221
},
210-
})
222+
jestCacheFS,
223+
)
211224

212225
const compiled = compiler.getCompiledOutput(source, fileName)
213226

214227
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
215228
})
216229

217230
it('should compile tsx file for other jsx options', () => {
218-
const compiler = makeCompiler({
219-
tsJestConfig: {
220-
tsconfig: {
221-
jsx: 'react' as any,
231+
const compiler = makeCompiler(
232+
{
233+
tsJestConfig: {
234+
tsconfig: {
235+
jsx: 'react' as any,
236+
},
222237
},
223238
},
224-
})
239+
jestCacheFS,
240+
)
225241
const compiled = compiler.getCompiledOutput(source, fileName)
226242

227243
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
@@ -231,9 +247,13 @@ const t: string = f(5)
231247
describe('source maps', () => {
232248
const source = 'const gsm = (v: number) => v\nconst h: number = gsm(5)'
233249
const fileName = 'test-source-map.ts'
250+
jestCacheFS.set(fileName, source)
234251

235252
it('should have correct source maps without mapRoot', () => {
236-
const compiler = makeCompiler({ tsJestConfig: { tsconfig: require.resolve('../../tsconfig.spec.json') } })
253+
const compiler = makeCompiler(
254+
{ tsJestConfig: { tsconfig: require.resolve('../../tsconfig.spec.json') } },
255+
jestCacheFS,
256+
)
237257
const compiled = compiler.getCompiledOutput(source, fileName)
238258

239259
expect(new ProcessedSource(compiled, fileName).outputSourceMaps).toMatchObject({
@@ -244,13 +264,16 @@ const t: string = f(5)
244264
})
245265

246266
it('should have correct source maps with mapRoot', () => {
247-
const compiler = makeCompiler({
248-
tsJestConfig: {
249-
tsconfig: {
250-
mapRoot: './',
267+
const compiler = makeCompiler(
268+
{
269+
tsJestConfig: {
270+
tsconfig: {
271+
mapRoot: './',
272+
},
251273
},
252274
},
253-
})
275+
jestCacheFS,
276+
)
254277
const compiled = compiler.getCompiledOutput(source, fileName)
255278

256279
expect(new ProcessedSource(compiled, fileName).outputSourceMaps).toMatchObject({
@@ -278,25 +301,84 @@ const t: string = f(5)
278301
})
279302
})
280303

281-
describe('diagnostics', () => {
282-
const importedFileName = require.resolve('../__mocks__/thing.ts')
283-
const importedFileContent = readFileSync(importedFileName, 'utf-8')
304+
describe('getResolvedModulesMap', () => {
305+
const fileName = 'foo.ts'
306+
const fileContent = 'const foo = 1'
284307

285-
it(`shouldn't report diagnostics when file name doesn't match diagnostic file pattern`, () => {
308+
test('should return undefined when file name is not known to compiler', () => {
309+
const compiler = makeCompiler({
310+
tsJestConfig: baseTsJestConfig,
311+
})
312+
313+
expect(compiler.getResolvedModulesMap(fileContent, fileName)).toBeUndefined()
314+
})
315+
316+
test('should return undefined when it is isolatedModules true', () => {
286317
const compiler = makeCompiler({
287318
tsJestConfig: {
288319
...baseTsJestConfig,
289-
diagnostics: { pathRegex: 'foo.spec.ts' },
320+
isolatedModules: true,
290321
},
291322
})
292323

324+
expect(compiler.getResolvedModulesMap(fileContent, fileName)).toBeUndefined()
325+
})
326+
327+
test('should return undefined when file has no resolved modules', () => {
328+
const jestCacheFS = new Map<string, string>()
329+
jestCacheFS.set(fileName, fileContent)
330+
const compiler = makeCompiler(
331+
{
332+
tsJestConfig: baseTsJestConfig,
333+
},
334+
jestCacheFS,
335+
)
336+
337+
expect(compiler.getResolvedModulesMap(fileContent, fileName)).toBeUndefined()
338+
})
339+
340+
test('should return resolved modules when file has resolved modules', () => {
341+
const jestCacheFS = new Map<string, string>()
342+
const fileContentWithModules = readFileSync(join(__dirname, '..', '__mocks__', 'thing.spec.ts'), 'utf-8')
343+
jestCacheFS.set(fileName, fileContentWithModules)
344+
const compiler = makeCompiler(
345+
{
346+
tsJestConfig: baseTsJestConfig,
347+
},
348+
jestCacheFS,
349+
)
350+
351+
expect(compiler.getResolvedModulesMap(fileContentWithModules, fileName)).toBeDefined()
352+
})
353+
})
354+
355+
describe('diagnostics', () => {
356+
const importedFileName = require.resolve('../__mocks__/thing.ts')
357+
const importedFileContent = readFileSync(importedFileName, 'utf-8')
358+
359+
it(`shouldn't report diagnostics when file name doesn't match diagnostic file pattern`, () => {
360+
jestCacheFS.set(importedFileName, importedFileContent)
361+
const compiler = makeCompiler(
362+
{
363+
tsJestConfig: {
364+
...baseTsJestConfig,
365+
diagnostics: { pathRegex: 'foo.spec.ts' },
366+
},
367+
},
368+
jestCacheFS,
369+
)
370+
293371
expect(() => compiler.getCompiledOutput(importedFileContent, importedFileName)).not.toThrowError()
294372
})
295373

296374
it(`shouldn't report diagnostic when processing file isn't used by any test files`, () => {
297-
const compiler = makeCompiler({
298-
tsJestConfig: baseTsJestConfig,
299-
})
375+
jestCacheFS.set('foo.ts', importedFileContent)
376+
const compiler = makeCompiler(
377+
{
378+
tsJestConfig: baseTsJestConfig,
379+
},
380+
jestCacheFS,
381+
)
300382
logTarget.clear()
301383

302384
compiler.getCompiledOutput(importedFileContent, 'foo.ts')
@@ -311,9 +393,13 @@ const t: string = f(5)
311393
a: string
312394
}
313395
`
314-
const compiler = makeCompiler({
315-
tsJestConfig: baseTsJestConfig,
316-
})
396+
jestCacheFS.set(fileName, source)
397+
const compiler = makeCompiler(
398+
{
399+
tsJestConfig: baseTsJestConfig,
400+
},
401+
jestCacheFS,
402+
)
317403

318404
expect(() => compiler.getCompiledOutput(source, fileName)).toThrowErrorMatchingSnapshot()
319405
})

‎src/compiler/ts-compiler.ts

+26-35
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,28 @@ import type {
1313
import { updateOutput } from './compiler-utils'
1414
import type { ConfigSet } from '../config/config-set'
1515
import { LINE_FEED } from '../constants'
16-
import type { CompilerInstance, ResolvedModulesMap, TTypeScript } from '../types'
16+
import type { CompilerInstance, ResolvedModulesMap, StringMap, TTypeScript } from '../types'
1717
import { rootLogger } from '../utils/logger'
1818
import { Errors, interpolate } from '../utils/messages'
1919

20-
interface TSFile {
21-
text?: string
22-
version: number
23-
}
24-
2520
/**
2621
* @internal
2722
*/
2823
export class TsCompiler implements CompilerInstance {
2924
private readonly _logger: Logger
3025
private readonly _ts: TTypeScript
3126
private readonly _parsedTsConfig: ParsedCommandLine
32-
private readonly _cacheFS: Map<string, TSFile> = new Map<string, TSFile>()
33-
private _cachedReadFile: any
27+
private readonly _compilerCacheFS: Map<string, number> = new Map<string, number>()
28+
private readonly _jestCacheFS: StringMap
29+
private _cachedReadFile: ((fileName: string) => string | undefined) | undefined
3430
private _projectVersion = 1
3531
private _languageService: LanguageService | undefined
3632

37-
constructor(private readonly configSet: ConfigSet) {
33+
constructor(readonly configSet: ConfigSet, readonly jestCacheFS: StringMap) {
3834
this._ts = configSet.compilerModule
3935
this._logger = rootLogger.child({ namespace: 'ts-compiler' })
4036
this._parsedTsConfig = this.configSet.parsedTsConfig as ParsedCommandLine
37+
this._jestCacheFS = jestCacheFS
4138
if (!this.configSet.isolatedModules) {
4239
this._createLanguageService()
4340
}
@@ -52,11 +49,7 @@ export class TsCompiler implements CompilerInstance {
5249
// Initialize memory cache for typescript compiler
5350
this._parsedTsConfig.fileNames
5451
.filter((fileName) => !this.configSet.isTestFile(fileName))
55-
.forEach((fileName) => {
56-
this._cacheFS.set(fileName, {
57-
version: 0,
58-
})
59-
})
52+
.forEach((fileName) => this._compilerCacheFS.set(fileName, 0))
6053
this._cachedReadFile = this._logger.wrap(serviceHostTraceCtx, 'readFile', memoize(this._ts.sys.readFile))
6154
/* istanbul ignore next */
6255
const moduleResolutionHost = {
@@ -75,10 +68,10 @@ export class TsCompiler implements CompilerInstance {
7568
/* istanbul ignore next */
7669
const serviceHost: LanguageServiceHost = {
7770
getProjectVersion: () => String(this._projectVersion),
78-
getScriptFileNames: () => [...this._cacheFS.keys()],
71+
getScriptFileNames: () => [...this._compilerCacheFS.keys()],
7972
getScriptVersion: (fileName: string) => {
8073
const normalizedFileName = normalize(fileName)
81-
const version = this._cacheFS.get(normalizedFileName)?.version
74+
const version = this._compilerCacheFS.get(normalizedFileName)
8275

8376
// We need to return `undefined` and not a string here because TypeScript will use
8477
// `getScriptVersion` and compare against their own version - which can be `undefined`.
@@ -95,12 +88,14 @@ export class TsCompiler implements CompilerInstance {
9588

9689
// Read contents from TypeScript memory cache.
9790
if (!hit) {
98-
this._cacheFS.set(normalizedFileName, {
99-
text: this._cachedReadFile(normalizedFileName),
100-
version: 1,
101-
})
91+
const fileContent =
92+
this._jestCacheFS.get(normalizedFileName) ?? this._cachedReadFile?.(normalizedFileName) ?? undefined
93+
if (fileContent) {
94+
this._jestCacheFS.set(normalizedFileName, fileContent)
95+
this._compilerCacheFS.set(normalizedFileName, 1)
96+
}
10297
}
103-
const contents = this._cacheFS.get(normalizedFileName)?.text
98+
const contents = this._jestCacheFS.get(normalizedFileName)
10499

105100
if (contents === undefined) return
106101

@@ -140,7 +135,7 @@ export class TsCompiler implements CompilerInstance {
140135
this._updateMemoryCache(fileContent, fileName)
141136

142137
// See https://github.com/microsoft/TypeScript/blob/master/src/compiler/utilities.ts#L164
143-
return (this._languageService?.getProgram()?.getSourceFile(fileName) as any).resolvedModules
138+
return (this._languageService?.getProgram()?.getSourceFile(fileName) as any)?.resolvedModules
144139
}
145140

146141
getCompiledOutput(fileContent: string, fileName: string): string {
@@ -187,8 +182,11 @@ export class TsCompiler implements CompilerInstance {
187182
}
188183

189184
private _isFileInCache(fileName: string): boolean {
190-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
191-
return this._cacheFS.has(fileName) && this._cacheFS.get(fileName)!.version !== 0
185+
return (
186+
this._jestCacheFS.has(fileName) &&
187+
this._compilerCacheFS.has(fileName) &&
188+
this._compilerCacheFS.get(fileName) !== 0
189+
)
192190
}
193191

194192
/* istanbul ignore next */
@@ -198,21 +196,14 @@ export class TsCompiler implements CompilerInstance {
198196
let shouldIncrementProjectVersion = false
199197
const hit = this._isFileInCache(fileName)
200198
if (!hit) {
201-
this._cacheFS.set(fileName, {
202-
text: contents,
203-
version: 1,
204-
})
199+
this._compilerCacheFS.set(fileName, 1)
205200
shouldIncrementProjectVersion = true
206201
} else {
207-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
208-
const cachedFileName = this._cacheFS.get(fileName)!
209-
const previousContents = cachedFileName.text
202+
const prevVersion = this._compilerCacheFS.get(fileName) ?? 0
203+
const previousContents = this._jestCacheFS.get(fileName)
210204
// Avoid incrementing cache when nothing has changed.
211205
if (previousContents !== contents) {
212-
this._cacheFS.set(fileName, {
213-
text: contents,
214-
version: cachedFileName.version + 1,
215-
})
206+
this._compilerCacheFS.set(fileName, prevVersion + 1)
216207
// Only bump project version when file is modified in cache, not when discovered for the first time
217208
if (hit) shouldIncrementProjectVersion = true
218209
}

‎src/compiler/ts-jest-compiler.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { TsCompiler } from './ts-compiler'
22
import type { ConfigSet } from '../config/config-set'
3-
import type { CompilerInstance, ResolvedModulesMap } from '../types'
3+
import type { CompilerInstance, ResolvedModulesMap, StringMap } from '../types'
44

55
/**
66
* @internal
77
*/
88
export class TsJestCompiler implements CompilerInstance {
99
private readonly _compilerInstance: CompilerInstance
1010

11-
constructor(private readonly configSet: ConfigSet) {
11+
constructor(readonly configSet: ConfigSet, readonly jestCacheFS: StringMap) {
1212
// Later we can add swc/esbuild or other typescript compiler instance here
13-
this._compilerInstance = new TsCompiler(this.configSet)
13+
this._compilerInstance = new TsCompiler(configSet, jestCacheFS)
1414
}
1515

1616
getResolvedModulesMap(fileContent: string, fileName: string): ResolvedModulesMap {

‎src/ts-jest-transformer.spec.ts

+20-9
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ beforeEach(() => {
3131
describe('TsJestTransformer', () => {
3232
describe('_configsFor', () => {
3333
test('should return the same config set for same values with jest config string is not in configSetsIndex', () => {
34-
const obj1 = { cwd: '/foo/.', rootDir: '/bar//dummy/..', globals: {} }
34+
const obj1 = { config: { cwd: '/foo/.', rootDir: '/bar//dummy/..', globals: {} } }
3535
// @ts-expect-error testing purpose
3636
const cs3 = new TsJestTransformer()._configsFor(obj1 as any)
3737

@@ -40,7 +40,7 @@ describe('TsJestTransformer', () => {
4040
})
4141

4242
test('should return the same config set for same values with jest config string in configSetsIndex', () => {
43-
const obj1 = { cwd: '/foo/.', rootDir: '/bar//dummy/..', globals: {} }
43+
const obj1 = { config: { cwd: '/foo/.', rootDir: '/bar//dummy/..', globals: {} } }
4444
const obj2 = { ...obj1 }
4545
// @ts-expect-error testing purpose
4646
const cs1 = new TsJestTransformer()._configsFor(obj1 as any)
@@ -128,14 +128,25 @@ describe('TsJestTransformer', () => {
128128
rootDir: '/foo',
129129
},
130130
} as any
131+
const transformOptionsWithCache = {
132+
...input.transformOptions,
133+
config: {
134+
...input.transformOptions.config,
135+
cache: true,
136+
cacheDirectory: cacheDir,
137+
},
138+
}
131139
const depGraphs: ResolvedModulesMap = new Map<string, ResolvedModuleFull | undefined>()
132140

133141
beforeEach(() => {
134142
depGraphs.clear()
143+
// @ts-expect-error testing purpose
144+
TsJestTransformer._cachedConfigSets = []
135145
tr = new TsJestTransformer()
136146
})
137147

138148
afterEach(() => {
149+
removeSync(cacheDir)
139150
jest.restoreAllMocks()
140151
})
141152

@@ -165,8 +176,8 @@ describe('TsJestTransformer', () => {
165176
depGraphs.set(input.fileName, resolvedModule)
166177
jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs)
167178

168-
const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, input.transformOptions)
169-
const cacheKey2 = tr.getCacheKey(input.fileContent, input.fileName, input.transformOptions)
179+
const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache)
180+
const cacheKey2 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache)
170181

171182
expect(cacheKey1).toEqual(cacheKey2)
172183
expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledTimes(1)
@@ -187,7 +198,7 @@ describe('TsJestTransformer', () => {
187198

188199
jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs)
189200
const tr1 = new TsJestTransformer()
190-
const cacheKey2 = tr1.getCacheKey(input.fileContent, input.fileName, input.transformOptions)
201+
const cacheKey2 = tr1.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache)
191202

192203
expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledTimes(1)
193204
expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledWith(input.fileContent, input.fileName)
@@ -198,11 +209,11 @@ describe('TsJestTransformer', () => {
198209
depGraphs.set(input.fileName, resolvedModule)
199210
jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs)
200211

201-
const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, input.transformOptions)
212+
const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache)
202213

203214
jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs)
204215
const newFileContent = 'const foo = 1'
205-
const cacheKey2 = tr.getCacheKey(newFileContent, input.fileName, input.transformOptions)
216+
const cacheKey2 = tr.getCacheKey(newFileContent, input.fileName, transformOptionsWithCache)
206217

207218
expect(cacheKey1).not.toEqual(cacheKey2)
208219
expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledTimes(2)
@@ -218,10 +229,10 @@ describe('TsJestTransformer', () => {
218229
depGraphs.set(input.fileName, resolvedModule)
219230
jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs)
220231

221-
const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, input.transformOptions)
232+
const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache)
222233

223234
jest.spyOn(fs, 'existsSync').mockReturnValueOnce(false)
224-
const cacheKey2 = tr.getCacheKey(input.fileContent, input.fileName, input.transformOptions)
235+
const cacheKey2 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache)
225236

226237
expect(cacheKey1).not.toEqual(cacheKey2)
227238
expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledTimes(1)

‎src/ts-jest-transformer.ts

+14-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { TransformedSource, Transformer, TransformOptions } from '@jest/transform'
22
import type { Config } from '@jest/types'
33
import type { Logger } from 'bs-logger'
4-
import { existsSync, readFileSync, statSync, writeFile } from 'fs'
4+
import { existsSync, readFileSync, statSync, writeFileSync } from 'fs'
55
import mkdirp from 'mkdirp'
66
import { join } from 'path'
77

@@ -53,9 +53,10 @@ export class TsJestTransformer implements Transformer {
5353
this._logger.debug('created new transformer')
5454
}
5555

56-
protected _configsFor(jestConfig: Config.ProjectConfig): ConfigSet {
56+
protected _configsFor(transformOptions: TransformOptions): ConfigSet {
57+
const { config, cacheFS } = transformOptions
5758
const ccs: CachedConfigSet | undefined = TsJestTransformer._cachedConfigSets.find(
58-
(cs) => cs.jestConfig.value === jestConfig,
59+
(cs) => cs.jestConfig.value === config,
5960
)
6061
let configSet: ConfigSet
6162
if (ccs) {
@@ -64,24 +65,24 @@ export class TsJestTransformer implements Transformer {
6465
configSet = ccs.configSet
6566
} else {
6667
// try to look-it up by stringified version
67-
const serializedJestCfg = stringify(jestConfig)
68+
const serializedJestCfg = stringify(config)
6869
const serializedCcs = TsJestTransformer._cachedConfigSets.find(
6970
(cs) => cs.jestConfig.serialized === serializedJestCfg,
7071
)
7172
if (serializedCcs) {
7273
// update the object so that we can find it later
7374
// this happens because jest first calls getCacheKey with stringified version of
7475
// the config, and then it calls the transformer with the proper object
75-
serializedCcs.jestConfig.value = jestConfig
76+
serializedCcs.jestConfig.value = config
7677
this._transformCfgStr = serializedCcs.transformerCfgStr
7778
this._compiler = serializedCcs.compiler
7879
configSet = serializedCcs.configSet
7980
} else {
8081
// create the new record in the index
8182
this._logger.info('no matching config-set found, creating a new one')
8283

83-
configSet = new ConfigSet(jestConfig)
84-
const jest = { ...jestConfig }
84+
configSet = new ConfigSet(config)
85+
const jest = { ...config }
8586
// we need to remove some stuff from jest config
8687
// this which does not depend on config
8788
jest.name = undefined as any
@@ -95,9 +96,9 @@ export class TsJestTransformer implements Transformer {
9596
raw: configSet.parsedTsConfig.raw,
9697
},
9798
}).serialized
98-
this._compiler = new TsJestCompiler(configSet)
99+
this._compiler = new TsJestCompiler(configSet, cacheFS)
99100
TsJestTransformer._cachedConfigSets.push({
100-
jestConfig: new JsonableValue(jestConfig),
101+
jestConfig: new JsonableValue(config),
101102
configSet,
102103
transformerCfgStr: this._transformCfgStr,
103104
compiler: this._compiler,
@@ -117,7 +118,7 @@ export class TsJestTransformer implements Transformer {
117118

118119
let result: string | TransformedSource
119120
const jestConfig = transformOptions.config
120-
const configs = this._configsFor(jestConfig)
121+
const configs = this._configsFor(transformOptions)
121122
const { hooks } = configs
122123
const shouldStringifyContent = configs.shouldStringifyContent(filePath)
123124
const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer
@@ -177,7 +178,7 @@ export class TsJestTransformer implements Transformer {
177178
* @public
178179
*/
179180
getCacheKey(fileContent: string, filePath: string, transformOptions: TransformOptions): string {
180-
const configs = this._configsFor(transformOptions.config)
181+
const configs = this._configsFor(transformOptions)
181182

182183
this._logger.debug({ fileName: filePath, transformOptions }, 'computing cache key for', filePath)
183184

@@ -194,7 +195,7 @@ export class TsJestTransformer implements Transformer {
194195
CACHE_KEY_EL_SEPARATOR,
195196
filePath,
196197
]
197-
if (!configs.isolatedModules) {
198+
if (!configs.isolatedModules && this._tsResolvedModulesCachePath) {
198199
let resolvedModuleNames: string[]
199200
if (this._depGraphs.get(filePath)?.fileContent === fileContent) {
200201
this._logger.debug(
@@ -225,14 +226,7 @@ export class TsJestTransformer implements Transformer {
225226
fileContent,
226227
resolveModuleNames: resolvedModuleNames,
227228
})
228-
/* istanbul ignore next */
229-
if (this._tsResolvedModulesCachePath) {
230-
// Cache resolved modules to disk so next run can reuse it
231-
void (async () => {
232-
// eslint-disable-next-line @typescript-eslint/await-thenable
233-
await writeFile(this._tsResolvedModulesCachePath as string, stringify([...this._depGraphs]), () => {})
234-
})()
235-
}
229+
writeFileSync(this._tsResolvedModulesCachePath, stringify([...this._depGraphs]))
236230
}
237231
resolvedModuleNames.forEach((moduleName) => {
238232
constructingCacheKeyElements.push(

‎src/types.ts

+4
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ export interface TsJestConfig {
173173
}
174174

175175
export type ResolvedModulesMap = Map<string, _ts.ResolvedModuleFull | undefined> | undefined
176+
/**
177+
* @internal
178+
*/
179+
export type StringMap = Map<string, string>
176180

177181
export interface CompilerInstance {
178182
getResolvedModulesMap(fileContent: string, fileName: string): ResolvedModulesMap

0 commit comments

Comments
 (0)
Please sign in to comment.