fix(ivy): ensure that changes to component resources trigger incremental builds (#30954)

Optimizations to skip compiling source files that had not changed
did not account for the case where only a resource file changes,
such as an external template or style file.

Now we track such dependencies and trigger a recompilation
if any of the previously tracked resources have changed.

This will require a change on the CLI side to provide the list of
resource files that changed to trigger the current compilation by
implementing `CompilerHost.getModifiedResourceFiles()`.

Closes #30947

PR Close #30954
This commit is contained in:
Pete Bacon Darwin
2019-06-10 16:22:56 +01:00
committed by Kara Erickson
parent dc613b336d
commit 48def92cad
13 changed files with 159 additions and 43 deletions

View File

@ -12,6 +12,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/metadata",
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
"//packages/compiler-cli/src/ngtsc/reflection",
"//packages/compiler-cli/src/ngtsc/util",
"@npm//typescript",
],
)

View File

@ -7,21 +7,26 @@
*/
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';
import {ResourceDependencyRecorder} from '../../util/src/resource_recorder';
/**
* Accumulates state between compilations.
*/
export class IncrementalState implements DependencyTracker, MetadataReader, MetadataRegistry {
export class IncrementalState implements DependencyTracker, MetadataReader, MetadataRegistry,
ResourceDependencyRecorder {
private constructor(
private unchangedFiles: Set<ts.SourceFile>,
private metadata: Map<ts.SourceFile, FileMetadata>) {}
private metadata: Map<ts.SourceFile, FileMetadata>,
private modifiedResourceFiles: Set<string>|null) {}
static reconcile(previousState: IncrementalState, oldProgram: ts.Program, newProgram: ts.Program):
IncrementalState {
static reconcile(
previousState: IncrementalState, oldProgram: ts.Program, newProgram: ts.Program,
modifiedResourceFiles: Set<string>|null): IncrementalState {
const unchangedFiles = new Set<ts.SourceFile>();
const metadata = new Map<ts.SourceFile, FileMetadata>();
const oldFiles = new Set<ts.SourceFile>(oldProgram.getSourceFiles());
@ -46,14 +51,17 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
}
}
return new IncrementalState(unchangedFiles, metadata);
return new IncrementalState(unchangedFiles, metadata, modifiedResourceFiles);
}
static fresh(): IncrementalState {
return new IncrementalState(new Set<ts.SourceFile>(), new Map<ts.SourceFile, FileMetadata>());
return new IncrementalState(
new Set<ts.SourceFile>(), new Map<ts.SourceFile, FileMetadata>(), null);
}
safeToSkip(sf: ts.SourceFile): boolean { return this.unchangedFiles.has(sf); }
safeToSkip(sf: ts.SourceFile): boolean|Promise<boolean> {
return this.unchangedFiles.has(sf) && !this.hasChangedResourceDependencies(sf);
}
trackFileDependency(dep: ts.SourceFile, src: ts.SourceFile) {
const metadata = this.ensureMetadata(src);
@ -92,11 +100,25 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
metadata.pipeMeta.set(meta.ref.node, meta);
}
recordResourceDependency(file: ts.SourceFile, resourcePath: string): void {
const metadata = this.ensureMetadata(file);
metadata.resourcePaths.add(resourcePath);
}
private ensureMetadata(sf: ts.SourceFile): FileMetadata {
const metadata = this.metadata.get(sf) || new FileMetadata();
this.metadata.set(sf, metadata);
return metadata;
}
private hasChangedResourceDependencies(sf: ts.SourceFile): boolean {
if (this.modifiedResourceFiles === undefined || !this.metadata.has(sf)) {
return false;
}
const resourceDeps = this.metadata.get(sf) !.resourcePaths;
return Array.from(resourceDeps.keys())
.some(resourcePath => this.modifiedResourceFiles !.has(resourcePath));
}
}
/**
@ -105,6 +127,7 @@ export class IncrementalState implements DependencyTracker, MetadataReader, Meta
class FileMetadata {
/** A set of source files that this file depends upon. */
fileDependencies = new Set<ts.SourceFile>();
resourcePaths = new Set<string>();
directiveMeta = new Map<ClassDeclaration, DirectiveMeta>();
ngModuleMeta = new Map<ClassDeclaration, NgModuleMeta>();
pipeMeta = new Map<ClassDeclaration, PipeMeta>();