6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import {
10
- CompilerHost ,
11
- CompilerOptions ,
12
- NgtscProgram ,
13
- readConfiguration ,
14
- } from '@angular/compiler-cli' ;
9
+ import type { CompilerHost , CompilerOptions , NgtscProgram } from '@angular/compiler-cli' ;
10
+ import { strict as assert } from 'assert' ;
15
11
import { createHash } from 'crypto' ;
16
12
import * as ts from 'typescript' ;
17
13
import type { Compilation , Compiler , Module , NormalModule } from 'webpack' ;
@@ -61,6 +57,7 @@ export interface AngularWebpackPluginOptions {
61
57
function initializeNgccProcessor (
62
58
compiler : Compiler ,
63
59
tsconfig : string ,
60
+ compilerNgccModule : typeof import ( '@angular/compiler-cli/ngcc' ) | undefined ,
64
61
) : { processor : NgccProcessor ; errors : string [ ] ; warnings : string [ ] } {
65
62
const { inputFileSystem, options : webpackOptions } = compiler ;
66
63
const mainFields = webpackOptions . resolve ?. mainFields ?. flat ( ) ?? [ ] ;
@@ -73,7 +70,14 @@ function initializeNgccProcessor(
73
70
extensions : [ '.json' ] ,
74
71
useSyncFileSystemCalls : true ,
75
72
} ) ;
73
+
74
+ // The compilerNgccModule field is guaranteed to be defined during a compilation
75
+ // due to the `beforeCompile` hook. Usage of this property accessor prior to the
76
+ // hook execution is an implementation error.
77
+ assert . ok ( compilerNgccModule , `'@angular/compiler-cli/ngcc' used prior to Webpack compilation.` ) ;
78
+
76
79
const processor = new NgccProcessor (
80
+ compilerNgccModule ,
77
81
mainFields ,
78
82
warnings ,
79
83
errors ,
@@ -95,6 +99,8 @@ const compilationFileEmitters = new WeakMap<Compilation, FileEmitterCollection>(
95
99
96
100
export class AngularWebpackPlugin {
97
101
private readonly pluginOptions : AngularWebpackPluginOptions ;
102
+ private compilerCliModule ?: typeof import ( '@angular/compiler-cli' ) ;
103
+ private compilerNgccModule ?: typeof import ( '@angular/compiler-cli/ngcc' ) ;
98
104
private watchMode ?: boolean ;
99
105
private ngtscNextProgram ?: NgtscProgram ;
100
106
private builder ?: ts . EmitAndSemanticDiagnosticsBuilderProgram ;
@@ -117,6 +123,15 @@ export class AngularWebpackPlugin {
117
123
} ;
118
124
}
119
125
126
+ private get compilerCli ( ) : typeof import ( '@angular/compiler-cli' ) {
127
+ // The compilerCliModule field is guaranteed to be defined during a compilation
128
+ // due to the `beforeCompile` hook. Usage of this property accessor prior to the
129
+ // hook execution is an implementation error.
130
+ assert . ok ( this . compilerCliModule , `'@angular/compiler-cli' used prior to Webpack compilation.` ) ;
131
+
132
+ return this . compilerCliModule ;
133
+ }
134
+
120
135
get options ( ) : AngularWebpackPluginOptions {
121
136
return this . pluginOptions ;
122
137
}
@@ -153,6 +168,9 @@ export class AngularWebpackPlugin {
153
168
} ) ;
154
169
} ) ;
155
170
171
+ // Load the compiler-cli if not already available
172
+ compiler . hooks . beforeCompile . tapPromise ( PLUGIN_NAME , ( ) => this . initializeCompilerCli ( ) ) ;
173
+
156
174
let ngccProcessor : NgccProcessor | undefined ;
157
175
let resourceLoader : WebpackResourceLoader | undefined ;
158
176
let previousUnused : Set < string > | undefined ;
@@ -172,6 +190,7 @@ export class AngularWebpackPlugin {
172
190
const { processor, errors, warnings } = initializeNgccProcessor (
173
191
compiler ,
174
192
this . pluginOptions . tsconfig ,
193
+ this . compilerNgccModule ,
175
194
) ;
176
195
177
196
processor . process ( ) ;
@@ -185,7 +204,9 @@ export class AngularWebpackPlugin {
185
204
const { compilerOptions, rootNames, errors } = this . loadConfiguration ( ) ;
186
205
187
206
// Create diagnostics reporter and report configuration file errors
188
- const diagnosticsReporter = createDiagnosticsReporter ( compilation ) ;
207
+ const diagnosticsReporter = createDiagnosticsReporter ( compilation , ( diagnostic ) =>
208
+ this . compilerCli . formatDiagnostics ( [ diagnostic ] ) ,
209
+ ) ;
189
210
diagnosticsReporter ( errors ) ;
190
211
191
212
// Update TypeScript path mapping plugin with new configuration
@@ -398,7 +419,10 @@ export class AngularWebpackPlugin {
398
419
options : compilerOptions ,
399
420
rootNames,
400
421
errors,
401
- } = readConfiguration ( this . pluginOptions . tsconfig , this . pluginOptions . compilerOptions ) ;
422
+ } = this . compilerCli . readConfiguration (
423
+ this . pluginOptions . tsconfig ,
424
+ this . pluginOptions . compilerOptions ,
425
+ ) ;
402
426
compilerOptions . enableIvy = true ;
403
427
compilerOptions . noEmitOnError = false ;
404
428
compilerOptions . suppressOutputPathCheck = true ;
@@ -422,7 +446,7 @@ export class AngularWebpackPlugin {
422
446
resourceLoader : WebpackResourceLoader ,
423
447
) {
424
448
// Create the Angular specific program that contains the Angular compiler
425
- const angularProgram = new NgtscProgram (
449
+ const angularProgram = new this . compilerCli . NgtscProgram (
426
450
rootNames ,
427
451
compilerOptions ,
428
452
host ,
@@ -561,8 +585,15 @@ export class AngularWebpackPlugin {
561
585
}
562
586
}
563
587
588
+ // Temporary workaround during transition to ESM-only @angular/compiler-cli
589
+ // TODO_ESM: This workaround should be removed prior to the final release of v13
590
+ // and replaced with only `this.compilerCli.OptimizeFor`.
591
+ const OptimizeFor =
592
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
593
+ ( this . compilerCli as any ) . OptimizeFor ??
594
+ require ( '@angular/compiler-cli/src/ngtsc/typecheck/api' ) . OptimizeFor ;
595
+
564
596
// Collect new Angular diagnostics for files affected by changes
565
- const { OptimizeFor } = require ( '@angular/compiler-cli/src/ngtsc/typecheck/api' ) ;
566
597
const optimizeDiagnosticsFor =
567
598
affectedFiles . size <= DIAGNOSTICS_AFFECTED_THRESHOLD
568
599
? OptimizeFor . SingleFile
@@ -628,7 +659,7 @@ export class AngularWebpackPlugin {
628
659
] ;
629
660
diagnosticsReporter ( diagnostics ) ;
630
661
631
- const transformers = createJitTransformers ( builder , this . pluginOptions ) ;
662
+ const transformers = createJitTransformers ( builder , this . compilerCli , this . pluginOptions ) ;
632
663
633
664
return {
634
665
fileEmitter : this . createFileEmitter ( builder , transformers , ( ) => [ ] ) ,
@@ -687,4 +718,37 @@ export class AngularWebpackPlugin {
687
718
return { content, map, dependencies, hash } ;
688
719
} ;
689
720
}
721
+
722
+ private async initializeCompilerCli ( ) : Promise < void > {
723
+ if ( this . compilerCliModule ) {
724
+ return ;
725
+ }
726
+
727
+ // This uses a dynamic import to load `@angular/compiler-cli` which may be ESM.
728
+ // CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
729
+ // will currently, unconditionally downlevel dynamic import into a require call.
730
+ // require calls cannot load ESM code and will result in a runtime error. To workaround
731
+ // this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
732
+ // Once TypeScript provides support for keeping the dynamic import this workaround can
733
+ // be dropped.
734
+ const compilerCliModule = await new Function ( `return import('@angular/compiler-cli');` ) ( ) ;
735
+ let compilerNgccModule ;
736
+ try {
737
+ compilerNgccModule = await new Function ( `return import('@angular/compiler-cli/ngcc');` ) ( ) ;
738
+ } catch {
739
+ // If the `exports` field entry is not present then try the file directly.
740
+ // TODO_ESM: This try/catch can be removed once the `exports` field is present in `@angular/compiler-cli`
741
+ compilerNgccModule = await new Function (
742
+ `return import('@angular/compiler-cli/ngcc/index.js');` ,
743
+ ) ( ) ;
744
+ }
745
+ // If it is not ESM then the functions needed will be stored in the `default` property.
746
+ // TODO_ESM: This conditional can be removed when `@angular/compiler-cli` is ESM only.
747
+ this . compilerCliModule = compilerCliModule . readConfiguration
748
+ ? compilerCliModule
749
+ : compilerCliModule . default ;
750
+ this . compilerNgccModule = compilerNgccModule . process
751
+ ? compilerNgccModule
752
+ : compilerNgccModule . default ;
753
+ }
690
754
}
0 commit comments