perf(ivy): use module resolution cache (#34332)

During TypeScript module resolution, a lot of filesystem requests are
done. This is quite an expensive operation, so a module resolution cache
can be used to speed up the process significantly.

This commit lets the Ivy compiler perform all module resolution with a
module resolution cache. Note that the module resolution behavior can be
changed with a custom compiler host, in which case that custom host
implementation is responsible for caching. In the case of the Angular
CLI a custom compiler host with proper module resolution caching is
already in place, so the CLI already has this optimization.

PR Close #34332
This commit is contained in:
JoostK 2019-12-07 22:38:36 +01:00 committed by Kara Erickson
parent 2f5ddd9c96
commit 8c2cbdd385
13 changed files with 56 additions and 46 deletions

View File

@ -59,6 +59,8 @@ export class DecorationAnalyzer {
* Map of NgModule declarations to the re-exports for that NgModule.
*/
private reexportMap = new Map<ts.Declaration, Map<string, [string, string]>>();
moduleResolver =
new ModuleResolver(this.program, this.options, this.host, /* moduleResolutionCache */ null);
resourceManager = new NgccResourceLoader(this.fs);
metaRegistry = new LocalMetadataRegistry();
dtsMetaReader = new DtsMetadataReader(this.typeChecker, this.reflectionHost);
@ -66,7 +68,7 @@ export class DecorationAnalyzer {
refEmitter = new ReferenceEmitter([
new LocalIdentifierStrategy(),
new AbsoluteModuleStrategy(
this.program, this.typeChecker, this.options, this.host, this.reflectionHost),
this.program, this.typeChecker, this.moduleResolver, this.reflectionHost),
// TODO(alxhub): there's no reason why ngcc needs the "logical file system" logic here, as ngcc
// projects only ever have one rootDir. Instead, ngcc should just switch its emitted import
// based on whether a bestGuessOwningModule is present in the Reference.
@ -81,7 +83,6 @@ export class DecorationAnalyzer {
fullRegistry = new CompoundMetadataRegistry([this.metaRegistry, this.scopeRegistry]);
evaluator =
new PartialEvaluator(this.reflectionHost, this.typeChecker, /* dependencyTracker */ null);
moduleResolver = new ModuleResolver(this.program, this.options, this.host);
importGraph = new ImportGraph(this.moduleResolver);
cycleAnalyzer = new CycleAnalyzer(this.importGraph);
handlers: DecoratorHandler<unknown, unknown, unknown>[] = [

View File

@ -757,7 +757,7 @@ export class ComponentDecoratorHandler implements
}
// Figure out what file is being imported.
return this.moduleResolver.resolveModuleName(expr.value.moduleName !, origin);
return this.moduleResolver.resolveModule(expr.value.moduleName !, origin.fileName);
}
private _isCyclicImport(expr: Expression, origin: ts.SourceFile): boolean {

View File

@ -48,7 +48,8 @@ runInEachFileSystem(() => {
const checker = program.getTypeChecker();
const reflectionHost = new TypeScriptReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker, /* dependencyTracker */ null);
const moduleResolver = new ModuleResolver(program, options, host);
const moduleResolver =
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
const importGraph = new ImportGraph(moduleResolver);
const cycleAnalyzer = new CycleAnalyzer(importGraph);
const metaRegistry = new LocalMetadataRegistry();

View File

@ -68,7 +68,7 @@ export class ImportGraph {
stmt.moduleSpecifier !== undefined && ts.isStringLiteral(stmt.moduleSpecifier)) {
// Resolve the module to a file, and check whether that file is in the ts.Program.
const moduleName = stmt.moduleSpecifier.text;
const moduleFile = this.resolver.resolveModuleName(moduleName, sf);
const moduleFile = this.resolver.resolveModule(moduleName, sf.fileName);
if (moduleFile !== null && isLocalFile(moduleFile)) {
// Record this local import.
imports.add(moduleFile);

View File

@ -62,9 +62,11 @@ runInEachFileSystem(() => {
function makeAnalyzer(graph: string): {program: ts.Program, analyzer: CycleAnalyzer} {
const {program, options, host} = makeProgramFromGraph(getFileSystem(), graph);
const moduleResolver =
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
return {
program,
analyzer: new CycleAnalyzer(new ImportGraph(new ModuleResolver(program, options, host))),
analyzer: new CycleAnalyzer(new ImportGraph(moduleResolver)),
};
}
});

View File

@ -55,9 +55,11 @@ runInEachFileSystem(() => {
function makeImportGraph(graph: string): {program: ts.Program, graph: ImportGraph} {
const {program, options, host} = makeProgramFromGraph(getFileSystem(), graph);
const moduleResolver =
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
return {
program,
graph: new ImportGraph(new ModuleResolver(program, options, host)),
graph: new ImportGraph(moduleResolver),
};
}

View File

@ -8,13 +8,14 @@
import {Expression, ExternalExpr, ExternalReference, WrappedNodeExpr} from '@angular/compiler';
import * as ts from 'typescript';
import {LogicalFileSystem, LogicalProjectPath, PathSegment, absoluteFrom, absoluteFromSourceFile, basename, dirname, relative, resolve} from '../../file_system';
import {LogicalFileSystem, LogicalProjectPath, PathSegment, absoluteFromSourceFile, dirname, relative} from '../../file_system';
import {stripExtension} from '../../file_system/src/util';
import {ReflectionHost} from '../../reflection';
import {getSourceFile, getSourceFileOrNull, isDeclaration, nodeNameForError, resolveModuleName} from '../../util/src/typescript';
import {getSourceFile, isDeclaration, nodeNameForError} from '../../util/src/typescript';
import {findExportedNameOfNode} from './find_export';
import {ImportMode, Reference} from './references';
import {ModuleResolver} from './resolver';
@ -118,8 +119,7 @@ export class AbsoluteModuleStrategy implements ReferenceEmitStrategy {
constructor(
protected program: ts.Program, protected checker: ts.TypeChecker,
protected options: ts.CompilerOptions, protected host: ts.CompilerHost,
private reflectionHost: ReflectionHost) {}
protected moduleResolver: ModuleResolver, private reflectionHost: ReflectionHost) {}
emit(ref: Reference<ts.Node>, context: ts.SourceFile, importMode: ImportMode): Expression|null {
if (ref.bestGuessOwningModule === null) {
@ -165,13 +165,7 @@ export class AbsoluteModuleStrategy implements ReferenceEmitStrategy {
protected enumerateExportsOfModule(specifier: string, fromFile: string):
Map<ts.Declaration, string>|null {
// First, resolve the module specifier to its entry point, and get the ts.Symbol for it.
const resolvedModule = resolveModuleName(specifier, fromFile, this.options, this.host);
if (resolvedModule === undefined) {
return null;
}
const entryPointFile =
getSourceFileOrNull(this.program, absoluteFrom(resolvedModule.resolvedFileName));
const entryPointFile = this.moduleResolver.resolveModule(specifier, fromFile);
if (entryPointFile === null) {
return null;
}

View File

@ -8,12 +8,6 @@
import * as ts from 'typescript';
import {absoluteFrom} from '../../file_system';
import {getSourceFileOrNull, resolveModuleName} from '../../util/src/typescript';
import {Reference} from './references';
export interface ReferenceResolver {
resolve(decl: ts.Declaration, importFromHint: string|null, fromFile: string):
Reference<ts.Declaration>;
}
/**
* Used by `RouterEntryPointManager` and `NgModuleRouteAnalyzer` (which is in turn is used by
@ -24,11 +18,12 @@ export interface ReferenceResolver {
export class ModuleResolver {
constructor(
private program: ts.Program, private compilerOptions: ts.CompilerOptions,
private host: ts.CompilerHost) {}
private host: ts.CompilerHost, private moduleResolutionCache: ts.ModuleResolutionCache|null) {
}
resolveModuleName(module: string, containingFile: ts.SourceFile): ts.SourceFile|null {
const resolved =
resolveModuleName(module, containingFile.fileName, this.compilerOptions, this.host);
resolveModule(moduleName: string, containingFile: string): ts.SourceFile|null {
const resolved = resolveModuleName(
moduleName, containingFile, this.compilerOptions, this.host, this.moduleResolutionCache);
if (resolved === undefined) {
return null;
}

View File

@ -177,7 +177,10 @@ export class NgtscProgram implements api.Program {
this.reuseTsProgram = this.tsProgram;
this.entryPoint = entryPoint !== null ? getSourceFileOrNull(this.tsProgram, entryPoint) : null;
this.moduleResolver = new ModuleResolver(this.tsProgram, options, this.host);
const moduleResolutionCache = ts.createModuleResolutionCache(
this.host.getCurrentDirectory(), fileName => this.host.getCanonicalFileName(fileName));
this.moduleResolver =
new ModuleResolver(this.tsProgram, options, this.host, moduleResolutionCache);
this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(this.moduleResolver));
this.defaultImportTracker = new DefaultImportTracker();
if (oldProgram === undefined) {
@ -287,7 +290,8 @@ export class NgtscProgram implements api.Program {
// of the root files.
const containingFile = this.tsProgram.getRootFileNames()[0];
const [entryPath, moduleName] = entryRoute.split('#');
const resolvedModule = resolveModuleName(entryPath, containingFile, this.options, this.host);
const resolvedModule =
resolveModuleName(entryPath, containingFile, this.options, this.host, null);
if (resolvedModule) {
entryRoute = entryPointKeyFor(resolvedModule.resolvedFileName, moduleName);
@ -587,8 +591,7 @@ export class NgtscProgram implements api.Program {
// First, try to use local identifiers if available.
new LocalIdentifierStrategy(),
// Next, attempt to use an absolute import.
new AbsoluteModuleStrategy(
this.tsProgram, checker, this.options, this.host, this.reflector),
new AbsoluteModuleStrategy(this.tsProgram, checker, this.moduleResolver, this.reflector),
// Finally, check if the reference is being written into a file within the project's .ts
// sources, and use a relative import if so. If this fails, ReferenceEmitter will throw
// an error.

View File

@ -39,7 +39,7 @@ export class RouterEntryPointManager {
if (moduleName === undefined) {
return null;
}
const resolvedSf = this.moduleResolver.resolveModuleName(relativeFile, context);
const resolvedSf = this.moduleResolver.resolveModule(relativeFile, context.fileName);
if (resolvedSf === null) {
return null;
}

View File

@ -11,7 +11,7 @@ import * as ts from 'typescript';
import {AbsoluteFsPath, LogicalFileSystem, absoluteFrom} from '../../file_system';
import {TestFile} from '../../file_system/testing';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, Reference, ReferenceEmitter} from '../../imports';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
import {ClassDeclaration, TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
import {makeProgram} from '../../testing';
import {getRootDirs} from '../../util/src/typescript';
@ -29,7 +29,7 @@ export function typescriptLibDts(): TestFile {
type Partial<T> = { [P in keyof T]?: T[P]; };
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
type NonNullable<T> = T extends null | undefined ? never : T;
// The following native type declarations are required for proper type inference
declare interface Function {
call(...args: any[]): any;
@ -40,7 +40,7 @@ export function typescriptLibDts(): TestFile {
declare interface String {
length: number;
}
declare interface Event {
preventDefault(): void;
}
@ -48,7 +48,7 @@ export function typescriptLibDts(): TestFile {
readonly x: number;
readonly y: number;
}
declare interface HTMLElementEventMap {
"click": MouseEvent;
}
@ -88,7 +88,7 @@ export function angularCoreDts(): TestFile {
abstract readonly elementRef: unknown;
abstract createEmbeddedView(context: C): unknown;
}
export declare class EventEmitter<T> {
subscribe(generatorOrNext?: any, error?: any, complete?: any): unknown;
}
@ -131,7 +131,7 @@ export function ngForDts(): TestFile {
export interface TrackByFunction<T> {
(index: number, item: T): any;
}
export declare class NgForOfContext<T> {
$implicit: T;
index: number;
@ -249,10 +249,12 @@ export function typecheck(
const checker = program.getTypeChecker();
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
const reflectionHost = new TypeScriptReflectionHost(checker);
const moduleResolver =
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
const emitter = new ReferenceEmitter([
new LocalIdentifierStrategy(),
new AbsoluteModuleStrategy(
program, checker, options, host, new TypeScriptReflectionHost(checker)),
program, checker, moduleResolver, new TypeScriptReflectionHost(checker)),
new LogicalProjectStrategy(reflectionHost, logicalFs),
]);
const ctx = new TypeCheckContext({...ALL_ENABLED_CONFIG, ...config}, emitter, typeCheckFilePath);

View File

@ -8,7 +8,7 @@
import * as ts from 'typescript';
import {LogicalFileSystem, absoluteFrom, getSourceFileOrError} from '../../file_system';
import {TestFile, runInEachFileSystem} from '../../file_system/testing';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, Reference, ReferenceEmitter} from '../../imports';
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
import {TypeScriptReflectionHost, isNamedClassDeclaration} from '../../reflection';
import {getDeclaration, makeProgram} from '../../testing';
import {getRootDirs} from '../../util/src/typescript';
@ -64,9 +64,11 @@ TestClass.ngTypeCtor({value: 'test'});
const checker = program.getTypeChecker();
const reflectionHost = new TypeScriptReflectionHost(checker);
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
const moduleResolver =
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
const emitter = new ReferenceEmitter([
new LocalIdentifierStrategy(),
new AbsoluteModuleStrategy(program, checker, options, host, reflectionHost),
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
new LogicalProjectStrategy(reflectionHost, logicalFs),
]);
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, emitter, _('/_typecheck_.ts'));
@ -97,9 +99,11 @@ TestClass.ngTypeCtor({value: 'test'});
const checker = program.getTypeChecker();
const reflectionHost = new TypeScriptReflectionHost(checker);
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
const moduleResolver =
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
const emitter = new ReferenceEmitter([
new LocalIdentifierStrategy(),
new AbsoluteModuleStrategy(program, checker, options, host, reflectionHost),
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
new LogicalProjectStrategy(reflectionHost, logicalFs),
]);
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, emitter, _('/_typecheck_.ts'));
@ -136,9 +140,11 @@ TestClass.ngTypeCtor({value: 'test'});
const checker = program.getTypeChecker();
const reflectionHost = new TypeScriptReflectionHost(checker);
const logicalFs = new LogicalFileSystem(getRootDirs(host, options));
const moduleResolver =
new ModuleResolver(program, options, host, /* moduleResolutionCache */ null);
const emitter = new ReferenceEmitter([
new LocalIdentifierStrategy(),
new AbsoluteModuleStrategy(program, checker, options, host, reflectionHost),
new AbsoluteModuleStrategy(program, checker, moduleResolver, reflectionHost),
new LogicalProjectStrategy(reflectionHost, logicalFs),
]);
const ctx = new TypeCheckContext(ALL_ENABLED_CONFIG, emitter, _('/_typecheck_.ts'));

View File

@ -111,14 +111,18 @@ export function nodeDebugInfo(node: ts.Node): string {
*/
export function resolveModuleName(
moduleName: string, containingFile: string, compilerOptions: ts.CompilerOptions,
compilerHost: ts.CompilerHost): ts.ResolvedModule|undefined {
compilerHost: ts.CompilerHost,
moduleResolutionCache: ts.ModuleResolutionCache | null): ts.ResolvedModule|undefined {
if (compilerHost.resolveModuleNames) {
// FIXME: Additional parameters are required in TS3.6, but ignored in 3.5.
// Remove the any cast once google3 is fully on TS3.6.
return (compilerHost as any)
.resolveModuleNames([moduleName], containingFile, undefined, undefined, compilerOptions)[0];
} else {
return ts.resolveModuleName(moduleName, containingFile, compilerOptions, compilerHost)
return ts
.resolveModuleName(
moduleName, containingFile, compilerOptions, compilerHost,
moduleResolutionCache !== null ? moduleResolutionCache : undefined)
.resolvedModule;
}
}