Skip to content

Commit

Permalink
fix(@ngtools/webpack): add local dts file as dependencies
Browse files Browse the repository at this point in the history
We now add non node_modules `.d.ts` as a dependency of the main chunk. This is important under Ivy, because NG metadata is now part of the declarations files ex:

```ts
export declare class FooComponent implements OnInit {
    constructor();
    ngOnInit(): void;
    static ɵfac: i0.ɵɵFactoryDef<FooComponent>;
    static ɵcmp: i0.ɵɵComponentDefWithMeta<FooComponent, "lib-foo", never, {}, {}, never>;
}
```

Previously such files were not being added as dependency and such files didn't get invalidated when changed.

Closes #16920 and closes #16921
  • Loading branch information
alan-agius4 authored and Keen Yee Liau committed Feb 18, 2020
1 parent fd13313 commit 352c574
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,41 @@ describe('Browser Builder rebuilds', () => {
.toPromise();
});

it('rebuilds on transitive non node package DTS file changes', async () => {
host.writeMultipleFiles({
'src/interface1.d.ts': `
import { Interface2 } from './interface2';
export interface Interface1 extends Interface2 { }
`,
'src/interface2.d.ts': `
import { Interface3 } from './interface3';
export interface Interface2 extends Interface3 { }
`,
'src/interface3.d.ts': `export interface Interface3 { nbr: number; }`,
});
host.appendToFile('src/main.ts', `
import { Interface1 } from './interface1';
const something: Interface1 = { nbr: 43 };
`);

const overrides = { watch: true };
const run = await architect.scheduleTarget(target, overrides);
let buildNumber = 0;
await run.output
.pipe(
debounceTime(rebuildDebounceTime),
tap(buildEvent => expect(buildEvent.success).toBe(true)),
tap(() => {
buildNumber++;
if (buildNumber === 1) {
host.appendToFile('src/interface3.d.ts', 'export declare type MyType = string;');
}
}),
take(2),
)
.toPromise();
});

it('rebuilds after errors in JIT', async () => {
const origContent = virtualFs.fileBufferToString(
host.scopedSync().read(normalize('src/app/app.component.ts')),
Expand Down
50 changes: 41 additions & 9 deletions packages/ngtools/webpack/src/angular_compiler_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,13 +385,44 @@ export class AngularCompilerPlugin {
}

const newTsProgram = this._getTsProgram();
if (oldTsProgram && newTsProgram) {
const newProgramSourceFiles = newTsProgram?.getSourceFiles();
const localDtsFiles = new Set(
newProgramSourceFiles?.filter(
f => f.isDeclarationFile && !this._nodeModulesRegExp.test(f.fileName),
)
.map(f => this._compilerHost.denormalizePath(f.fileName)),
);

if (!oldTsProgram) {
// Add all non node package dts files as depedencies when not having an old program
for (const dts of localDtsFiles) {
this._typeDeps.add(dts);
}
} else if (oldTsProgram && newProgramSourceFiles) {
// The invalidation should only happen if we have an old program
// as otherwise we will invalidate all the sourcefiles.
const oldFiles = new Set(oldTsProgram.getSourceFiles().map(sf => sf.fileName));
const newFiles = newTsProgram.getSourceFiles().filter(sf => !oldFiles.has(sf.fileName));
for (const newFile of newFiles) {
this._compilerHost.invalidate(newFile.fileName);
const newProgramFiles = new Set(newProgramSourceFiles.map(sf => sf.fileName));

for (const dependency of this._typeDeps) {
// Remove type dependencies of no longer existing files
if (!newProgramFiles.has(forwardSlashPath(dependency))) {
this._typeDeps.delete(dependency);
}
}

for (const fileName of newProgramFiles) {
if (oldFiles.has(fileName)) {
continue;
}

this._compilerHost.invalidate(fileName);

const denormalizedFileName = this._compilerHost.denormalizePath(fileName);
if (localDtsFiles.has(denormalizedFileName)) {
// Add new dts file as a type depedency
this._typeDeps.add(denormalizedFileName);
}
}
}

Expand Down Expand Up @@ -630,8 +661,7 @@ export class AngularCompilerPlugin {

// This function removes a source file name and all its dependencies from the set.
const removeSourceFile = (fileName: string, originalModule = false) => {
if (unusedSourceFileNames.has(fileName)
|| (originalModule && typeDepFileNames.has(fileName))) {
if (unusedSourceFileNames.has(fileName) || (originalModule && typeDepFileNames.has(fileName))) {
unusedSourceFileNames.delete(fileName);
if (originalModule) {
typeDepFileNames.delete(fileName);
Expand All @@ -649,7 +679,7 @@ export class AngularCompilerPlugin {
compilation.warnings.push(
`${fileName} is part of the TypeScript compilation but it's unused.\n` +
`Add only entry points to the 'files' or 'include' properties in your tsconfig.`,
);
);
this._unusedFiles.add(fileName);
// Remove the truly unused from the type dep list.
typeDepFileNames.delete(fileName);
Expand All @@ -659,7 +689,9 @@ export class AngularCompilerPlugin {
// These are the TS files that weren't part of the compilation modules, aren't unused, but were
// part of the TS original source list.
// Next build we add them to the TS entry points so that they trigger rebuilds.
this._typeDeps = typeDepFileNames;
for (const fileName of typeDepFileNames) {
this._typeDeps.add(fileName);
}
}

// Registration hook for webpack plugin.
Expand Down Expand Up @@ -1233,7 +1265,7 @@ export class AngularCompilerPlugin {

for (const resource of resourceImports) {
for (const dep of this.getResourceDependencies(
this._compilerHost.denormalizePath(resource))) {
this._compilerHost.denormalizePath(resource))) {
resourceDependencies.push(dep);
}
}
Expand Down

0 comments on commit 352c574

Please sign in to comment.