@@ -16,11 +16,13 @@ import type {
16
16
Bundle ,
17
17
CustomTransformerFactory ,
18
18
CustomTransformers ,
19
+ ModuleResolutionHost ,
20
+ ModuleResolutionCache ,
19
21
} from 'typescript'
20
22
21
23
import { ConfigSet , TS_JEST_OUT_DIR } from '../config/config-set'
22
24
import { LINE_FEED } from '../constants'
23
- import type { ResolvedModulesMap , StringMap , TsCompilerInstance , TsJestAstTransformer , TTypeScript } from '../types'
25
+ import type { StringMap , TsCompilerInstance , TsJestAstTransformer , TTypeScript } from '../types'
24
26
import { rootLogger } from '../utils/logger'
25
27
import { Errors , interpolate } from '../utils/messages'
26
28
@@ -31,18 +33,26 @@ export class TsCompiler implements TsCompilerInstance {
31
33
protected readonly _ts : TTypeScript
32
34
protected readonly _initialCompilerOptions : CompilerOptions
33
35
protected _compilerOptions : CompilerOptions
36
+ /**
37
+ * @private
38
+ */
39
+ private _runtimeCacheFS : StringMap
40
+ /**
41
+ * @private
42
+ */
43
+ private _fileContentCache : StringMap | undefined
34
44
/**
35
45
* @internal
36
46
*/
37
47
private readonly _parsedTsConfig : ParsedCommandLine
38
48
/**
39
49
* @internal
40
50
*/
41
- private readonly _compilerCacheFS : Map < string , number > = new Map < string , number > ( )
51
+ private readonly _fileVersionCache : Map < string , number > | undefined
42
52
/**
43
53
* @internal
44
54
*/
45
- private _cachedReadFile : ( ( fileName : string ) => string | undefined ) | undefined
55
+ private readonly _cachedReadFile : ( ( fileName : string ) => string | undefined ) | undefined
46
56
/**
47
57
* @internal
48
58
*/
@@ -51,15 +61,50 @@ export class TsCompiler implements TsCompilerInstance {
51
61
* @internal
52
62
*/
53
63
private _languageService : LanguageService | undefined
64
+ /**
65
+ * @internal
66
+ */
67
+ private readonly _moduleResolutionHost : ModuleResolutionHost | undefined
68
+ /**
69
+ * @internal
70
+ */
71
+ private readonly _moduleResolutionCache : ModuleResolutionCache | undefined
72
+
54
73
program : Program | undefined
55
74
56
- constructor ( readonly configSet : ConfigSet , readonly jestCacheFS : StringMap ) {
75
+ constructor ( readonly configSet : ConfigSet , readonly runtimeCacheFS : StringMap ) {
57
76
this . _ts = configSet . compilerModule
58
77
this . _logger = rootLogger . child ( { namespace : 'ts-compiler' } )
59
78
this . _parsedTsConfig = this . configSet . parsedTsConfig as ParsedCommandLine
60
79
this . _initialCompilerOptions = { ...this . _parsedTsConfig . options }
61
80
this . _compilerOptions = { ...this . _initialCompilerOptions }
81
+ this . _runtimeCacheFS = runtimeCacheFS
62
82
if ( ! this . configSet . isolatedModules ) {
83
+ this . _fileContentCache = new Map < string , string > ( )
84
+ this . _fileVersionCache = new Map < string , number > ( )
85
+ this . _cachedReadFile = this . _logger . wrap (
86
+ {
87
+ namespace : 'ts:serviceHost' ,
88
+ call : null ,
89
+ [ LogContexts . logLevel ] : LogLevels . trace ,
90
+ } ,
91
+ 'readFile' ,
92
+ memoize ( this . _ts . sys . readFile ) ,
93
+ )
94
+ /* istanbul ignore next */
95
+ this . _moduleResolutionHost = {
96
+ fileExists : memoize ( this . _ts . sys . fileExists ) ,
97
+ readFile : this . _cachedReadFile ,
98
+ directoryExists : memoize ( this . _ts . sys . directoryExists ) ,
99
+ getCurrentDirectory : ( ) => this . configSet . cwd ,
100
+ realpath : this . _ts . sys . realpath && memoize ( this . _ts . sys . realpath ) ,
101
+ getDirectories : memoize ( this . _ts . sys . getDirectories ) ,
102
+ }
103
+ this . _moduleResolutionCache = this . _ts . createModuleResolutionCache (
104
+ this . configSet . cwd ,
105
+ ( x ) => x ,
106
+ this . _compilerOptions ,
107
+ )
63
108
this . _createLanguageService ( )
64
109
}
65
110
}
@@ -68,11 +113,6 @@ export class TsCompiler implements TsCompilerInstance {
68
113
* @internal
69
114
*/
70
115
private _createLanguageService ( ) : void {
71
- const serviceHostTraceCtx = {
72
- namespace : 'ts:serviceHost' ,
73
- call : null ,
74
- [ LogContexts . logLevel ] : LogLevels . trace ,
75
- }
76
116
// Initialize memory cache for typescript compiler
77
117
this . _parsedTsConfig . fileNames
78
118
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -81,29 +121,17 @@ export class TsCompiler implements TsCompilerInstance {
81
121
! this . configSet . isTestFile ( fileName ) &&
82
122
! fileName . includes ( this . _parsedTsConfig . options . outDir ?? TS_JEST_OUT_DIR ) ,
83
123
)
84
- . forEach ( ( fileName ) => this . _compilerCacheFS . set ( fileName , 0 ) )
85
- this . _cachedReadFile = this . _logger . wrap ( serviceHostTraceCtx , 'readFile' , memoize ( this . _ts . sys . readFile ) )
86
- /* istanbul ignore next */
87
- const moduleResolutionHost = {
88
- fileExists : memoize ( this . _ts . sys . fileExists ) ,
89
- readFile : this . _cachedReadFile ,
90
- directoryExists : memoize ( this . _ts . sys . directoryExists ) ,
91
- getCurrentDirectory : ( ) => this . configSet . cwd ,
92
- realpath : this . _ts . sys . realpath && memoize ( this . _ts . sys . realpath ) ,
93
- getDirectories : memoize ( this . _ts . sys . getDirectories ) ,
94
- }
95
- const moduleResolutionCache = this . _ts . createModuleResolutionCache (
96
- this . configSet . cwd ,
97
- ( x ) => x ,
98
- this . _compilerOptions ,
99
- )
124
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
125
+ . forEach ( ( fileName ) => this . _fileVersionCache ! . set ( fileName , 0 ) )
100
126
/* istanbul ignore next */
101
127
const serviceHost : LanguageServiceHost = {
102
128
getProjectVersion : ( ) => String ( this . _projectVersion ) ,
103
- getScriptFileNames : ( ) => [ ...this . _compilerCacheFS . keys ( ) ] ,
129
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
130
+ getScriptFileNames : ( ) => [ ...this . _fileVersionCache ! . keys ( ) ] ,
104
131
getScriptVersion : ( fileName : string ) => {
105
132
const normalizedFileName = normalize ( fileName )
106
- const version = this . _compilerCacheFS . get ( normalizedFileName )
133
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
134
+ const version = this . _fileVersionCache ! . get ( normalizedFileName )
107
135
108
136
// We need to return `undefined` and not a string here because TypeScript will use
109
137
// `getScriptVersion` and compare against their own version - which can be `undefined`.
@@ -122,13 +150,20 @@ export class TsCompiler implements TsCompilerInstance {
122
150
// Read contents from TypeScript memory cache.
123
151
if ( ! hit ) {
124
152
const fileContent =
125
- this . jestCacheFS . get ( normalizedFileName ) ?? this . _cachedReadFile ?.( normalizedFileName ) ?? undefined
153
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
154
+ this . _fileContentCache ! . get ( normalizedFileName ) ??
155
+ this . _runtimeCacheFS . get ( normalizedFileName ) ??
156
+ this . _cachedReadFile ?.( normalizedFileName ) ??
157
+ undefined
126
158
if ( fileContent ) {
127
- this . jestCacheFS . set ( normalizedFileName , fileContent )
128
- this . _compilerCacheFS . set ( normalizedFileName , 1 )
159
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
160
+ this . _fileContentCache ! . set ( normalizedFileName , fileContent )
161
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
162
+ this . _fileVersionCache ! . set ( normalizedFileName , 1 )
129
163
}
130
164
}
131
- const contents = this . jestCacheFS . get ( normalizedFileName )
165
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
166
+ const contents = this . _fileContentCache ! . get ( normalizedFileName )
132
167
133
168
if ( contents === undefined ) return
134
169
@@ -151,8 +186,10 @@ export class TsCompiler implements TsCompilerInstance {
151
186
moduleName ,
152
187
containingFile ,
153
188
this . _compilerOptions ,
154
- moduleResolutionHost ,
155
- moduleResolutionCache ,
189
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
190
+ this . _moduleResolutionHost ! ,
191
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
192
+ this . _moduleResolutionCache ! ,
156
193
)
157
194
158
195
return resolvedModule
@@ -165,12 +202,29 @@ export class TsCompiler implements TsCompilerInstance {
165
202
this . program = this . _languageService . getProgram ( )
166
203
}
167
204
168
- getResolvedModulesMap ( fileContent : string , fileName : string ) : ResolvedModulesMap {
169
- this . _updateMemoryCache ( fileContent , fileName )
205
+ getResolvedModules ( fileContent : string , fileName : string , runtimeCacheFS : StringMap ) : string [ ] {
206
+ // In watch mode, it is possible that the initial cacheFS becomes empty
207
+ if ( ! this . runtimeCacheFS . size ) {
208
+ this . _runtimeCacheFS = runtimeCacheFS
209
+ }
210
+
211
+ return this . _ts
212
+ . preProcessFile ( fileContent , true , true )
213
+ . importedFiles . map ( ( importedFile ) => {
214
+ const { resolvedModule } = this . _ts . resolveModuleName (
215
+ importedFile . fileName ,
216
+ fileName ,
217
+ this . _compilerOptions ,
218
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
219
+ this . _moduleResolutionHost ! ,
220
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
221
+ this . _moduleResolutionCache ! ,
222
+ )
170
223
171
- // See https://github.com/microsoft/TypeScript/blob/master/src/compiler/utilities.ts#L164
172
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
173
- return ( this . _languageService ?. getProgram ( ) ?. getSourceFile ( fileName ) as any ) ?. resolvedModules
224
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
225
+ return resolvedModule ?. resolvedFileName ?? ''
226
+ } )
227
+ . filter ( ( resolvedFileName ) => ! ! resolvedFileName )
174
228
}
175
229
176
230
getCompiledOutput ( fileContent : string , fileName : string , supportsStaticESM : boolean ) : string {
@@ -261,7 +315,12 @@ export class TsCompiler implements TsCompilerInstance {
261
315
*/
262
316
private _isFileInCache ( fileName : string ) : boolean {
263
317
return (
264
- this . jestCacheFS . has ( fileName ) && this . _compilerCacheFS . has ( fileName ) && this . _compilerCacheFS . get ( fileName ) !== 0
318
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
319
+ this . _fileContentCache ! . has ( fileName ) &&
320
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
321
+ this . _fileVersionCache ! . has ( fileName ) &&
322
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
323
+ this . _fileVersionCache ! . get ( fileName ) !== 0
265
324
)
266
325
}
267
326
@@ -275,14 +334,20 @@ export class TsCompiler implements TsCompilerInstance {
275
334
let shouldIncrementProjectVersion = false
276
335
const hit = this . _isFileInCache ( fileName )
277
336
if ( ! hit ) {
278
- this . _compilerCacheFS . set ( fileName , 1 )
337
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
338
+ this . _fileVersionCache ! . set ( fileName , 1 )
279
339
shouldIncrementProjectVersion = true
280
340
} else {
281
- const prevVersion = this . _compilerCacheFS . get ( fileName ) ?? 0
282
- const previousContents = this . jestCacheFS . get ( fileName )
341
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
342
+ const prevVersion = this . _fileVersionCache ! . get ( fileName ) ?? 0
343
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
344
+ const previousContents = this . _fileContentCache ! . get ( fileName )
283
345
// Avoid incrementing cache when nothing has changed.
284
346
if ( previousContents !== contents ) {
285
- this . _compilerCacheFS . set ( fileName , prevVersion + 1 )
347
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
348
+ this . _fileVersionCache ! . set ( fileName , prevVersion + 1 )
349
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
350
+ this . _fileContentCache ! . set ( fileName , contents )
286
351
// Only bump project version when file is modified in cache, not when discovered for the first time
287
352
if ( hit ) shouldIncrementProjectVersion = true
288
353
}
0 commit comments