@@ -47,11 +47,6 @@ namespace ts {
4747 return resolved . path ;
4848 }
4949
50- /** Create Resolved from a file with unknown extension. */
51- function resolvedFromAnyFile ( path : string ) : Resolved | undefined {
52- return { path, extension : extensionFromPath ( path ) } ;
53- }
54-
5550 /** Adds `isExernalLibraryImport` to a Resolved to get a ResolvedModule. */
5651 function resolvedModuleFromResolved ( { path, extension } : Resolved , isExternalLibraryImport : boolean ) : ResolvedModuleFull {
5752 return { resolvedFileName : path , extension, isExternalLibraryImport } ;
@@ -71,7 +66,8 @@ namespace ts {
7166 traceEnabled : boolean ;
7267 }
7368
74- function tryReadTypesSection ( extensions : Extensions , packageJsonPath : string , baseDirectory : string , state : ModuleResolutionState ) : string {
69+ /** Reads from "main" or "types"/"typings" depending on `extensions`. */
70+ function tryReadPackageJsonMainOrTypes ( extensions : Extensions , packageJsonPath : string , baseDirectory : string , state : ModuleResolutionState ) : string {
7571 const jsonContent = readJson ( packageJsonPath , state . host ) ;
7672
7773 switch ( extensions ) {
@@ -293,33 +289,69 @@ namespace ts {
293289 return result ;
294290 }
295291
296- export function resolveModuleName ( moduleName : string , containingFile : string , compilerOptions : CompilerOptions , host : ModuleResolutionHost ) : ResolvedModuleWithFailedLookupLocations {
292+ /**
293+ * Cached module resolutions per containing directory.
294+ * This assumes that any module id will have the same resolution for sibling files located in the same folder.
295+ */
296+ export interface ModuleResolutionCache {
297+ getOrCreateCacheForDirectory ( directoryName : string ) : Map < ResolvedModuleWithFailedLookupLocations > ;
298+ }
299+
300+ export function createModuleResolutionCache ( currentDirectory : string , getCanonicalFileName : ( s : string ) => string ) {
301+ const map = createFileMap < Map < ResolvedModuleWithFailedLookupLocations > > ( ) ;
302+
303+ return { getOrCreateCacheForDirectory } ;
304+
305+ function getOrCreateCacheForDirectory ( directoryName : string ) {
306+ const path = toPath ( directoryName , currentDirectory , getCanonicalFileName ) ;
307+ let perFolderCache = map . get ( path ) ;
308+ if ( ! perFolderCache ) {
309+ perFolderCache = createMap < ResolvedModuleWithFailedLookupLocations > ( ) ;
310+ map . set ( path , perFolderCache ) ;
311+ }
312+ return perFolderCache ;
313+ }
314+ }
315+
316+ export function resolveModuleName ( moduleName : string , containingFile : string , compilerOptions : CompilerOptions , host : ModuleResolutionHost , cache ?: ModuleResolutionCache ) : ResolvedModuleWithFailedLookupLocations {
297317 const traceEnabled = isTraceEnabled ( compilerOptions , host ) ;
298318 if ( traceEnabled ) {
299319 trace ( host , Diagnostics . Resolving_module_0_from_1 , moduleName , containingFile ) ;
300320 }
321+ const perFolderCache = cache && cache . getOrCreateCacheForDirectory ( getDirectoryPath ( containingFile ) ) ;
322+ let result = perFolderCache && perFolderCache [ moduleName ] ;
301323
302- let moduleResolution = compilerOptions . moduleResolution ;
303- if ( moduleResolution === undefined ) {
304- moduleResolution = getEmitModuleKind ( compilerOptions ) === ModuleKind . CommonJS ? ModuleResolutionKind . NodeJs : ModuleResolutionKind . Classic ;
324+ if ( result ) {
305325 if ( traceEnabled ) {
306- trace ( host , Diagnostics . Module_resolution_kind_is_not_specified_using_0 , ModuleResolutionKind [ moduleResolution ] ) ;
326+ trace ( host , Diagnostics . Resolution_for_module_0_was_found_in_cache , moduleName ) ;
307327 }
308328 }
309329 else {
310- if ( traceEnabled ) {
311- trace ( host , Diagnostics . Explicitly_specified_module_resolution_kind_Colon_0 , ModuleResolutionKind [ moduleResolution ] ) ;
330+ let moduleResolution = compilerOptions . moduleResolution ;
331+ if ( moduleResolution === undefined ) {
332+ moduleResolution = getEmitModuleKind ( compilerOptions ) === ModuleKind . CommonJS ? ModuleResolutionKind . NodeJs : ModuleResolutionKind . Classic ;
333+ if ( traceEnabled ) {
334+ trace ( host , Diagnostics . Module_resolution_kind_is_not_specified_using_0 , ModuleResolutionKind [ moduleResolution ] ) ;
335+ }
336+ }
337+ else {
338+ if ( traceEnabled ) {
339+ trace ( host , Diagnostics . Explicitly_specified_module_resolution_kind_Colon_0 , ModuleResolutionKind [ moduleResolution ] ) ;
340+ }
341+ }
342+
343+ switch ( moduleResolution ) {
344+ case ModuleResolutionKind . NodeJs :
345+ result = nodeModuleNameResolver ( moduleName , containingFile , compilerOptions , host ) ;
346+ break ;
347+ case ModuleResolutionKind . Classic :
348+ result = classicNameResolver ( moduleName , containingFile , compilerOptions , host ) ;
349+ break ;
312350 }
313- }
314351
315- let result : ResolvedModuleWithFailedLookupLocations ;
316- switch ( moduleResolution ) {
317- case ModuleResolutionKind . NodeJs :
318- result = nodeModuleNameResolver ( moduleName , containingFile , compilerOptions , host ) ;
319- break ;
320- case ModuleResolutionKind . Classic :
321- result = classicNameResolver ( moduleName , containingFile , compilerOptions , host ) ;
322- break ;
352+ if ( perFolderCache ) {
353+ perFolderCache [ moduleName ] = result ;
354+ }
323355 }
324356
325357 if ( traceEnabled ) {
@@ -678,18 +710,21 @@ namespace ts {
678710 if ( state . traceEnabled ) {
679711 trace ( state . host , Diagnostics . Found_package_json_at_0 , packageJsonPath ) ;
680712 }
681- const typesFile = tryReadTypesSection ( extensions , packageJsonPath , candidate , state ) ;
682- if ( typesFile ) {
683- const onlyRecordFailures = ! directoryProbablyExists ( getDirectoryPath ( typesFile ) , state . host ) ;
713+ const mainOrTypesFile = tryReadPackageJsonMainOrTypes ( extensions , packageJsonPath , candidate , state ) ;
714+ if ( mainOrTypesFile ) {
715+ const onlyRecordFailures = ! directoryProbablyExists ( getDirectoryPath ( mainOrTypesFile ) , state . host ) ;
684716 // A package.json "typings" may specify an exact filename, or may choose to omit an extension.
685- const fromFile = tryFile ( typesFile , failedLookupLocations , onlyRecordFailures , state ) ;
686- if ( fromFile ) {
687- // Note: this would allow a package.json to specify a ".js" file as typings. Maybe that should be forbidden.
688- return resolvedFromAnyFile ( fromFile ) ;
717+ const fromExactFile = tryFile ( mainOrTypesFile , failedLookupLocations , onlyRecordFailures , state ) ;
718+ if ( fromExactFile ) {
719+ const resolved = fromExactFile && resolvedIfExtensionMatches ( extensions , fromExactFile ) ;
720+ if ( resolved ) {
721+ return resolved ;
722+ }
723+ trace ( state . host , Diagnostics . File_0_has_an_unsupported_extension_so_skipping_it , fromExactFile ) ;
689724 }
690- const x = tryAddingExtensions ( typesFile , Extensions . TypeScript , failedLookupLocations , onlyRecordFailures , state ) ;
691- if ( x ) {
692- return x ;
725+ const resolved = tryAddingExtensions ( mainOrTypesFile , Extensions . TypeScript , failedLookupLocations , onlyRecordFailures , state ) ;
726+ if ( resolved ) {
727+ return resolved ;
693728 }
694729 }
695730 else {
@@ -709,6 +744,24 @@ namespace ts {
709744 return loadModuleFromFile ( extensions , combinePaths ( candidate , "index" ) , failedLookupLocations , ! directoryExists , state ) ;
710745 }
711746
747+ /** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */
748+ function resolvedIfExtensionMatches ( extensions : Extensions , path : string ) : Resolved | undefined {
749+ const extension = tryGetExtensionFromPath ( path ) ;
750+ return extension !== undefined && extensionIsOk ( extensions , extension ) ? { path, extension } : undefined ;
751+ }
752+
753+ /** True if `extension` is one of the supported `extensions`. */
754+ function extensionIsOk ( extensions : Extensions , extension : Extension ) : boolean {
755+ switch ( extensions ) {
756+ case Extensions . JavaScript :
757+ return extension === Extension . Js || extension === Extension . Jsx ;
758+ case Extensions . TypeScript :
759+ return extension === Extension . Ts || extension === Extension . Tsx || extension === Extension . Dts ;
760+ case Extensions . DtsOnly :
761+ return extension === Extension . Dts ;
762+ }
763+ }
764+
712765 function pathToPackageJson ( directory : string ) : string {
713766 return combinePaths ( directory , "package.json" ) ;
714767 }
0 commit comments