fix(ivy): recompile on template change in ngc watch mode on Windows (#34015)
In #33551, a bug in `ngc --watch` mode was fixed so that a component is recompiled when its template file is changed. Due to insufficient normalization of files paths, this fix did not have the desired effect on Windows. Fixes #32869 PR Close #34015
This commit is contained in:
parent
f7d4cc9cab
commit
5cada5cce1
|
@ -273,7 +273,7 @@ export class ComponentDecoratorHandler implements
|
||||||
const resourceStr = this.resourceLoader.load(resourceUrl);
|
const resourceStr = this.resourceLoader.load(resourceUrl);
|
||||||
styles.push(resourceStr);
|
styles.push(resourceStr);
|
||||||
if (this.depTracker !== null) {
|
if (this.depTracker !== null) {
|
||||||
this.depTracker.addResourceDependency(node.getSourceFile(), resourceUrl);
|
this.depTracker.addResourceDependency(node.getSourceFile(), absoluteFrom(resourceUrl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -675,7 +675,7 @@ export class ComponentDecoratorHandler implements
|
||||||
resourceUrl: string): ParsedTemplateWithSource {
|
resourceUrl: string): ParsedTemplateWithSource {
|
||||||
const templateStr = this.resourceLoader.load(resourceUrl);
|
const templateStr = this.resourceLoader.load(resourceUrl);
|
||||||
if (this.depTracker !== null) {
|
if (this.depTracker !== null) {
|
||||||
this.depTracker.addResourceDependency(node.getSourceFile(), resourceUrl);
|
this.depTracker.addResourceDependency(node.getSourceFile(), absoluteFrom(resourceUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
const template = this._parseTemplate(
|
const template = this._parseTemplate(
|
||||||
|
|
|
@ -9,6 +9,7 @@ ts_library(
|
||||||
]),
|
]),
|
||||||
deps = [
|
deps = [
|
||||||
":api",
|
":api",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||||
"//packages/compiler-cli/src/ngtsc/imports",
|
"//packages/compiler-cli/src/ngtsc/imports",
|
||||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||||
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
||||||
|
@ -24,6 +25,7 @@ ts_library(
|
||||||
name = "api",
|
name = "api",
|
||||||
srcs = ["api.ts"],
|
srcs = ["api.ts"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||||
"@npm//typescript",
|
"@npm//typescript",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
import {AbsoluteFsPath} from '../file_system';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface of the incremental build engine.
|
* Interface of the incremental build engine.
|
||||||
|
@ -33,7 +34,7 @@ export interface DependencyTracker<T extends{fileName: string} = ts.SourceFile>
|
||||||
/**
|
/**
|
||||||
* Record that the file `from` depends on the resource file `on`.
|
* Record that the file `from` depends on the resource file `on`.
|
||||||
*/
|
*/
|
||||||
addResourceDependency(from: T, on: string): void;
|
addResourceDependency(from: T, on: AbsoluteFsPath): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record that the file `from` depends on the file `on` as well as `on`'s direct dependencies.
|
* Record that the file `from` depends on the file `on` as well as `on`'s direct dependencies.
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '../../file_system';
|
||||||
import {DependencyTracker} from '../api';
|
import {DependencyTracker} from '../api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +29,7 @@ export class FileDependencyGraph<T extends{fileName: string} = ts.SourceFile> im
|
||||||
|
|
||||||
addDependency(from: T, on: T): void { this.nodeFor(from).dependsOn.add(on.fileName); }
|
addDependency(from: T, on: T): void { this.nodeFor(from).dependsOn.add(on.fileName); }
|
||||||
|
|
||||||
addResourceDependency(from: T, resource: string): void {
|
addResourceDependency(from: T, resource: AbsoluteFsPath): void {
|
||||||
this.nodeFor(from).usesResources.add(resource);
|
this.nodeFor(from).usesResources.add(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ export class FileDependencyGraph<T extends{fileName: string} = ts.SourceFile> im
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isStale(sf: T, changedTsPaths: Set<string>, changedResources: Set<string>): boolean {
|
isStale(sf: T, changedTsPaths: Set<string>, changedResources: Set<AbsoluteFsPath>): boolean {
|
||||||
return isLogicallyChanged(sf, this.nodeFor(sf), changedTsPaths, EMPTY_SET, changedResources);
|
return isLogicallyChanged(sf, this.nodeFor(sf), changedTsPaths, EMPTY_SET, changedResources);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ export class FileDependencyGraph<T extends{fileName: string} = ts.SourceFile> im
|
||||||
*/
|
*/
|
||||||
updateWithPhysicalChanges(
|
updateWithPhysicalChanges(
|
||||||
previous: FileDependencyGraph<T>, changedTsPaths: Set<string>, deletedTsPaths: Set<string>,
|
previous: FileDependencyGraph<T>, changedTsPaths: Set<string>, deletedTsPaths: Set<string>,
|
||||||
changedResources: Set<string>): Set<string> {
|
changedResources: Set<AbsoluteFsPath>): Set<string> {
|
||||||
const logicallyChanged = new Set<string>();
|
const logicallyChanged = new Set<string>();
|
||||||
|
|
||||||
for (const sf of previous.nodes.keys()) {
|
for (const sf of previous.nodes.keys()) {
|
||||||
|
@ -99,7 +100,7 @@ export class FileDependencyGraph<T extends{fileName: string} = ts.SourceFile> im
|
||||||
if (!this.nodes.has(sf)) {
|
if (!this.nodes.has(sf)) {
|
||||||
this.nodes.set(sf, {
|
this.nodes.set(sf, {
|
||||||
dependsOn: new Set<string>(),
|
dependsOn: new Set<string>(),
|
||||||
usesResources: new Set<string>(),
|
usesResources: new Set<AbsoluteFsPath>(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this.nodes.get(sf) !;
|
return this.nodes.get(sf) !;
|
||||||
|
@ -112,13 +113,13 @@ export class FileDependencyGraph<T extends{fileName: string} = ts.SourceFile> im
|
||||||
*/
|
*/
|
||||||
function isLogicallyChanged<T extends{fileName: string}>(
|
function isLogicallyChanged<T extends{fileName: string}>(
|
||||||
sf: T, node: FileNode, changedTsPaths: ReadonlySet<string>, deletedTsPaths: ReadonlySet<string>,
|
sf: T, node: FileNode, changedTsPaths: ReadonlySet<string>, deletedTsPaths: ReadonlySet<string>,
|
||||||
changedResources: ReadonlySet<string>): boolean {
|
changedResources: ReadonlySet<AbsoluteFsPath>): boolean {
|
||||||
// A file is logically changed if it has physically changed itself (including being deleted).
|
// A file is logically changed if it has physically changed itself (including being deleted).
|
||||||
if (changedTsPaths.has(sf.fileName) || deletedTsPaths.has(sf.fileName)) {
|
if (changedTsPaths.has(sf.fileName) || deletedTsPaths.has(sf.fileName)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A file is logically changed if one of its dependencies has physically cxhanged.
|
// A file is logically changed if one of its dependencies has physically changed.
|
||||||
for (const dep of node.dependsOn) {
|
for (const dep of node.dependsOn) {
|
||||||
if (changedTsPaths.has(dep) || deletedTsPaths.has(dep)) {
|
if (changedTsPaths.has(dep) || deletedTsPaths.has(dep)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -136,7 +137,7 @@ function isLogicallyChanged<T extends{fileName: string}>(
|
||||||
|
|
||||||
interface FileNode {
|
interface FileNode {
|
||||||
dependsOn: Set<string>;
|
dependsOn: Set<string>;
|
||||||
usesResources: Set<string>;
|
usesResources: Set<AbsoluteFsPath>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMPTY_SET: ReadonlySet<any> = new Set<any>();
|
const EMPTY_SET: ReadonlySet<any> = new Set<any>();
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath, absoluteFrom} from '../../file_system';
|
||||||
import {ClassRecord, TraitCompiler} from '../../transform';
|
import {ClassRecord, TraitCompiler} from '../../transform';
|
||||||
import {IncrementalBuild} from '../api';
|
import {IncrementalBuild} from '../api';
|
||||||
|
|
||||||
|
@ -52,7 +53,7 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord> {
|
||||||
state = {
|
state = {
|
||||||
kind: BuildStateKind.Pending,
|
kind: BuildStateKind.Pending,
|
||||||
pendingEmit: oldDriver.state.pendingEmit,
|
pendingEmit: oldDriver.state.pendingEmit,
|
||||||
changedResourcePaths: new Set<string>(),
|
changedResourcePaths: new Set<AbsoluteFsPath>(),
|
||||||
changedTsPaths: new Set<string>(),
|
changedTsPaths: new Set<string>(),
|
||||||
lastGood: oldDriver.state.lastGood,
|
lastGood: oldDriver.state.lastGood,
|
||||||
};
|
};
|
||||||
|
@ -61,7 +62,7 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord> {
|
||||||
// Merge the freshly modified resource files with any prior ones.
|
// Merge the freshly modified resource files with any prior ones.
|
||||||
if (modifiedResourceFiles !== null) {
|
if (modifiedResourceFiles !== null) {
|
||||||
for (const resFile of modifiedResourceFiles) {
|
for (const resFile of modifiedResourceFiles) {
|
||||||
state.changedResourcePaths.add(resFile);
|
state.changedResourcePaths.add(absoluteFrom(resFile));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +154,7 @@ export class IncrementalDriver implements IncrementalBuild<ClassRecord> {
|
||||||
const state: PendingBuildState = {
|
const state: PendingBuildState = {
|
||||||
kind: BuildStateKind.Pending,
|
kind: BuildStateKind.Pending,
|
||||||
pendingEmit: new Set<string>(tsFiles.map(sf => sf.fileName)),
|
pendingEmit: new Set<string>(tsFiles.map(sf => sf.fileName)),
|
||||||
changedResourcePaths: new Set<string>(),
|
changedResourcePaths: new Set<AbsoluteFsPath>(),
|
||||||
changedTsPaths: new Set<string>(),
|
changedTsPaths: new Set<string>(),
|
||||||
lastGood: null,
|
lastGood: null,
|
||||||
};
|
};
|
||||||
|
@ -287,7 +288,7 @@ interface PendingBuildState extends BaseBuildState {
|
||||||
/**
|
/**
|
||||||
* Set of resource file paths which have changed since the last successfully analyzed build.
|
* Set of resource file paths which have changed since the last successfully analyzed build.
|
||||||
*/
|
*/
|
||||||
changedResourcePaths: Set<string>;
|
changedResourcePaths: Set<AbsoluteFsPath>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AnalyzedBuildState extends BaseBuildState {
|
interface AnalyzedBuildState extends BaseBuildState {
|
||||||
|
|
|
@ -246,11 +246,13 @@ export function performWatchCompilation(host: PerformWatchHost):
|
||||||
}
|
}
|
||||||
|
|
||||||
function watchedFileChanged(event: FileChangeEvent, fileName: string) {
|
function watchedFileChanged(event: FileChangeEvent, fileName: string) {
|
||||||
|
const normalizedPath = path.normalize(fileName);
|
||||||
|
|
||||||
if (cachedOptions && event === FileChangeEvent.Change &&
|
if (cachedOptions && event === FileChangeEvent.Change &&
|
||||||
// TODO(chuckj): validate that this is sufficient to skip files that were written.
|
// TODO(chuckj): validate that this is sufficient to skip files that were written.
|
||||||
// This assumes that the file path we write is the same file path we will receive in the
|
// This assumes that the file path we write is the same file path we will receive in the
|
||||||
// change notification.
|
// change notification.
|
||||||
path.normalize(fileName) === path.normalize(cachedOptions.project)) {
|
normalizedPath === path.normalize(cachedOptions.project)) {
|
||||||
// If the configuration file changes, forget everything and start the recompilation timer
|
// If the configuration file changes, forget everything and start the recompilation timer
|
||||||
resetOptions();
|
resetOptions();
|
||||||
} else if (
|
} else if (
|
||||||
|
@ -263,12 +265,12 @@ export function performWatchCompilation(host: PerformWatchHost):
|
||||||
if (event === FileChangeEvent.CreateDeleteDir) {
|
if (event === FileChangeEvent.CreateDeleteDir) {
|
||||||
fileCache.clear();
|
fileCache.clear();
|
||||||
} else {
|
} else {
|
||||||
fileCache.delete(path.normalize(fileName));
|
fileCache.delete(normalizedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ignoreFilesForWatch.has(path.normalize(fileName))) {
|
if (!ignoreFilesForWatch.has(normalizedPath)) {
|
||||||
// Ignore the file if the file is one that was written by the compiler.
|
// Ignore the file if the file is one that was written by the compiler.
|
||||||
startTimerForRecompilation(fileName);
|
startTimerForRecompilation(normalizedPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ describe('perform watch', () => {
|
||||||
const watchResult = performWatchCompilation(host);
|
const watchResult = performWatchCompilation(host);
|
||||||
expectNoDiagnostics(config.options, watchResult.firstCompileResult);
|
expectNoDiagnostics(config.options, watchResult.firstCompileResult);
|
||||||
|
|
||||||
const htmlPath = path.posix.join(testSupport.basePath, 'src', 'main.html');
|
const htmlPath = path.join(testSupport.basePath, 'src', 'main.html');
|
||||||
const genPath = ivyEnabled ? path.posix.join(outDir, 'src', 'main.js') :
|
const genPath = ivyEnabled ? path.posix.join(outDir, 'src', 'main.js') :
|
||||||
path.posix.join(outDir, 'src', 'main.ngfactory.js');
|
path.posix.join(outDir, 'src', 'main.ngfactory.js');
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue