Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache module resolutions across tsbuild to be able to resolve the modules faster #31100

Merged
merged 3 commits into from
Apr 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ namespace ts {
*/
export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache {
getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map<ResolvedModuleWithFailedLookupLocations>;
/*@internal*/ directoryToModuleNameMap: CacheWithRedirects<Map<ResolvedModuleWithFailedLookupLocations>>;
}

/**
Expand All @@ -429,49 +430,64 @@ namespace ts {
*/
export interface NonRelativeModuleNameResolutionCache {
getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache;
/*@internal*/ moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>;
}

export interface PerModuleNameCache {
get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined;
set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void;
}

export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache {
export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions): ModuleResolutionCache {
return createModuleResolutionCacheWithMaps(
createCacheWithRedirects(),
createCacheWithRedirects(),
createCacheWithRedirects(options),
createCacheWithRedirects(options),
currentDirectory,
getCanonicalFileName
);
}


/*@internal*/
export interface CacheWithRedirects<T> {
ownMap: Map<T>;
redirectsMap: Map<Map<T>>;
getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map<T>;
clear(): void;
setOwnOptions(newOptions: CompilerOptions): void;
setOwnMap(newOwnMap: Map<T>): void;
}

/*@internal*/
export function createCacheWithRedirects<T>(): CacheWithRedirects<T> {
const ownMap: Map<T> = createMap();
export function createCacheWithRedirects<T>(options?: CompilerOptions): CacheWithRedirects<T> {
let ownMap: Map<T> = createMap();
const redirectsMap: Map<Map<T>> = createMap();
return {
ownMap,
redirectsMap,
getOrCreateMapOfCacheRedirects,
clear
clear,
setOwnOptions,
setOwnMap
};

function setOwnOptions(newOptions: CompilerOptions) {
options = newOptions;
}

function setOwnMap(newOwnMap: Map<T>) {
ownMap = newOwnMap;
}

function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) {
if (!redirectedReference) {
return ownMap;
}
const path = redirectedReference.sourceFile.path;
let redirects = redirectsMap.get(path);
if (!redirects) {
redirects = createMap();
// Reuse map if redirected reference map uses same resolution
redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? createMap() : ownMap;
redirectsMap.set(path, redirects);
}
return redirects;
Expand All @@ -490,7 +506,7 @@ namespace ts {
currentDirectory: string,
getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache {

return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName };
return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName, directoryToModuleNameMap, moduleNameToDirectoryMap };

function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) {
const path = toPath(directoryName, currentDirectory, getCanonicalFileName);
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,8 @@ namespace ts {
}
}

function loadWithLocalCache<T>(names: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] {
/* @internal */
export function loadWithLocalCache<T>(names: string[], containingFile: string, redirectedReference: ResolvedProjectReference | undefined, loader: (name: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => T): T[] {
if (names.length === 0) {
return [];
}
Expand Down Expand Up @@ -773,7 +774,7 @@ namespace ts {
});
}
else {
moduleResolutionCache = createModuleResolutionCache(currentDirectory, x => host.getCanonicalFileName(x));
moduleResolutionCache = createModuleResolutionCache(currentDirectory, x => host.getCanonicalFileName(x), options);
const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, options, host, moduleResolutionCache, redirectedReference).resolvedModule!; // TODO: GH#18217
resolveModuleNamesWorker = (moduleNames, containingFile, _reusedNames, redirectedReference) => loadWithLocalCache<ResolvedModuleFull>(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader);
}
Expand Down
37 changes: 37 additions & 0 deletions src/compiler/tsbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,10 @@ namespace ts {
const compilerHost = createCompilerHostFromProgramHost(host, () => projectCompilerOptions);
setGetSourceFileAsHashVersioned(compilerHost, host);

compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames);
compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives);
let moduleResolutionCache = !compilerHost.resolveModuleNames ? createModuleResolutionCache(currentDirectory, getCanonicalFileName) : undefined;

const buildInfoChecked = createFileMap<true>(toPath);

// Watch state
Expand Down Expand Up @@ -1097,6 +1101,30 @@ namespace ts {

// TODO: handle resolve module name to cache result in project reference redirect
projectCompilerOptions = configFile.options;
// Update module resolution cache if needed
if (moduleResolutionCache) {
const projPath = toPath(proj);
if (moduleResolutionCache.directoryToModuleNameMap.redirectsMap.size === 0) {
// The own map will be for projectCompilerOptions
Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size === 0);
moduleResolutionCache.directoryToModuleNameMap.redirectsMap.set(projPath, moduleResolutionCache.directoryToModuleNameMap.ownMap);
moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.set(projPath, moduleResolutionCache.moduleNameToDirectoryMap.ownMap);
}
else {
// Set correct own map
Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size > 0);

const ref: ResolvedProjectReference = {
sourceFile: projectCompilerOptions.configFile!,
commandLine: configFile
};
moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref));
moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref));
}
moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(projectCompilerOptions);
moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(projectCompilerOptions);
}

const program = host.createProgram(
configFile.fileNames,
configFile.options,
Expand Down Expand Up @@ -1368,6 +1396,13 @@ namespace ts {
readFileWithCache = newReadFileWithCache;
compilerHost.getSourceFile = getSourceFileWithCache!;

const originalResolveModuleNames = compilerHost.resolveModuleNames;
if (!compilerHost.resolveModuleNames) {
const loader = (moduleName: string, containingFile: string, redirectedReference: ResolvedProjectReference | undefined) => resolveModuleName(moduleName, containingFile, projectCompilerOptions, compilerHost, moduleResolutionCache, redirectedReference).resolvedModule!;
compilerHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference) =>
loadWithLocalCache<ResolvedModuleFull>(Debug.assertEachDefined(moduleNames), containingFile, redirectedReference, loader);
}

const graph = getGlobalDependencyGraph();
reportBuildQueue(graph);
let anyFailed = false;
Expand Down Expand Up @@ -1428,6 +1463,8 @@ namespace ts {
host.writeFile = originalWriteFile;
compilerHost.getSourceFile = savedGetSourceFile;
readFileWithCache = savedReadFileWithCache;
compilerHost.resolveModuleNames = originalResolveModuleNames;
moduleResolutionCache = undefined;
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
}

Expand Down
7 changes: 6 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@ namespace ts {
}

export function changesAffectModuleResolution(oldOptions: CompilerOptions, newOptions: CompilerOptions): boolean {
return oldOptions.configFilePath !== newOptions.configFilePath || moduleResolutionOptionDeclarations.some(o =>
return oldOptions.configFilePath !== newOptions.configFilePath ||
optionsHaveModuleResolutionChanges(oldOptions, newOptions);
}

export function optionsHaveModuleResolutionChanges(oldOptions: CompilerOptions, newOptions: CompilerOptions) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sheetalkamat would it be reasonable to expose this function in the public API? That would allow API users to reuse their ModuleResolutionCache until changes in CompilerOptions require a new cache.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Note that this only compares CompilerOptions and not changes in directory structure which could affect the module resolution.

return moduleResolutionOptionDeclarations.some(o =>
!isJsonEqual(getCompilerOptionValue(oldOptions, o), getCompilerOptionValue(newOptions, o)));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3718,7 +3718,7 @@ declare namespace ts {
get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined;
set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void;
}
function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache;
function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions): ModuleResolutionCache;
function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined;
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3718,7 +3718,7 @@ declare namespace ts {
get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined;
set(directory: string, result: ResolvedModuleWithFailedLookupLocations): void;
}
function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache;
function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string, options?: CompilerOptions): ModuleResolutionCache;
function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined;
function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations;
Expand Down