Skip to content

Commit d83b973

Browse files
committed
fix(@ngtools/webpack): correctly determine root files
Fix angular#8228
1 parent d2e22b2 commit d83b973

File tree

14 files changed

+207
-262
lines changed

14 files changed

+207
-262
lines changed

packages/@angular/cli/models/webpack-configs/typescript.ts

-3
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) {
9292
missingTranslation: buildOptions.missingTranslation,
9393
hostReplacementPaths,
9494
sourceMap: buildOptions.sourcemaps,
95-
// If we don't explicitely list excludes, it will default to `['**/*.spec.ts']`.
96-
exclude: [],
97-
include: options.include,
9895
}, options);
9996
return new AngularCompilerPlugin(pluginOptions);
10097
} else {

packages/@ngtools/webpack/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ The loader works with webpack plugin to compile your TypeScript. It's important
6666
* `mainPath`. Optional if `entryModule` is specified. The `main.ts` file containing the bootstrap code. The plugin will use AST to determine the `entryModule`.
6767
* `skipCodeGeneration`. Optional, defaults to false. Disable code generation and do not refactor the code to bootstrap. This replaces `templateUrl: "string"` with `template: require("string")` (and similar for styles) to allow for webpack to properly link the resources. Only available in `AotPlugin`.
6868
* `typeChecking`. Optional, defaults to true. Enable type checking through your application. This will slow down compilation, but show syntactic and semantic errors in webpack. Only available in `AotPlugin`.
69-
* `exclude`. Optional. Extra files to exclude from TypeScript compilation.
69+
* `exclude`. Optional. Extra files to exclude from TypeScript compilation. Not supported with `AngularCompilerPlugin`.
7070
* `sourceMap`. Optional. Include sourcemaps.
7171
* `compilerOptions`. Optional. Override options in `tsconfig.json`.
7272

packages/@ngtools/webpack/src/angular_compiler_plugin.ts

+61-123
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
createProgram,
4848
createCompilerHost,
4949
formatDiagnostics,
50+
readConfiguration,
5051
} from './ngtools_api';
5152
import { findAstNodes } from './transformers/ast_helpers';
5253

@@ -72,8 +73,6 @@ export interface AngularCompilerPluginOptions {
7273
platform?: PLATFORM;
7374

7475
// Use tsconfig to include path globs.
75-
exclude?: string | string[];
76-
include?: string[];
7776
compilerOptions?: ts.CompilerOptions;
7877
}
7978

@@ -86,13 +85,11 @@ export class AngularCompilerPlugin implements Tapable {
8685
private _options: AngularCompilerPluginOptions;
8786

8887
// TS compilation.
89-
private _compilerOptions: ts.CompilerOptions;
90-
private _angularCompilerOptions: CompilerOptions;
91-
private _tsFilenames: string[];
88+
private _compilerOptions: CompilerOptions;
89+
private _rootNames: string[];
9290
private _program: (ts.Program | Program);
93-
private _compilerHost: WebpackCompilerHost;
91+
private _compilerHost: WebpackCompilerHost & CompilerHost;
9492
private _moduleResolutionCache: ts.ModuleResolutionCache;
95-
private _angularCompilerHost: WebpackCompilerHost & CompilerHost;
9693
private _resourceLoader: WebpackResourceLoader;
9794
// Contains `moduleImportPath#exportName` => `fullModulePath`.
9895
private _lazyRoutes: LazyRouteMap = Object.create(null);
@@ -186,39 +183,11 @@ export class AngularCompilerPlugin implements Tapable {
186183
);
187184
}
188185

189-
// Default exclude to **/*.spec.ts files.
190-
if (!options.hasOwnProperty('exclude')) {
191-
options['exclude'] = ['**/*.spec.ts'];
192-
}
193-
194-
// Add custom excludes to default TypeScript excludes.
195-
if (options.hasOwnProperty('exclude')) {
196-
// If the tsconfig doesn't contain any excludes, we must add the default ones before adding
197-
// any extra ones (otherwise we'd include all of these which can cause unexpected errors).
198-
// This is the same logic as present in TypeScript.
199-
if (!tsConfigJson.exclude) {
200-
tsConfigJson['exclude'] = ['node_modules', 'bower_components', 'jspm_packages'];
201-
if (tsConfigJson.compilerOptions && tsConfigJson.compilerOptions.outDir) {
202-
tsConfigJson.exclude.push(tsConfigJson.compilerOptions.outDir);
203-
}
204-
}
205-
206-
// Join our custom excludes with the existing ones.
207-
tsConfigJson.exclude = tsConfigJson.exclude.concat(options.exclude);
208-
}
209-
210-
// Add extra includes.
211-
if (options.hasOwnProperty('include') && Array.isArray(options.include)) {
212-
tsConfigJson.include = tsConfigJson.include || [];
213-
tsConfigJson.include.push(...options.include);
214-
}
215-
216186
// Parse the tsconfig contents.
217-
const tsConfig = ts.parseJsonConfigFileContent(
218-
tsConfigJson, ts.sys, basePath, undefined, this._tsConfigPath);
187+
const config = readConfiguration(this._tsConfigPath, tsConfigJson.compilerOptions);
219188

220-
this._tsFilenames = tsConfig.fileNames;
221-
this._compilerOptions = tsConfig.options;
189+
this._rootNames = config.rootNames;
190+
this._compilerOptions = config.options;
222191

223192
// Overwrite outDir so we can find generated files next to their .ts origin in compilerHost.
224193
this._compilerOptions.outDir = '';
@@ -250,55 +219,54 @@ export class AngularCompilerPlugin implements Tapable {
250219
// to the webpack dependency tree and rebuilds triggered by file edits.
251220
this._compilerOptions.noEmitOnError = false;
252221

253-
// Compose Angular Compiler Options.
254-
this._angularCompilerOptions = Object.assign(
255-
this._compilerOptions,
256-
tsConfig.raw['angularCompilerOptions'],
257-
{ basePath }
258-
);
259-
260222
// Set JIT (no code generation) or AOT mode.
261223
if (options.skipCodeGeneration !== undefined) {
262224
this._JitMode = options.skipCodeGeneration;
263225
}
264226

265227
// Process i18n options.
266228
if (options.hasOwnProperty('i18nInFile')) {
267-
this._angularCompilerOptions.i18nInFile = options.i18nInFile;
229+
this._compilerOptions.i18nInFile = options.i18nInFile;
268230
}
269231
if (options.hasOwnProperty('i18nInFormat')) {
270-
this._angularCompilerOptions.i18nInFormat = options.i18nInFormat;
232+
this._compilerOptions.i18nInFormat = options.i18nInFormat;
271233
}
272234
if (options.hasOwnProperty('i18nOutFile')) {
273-
this._angularCompilerOptions.i18nOutFile = options.i18nOutFile;
235+
this._compilerOptions.i18nOutFile = options.i18nOutFile;
274236
}
275237
if (options.hasOwnProperty('i18nOutFormat')) {
276-
this._angularCompilerOptions.i18nOutFormat = options.i18nOutFormat;
238+
this._compilerOptions.i18nOutFormat = options.i18nOutFormat;
277239
}
278240
if (options.hasOwnProperty('locale') && options.locale) {
279-
this._angularCompilerOptions.i18nInLocale = this._validateLocale(options.locale);
241+
this._compilerOptions.i18nInLocale = this._validateLocale(options.locale);
280242
}
281243
if (options.hasOwnProperty('missingTranslation')) {
282-
this._angularCompilerOptions.i18nInMissingTranslations =
244+
this._compilerOptions.i18nInMissingTranslations =
283245
options.missingTranslation as 'error' | 'warning' | 'ignore';
284246
}
285247

286248
// Use entryModule if available in options, otherwise resolve it from mainPath after program
287249
// creation.
288250
if (this._options.entryModule) {
289251
this._entryModule = this._options.entryModule;
290-
} else if (this._angularCompilerOptions.entryModule) {
252+
} else if (this._compilerOptions.entryModule) {
291253
this._entryModule = path.resolve(this._basePath,
292-
this._angularCompilerOptions.entryModule);
254+
this._compilerOptions.entryModule);
293255
}
294256

295257
// Create the webpack compiler host.
296-
this._compilerHost = new WebpackCompilerHost(this._compilerOptions, this._basePath);
297-
this._compilerHost.enableCaching();
258+
const webpackCompilerHost = new WebpackCompilerHost(this._compilerOptions, this._basePath);
259+
webpackCompilerHost.enableCaching();
298260

299261
// Create and set a new WebpackResourceLoader.
300262
this._resourceLoader = new WebpackResourceLoader();
301-
this._compilerHost.setResourceLoader(this._resourceLoader);
263+
webpackCompilerHost.setResourceLoader(this._resourceLoader);
264+
265+
// Use the WebpackCompilerHost with a resource loader to create an AngularCompilerHost.
266+
this._compilerHost = createCompilerHost({
267+
options: this._compilerOptions,
268+
tsHost: webpackCompilerHost
269+
}) as CompilerHost & WebpackCompilerHost;
302270

303271
// Override some files in the FileSystem.
304272
if (this._options.hostOverrideFileSystem) {
@@ -342,28 +310,24 @@ export class AngularCompilerPlugin implements Tapable {
342310
private _createOrUpdateProgram() {
343311
return Promise.resolve()
344312
.then(() => {
345-
const changedTsFiles = this._getChangedTsFiles();
313+
// Get the root files from the ts config.
314+
// When a new root name (like a lazy route) is added, it won't be available from
315+
// following imports on the existing files, so we need to get the new list of root files.
316+
this._rootNames = readConfiguration(this._tsConfigPath).rootNames;
346317

347-
changedTsFiles.forEach((file) => {
348-
if (!this._tsFilenames.includes(file)) {
349-
// TODO: figure out if action is needed for files that were removed from the
350-
// compilation.
351-
this._tsFilenames.push(file);
352-
}
353-
});
354-
355-
// Update the forked type checker.
318+
// Update the forked type checker with all changed compilation files.
319+
// This includes templates, that also need to be reloaded on the type checker.
356320
if (this._forkTypeChecker && !this._firstRun) {
357-
this._updateForkedTypeChecker(changedTsFiles);
321+
this._updateForkedTypeChecker(this._rootNames, this._getChangedCompilationFiles());
358322
}
359323

360324
if (this._JitMode) {
361325
// Create the TypeScript program.
362326
time('AngularCompilerPlugin._createOrUpdateProgram.ts.createProgram');
363327
this._program = ts.createProgram(
364-
this._tsFilenames,
365-
this._angularCompilerOptions,
366-
this._angularCompilerHost,
328+
this._rootNames,
329+
this._compilerOptions,
330+
this._compilerHost,
367331
this._program as ts.Program
368332
);
369333
timeEnd('AngularCompilerPlugin._createOrUpdateProgram.ts.createProgram');
@@ -372,26 +336,19 @@ export class AngularCompilerPlugin implements Tapable {
372336
} else {
373337
time('AngularCompilerPlugin._createOrUpdateProgram.ng.createProgram');
374338
// Create the Angular program.
375-
try {
376-
this._program = createProgram({
377-
rootNames: this._tsFilenames,
378-
options: this._angularCompilerOptions,
379-
host: this._angularCompilerHost,
380-
oldProgram: this._program as Program
339+
this._program = createProgram({
340+
rootNames: this._rootNames,
341+
options: this._compilerOptions,
342+
host: this._compilerHost,
343+
oldProgram: this._program as Program
344+
});
345+
timeEnd('AngularCompilerPlugin._createOrUpdateProgram.ng.createProgram');
346+
347+
time('AngularCompilerPlugin._createOrUpdateProgram.ng.loadNgStructureAsync');
348+
return this._program.loadNgStructureAsync()
349+
.then(() => {
350+
timeEnd('AngularCompilerPlugin._createOrUpdateProgram.ng.loadNgStructureAsync');
381351
});
382-
timeEnd('AngularCompilerPlugin._createOrUpdateProgram.ng.createProgram');
383-
384-
time('AngularCompilerPlugin._createOrUpdateProgram.ng.loadNgStructureAsync');
385-
return this._program.loadNgStructureAsync()
386-
.then(() => {
387-
timeEnd('AngularCompilerPlugin._createOrUpdateProgram.ng.loadNgStructureAsync');
388-
});
389-
} catch (e) {
390-
// TODO: remove this when the issue is addressed.
391-
// Temporary workaround for https://github.com/angular/angular/issues/19951
392-
this._program = undefined;
393-
throw e;
394-
}
395352
}
396353
})
397354
.then(() => {
@@ -412,7 +369,7 @@ export class AngularCompilerPlugin implements Tapable {
412369
const result = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
413370
program: this._getTsProgram(),
414371
host: this._compilerHost,
415-
angularCompilerOptions: Object.assign({}, this._angularCompilerOptions, {
372+
angularCompilerOptions: Object.assign({}, this._compilerOptions, {
416373
// genDir seems to still be needed in @angular\compiler-cli\src\compiler_host.js:226.
417374
genDir: ''
418375
}),
@@ -506,10 +463,8 @@ export class AngularCompilerPlugin implements Tapable {
506463
);
507464
}
508465
} else {
509-
// Found a new route, add it to the map and read it into the compiler host.
466+
// Found a new route, add it to the map.
510467
this._lazyRoutes[moduleKey] = modulePath;
511-
this._angularCompilerHost.readFile(lazyRouteTSFile);
512-
this._angularCompilerHost.invalidate(lazyRouteTSFile);
513468
}
514469
});
515470
}
@@ -545,7 +500,7 @@ export class AngularCompilerPlugin implements Tapable {
545500

546501
this._typeCheckerProcess = fork(path.resolve(__dirname, typeCheckerFile), [], forkOptions);
547502
this._typeCheckerProcess.send(new InitMessage(this._compilerOptions, this._basePath,
548-
this._JitMode, this._tsFilenames));
503+
this._JitMode, this._rootNames));
549504

550505
// Cleanup.
551506
const killTypeCheckerProcess = () => {
@@ -557,8 +512,8 @@ export class AngularCompilerPlugin implements Tapable {
557512
process.once('uncaughtException', killTypeCheckerProcess);
558513
}
559514

560-
private _updateForkedTypeChecker(changedTsFiles: string[]) {
561-
this._typeCheckerProcess.send(new UpdateMessage(changedTsFiles));
515+
private _updateForkedTypeChecker(rootNames: string[], changedCompilationFiles: string[]) {
516+
this._typeCheckerProcess.send(new UpdateMessage(rootNames, changedCompilationFiles));
562517
}
563518

564519

@@ -682,22 +637,12 @@ export class AngularCompilerPlugin implements Tapable {
682637
// Update the resource loader with the new webpack compilation.
683638
this._resourceLoader.update(compilation);
684639

640+
// Create a new process for the type checker on the second build if there isn't one yet.
641+
if (this._forkTypeChecker && !this._firstRun && !this._typeCheckerProcess) {
642+
this._createForkedTypeChecker();
643+
}
644+
685645
this._donePromise = Promise.resolve()
686-
.then(() => {
687-
// Create a new process for the type checker.
688-
if (this._forkTypeChecker && !this._firstRun && !this._typeCheckerProcess) {
689-
this._createForkedTypeChecker();
690-
}
691-
})
692-
.then(() => {
693-
if (this._firstRun) {
694-
// Use the WebpackResourceLoader with a resource loader to create an AngularCompilerHost.
695-
this._angularCompilerHost = createCompilerHost({
696-
options: this._angularCompilerOptions,
697-
tsHost: this._compilerHost
698-
}) as CompilerHost & WebpackCompilerHost;
699-
}
700-
})
701646
.then(() => this._update())
702647
.then(() => {
703648
timeEnd('AngularCompilerPlugin._make');
@@ -731,18 +676,14 @@ export class AngularCompilerPlugin implements Tapable {
731676
const changedTsFiles = this._getChangedTsFiles();
732677
if (this._ngCompilerSupportsNewApi) {
733678
this._processLazyRoutes(this._listLazyRoutesFromProgram());
734-
// TODO: remove this when the issue is addressed.
735-
// Fix for a bug in compiler where the program needs to be updated after
736-
// _listLazyRoutesFromProgram is called.
737-
return this._createOrUpdateProgram();
738679
} else if (this._firstRun) {
739680
this._processLazyRoutes(this._getLazyRoutesFromNgtools());
740681
} else if (changedTsFiles.length > 0) {
741682
this._processLazyRoutes(this._findLazyRoutesInAst(changedTsFiles));
742683
}
743684
})
744685
.then(() => {
745-
// Build transforms, emit and report errorsn.
686+
// Build transforms, emit and report errors.
746687

747688
// We now have the final list of changed TS files.
748689
// Go through each changed file and add transforms as needed.
@@ -772,11 +713,11 @@ export class AngularCompilerPlugin implements Tapable {
772713
}
773714

774715
// If we have a locale, auto import the locale data file.
775-
if (this._angularCompilerOptions.i18nInLocale) {
716+
if (this._compilerOptions.i18nInLocale) {
776717
transformOps.push(...registerLocaleData(
777718
sf,
778719
this.entryModule,
779-
this._angularCompilerOptions.i18nInLocale
720+
this._compilerOptions.i18nInLocale
780721
));
781722
}
782723
} else if (this._platform === PLATFORM.Server) {
@@ -846,7 +787,7 @@ export class AngularCompilerPlugin implements Tapable {
846787
}
847788

848789
// Write the extracted messages to disk.
849-
const i18nOutFilePath = path.resolve(this._basePath, this._angularCompilerOptions.i18nOutFile);
790+
const i18nOutFilePath = path.resolve(this._basePath, this._compilerOptions.i18nOutFile);
850791
const i18nOutFileContent = this._compilerHost.readFile(i18nOutFilePath);
851792
if (i18nOutFileContent) {
852793
_recursiveMkDir(path.dirname(i18nOutFilePath))
@@ -994,17 +935,14 @@ export class AngularCompilerPlugin implements Tapable {
994935

995936
if (!hasErrors(allDiagnostics)) {
996937
time('AngularCompilerPlugin._emit.ng.emit');
997-
const extractI18n = !!this._angularCompilerOptions.i18nOutFile;
938+
const extractI18n = !!this._compilerOptions.i18nOutFile;
998939
const emitFlags = extractI18n ? EmitFlags.I18nBundle : EmitFlags.Default;
999940
emitResult = angularProgram.emit({ emitFlags, customTransformers });
1000941
allDiagnostics.push(...emitResult.diagnostics);
1001942
if (extractI18n) {
1002943
this.writeI18nOutFile();
1003944
}
1004945
timeEnd('AngularCompilerPlugin._emit.ng.emit');
1005-
} else {
1006-
// Throw away the old program if there was an error.
1007-
this._program = undefined;
1008946
}
1009947
}
1010948
} catch (e) {

0 commit comments

Comments
 (0)