@@ -110,7 +110,9 @@ export class AngularCompilerPlugin {
110110 // even whe only a single file gets updated.
111111 private _hadFullJitEmit : boolean | undefined ;
112112 private _unusedFiles = new Set < string > ( ) ;
113+ private _typeDeps = new Set < string > ( ) ;
113114 private _changedFileExtensions = new Set ( [ 'ts' , 'tsx' , 'html' , 'css' , 'js' , 'json' ] ) ;
115+ private _nodeModulesRegExp = / [ \\ \/ ] n o d e _ m o d u l e s [ \\ \/ ] / ;
114116
115117 // Webpack plugin.
116118 private _firstRun = true ;
@@ -592,7 +594,7 @@ export class AngularCompilerPlugin {
592594 }
593595 }
594596
595- private _warnOnUnusedFiles ( compilation : compilation . Compilation ) {
597+ private _checkUnusedFiles ( compilation : compilation . Compilation ) {
596598 // Only do the unused TS files checks when under Ivy
597599 // since previously we did include unused files in the compilation
598600 // See: https://github.com/angular/angular-cli/pull/15030
@@ -601,6 +603,7 @@ export class AngularCompilerPlugin {
601603 return ;
602604 }
603605
606+ // Bail if there's no TS program. Nothing to do in that case.
604607 const program = this . _getTsProgram ( ) ;
605608 if ( ! program ) {
606609 return ;
@@ -609,26 +612,36 @@ export class AngularCompilerPlugin {
609612 // Exclude the following files from unused checks
610613 // - ngfactories & ngstyle might not have a correspondent
611614 // JS file example `@angular/core/core.ngfactory.ts`.
612- // - .d.ts files might not have a correspondent JS file due to bundling.
613615 // - __ng_typecheck__.ts will never be requested.
614- const fileExcludeRegExp = / ( \. ( d | n g f a c t o r y | n g s t y l e | n g s u m m a r y ) \. t s | n g _ t y p e c h e c k _ _ \. t s ) $ / ;
616+ const fileExcludeRegExp = / ( \. ( n g f a c t o r y | n g s t y l e | n g s u m m a r y ) \. t s | n g _ t y p e c h e c k _ _ \. t s ) $ / ;
617+
618+ // Start all the source file names we care about.
619+ // Ignore matches to the regexp above, files we've already reported once before, and
620+ // node_modules.
621+ const sourceFiles = program . getSourceFiles ( )
622+ . map ( x => this . _compilerHost . denormalizePath ( x . fileName ) )
623+ . filter ( f => ! ( fileExcludeRegExp . test ( f ) || this . _unusedFiles . has ( f )
624+ || this . _nodeModulesRegExp . test ( f ) ) ) ;
625+
626+ // Make a set with the sources, but exclude .d.ts files since those are type-only.
627+ const unusedSourceFileNames = new Set ( sourceFiles . filter ( f => ! f . endsWith ( '.d.ts' ) ) ) ;
628+ // Separately keep track of type-only deps.
629+ const typeDepFileNames = new Set ( sourceFiles ) ;
615630
616- // Start with a set of all the source file names we care about.
617- const unusedSourceFileNames = new Set (
618- program . getSourceFiles ( )
619- . map ( x => this . _compilerHost . denormalizePath ( x . fileName ) )
620- . filter ( f => ! ( fileExcludeRegExp . test ( f ) || this . _unusedFiles . has ( f ) ) ) ,
621- ) ;
622631 // This function removes a source file name and all its dependencies from the set.
623- const removeSourceFile = ( fileName : string ) => {
624- if ( unusedSourceFileNames . has ( fileName ) ) {
632+ const removeSourceFile = ( fileName : string , originalModule = false ) => {
633+ if ( unusedSourceFileNames . has ( fileName )
634+ || ( originalModule && typeDepFileNames . has ( fileName ) ) ) {
625635 unusedSourceFileNames . delete ( fileName ) ;
636+ if ( originalModule ) {
637+ typeDepFileNames . delete ( fileName ) ;
638+ }
626639 this . getDependencies ( fileName , false ) . forEach ( f => removeSourceFile ( f ) ) ;
627640 }
628641 } ;
629642
630- // Go over all the modules in the webpack compilation and remove them from the set .
631- compilation . modules . forEach ( m => m . resource ? removeSourceFile ( m . resource ) : null ) ;
643+ // Go over all the modules in the webpack compilation and remove them from the sets .
644+ compilation . modules . forEach ( m => m . resource ? removeSourceFile ( m . resource , true ) : null ) ;
632645
633646 // Anything that remains is unused, because it wasn't referenced directly or transitively
634647 // on the files in the compilation.
@@ -638,7 +651,15 @@ export class AngularCompilerPlugin {
638651 `Add only entry points to the 'files' or 'include' properties in your tsconfig.` ,
639652 ) ;
640653 this . _unusedFiles . add ( fileName ) ;
654+ // Remove the truly unused from the type dep list.
655+ typeDepFileNames . delete ( fileName ) ;
641656 }
657+
658+ // At this point we know what the type deps are.
659+ // These are the TS files that weren't part of the compilation modules, aren't unused, but were
660+ // part of the TS original source list.
661+ // Next build we add them to the TS entry points so that they trigger rebuilds.
662+ this . _typeDeps = typeDepFileNames ;
642663 }
643664
644665 // Registration hook for webpack plugin.
@@ -657,7 +678,7 @@ export class AngularCompilerPlugin {
657678 // cleanup if not watching
658679 compiler . hooks . thisCompilation . tap ( 'angular-compiler' , compilation => {
659680 compilation . hooks . finishModules . tap ( 'angular-compiler' , ( ) => {
660- this . _warnOnUnusedFiles ( compilation ) ;
681+ this . _checkUnusedFiles ( compilation ) ;
661682
662683 let rootCompiler = compiler ;
663684 while ( rootCompiler . parentCompilation ) {
@@ -1188,7 +1209,7 @@ export class AngularCompilerPlugin {
11881209 let msg = `${ fileName } is missing from the TypeScript compilation. `
11891210 + `Please make sure it is in your tsconfig via the 'files' or 'include' property.` ;
11901211
1191- if ( / ( \\ | \/ ) n o d e _ m o d u l e s ( \\ | \/ ) / . test ( fileName ) ) {
1212+ if ( this . _nodeModulesRegExp . test ( fileName ) ) {
11921213 msg += '\nThe missing file seems to be part of a third party library. '
11931214 + 'TS files in published libraries are often a sign of a badly packaged library. '
11941215 + 'Please open an issue in the library repository to alert its author and ask them '
@@ -1267,6 +1288,18 @@ export class AngularCompilerPlugin {
12671288 return this . _resourceLoader . getResourceDependencies ( resolvedFileName ) ;
12681289 }
12691290
1291+ getTypeDependencies ( fileName : string ) : string [ ] {
1292+ // We currently add all type deps directly to the main path.
1293+ // If there's no main path or the lookup isn't the main path, bail.
1294+ if ( ! this . _mainPath || this . _compilerHost . resolve ( fileName ) != this . _mainPath ) {
1295+ return [ ] ;
1296+ }
1297+
1298+ // Note: this set is always for the previous build, not the current build.
1299+ // It should be better than not having rebuilds on type deps but isn't 100% correct.
1300+ return Array . from ( this . _typeDeps ) ;
1301+ }
1302+
12701303 // This code mostly comes from `performCompilation` in `@angular/compiler-cli`.
12711304 // It skips the program creation because we need to use `loadNgStructureAsync()`,
12721305 // and uses CustomTransformers.
0 commit comments