fix(ivy): correctly emit component when it's removed from its module (#34912)
This commit fixes a bug in the incremental rebuild engine of ngtsc, where if a component was removed from its NgModule, it would not be properly re-emitted. The bug stemmed from the fact that whether to emit a file was a decision based purely on the updated dependency graph, which captures the dependency structure of the rebuild program. This graph has no edge from the component to its former module (as it was removed, of course), so the compiler erroneously decides not to emit the component. The bug here is that the compiler does know, from the previous dependency graph, that the component file has logically changed, since its previous dependency (the module file) has changed. This information was not carried forward into the set of files which need to be emitted, because it was assumed that the updated dependency graph was a more accurate source of that information. With this commit, the set of files which need emit is pre-populated with the set of logically changed files, to cover edge cases like this. Fixes #34813 PR Close #34912
This commit is contained in:

committed by
Andrew Kushnir

parent
1e7851cf7c
commit
adc663e43b
@ -251,6 +251,72 @@ runInEachFileSystem(() => {
|
||||
expect(written).toContain('/foo_module.js');
|
||||
});
|
||||
|
||||
it('should rebuild a component if removed from an NgModule', () => {
|
||||
// This test consists of a component with a dependency (the directive DepDir) provided via an
|
||||
// NgModule. Initially this configuration is built, then the component is removed from its
|
||||
// module (which removes DepDir from the component's scope) and a rebuild is performed.
|
||||
// The compiler should re-emit the component without DepDir in its scope.
|
||||
//
|
||||
// This is a tricky scenario due to the backwards dependency arrow from a component to its
|
||||
// module.
|
||||
env.write('dep.ts', `
|
||||
import {Directive, NgModule} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[dep]'})
|
||||
export class DepDir {}
|
||||
|
||||
@NgModule({
|
||||
declarations: [DepDir],
|
||||
exports: [DepDir],
|
||||
})
|
||||
export class DepModule {}
|
||||
`);
|
||||
|
||||
env.write('cmp.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
template: '<div dep></div>',
|
||||
})
|
||||
export class Cmp {}
|
||||
`);
|
||||
|
||||
env.write('module.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {Cmp} from './cmp';
|
||||
import {DepModule} from './dep';
|
||||
|
||||
@NgModule({
|
||||
declarations: [Cmp],
|
||||
imports: [DepModule],
|
||||
})
|
||||
export class Module {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
env.flushWrittenFileTracking();
|
||||
|
||||
// Remove the component from the module and recompile.
|
||||
env.write('module.ts', `
|
||||
import {NgModule} from '@angular/core';
|
||||
import {DepModule} from './dep';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [DepModule],
|
||||
})
|
||||
export class Module {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
|
||||
// After removing the component from the module, it should have been re-emitted without DepDir
|
||||
// in its scope.
|
||||
expect(env.getFilesWrittenSinceLastFlush()).toContain('/cmp.js');
|
||||
expect(env.getContents('cmp.js')).not.toContain('DepDir');
|
||||
});
|
||||
|
||||
it('should rebuild only a Component (but with the correct CompilationScope) and its module if its template has changed',
|
||||
() => {
|
||||
setupFooBarProgram(env);
|
||||
|
Reference in New Issue
Block a user