@@ -16,11 +16,13 @@ import type {
1616 Bundle ,
1717 CustomTransformerFactory ,
1818 CustomTransformers ,
19+ ModuleResolutionHost ,
20+ ModuleResolutionCache ,
1921} from 'typescript'
2022
2123import { ConfigSet , TS_JEST_OUT_DIR } from '../config/config-set'
2224import { LINE_FEED } from '../constants'
23- import type { ResolvedModulesMap , StringMap , TsCompilerInstance , TsJestAstTransformer , TTypeScript } from '../types'
25+ import type { StringMap , TsCompilerInstance , TsJestAstTransformer , TTypeScript } from '../types'
2426import { rootLogger } from '../utils/logger'
2527import { Errors , interpolate } from '../utils/messages'
2628
@@ -31,18 +33,26 @@ export class TsCompiler implements TsCompilerInstance {
3133 protected readonly _ts : TTypeScript
3234 protected readonly _initialCompilerOptions : CompilerOptions
3335 protected _compilerOptions : CompilerOptions
36+ /**
37+ * @private
38+ */
39+ private _runtimeCacheFS : StringMap
40+ /**
41+ * @private
42+ */
43+ private _fileContentCache : StringMap | undefined
3444 /**
3545 * @internal
3646 */
3747 private readonly _parsedTsConfig : ParsedCommandLine
3848 /**
3949 * @internal
4050 */
41- private readonly _compilerCacheFS : Map < string , number > = new Map < string , number > ( )
51+ private readonly _fileVersionCache : Map < string , number > | undefined
4252 /**
4353 * @internal
4454 */
45- private _cachedReadFile : ( ( fileName : string ) => string | undefined ) | undefined
55+ private readonly _cachedReadFile : ( ( fileName : string ) => string | undefined ) | undefined
4656 /**
4757 * @internal
4858 */
@@ -51,15 +61,50 @@ export class TsCompiler implements TsCompilerInstance {
5161 * @internal
5262 */
5363 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+
5473 program : Program | undefined
5574
56- constructor ( readonly configSet : ConfigSet , readonly jestCacheFS : StringMap ) {
75+ constructor ( readonly configSet : ConfigSet , readonly runtimeCacheFS : StringMap ) {
5776 this . _ts = configSet . compilerModule
5877 this . _logger = rootLogger . child ( { namespace : 'ts-compiler' } )
5978 this . _parsedTsConfig = this . configSet . parsedTsConfig as ParsedCommandLine
6079 this . _initialCompilerOptions = { ...this . _parsedTsConfig . options }
6180 this . _compilerOptions = { ...this . _initialCompilerOptions }
81+ this . _runtimeCacheFS = runtimeCacheFS
6282 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+ )
63108 this . _createLanguageService ( )
64109 }
65110 }
@@ -68,11 +113,6 @@ export class TsCompiler implements TsCompilerInstance {
68113 * @internal
69114 */
70115 private _createLanguageService ( ) : void {
71- const serviceHostTraceCtx = {
72- namespace : 'ts:serviceHost' ,
73- call : null ,
74- [ LogContexts . logLevel ] : LogLevels . trace ,
75- }
76116 // Initialize memory cache for typescript compiler
77117 this . _parsedTsConfig . fileNames
78118 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -81,29 +121,17 @@ export class TsCompiler implements TsCompilerInstance {
81121 ! this . configSet . isTestFile ( fileName ) &&
82122 ! fileName . includes ( this . _parsedTsConfig . options . outDir ?? TS_JEST_OUT_DIR ) ,
83123 )
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 ) )
100126 /* istanbul ignore next */
101127 const serviceHost : LanguageServiceHost = {
102128 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 ( ) ] ,
104131 getScriptVersion : ( fileName : string ) => {
105132 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 )
107135
108136 // We need to return `undefined` and not a string here because TypeScript will use
109137 // `getScriptVersion` and compare against their own version - which can be `undefined`.
@@ -122,13 +150,20 @@ export class TsCompiler implements TsCompilerInstance {
122150 // Read contents from TypeScript memory cache.
123151 if ( ! hit ) {
124152 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
126158 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 )
129163 }
130164 }
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 )
132167
133168 if ( contents === undefined ) return
134169
@@ -151,8 +186,10 @@ export class TsCompiler implements TsCompilerInstance {
151186 moduleName ,
152187 containingFile ,
153188 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 ! ,
156193 )
157194
158195 return resolvedModule
@@ -165,12 +202,29 @@ export class TsCompiler implements TsCompilerInstance {
165202 this . program = this . _languageService . getProgram ( )
166203 }
167204
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+ )
170223
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 )
174228 }
175229
176230 getCompiledOutput ( fileContent : string , fileName : string , supportsStaticESM : boolean ) : string {
@@ -261,7 +315,12 @@ export class TsCompiler implements TsCompilerInstance {
261315 */
262316 private _isFileInCache ( fileName : string ) : boolean {
263317 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
265324 )
266325 }
267326
@@ -275,14 +334,20 @@ export class TsCompiler implements TsCompilerInstance {
275334 let shouldIncrementProjectVersion = false
276335 const hit = this . _isFileInCache ( fileName )
277336 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 )
279339 shouldIncrementProjectVersion = true
280340 } 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 )
283345 // Avoid incrementing cache when nothing has changed.
284346 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 )
286351 // Only bump project version when file is modified in cache, not when discovered for the first time
287352 if ( hit ) shouldIncrementProjectVersion = true
288353 }
0 commit comments