Skip to content

Commit 3ee989d

Browse files
committed
feat(@ngtools/webpack): use forked type checker
1 parent f89ca44 commit 3ee989d

File tree

5 files changed

+343
-54
lines changed

5 files changed

+343
-54
lines changed

packages/@ngtools/webpack/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
"npm": ">= 3.0.0"
2626
},
2727
"dependencies": {
28+
"tree-kill": "^1.0.0",
29+
"chalk": "^2.0.1",
2830
"loader-utils": "^1.0.2",
2931
"enhanced-resolve": "^3.1.0",
3032
"magic-string": "^0.22.3",

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

Lines changed: 106 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
// @ignoreDep @angular/compiler-cli
2-
// @ignoreDep @angular/compiler-cli/ngtools2
32
import * as fs from 'fs';
3+
import { fork, ChildProcess } from 'child_process';
44
import * as path from 'path';
55
import * as ts from 'typescript';
66

77
const { __NGTOOLS_PRIVATE_API_2, VERSION } = require('@angular/compiler-cli');
88
const ContextElementDependency = require('webpack/lib/dependencies/ContextElementDependency');
99
const NodeWatchFileSystem = require('webpack/lib/node/NodeWatchFileSystem');
10+
const treeKill = require('tree-kill');
1011

1112
import { WebpackResourceLoader } from './resource_loader';
1213
import { WebpackCompilerHost } from './compiler_host';
@@ -24,10 +25,10 @@ import {
2425
replaceResources
2526
} from './transformers';
2627
import { time, timeEnd } from './benchmark';
28+
import { InitMessage, UpdateMessage } from './type_checker';
2729

2830
// These imports do not exist on Angular versions lower than 5, so we cannot use a static ES6
2931
// import. Instead we copy their types into './ngtools_api2.d.ts'.
30-
// @ignoreDep @angular/compiler-cli/src/transformers/api
3132
let compilerCliNgtools: any = {};
3233
try {
3334
compilerCliNgtools = require('@angular/compiler-cli/ngtools2');
@@ -41,6 +42,7 @@ import {
4142
UNKNOWN_ERROR_CODE,
4243
SOURCE,
4344
Program,
45+
CompilerOptions,
4446
CompilerHost,
4547
CreateProgramInterface,
4648
CreateCompilerHostInterface,
@@ -88,9 +90,9 @@ export class AngularCompilerPlugin implements Tapable {
8890

8991
// TS compilation.
9092
private _compilerOptions: ts.CompilerOptions;
91-
private _angularCompilerOptions: any;
93+
private _angularCompilerOptions: CompilerOptions;
9294
private _tsFilenames: string[];
93-
private _program: ts.Program & Program;
95+
private _program: ts.Program | Program;
9496
private _compilerHost: WebpackCompilerHost;
9597
private _angularCompilerHost: WebpackCompilerHost & CompilerHost;
9698
// Contains `moduleImportPath#exportName` => `fullModulePath`.
@@ -109,6 +111,10 @@ export class AngularCompilerPlugin implements Tapable {
109111
private _compilation: any = null;
110112
private _failedCompilation = false;
111113

114+
// TypeChecker process.
115+
private _forkTypeChecker = true;
116+
private _typeCheckerProcess: ChildProcess;
117+
112118
constructor(options: AngularCompilerPluginOptions) {
113119
this._options = Object.assign({}, options);
114120
this._setupOptions(this._options);
@@ -244,7 +250,8 @@ export class AngularCompilerPlugin implements Tapable {
244250
this._angularCompilerOptions.i18nInLocale = options.locale;
245251
}
246252
if (options.hasOwnProperty('missingTranslation')) {
247-
this._angularCompilerOptions.i18nInMissingTranslations = options.missingTranslation;
253+
this._angularCompilerOptions.i18nInMissingTranslations =
254+
options.missingTranslation as 'error' | 'warning' | 'ignore';
248255
}
249256

250257
// Use entryModule if available in options, otherwise resolve it from mainPath after program
@@ -278,10 +285,15 @@ export class AngularCompilerPlugin implements Tapable {
278285

279286
// TODO: consider really using platform names in the plugin options.
280287
this._platform = options.replaceExport ? PLATFORM.Server : PLATFORM.Browser;
288+
289+
// Create a new process for the type checker.
290+
if (this._forkTypeChecker) {
291+
this._createForkedTypeChecker();
292+
}
281293
}
282294

283295
private _getTsProgram() {
284-
return this._JitMode ? this._program : this._program.getTsProgram();
296+
return this._JitMode ? this._program as ts.Program : (this._program as Program).getTsProgram();
285297
}
286298

287299
private _getChangedTsFiles() {
@@ -293,19 +305,26 @@ export class AngularCompilerPlugin implements Tapable {
293305
private _createOrUpdateProgram() {
294306
this._getChangedTsFiles().forEach((file) => {
295307
if (!this._tsFilenames.includes(file)) {
308+
// TODO: figure out if action is needed for files that were removed from the compilation.
296309
this._tsFilenames.push(file);
297310
}
298311
});
299312

313+
// Update the forked type checker.
314+
if (!this._firstRun && this._forkTypeChecker) {
315+
this._updateForkedTypeChecker(this._tsFilenames);
316+
}
317+
300318
if (this._JitMode) {
319+
301320
// Create the TypeScript program.
302321
time('_createOrUpdateProgram.ts.createProgram');
303322
this._program = ts.createProgram(
304323
this._tsFilenames,
305324
this._angularCompilerOptions,
306325
this._angularCompilerHost,
307-
this._program
308-
) as ts.Program & Program;
326+
this._program as ts.Program
327+
);
309328
timeEnd('_createOrUpdateProgram.ts.createProgram');
310329

311330
return Promise.resolve();
@@ -316,8 +335,8 @@ export class AngularCompilerPlugin implements Tapable {
316335
rootNames: this._tsFilenames,
317336
options: this._angularCompilerOptions,
318337
host: this._angularCompilerHost,
319-
oldProgram: this._program
320-
}) as ts.Program & Program;
338+
oldProgram: this._program as Program
339+
});
321340
timeEnd('_createOrUpdateProgram.ng.createProgram');
322341

323342
time('_createOrUpdateProgram.ng.loadNgStructureAsync');
@@ -408,6 +427,26 @@ export class AngularCompilerPlugin implements Tapable {
408427
});
409428
}
410429

430+
private _createForkedTypeChecker() {
431+
// Bootstrap type checker is using local CLI.
432+
const g: any = global;
433+
const typeCheckerFile: string = g['angularCliIsLocal']
434+
? './type_checker_bootstrap.js'
435+
: './type_checker.js';
436+
437+
this._typeCheckerProcess = fork(path.resolve(__dirname, typeCheckerFile));
438+
this._typeCheckerProcess.send(new InitMessage(
439+
this._compilerOptions, this._basePath, this._JitMode));
440+
441+
process.on('exit', () => {
442+
treeKill(process.pid, 'SIGTERM');
443+
});
444+
}
445+
446+
private _updateForkedTypeChecker(changedTsFiles: string[]) {
447+
this._typeCheckerProcess.send(new UpdateMessage(changedTsFiles));
448+
}
449+
411450

412451
// Registration hook for webpack plugin.
413452
apply(compiler: any) {
@@ -596,17 +635,20 @@ export class AngularCompilerPlugin implements Tapable {
596635
// Build transforms, emit and report errors if there are changes or it's the first run.
597636
if (changedFiles.length > 0 || this._firstRun) {
598637

638+
// We now have the final list of changed TS files.
599639
// Go through each changed file and add transforms as needed.
600-
time('_update.transformOps');
601640
const sourceFiles = this._getChangedTsFiles().map((fileName) => {
641+
time('_update.getSourceFile');
602642
const sourceFile = this._getTsProgram().getSourceFile(fileName);
603643
if (!sourceFile) {
604644
throw new Error(`${fileName} is not part of the TypeScript compilation. `
605-
+ `Please include it in your tsconfig via the 'files' or 'include' property.`);
645+
+ `Please include it in your tsconfig via the 'files' or 'include' property.`);
606646
}
647+
timeEnd('_update.getSourceFile');
607648
return sourceFile;
608649
});
609650

651+
time('_update.transformOps');
610652
sourceFiles.forEach((sf) => {
611653
const fileName = this._compilerHost.resolve(sf.fileName);
612654
let transformOps = [];
@@ -710,23 +752,26 @@ export class AngularCompilerPlugin implements Tapable {
710752
let shouldEmit = true;
711753

712754
if (this._JitMode) {
713-
const tsProgram: ts.Program = program;
755+
const tsProgram = program as ts.Program;
714756

715-
// Check parameter diagnostics.
716-
// TODO: check this only on initial program creation.
717-
time('_emit.ts.getOptionsDiagnostics');
718-
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getOptionsDiagnostics());
719-
timeEnd('_emit.ts.getOptionsDiagnostics');
757+
if (this._firstRun) {
758+
// Check parameter diagnostics.
759+
time('_emit.ts.getOptionsDiagnostics');
760+
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getOptionsDiagnostics());
761+
timeEnd('_emit.ts.getOptionsDiagnostics');
762+
}
720763

721-
// Check syntactic diagnostics.
722-
time('_emit.ts.getSyntacticDiagnostics');
723-
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getSyntacticDiagnostics());
724-
timeEnd('_emit.ts.getSyntacticDiagnostics');
764+
if (this._firstRun || !this._forkTypeChecker) {
765+
// Check syntactic diagnostics.
766+
time('_emit.ts.getSyntacticDiagnostics');
767+
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getSyntacticDiagnostics());
768+
timeEnd('_emit.ts.getSyntacticDiagnostics');
725769

726-
// Check semantic diagnostics.
727-
time('_emit.ts.getSemanticDiagnostics');
728-
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getSemanticDiagnostics());
729-
timeEnd('_emit.ts.getSemanticDiagnostics');
770+
// Check semantic diagnostics.
771+
time('_emit.ts.getSemanticDiagnostics');
772+
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getSemanticDiagnostics());
773+
timeEnd('_emit.ts.getSemanticDiagnostics');
774+
}
730775

731776
if (shouldEmit) {
732777
sourceFiles.forEach((sf) => {
@@ -740,31 +785,41 @@ export class AngularCompilerPlugin implements Tapable {
740785
});
741786
}
742787
} else {
743-
const angularProgram: Program = program;
744-
745-
// Check parameter diagnostics.
746-
// TODO: check this only on initial program creation.
747-
time('_emit.ng.getTsOptionDiagnostics+getNgOptionDiagnostics');
748-
shouldEmit = shouldEmit && checkDiagnostics([
749-
...angularProgram.getTsOptionDiagnostics(), ...angularProgram.getNgOptionDiagnostics()
750-
]);
751-
timeEnd('_emit.ng.getTsOptionDiagnostics+getNgOptionDiagnostics');
752-
753-
// Check syntactic diagnostics.
754-
time('_emit.ng.getTsSyntacticDiagnostics');
755-
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getTsSyntacticDiagnostics());
756-
timeEnd('_emit.ng.getTsSyntacticDiagnostics');
757-
758-
// Check TypeScript semantic and Angular structure diagnostics.
759-
time('_emit.ng.getTsSemanticDiagnostics+getNgStructuralDiagnostics');
760-
shouldEmit = shouldEmit && checkDiagnostics(
761-
[...angularProgram.getTsSemanticDiagnostics(),
762-
...angularProgram.getNgStructuralDiagnostics()
763-
]);
764-
timeEnd('_emit.ng.getTsSemanticDiagnostics+getNgStructuralDiagnostics');
765-
766-
// Check Angular semantic diagnostics
767-
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getNgSemanticDiagnostics());
788+
const angularProgram = program as Program;
789+
790+
if (this._firstRun) {
791+
// Check TypeScript parameter diagnostics.
792+
time('_emit.ng.getTsOptionDiagnostics');
793+
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getTsOptionDiagnostics());
794+
timeEnd('_emit.ng.getTsOptionDiagnostics');
795+
796+
// Check Angular parameter diagnostics.
797+
time('_emit.ng.getNgOptionDiagnostics');
798+
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getNgOptionDiagnostics());
799+
timeEnd('_emit.ng.getNgOptionDiagnostics');
800+
}
801+
802+
if (this._firstRun || !this._forkTypeChecker) {
803+
// Check TypeScript syntactic diagnostics.
804+
time('_emit.ng.getTsSyntacticDiagnostics');
805+
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getTsSyntacticDiagnostics());
806+
timeEnd('_emit.ng.getTsSyntacticDiagnostics');
807+
808+
// Check TypeScript semantic.
809+
time('_emit.ng.getTsSemanticDiagnostics');
810+
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getTsSemanticDiagnostics());
811+
timeEnd('_emit.ng.getTsSemanticDiagnostics');
812+
813+
// Check Angular structural diagnostics.
814+
time('_emit.ng.getNgStructuralDiagnostics');
815+
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getNgStructuralDiagnostics());
816+
timeEnd('_emit.ng.getNgStructuralDiagnostics');
817+
818+
// Check Angular semantic diagnostics
819+
time('_emit.ng.getNgSemanticDiagnostics');
820+
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getNgSemanticDiagnostics());
821+
timeEnd('_emit.ng.getNgSemanticDiagnostics');
822+
}
768823

769824
if (shouldEmit) {
770825
time('_emit.ng.emit');

0 commit comments

Comments
 (0)