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:
parent
2f5ddd9c96
commit
8c2cbdd385
|
@ -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>[] = [
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue