Skip to content

Commit 376e335

Browse files
committed
refactor(@ngtools/webpack): add internal only benchmark timings
1 parent c5cb888 commit 376e335

File tree

4 files changed

+120
-9
lines changed

4 files changed

+120
-9
lines changed

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
registerLocaleData,
2525
replaceResources,
2626
} from './transformers';
27+
import { time, timeEnd } from './benchmark';
2728

2829
// These imports do not exist on Angular versions lower than 5, so we cannot use a static ES6
2930
// import. Instead we copy their types into './ngtools_api2.d.ts'.
@@ -300,29 +301,36 @@ export class AngularCompilerPlugin implements Tapable {
300301

301302
if (this._JitMode) {
302303
// Create the TypeScript program.
304+
time('_createOrUpdateProgram.ts.createProgram');
303305
this._program = ts.createProgram(
304306
this._tsFilenames,
305307
this._angularCompilerOptions,
306308
this._angularCompilerHost,
307309
this._program
308310
) as ts.Program & Program;
311+
timeEnd('_createOrUpdateProgram.ts.createProgram');
309312

310313
return Promise.resolve();
311314
} else {
315+
time('_createOrUpdateProgram.ng.createProgram');
312316
// Create the Angular program.
313317
this._program = createProgram({
314318
rootNames: this._tsFilenames,
315319
options: this._angularCompilerOptions,
316320
host: this._angularCompilerHost,
317321
oldProgram: this._program
318322
}) as ts.Program & Program;
323+
timeEnd('_createOrUpdateProgram.ng.createProgram');
319324

320-
return this._program.loadNgStructureAsync();
325+
time('_createOrUpdateProgram.ng.loadNgStructureAsync');
326+
return this._program.loadNgStructureAsync()
327+
.then(() => timeEnd('_createOrUpdateProgram.ng.loadNgStructureAsync'));
321328
}
322329
}
323330

324331
private _getLazyRoutesFromNgtools() {
325332
try {
333+
time('_getLazyRoutesFromNgtools');
326334
const result = __NGTOOLS_PRIVATE_API_2.listLazyRoutes({
327335
program: this._getTsProgram(),
328336
host: this._compilerHost,
@@ -332,6 +340,7 @@ export class AngularCompilerPlugin implements Tapable {
332340
}),
333341
entryModule: this._entryModule
334342
});
343+
timeEnd('_getLazyRoutesFromNgtools');
335344
return result;
336345
} catch (err) {
337346
// We silence the error that the @angular/router could not be found. In that case, there is
@@ -345,6 +354,7 @@ export class AngularCompilerPlugin implements Tapable {
345354
}
346355

347356
private _findLazyRoutesInAst(changedFilePaths: string[]): LazyRouteMap {
357+
time('_findLazyRoutesInAst');
348358
const result: LazyRouteMap = Object.create(null);
349359
for (const filePath of changedFilePaths) {
350360
const fileLazyRoutes = findLazyRoutes(filePath, this._compilerHost, undefined,
@@ -354,6 +364,7 @@ export class AngularCompilerPlugin implements Tapable {
354364
result[routeKey] = route;
355365
}
356366
}
367+
timeEnd('_findLazyRoutesInAst');
357368
return result;
358369
}
359370

@@ -508,6 +519,7 @@ export class AngularCompilerPlugin implements Tapable {
508519
}
509520

510521
private _make(compilation: any, cb: (err?: any, request?: any) => void) {
522+
time('_make');
511523
this._compilation = compilation;
512524
if (this._compilation._ngToolsWebpackPluginInstance) {
513525
return cb(new Error('An @ngtools/webpack plugin already exist for this compilation.'));
@@ -516,8 +528,10 @@ export class AngularCompilerPlugin implements Tapable {
516528
this._compilation._ngToolsWebpackPluginInstance = this;
517529

518530
// Create the resource loader with the webpack compilation.
531+
time('_make.setResourceLoader');
519532
const resourceLoader = new WebpackResourceLoader(compilation);
520533
this._compilerHost.setResourceLoader(resourceLoader);
534+
timeEnd('_make.setResourceLoader');
521535

522536
this._donePromise = Promise.resolve()
523537
.then(() => {
@@ -532,26 +546,31 @@ export class AngularCompilerPlugin implements Tapable {
532546
.then(() => {
533547
// If there's still no entryModule try to resolve from mainPath.
534548
if (!this._entryModule && this._options.mainPath) {
549+
time('_make.resolveEntryModuleFromMain');
535550
const mainPath = path.resolve(this._basePath, this._options.mainPath);
536551
this._entryModule = resolveEntryModuleFromMain(
537552
mainPath, this._compilerHost, this._getTsProgram());
553+
timeEnd('_make.resolveEntryModuleFromMain');
538554
}
539555
});
540556
}
541557
})
542558
.then(() => this._update())
543559
.then(() => {
560+
timeEnd('_make');
544561
cb();
545562
}, (err: any) => {
546563
this._failedCompilation = true;
547564
compilation.errors.push(err.stack);
565+
timeEnd('_make');
548566
cb();
549567
});
550568
}
551569

552570
private _update() {
553571
// We only want to update on TS and template changes, but all kinds of files are on this
554572
// list, like package.json and .ngsummary.json files.
573+
time('_update');
555574
let changedFiles = this._compilerHost.getChangedFilePaths()
556575
.filter(k => /(ts|html|css|scss|sass|less|styl)/.test(k));
557576

@@ -580,6 +599,7 @@ export class AngularCompilerPlugin implements Tapable {
580599
if (changedFiles.length > 0 || this._firstRun) {
581600

582601
// Go through each changed file and add transforms as needed.
602+
time('_update.transformOps');
583603
const sourceFiles = this._getChangedTsFiles().map((fileName) => {
584604
const sourceFile = this._getTsProgram().getSourceFile(fileName);
585605
if (!sourceFile) {
@@ -627,13 +647,18 @@ export class AngularCompilerPlugin implements Tapable {
627647
for (let fileTransformOps of this._transformMap.values()) {
628648
transformOps.push(...fileTransformOps);
629649
}
650+
timeEnd('_update.transformOps');
630651

652+
time('_update.makeTransform');
631653
const transformers: CustomTransformers = {
632654
beforeTs: transformOps.length > 0 ? [makeTransform(transformOps)] : []
633655
};
656+
timeEnd('_update.makeTransform');
634657

635658
// Emit files.
659+
time('_update._emit');
636660
const { emitResult, diagnostics } = this._emit(sourceFiles, transformers);
661+
timeEnd('_update._emit');
637662

638663
// Report diagnostics.
639664
// TODO: check if the old _translateSourceMap function is needed.
@@ -659,6 +684,7 @@ export class AngularCompilerPlugin implements Tapable {
659684
this._failedCompilation = true;
660685
}
661686
}
687+
timeEnd('_update');
662688
});
663689
}
664690

@@ -678,6 +704,7 @@ export class AngularCompilerPlugin implements Tapable {
678704
sourceFiles: ts.SourceFile[],
679705
customTransformers: ts.CustomTransformers & CustomTransformers
680706
) {
707+
time('_emit');
681708
const program = this._program;
682709
const allDiagnostics: Diagnostics = [];
683710

@@ -697,48 +724,68 @@ export class AngularCompilerPlugin implements Tapable {
697724
const tsProgram: ts.Program = program;
698725

699726
// Check parameter diagnostics.
727+
// TODO: check this only on initial program creation.
728+
time('_emit.ts.getOptionsDiagnostics');
700729
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getOptionsDiagnostics());
730+
timeEnd('_emit.ts.getOptionsDiagnostics');
701731

702732
// Check syntactic diagnostics.
733+
time('_emit.ts.getSyntacticDiagnostics');
703734
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getSyntacticDiagnostics());
735+
timeEnd('_emit.ts.getSyntacticDiagnostics');
704736

705737
// Check semantic diagnostics.
738+
time('_emit.ts.getSemanticDiagnostics');
706739
shouldEmit = shouldEmit && checkDiagnostics(tsProgram.getSemanticDiagnostics());
740+
timeEnd('_emit.ts.getSemanticDiagnostics');
707741

708742
if (shouldEmit) {
709743
sourceFiles.forEach((sf) => {
744+
const timeLabel = `_emit.ts+${sf.fileName}+.emit`;
745+
time(timeLabel);
710746
emitResult = tsProgram.emit(sf, undefined, undefined, undefined,
711747
{ before: customTransformers.beforeTs }
712748
);
713749
allDiagnostics.push(...emitResult.diagnostics);
750+
timeEnd(timeLabel);
714751
});
715752
}
716753
} else {
717754
const angularProgram: Program = program;
718755

719756
// Check parameter diagnostics.
757+
// TODO: check this only on initial program creation.
758+
time('_emit.ng.getTsOptionDiagnostics+getNgOptionDiagnostics');
720759
shouldEmit = shouldEmit && checkDiagnostics([
721760
...angularProgram.getTsOptionDiagnostics(), ...angularProgram.getNgOptionDiagnostics()
722761
]);
762+
timeEnd('_emit.ng.getTsOptionDiagnostics+getNgOptionDiagnostics');
723763

724764
// Check syntactic diagnostics.
765+
time('_emit.ng.getTsSyntacticDiagnostics');
725766
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getTsSyntacticDiagnostics());
767+
timeEnd('_emit.ng.getTsSyntacticDiagnostics');
726768

727769
// Check TypeScript semantic and Angular structure diagnostics.
770+
time('_emit.ng.getTsSemanticDiagnostics+getNgStructuralDiagnostics');
728771
shouldEmit = shouldEmit && checkDiagnostics(
729772
[...angularProgram.getTsSemanticDiagnostics(),
730773
...angularProgram.getNgStructuralDiagnostics()
731774
]);
775+
timeEnd('_emit.ng.getTsSemanticDiagnostics+getNgStructuralDiagnostics');
732776

733777
// Check Angular semantic diagnostics
734778
shouldEmit = shouldEmit && checkDiagnostics(angularProgram.getNgSemanticDiagnostics());
735779

736780
if (shouldEmit) {
781+
time('_emit.ng.emit');
737782
emitResult = angularProgram.emit({ emitFlags: EmitFlags.Default, customTransformers });
738783
allDiagnostics.push(...emitResult.diagnostics);
784+
timeEnd('_emit.ng.emit');
739785
}
740786
}
741787
} catch (e) {
788+
time('_emit.catch');
742789
// This function is available in the import below, but this way we avoid the dependency.
743790
// import { isSyntaxError } from '@angular/compiler';
744791
function isSyntaxError(error: Error): boolean {
@@ -759,7 +806,9 @@ export class AngularCompilerPlugin implements Tapable {
759806
}
760807
allDiagnostics.push(
761808
{ category: ts.DiagnosticCategory.Error, messageText: errMsg, code, source: SOURCE });
809+
timeEnd('_emit.catch');
762810
}
811+
timeEnd('_emit');
763812
return { program, emitResult, diagnostics: allDiagnostics };
764813
}
765814
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Internal benchmark reporting flag.
2+
// Use with CLI --no-progress flag for best results.
3+
// This should be false for commited code.
4+
const _benchmark = false;
5+
6+
export function time(label: string) {
7+
if (_benchmark) {
8+
console.time(label);
9+
}
10+
}
11+
12+
export function timeEnd(label: string) {
13+
if (_benchmark) {
14+
console.timeEnd(label);
15+
}
16+
}

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

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {AotPlugin} from './plugin';
44
import {AngularCompilerPlugin} from './angular_compiler_plugin';
55
import {TypeScriptFileRefactor} from './refactor';
66
import {LoaderContext, ModuleReason} from './webpack';
7+
import {time, timeEnd} from './benchmark';
78

89
interface Platform {
910
name: string;
@@ -523,6 +524,8 @@ export function _exportModuleMap(plugin: AotPlugin, refactor: TypeScriptFileRefa
523524
export function ngcLoader(this: LoaderContext & { _compilation: any }, source: string | null) {
524525
const cb = this.async();
525526
const sourceFileName: string = this.resourcePath;
527+
const timeLabel = `ngcLoader+${sourceFileName}+`;
528+
time(timeLabel);
526529

527530
const plugin = this._compilation._ngToolsWebpackPluginInstance;
528531
if (plugin) {
@@ -536,19 +539,27 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
536539
}
537540

538541
if (plugin instanceof AngularCompilerPlugin) {
542+
time(timeLabel + '.ngcLoader.AngularCompilerPlugin');
539543
plugin.done
540544
.then(() => {
545+
timeEnd(timeLabel + '.ngcLoader.AngularCompilerPlugin');
541546
const result = plugin.getFile(sourceFileName);
542547
if (plugin.failedCompilation) {
543548
// Return an empty string if there is no result to prevent extra loader errors.
544549
// Plugin errors were already pushed to the compilation errors.
550+
timeEnd(timeLabel);
545551
cb(null, result.outputText || '', result.sourceMap);
546552
} else {
547-
cb(null, result.outputText, result.sourceMap);
548-
}
549-
})
550-
.catch(err => cb(err));
553+
timeEnd(timeLabel);
554+
cb(null, result.outputText, result.sourceMap);
555+
}
556+
})
557+
.catch(err => {
558+
timeEnd(timeLabel + '.ngcLoader.AngularCompilerPlugin');
559+
cb(err);
560+
});
551561
} else if (plugin instanceof AotPlugin) {
562+
time(timeLabel + '.ngcLoader.AotPlugin');
552563
if (plugin.compilerHost.readFile(sourceFileName) == source) {
553564
// In the case where the source is the same as the one in compilerHost, we don't have
554565
// extra TS loaders and there's no need to do any trickery.
@@ -557,40 +568,49 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
557568
const refactor = new TypeScriptFileRefactor(
558569
sourceFileName, plugin.compilerHost, plugin.program, source);
559570

571+
560572
Promise.resolve()
561573
.then(() => {
574+
time(timeLabel + '.ngcLoader.AotPlugin.refactor');
575+
let promise: Promise<any>;
562576
if (!plugin.skipCodeGeneration) {
563-
return Promise.resolve()
577+
promise = Promise.resolve()
564578
.then(() => _removeDecorators(refactor))
565579
.then(() => _refactorBootstrap(plugin, refactor))
566580
.then(() => _replaceExport(plugin, refactor))
567581
.then(() => _exportModuleMap(plugin, refactor));
568582
} else {
569-
return Promise.resolve()
583+
promise = Promise.resolve()
570584
.then(() => _replaceResources(refactor))
571585
.then(() => _removeModuleId(refactor))
572586
.then(() => _exportModuleMap(plugin, refactor));
573587
}
588+
return promise.then(() => timeEnd(timeLabel + '.ngcLoader.AotPlugin.refactor'));
574589
})
575590
.then(() => {
576591
if (plugin.typeCheck) {
592+
time(timeLabel + '.ngcLoader.AotPlugin.typeCheck');
577593
// Check all diagnostics from this and reverse dependencies also.
578594
if (!plugin.firstRun) {
579595
_diagnoseDeps(this._module.reasons, plugin, new Set<string>());
580596
}
581597
// We do this here because it will throw on error, resulting in rebuilding this file
582598
// the next time around if it changes.
583599
plugin.diagnose(sourceFileName);
600+
timeEnd(timeLabel + '.ngcLoader.AotPlugin.typeCheck');
584601
}
585602
})
586603
.then(() => {
604+
time(timeLabel + '.ngcLoader.AotPlugin.addDependency');
587605
// Add resources as dependencies.
588606
_getResourcesUrls(refactor).forEach((url: string) => {
589607
this.addDependency(path.resolve(path.dirname(sourceFileName), url));
590608
});
609+
timeEnd(timeLabel + '.ngcLoader.AotPlugin.addDependency');
591610
})
592611
.then(() => {
593612
if (source) {
613+
time(timeLabel + '.ngcLoader.AotPlugin.getDiagnostics');
594614
// We need to validate diagnostics. We ignore type checking though, to save time.
595615
const diagnostics = refactor.getDiagnostics(false);
596616
if (diagnostics.length) {
@@ -610,6 +630,7 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
610630
});
611631
throw new Error(message);
612632
}
633+
timeEnd(timeLabel + '.ngcLoader.AotPlugin.getDiagnostics');
613634
}
614635

615636
// Force a few compiler options to make sure we get the result we want.
@@ -619,13 +640,18 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
619640
sourceRoot: plugin.basePath
620641
});
621642

643+
time(timeLabel + '.ngcLoader.AotPlugin.transpile');
622644
const result = refactor.transpile(compilerOptions);
645+
timeEnd(timeLabel + '.ngcLoader.AotPlugin.transpile');
623646

647+
timeEnd(timeLabel + '.ngcLoader.AotPlugin');
624648
if (plugin.failedCompilation && plugin.compilerOptions.noEmitOnError) {
625649
// Return an empty string to prevent extra loader errors (missing imports etc).
626650
// Plugin errors were already pushed to the compilation errors.
651+
timeEnd(timeLabel);
627652
cb(null, '');
628653
} else {
654+
timeEnd(timeLabel);
629655
cb(null, result.outputText, result.sourceMap);
630656
}
631657
})
@@ -661,6 +687,7 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }, source: s
661687
const result = refactor.transpile(compilerOptions);
662688
// Webpack is going to take care of this.
663689
result.outputText = result.outputText.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '');
690+
timeEnd(timeLabel);
664691
cb(null, result.outputText, result.sourceMap);
665692
}
666693
}

0 commit comments

Comments
 (0)