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:
parent
fb8f4b4d72
commit
1388c1761f
|
@ -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.
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
Loading…
Reference in New Issue