diff --git a/packages/compiler-cli/src/ngtsc/incremental/BUILD.bazel b/packages/compiler-cli/src/ngtsc/incremental/BUILD.bazel index aebea07d90..c8772d5187 100644 --- a/packages/compiler-cli/src/ngtsc/incremental/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/incremental/BUILD.bazel @@ -8,7 +8,10 @@ ts_library( "src/**/*.ts", ]), deps = [ + "//packages/compiler-cli/src/ngtsc/imports", + "//packages/compiler-cli/src/ngtsc/metadata", "//packages/compiler-cli/src/ngtsc/partial_evaluator", + "//packages/compiler-cli/src/ngtsc/reflection", "@npm//typescript", ], ) diff --git a/packages/compiler-cli/src/ngtsc/incremental/src/state.ts b/packages/compiler-cli/src/ngtsc/incremental/src/state.ts index bfe2a20cf2..2b6ca5c236 100644 --- a/packages/compiler-cli/src/ngtsc/incremental/src/state.ts +++ b/packages/compiler-cli/src/ngtsc/incremental/src/state.ts @@ -7,12 +7,15 @@ */ import * as ts from 'typescript'; +import {Reference} from '../../imports'; +import {DirectiveMeta, MetadataReader, MetadataRegistry, NgModuleMeta, PipeMeta} from '../../metadata'; import {DependencyTracker} from '../../partial_evaluator'; +import {ClassDeclaration} from '../../reflection'; /** * Accumulates state between compilations. */ -export class IncrementalState implements DependencyTracker { +export class IncrementalState implements DependencyTracker, MetadataReader, MetadataRegistry { private constructor( private unchangedFiles: Set, private metadata: Map) {} @@ -85,6 +88,33 @@ export class IncrementalState implements DependencyTracker { metadata.fileDependencies.add(dep); } + getNgModuleMetadata(ref: Reference): NgModuleMeta|null { + const metadata = this.metadata.get(ref.node.getSourceFile()) || null; + return metadata && metadata.ngModuleMeta.get(ref.node) || null; + } + registerNgModuleMetadata(meta: NgModuleMeta): void { + const metadata = this.ensureMetadata(meta.ref.node.getSourceFile()); + metadata.ngModuleMeta.set(meta.ref.node, meta); + } + + getDirectiveMetadata(ref: Reference): DirectiveMeta|null { + const metadata = this.metadata.get(ref.node.getSourceFile()) || null; + return metadata && metadata.directiveMeta.get(ref.node) || null; + } + registerDirectiveMetadata(meta: DirectiveMeta): void { + const metadata = this.ensureMetadata(meta.ref.node.getSourceFile()); + metadata.directiveMeta.set(meta.ref.node, meta); + } + + getPipeMetadata(ref: Reference): PipeMeta|null { + const metadata = this.metadata.get(ref.node.getSourceFile()) || null; + return metadata && metadata.pipeMeta.get(ref.node) || null; + } + registerPipeMetadata(meta: PipeMeta): void { + const metadata = this.ensureMetadata(meta.ref.node.getSourceFile()); + metadata.pipeMeta.set(meta.ref.node, meta); + } + private ensureMetadata(sf: ts.SourceFile): FileMetadata { const metadata = this.metadata.get(sf) || new FileMetadata(); this.metadata.set(sf, metadata); @@ -100,4 +130,7 @@ class FileMetadata { safeToSkipEmitIfUnchanged = false; /** A set of source files that this file depends upon. */ fileDependencies = new Set(); + directiveMeta = new Map(); + ngModuleMeta = new Map(); + pipeMeta = new Map(); } diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index cce1a6b87d..6bbc1fe54c 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -460,12 +460,14 @@ export class NgtscProgram implements api.Program { const evaluator = new PartialEvaluator(this.reflector, checker, this.incrementalState); const dtsReader = new DtsMetadataReader(checker, this.reflector); const localMetaRegistry = new LocalMetadataRegistry(); + const localMetaReader = new CompoundMetadataReader([localMetaRegistry, this.incrementalState]); const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasGenerator); const scopeRegistry = new LocalModuleScopeRegistry( - localMetaRegistry, depScopeReader, this.refEmitter, aliasGenerator); - const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry]); + localMetaReader, depScopeReader, this.refEmitter, aliasGenerator); + const metaRegistry = + new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry, this.incrementalState]); - this.metaReader = new CompoundMetadataReader([localMetaRegistry, dtsReader]); + this.metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]); // If a flat module entrypoint was specified, then track references via a `ReferenceGraph` @@ -504,8 +506,8 @@ export class NgtscProgram implements api.Program { ]; return new IvyCompilation( - handlers, checker, this.reflector, this.importRewriter, this.incrementalState, - this.perfRecorder, this.sourceToFactorySymbols); + handlers, this.reflector, this.importRewriter, this.incrementalState, this.perfRecorder, + this.sourceToFactorySymbols, scopeRegistry); } private get reflector(): TypeScriptReflectionHost { diff --git a/packages/compiler-cli/src/ngtsc/scope/src/local.ts b/packages/compiler-cli/src/ngtsc/scope/src/local.ts index 504c1f3971..1d7a95e21f 100644 --- a/packages/compiler-cli/src/ngtsc/scope/src/local.ts +++ b/packages/compiler-cli/src/ngtsc/scope/src/local.ts @@ -29,6 +29,16 @@ export interface LocalModuleScope extends ExportScope { reexports: Reexport[]|null; } +/** + * Information about the compilation scope of a registered declaration. + */ +export interface CompilationScope extends ScopeData { + /** The declaration whose compilation scope is described here. */ + declaration: ClassDeclaration; + /** The declaration of the NgModule that declares this `declaration`. */ + ngModule: ClassDeclaration; +} + /** * A registry which collects information about NgModules, Directives, Components, and Pipes which * are local (declared in the ts.Program being compiled), and can produce `LocalModuleScope`s @@ -73,7 +83,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry { * A value of `undefined` indicates the scope was invalid and produced errors (therefore, * diagnostics should exist in the `scopeErrors` map). */ - private cache = new Map(); + private cache = new Map(); /** * Tracks whether a given component requires "remote scoping". @@ -125,11 +135,9 @@ export class LocalModuleScopeRegistry implements MetadataRegistry { * available or the scope contains errors. */ getScopeOfModule(clazz: ClassDeclaration): LocalModuleScope|null { - if (!this.moduleToRef.has(clazz)) { - return null; - } - - const scope = this.getScopeOfModuleInternal(this.moduleToRef.get(clazz) !); + const scope = this.moduleToRef.has(clazz) ? + this.getScopeOfModuleReference(this.moduleToRef.get(clazz) !) : + null; // Translate undefined -> null. return scope !== undefined ? scope : null; } @@ -151,11 +159,32 @@ export class LocalModuleScopeRegistry implements MetadataRegistry { } /** - * Implementation of `getScopeOfModule` which differentiates between no scope being available - * (returns `null`) and a scope being produced with errors (returns `undefined`). + * Returns a collection of the compilation scope for each registered declaration. */ - private getScopeOfModuleInternal(ref: Reference): LocalModuleScope|null + getCompilationScopes(): CompilationScope[] { + const scopes: CompilationScope[] = []; + this.declarationToModule.forEach((ngModule, declaration) => { + const scope = this.getScopeOfModule(ngModule); + if (scope !== null) { + scopes.push({declaration, ngModule, ...scope.compilation}); + } + }); + return scopes; + } + + /** + * Implementation of `getScopeOfModule` which accepts a reference to a class and differentiates + * between: + * + * * no scope being available (returns `null`) + * * a scope being produced with errors (returns `undefined`). + */ + private getScopeOfModuleReference(ref: Reference): LocalModuleScope|null |undefined { + if (this.cache.has(ref.node)) { + return this.cache.get(ref.node); + } + // Seal the registry to protect the integrity of the `LocalModuleScope` cache. this.sealed = true; @@ -163,6 +192,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry { // cannot be produced. const ngModule = this.localReader.getNgModuleMetadata(ref); if (ngModule === null) { + this.cache.set(ref.node, null); return null; } @@ -326,6 +356,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry { this.scopeErrors.set(ref.node, diagnostics); // Return undefined to indicate the scope is invalid. + this.cache.set(ref.node, undefined); return undefined; } @@ -360,8 +391,10 @@ export class LocalModuleScopeRegistry implements MetadataRegistry { * The NgModule in question may be declared locally in the current ts.Program, or it may be * declared in a .d.ts file. * - * This function will return `null` if no scope could be found, or `undefined` if an invalid scope - * was found. It can also contribute diagnostics of its own by adding to the given `diagnostics` + * @returns `null` if no scope could be found, or `undefined` if an invalid scope + * was found. + * + * May also contribute diagnostics of its own by adding to the given `diagnostics` * array parameter. */ private getExportedScope( @@ -382,7 +415,7 @@ export class LocalModuleScopeRegistry implements MetadataRegistry { return this.dependencyScopeReader.resolve(ref); } else { // The NgModule is declared locally in the current program. Resolve it from the registry. - return this.getScopeOfModuleInternal(ref); + return this.getScopeOfModuleReference(ref); } } diff --git a/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel b/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel index e45022d9ec..42d58f5792 100644 --- a/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/transform/BUILD.bazel @@ -14,6 +14,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/incremental", "//packages/compiler-cli/src/ngtsc/perf", "//packages/compiler-cli/src/ngtsc/reflection", + "//packages/compiler-cli/src/ngtsc/scope", "//packages/compiler-cli/src/ngtsc/translator", "//packages/compiler-cli/src/ngtsc/typecheck", "//packages/compiler-cli/src/ngtsc/util", diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index 51e978c4bd..f23c2a19f6 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -14,6 +14,7 @@ import {ImportRewriter} from '../../imports'; import {IncrementalState} from '../../incremental'; import {PerfRecorder} from '../../perf'; import {ClassDeclaration, ReflectionHost, isNamedClassDeclaration, reflectNameOfDeclaration} from '../../reflection'; +import {LocalModuleScopeRegistry} from '../../scope'; import {TypeCheckContext} from '../../typecheck'; import {getSourceFile} from '../../util/src/typescript'; @@ -75,10 +76,10 @@ export class IvyCompilation { * `null` in most cases. */ constructor( - private handlers: DecoratorHandler[], private checker: ts.TypeChecker, - private reflector: ReflectionHost, private importRewriter: ImportRewriter, - private incrementalState: IncrementalState, private perf: PerfRecorder, - private sourceToFactorySymbols: Map>|null) {} + private handlers: DecoratorHandler[], private reflector: ReflectionHost, + private importRewriter: ImportRewriter, private incrementalState: IncrementalState, + private perf: PerfRecorder, private sourceToFactorySymbols: Map>|null, + private scopeRegistry: LocalModuleScopeRegistry) {} get exportStatements(): Map> { return this.reexportMap; } @@ -302,6 +303,22 @@ export class IvyCompilation { } }); this.perf.stop(resolveSpan); + this.recordNgModuleScopeDependencies(); + } + + private recordNgModuleScopeDependencies() { + const recordSpan = this.perf.start('recordDependencies'); + this.scopeRegistry !.getCompilationScopes().forEach(scope => { + const file = scope.declaration.getSourceFile(); + // Register the file containing the NgModule where the declaration is declared. + this.incrementalState.trackFileDependency(scope.ngModule.getSourceFile(), file); + scope.directives.forEach( + directive => + this.incrementalState.trackFileDependency(directive.ref.node.getSourceFile(), file)); + scope.pipes.forEach( + pipe => this.incrementalState.trackFileDependency(pipe.ref.node.getSourceFile(), file)); + }); + this.perf.stop(recordSpan); } typeCheck(context: TypeCheckContext): void {