fix(language-service): Use module resolution cache (#32479)
This PR fixes a critical performance issue where the language service makes a MASSIVE number of filesystem calls when performing module resolution. This is because there is no caching. To make matters worse, module resolution is performed for **every** program change (which means every few keystrokes trigger a massive number of fs calls). PR Close #32479
This commit is contained in:

committed by
Matias Niemelä

parent
ded57245e1
commit
6052b12fb3
@ -48,11 +48,22 @@ class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost, Me
|
||||
}
|
||||
|
||||
export class ReflectorHost implements StaticSymbolResolverHost {
|
||||
private hostAdapter: ReflectorModuleModuleResolutionHost;
|
||||
private metadataReaderCache = createMetadataReaderCache();
|
||||
private readonly hostAdapter: ReflectorModuleModuleResolutionHost;
|
||||
private readonly metadataReaderCache = createMetadataReaderCache();
|
||||
private readonly moduleResolutionCache: ts.ModuleResolutionCache;
|
||||
private readonly fakeContainingPath: string;
|
||||
|
||||
constructor(getProgram: () => ts.Program, private readonly serviceHost: ts.LanguageServiceHost) {
|
||||
this.hostAdapter = new ReflectorModuleModuleResolutionHost(serviceHost, getProgram);
|
||||
constructor(getProgram: () => ts.Program, private readonly tsLSHost: ts.LanguageServiceHost) {
|
||||
// tsLSHost.getCurrentDirectory() returns the directory where tsconfig.json
|
||||
// is located. This is not the same as process.cwd() because the language
|
||||
// service host sets the "project root path" as its current directory.
|
||||
const currentDir = tsLSHost.getCurrentDirectory();
|
||||
this.fakeContainingPath = currentDir ? path.join(currentDir, 'fakeContainingFile.ts') : '';
|
||||
this.hostAdapter = new ReflectorModuleModuleResolutionHost(tsLSHost, getProgram);
|
||||
this.moduleResolutionCache = ts.createModuleResolutionCache(
|
||||
currentDir,
|
||||
s => s, // getCanonicalFileName
|
||||
tsLSHost.getCompilationSettings());
|
||||
}
|
||||
|
||||
getMetadataFor(modulePath: string): {[key: string]: any}[]|undefined {
|
||||
@ -64,23 +75,19 @@ export class ReflectorHost implements StaticSymbolResolverHost {
|
||||
if (moduleName.startsWith('.')) {
|
||||
throw new Error('Resolution of relative paths requires a containing file.');
|
||||
}
|
||||
// serviceHost.getCurrentDirectory() returns the directory where tsconfig.json
|
||||
// is located. This is not the same as process.cwd() because the language
|
||||
// service host sets the "project root path" as its current directory.
|
||||
const currentDirectory = this.serviceHost.getCurrentDirectory();
|
||||
if (!currentDirectory) {
|
||||
if (!this.fakeContainingPath) {
|
||||
// If current directory is empty then the file must belong to an inferred
|
||||
// project (no tsconfig.json), in which case it's not possible to resolve
|
||||
// the module without the caller explicitly providing a containing file.
|
||||
throw new Error(`Could not resolve '${moduleName}' without a containing file.`);
|
||||
}
|
||||
// Any containing file gives the same result for absolute imports
|
||||
containingFile = path.join(currentDirectory, 'index.ts');
|
||||
containingFile = this.fakeContainingPath;
|
||||
}
|
||||
const compilerOptions = this.serviceHost.getCompilationSettings();
|
||||
const resolved =
|
||||
ts.resolveModuleName(moduleName, containingFile, compilerOptions, this.hostAdapter)
|
||||
.resolvedModule;
|
||||
const compilerOptions = this.tsLSHost.getCompilationSettings();
|
||||
const resolved = ts.resolveModuleName(
|
||||
moduleName, containingFile, compilerOptions, this.hostAdapter,
|
||||
this.moduleResolutionCache)
|
||||
.resolvedModule;
|
||||
return resolved ? resolved.resolvedFileName : null;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user