perf(compiler-cli): don't emit template guards when child scope is empty (#38418)

For a template that contains for example `<span *ngIf="first"></span>`
there's no need to render the `NgIf` guard expression, as the child
scope does not have any type-checking statements, so any narrowing
effect of the guard is not applicable.

This seems like a minor improvement, however it reduces the number of
flow-node antecedents that TypeScript needs to keep into account for
such cases, resulting in an overall reduction of type-checking time.

PR Close #38418
This commit is contained in:
JoostK 2020-08-11 23:26:57 +02:00 committed by Andrew Scott
parent fb8f4b4d72
commit 1388c1761f
2 changed files with 32 additions and 5 deletions

View File

@ -304,8 +304,19 @@ class TcbTemplateBodyOp extends TcbOp {
// children, as well as tracks bindings within the template.
const tmplScope = Scope.forNodes(this.tcb, this.scope, this.template, guard);
// Render the template's `Scope` into a block.
let tmplBlock: ts.Statement = ts.createBlock(tmplScope.render());
// Render the template's `Scope` into its statements.
const statements = tmplScope.render();
if (statements.length === 0) {
// As an optimization, don't generate the scope's block if it has no statements. This is
// beneficial for templates that contain for example `<span *ngIf="first"></span>`, in which
// case there's no need to render the `NgIf` guard expression. This seems like a minor
// improvement, however it reduces the number of flow-node antecedents that TypeScript needs
// to keep into account for such cases, resulting in an overall reduction of
// type-checking time.
return null;
}
let tmplBlock: ts.Statement = ts.createBlock(statements);
if (guard !== null) {
// The scope has a guard that needs to be applied, so wrap the template block into an `if`
// statement containing the guard expression.

View File

@ -585,7 +585,7 @@ describe('type check blocks', () => {
type: 'invocation',
}]
}];
const TEMPLATE = `<div *ngIf="person"></div>`;
const TEMPLATE = `<div *ngIf="person">{{person.name}}</div>`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('if (NgIf.ngTemplateGuard_ngIf(_t1, ((ctx).person)))');
});
@ -601,10 +601,26 @@ describe('type check blocks', () => {
type: 'binding',
}]
}];
const TEMPLATE = `<div *ngIf="person !== null"></div>`;
const TEMPLATE = `<div *ngIf="person !== null">{{person.name}}</div>`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('if ((((ctx).person)) !== (null))');
});
it('should not emit guards when the child scope is empty', () => {
const DIRECTIVES: TestDeclaration[] = [{
type: 'directive',
name: 'NgIf',
selector: '[ngIf]',
inputs: {'ngIf': 'ngIf'},
ngTemplateGuards: [{
inputName: 'ngIf',
type: 'invocation',
}]
}];
const TEMPLATE = `<div *ngIf="person">static</div>`;
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).not.toContain('NgIf.ngTemplateGuard_ngIf');
});
});
describe('outputs', () => {
@ -681,7 +697,7 @@ describe('type check blocks', () => {
};
describe('config.applyTemplateContextGuards', () => {
const TEMPLATE = `<div *dir></div>`;
const TEMPLATE = `<div *dir>{{ value }}</div>`;
const GUARD_APPLIED = 'if (Dir.ngTemplateContextGuard(';
it('should apply template context guards when enabled', () => {