From 1388c1761f1a474de852c50f56e056e48e821bb0 Mon Sep 17 00:00:00 2001 From: JoostK Date: Tue, 11 Aug 2020 23:26:57 +0200 Subject: [PATCH] perf(compiler-cli): don't emit template guards when child scope is empty (#38418) For a template that contains for example `` 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 --- .../ngtsc/typecheck/src/type_check_block.ts | 15 +++++++++++-- .../typecheck/test/type_check_block_spec.ts | 22 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts index 5a5535057c..5576556a71 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/type_check_block.ts @@ -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 ``, 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. diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts index ab7c6e73aa..9dafff8bde 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts @@ -585,7 +585,7 @@ describe('type check blocks', () => { type: 'invocation', }] }]; - const TEMPLATE = `
`; + const TEMPLATE = `
{{person.name}}
`; 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 = `
`; + const TEMPLATE = `
{{person.name}}
`; 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 = `
static
`; + 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 = `
`; + const TEMPLATE = `
{{ value }}
`; const GUARD_APPLIED = 'if (Dir.ngTemplateContextGuard('; it('should apply template context guards when enabled', () => {