From b415010222c4616a45cf06fb85221f2452aa05e4 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Fri, 25 May 2018 16:23:00 -0700 Subject: [PATCH] feat(ivy): add element instruction (#23899) Adds a simplified element instruction that can be used if an element has no children. PR Close #23899 --- .../compiler/src/render3/r3_identifiers.ts | 4 +- .../compiler/src/render3/view/template.ts | 61 +++++++++++-------- .../render3/r3_compiler_compliance_spec.ts | 36 ++++------- .../render3/r3_view_compiler_binding_spec.ts | 6 +- .../render3/r3_view_compiler_i18n_spec.ts | 3 +- .../render3/r3_view_compiler_template_spec.ts | 39 ++++++++++++ .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/index.ts | 1 + packages/core/src/render3/instructions.ts | 9 +++ .../core/src/render3/node_selector_matcher.ts | 1 + .../component_directives_spec.ts | 15 ++--- .../render3/compiler_canonical/query_spec.ts | 3 +- .../core/test/render3/instructions_spec.ts | 16 ++--- 13 files changed, 117 insertions(+), 78 deletions(-) diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index b9bf96b0d3..a37e2a7bdc 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -17,10 +17,12 @@ export class Identifiers { static PATCH_DEPS = 'patchedDeps'; /* Instructions */ - static createElement: o.ExternalReference = {name: 'ɵE', moduleName: CORE}; + static elementStart: o.ExternalReference = {name: 'ɵE', moduleName: CORE}; static elementEnd: o.ExternalReference = {name: 'ɵe', moduleName: CORE}; + static element: o.ExternalReference = {name: 'ɵEe', moduleName: CORE}; + static elementProperty: o.ExternalReference = {name: 'ɵp', moduleName: CORE}; static elementAttribute: o.ExternalReference = {name: 'ɵa', moduleName: CORE}; diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index b0d21a9a11..607703732d 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -314,32 +314,42 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (i18nMessages.length > 0) { this._creationCode.push(...i18nMessages); } - this.instruction( - this._creationCode, element.sourceSpan, R3.createElement, ...trimTrailingNulls(parameters)); + + let isSelfClosingElement = element.outputs.length === 0 && element.children.length === 0; + const implicit = o.variable(CONTEXT_NAME); - // Generate Listeners (outputs) - element.outputs.forEach((outputAst: t.BoundEvent) => { - const elName = sanitizeIdentifier(element.name); - const evName = sanitizeIdentifier(outputAst.name); - const functionName = `${this.templateName}_${elName}_${evName}_listener`; - const localVars: o.Statement[] = []; - const bindingScope = - this._bindingScope.nestedScope((lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => { - localVars.push( - lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); - }); - const bindingExpr = convertActionBinding( - bindingScope, implicit, outputAst.handler, 'b', () => error('Unexpected interpolation')); - const handler = o.fn( - [new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts], - o.INFERRED_TYPE, null, functionName); + if (isSelfClosingElement) { this.instruction( - this._creationCode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), - handler); - }); + this._creationCode, element.sourceSpan, R3.element, ...trimTrailingNulls(parameters)); + } else { + this.instruction( + this._creationCode, element.sourceSpan, R3.elementStart, + ...trimTrailingNulls(parameters)); + // Generate Listeners (outputs) + element.outputs.forEach((outputAst: t.BoundEvent) => { + const elName = sanitizeIdentifier(element.name); + const evName = sanitizeIdentifier(outputAst.name); + const functionName = `${this.templateName}_${elName}_${evName}_listener`; + const localVars: o.Statement[] = []; + const bindingScope = + this._bindingScope.nestedScope((lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => { + localVars.push( + lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final])); + }); + const bindingExpr = convertActionBinding( + bindingScope, implicit, outputAst.handler, 'b', + () => error('Unexpected interpolation')); + const handler = o.fn( + [new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts], + o.INFERRED_TYPE, null, functionName); + this.instruction( + this._creationCode, outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), + handler); + }); + } // Generate element input bindings element.inputs.forEach((input: t.BoundAttribute) => { @@ -367,10 +377,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver t.visitAll(this, element.children); } - // Finish element construction mode. - this.instruction( - this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd); - + if (!isSelfClosingElement) { + // Finish element construction mode. + this.instruction( + this._creationCode, element.endSourceSpan || element.sourceSpan, R3.elementEnd); + } // Restore the state before exiting this node this._inI18nSection = wasInI18nSection; } diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index a60ccb687c..74045c062b 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -90,8 +90,7 @@ describe('compiler compliance', () => { const template = ` template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'div'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'div'); } if (rf & 2) { $r3$.ɵp(0, 'id', $r3$.ɵb(ctx.id)); @@ -135,9 +134,8 @@ describe('compiler compliance', () => { const template = ` template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'div'); + $r3$.ɵEe(0, 'div'); $r3$.ɵPp(1,'pipe'); - $r3$.ɵe(); $r3$.ɵrS(10); } if (rf & 2) { @@ -181,8 +179,7 @@ describe('compiler compliance', () => { const template = ` template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'div'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'div'); } if (rf & 2) { $r3$.ɵkn(0, 'error', $r3$.ɵb(ctx.error)); @@ -254,8 +251,7 @@ describe('compiler compliance', () => { factory: function MyComponent_Factory() { return new MyComponent(); }, template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'child', $c1$); - $r3$.ɵe(); + $r3$.ɵEe(0, 'child', $c1$); $r3$.ɵT(1, '!'); } }, @@ -461,8 +457,7 @@ describe('compiler compliance', () => { factory: function MyApp_Factory() { return new MyApp(); }, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { if (rf & 1) { - $r3$.ɵE(0, 'my-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'my-comp'); $r3$.ɵrS(2); } if (rf & 2) { @@ -541,8 +536,7 @@ describe('compiler compliance', () => { factory: function MyApp_Factory() { return new MyApp(); }, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { if (rf & 1) { - $r3$.ɵE(0, 'my-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'my-comp'); $r3$.ɵrS(10); } if (rf & 2) { @@ -603,8 +597,7 @@ describe('compiler compliance', () => { factory: function MyApp_Factory() { return new MyApp(); }, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { if (rf & 1) { - $r3$.ɵE(0, 'object-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'object-comp'); $r3$.ɵrS(2); } if (rf & 2) { @@ -669,8 +662,7 @@ describe('compiler compliance', () => { factory: function MyApp_Factory() { return new MyApp(); }, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { if (rf & 1) { - $r3$.ɵE(0, 'nested-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'nested-comp'); $r3$.ɵrS(7); } if (rf & 2) { @@ -814,8 +806,7 @@ describe('compiler compliance', () => { var $tmp$: $any$; if (rf & 1) { $r3$.ɵQ(0, SomeDirective, true); - $r3$.ɵE(1, 'div', $e0_attrs$); - $r3$.ɵe(); + $r3$.ɵEe(1, 'div', $e0_attrs$); } if (rf & 2) { ($r3$.ɵqR(($tmp$ = $r3$.ɵld(0))) && (ctx.someDir = $tmp$.first)); @@ -1009,8 +1000,7 @@ describe('compiler compliance', () => { factory: function MyComponent_Factory() { return new MyComponent(); }, template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'input', null, $c1$); - $r3$.ɵe(); + $r3$.ɵEe(0, 'input', null, $c1$); $r3$.ɵT(2); } const $user$ = $r3$.ɵld(1); @@ -1089,10 +1079,8 @@ describe('compiler compliance', () => { factory: function SimpleLayout_Factory() { return new SimpleLayout(); }, template: function SimpleLayout_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'lifecycle-comp'); - $r3$.ɵe(); - $r3$.ɵE(1, 'lifecycle-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'lifecycle-comp'); + $r3$.ɵEe(1, 'lifecycle-comp'); } if (rf & 2) { $r3$.ɵp(0, 'name', $r3$.ɵb(ctx.name1)); diff --git a/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts b/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts index 8b0a9edcbe..a9f4f3672e 100644 --- a/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_binding_spec.ts @@ -75,8 +75,7 @@ describe('compiler compliance: bindings', () => { const template = ` template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ if (rf & 1) { - $i0$.ɵE(0, 'a'); - $i0$.ɵe(); + $i0$.ɵEe(0, 'a'); } if (rf & 2) { $i0$.ɵp(0, 'title', $i0$.ɵb($ctx$.title)); @@ -108,8 +107,7 @@ describe('compiler compliance: bindings', () => { const template = ` template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ if (rf & 1) { - $i0$.ɵE(0, 'a'); - $i0$.ɵe(); + $i0$.ɵEe(0, 'a'); } if (rf & 2) { $i0$.ɵp(0, 'title', $i0$.ɵi1('Hello ', $ctx$.name, '')); diff --git a/packages/compiler/test/render3/r3_view_compiler_i18n_spec.ts b/packages/compiler/test/render3/r3_view_compiler_i18n_spec.ts index 3839df2615..1065b3acb9 100644 --- a/packages/compiler/test/render3/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_i18n_spec.ts @@ -148,8 +148,7 @@ describe('i18n support in the view compiler', () => { … template: function MyComponent_Template(rf: IDENT, ctx: IDENT) { if (rf & 1) { - $r3$.ɵE(0, 'div', $c1$); - $r3$.ɵe(); + $r3$.ɵEe(0, 'div', $c1$); } } `; diff --git a/packages/compiler/test/render3/r3_view_compiler_template_spec.ts b/packages/compiler/test/render3/r3_view_compiler_template_spec.ts index 12041faec4..86d96f9850 100644 --- a/packages/compiler/test/render3/r3_view_compiler_template_spec.ts +++ b/packages/compiler/test/render3/r3_view_compiler_template_spec.ts @@ -109,4 +109,43 @@ describe('compiler compliance: template', () => { expectEmit(result.source, template, 'Incorrect template'); }); + // tslint:disable-next-line:ban + describe('element with no children', () => { + it('should just use the element instruction', () => { + const files = { + app: { + 'spec.ts': ` + import {Component, NgModule} from '@angular/core'; + import {CommonModule} from '@angular/common'; + + @Component({ + selector: 'my-component', + template: \` +
\` + }) + export class MyComponent { + } + + @NgModule({declarations: [MyComponent], imports: [CommonModule]}) + export class MyModule {} + ` + } + }; + + const template = ` + // ... + template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){ + if (rf & 1) { + $i0$.ɵEe(0,'div'); + } + }`; + + debugger; + + const result = compile(files, angularFiles); + + expectEmit(result.source, template, 'Incorrect template'); + }); + }); + }); diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index ff8c7449b4..edcd9abbf5 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -28,6 +28,7 @@ export { NC as ɵNC, C as ɵC, E as ɵE, + Ee as ɵEe, L as ɵL, T as ɵT, V as ɵV, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 0e8e091bc3..19929ac932 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -50,6 +50,7 @@ export { elementEnd as e, elementProperty as p, elementStart as E, + element as Ee, elementStyle as s, elementStyleNamed as sn, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index ef9e7d9540..30fcc1fe80 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1032,6 +1032,15 @@ export function elementEnd() { queueLifecycleHooks(previousOrParentNode.tNode.flags, currentView); } +/** Marks the beginning and end of an element in one call. */ +export function element( + index: number, name: string, attrs?: TAttributes | null | undefined, + localRefs?: string[] | null | undefined): RElement { + const relement = elementStart(index, name, attrs, localRefs); + elementEnd(); + return relement; +} + /** * Updates the value of removes an attribute on an Element. * diff --git a/packages/core/src/render3/node_selector_matcher.ts b/packages/core/src/render3/node_selector_matcher.ts index 5f6f8b5ed0..efba0295ee 100644 --- a/packages/core/src/render3/node_selector_matcher.ts +++ b/packages/core/src/render3/node_selector_matcher.ts @@ -12,6 +12,7 @@ import {assertNotNull} from './assert'; import {AttributeMarker, TAttributes, TNode, unusedValueExportToPlacateAjd as unused1} from './interfaces/node'; import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection'; + const unusedValueToPlacateAjd = unused1 + unused2; function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string): boolean { diff --git a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts index 7c1e6a7845..f928655a46 100644 --- a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts +++ b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts @@ -69,8 +69,7 @@ describe('components & directives', () => { factory: () => new MyComponent(), template: function(rf: $RenderFlags$, ctx: $MyComponent$) { if (rf & 1) { - $r3$.ɵE(0, 'child', $e0_attrs$); - $r3$.ɵe(); + $r3$.ɵEe(0, 'child', $e0_attrs$); $r3$.ɵT(1, '!'); } } @@ -455,8 +454,7 @@ describe('components & directives', () => { factory: function MyApp_Factory() { return new MyApp(); }, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { if (rf & 1) { - $r3$.ɵE(0, 'my-array-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'my-array-comp'); } if (rf & 2) { $r3$.ɵp(0, 'names', rf & 1 ? $e0_arr$ : $r3$.ɵNC); @@ -500,8 +498,7 @@ describe('components & directives', () => { factory: function MyApp_Factory() { return new MyApp(); }, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { if (rf & 1) { - $r3$.ɵE(0, 'my-array-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'my-array-comp'); $r3$.ɵrS(1); } if (rf & 2) { @@ -606,8 +603,7 @@ describe('components & directives', () => { factory: function MyApp_Factory() { return new MyApp(); }, template: function MyApp_Template(rf: $RenderFlags$, ctx: $MyApp$) { if (rf & 1) { - $r3$.ɵE(0, 'my-array-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'my-array-comp'); $r3$.ɵrS(2); } if (rf & 2) { @@ -717,8 +713,7 @@ describe('components & directives', () => { factory: function MyApp_Factory() { return new MyApp(); }, template: function MyApp_Template(rf: $RenderFlags$, c: $any$) { if (rf & 1) { - $r3$.ɵE(0, 'my-comp'); - $r3$.ɵe(); + $r3$.ɵEe(0, 'my-comp'); $r3$.ɵrS(10); } if (rf & 2) { diff --git a/packages/core/test/render3/compiler_canonical/query_spec.ts b/packages/core/test/render3/compiler_canonical/query_spec.ts index d3e19413e3..a5d51d0391 100644 --- a/packages/core/test/render3/compiler_canonical/query_spec.ts +++ b/packages/core/test/render3/compiler_canonical/query_spec.ts @@ -58,8 +58,7 @@ describe('queries', () => { if (rf & 1) { $r3$.ɵQ(0, SomeDirective, false); $r3$.ɵQ(1, SomeDirective, false); - $r3$.ɵE(2, 'div', $e1_attrs$); - $r3$.ɵe(); + $r3$.ɵEe(2, 'div', $e1_attrs$); } if (rf & 2) { $r3$.ɵqR($tmp$ = $r3$.ɵld>(0)) && (ctx.someDir = $tmp$.first); diff --git a/packages/core/test/render3/instructions_spec.ts b/packages/core/test/render3/instructions_spec.ts index 7056457990..237744cee4 100644 --- a/packages/core/test/render3/instructions_spec.ts +++ b/packages/core/test/render3/instructions_spec.ts @@ -10,14 +10,14 @@ import {NgForOfContext} from '@angular/common'; import {RenderFlags, directiveInject} from '../../src/render3'; import {defineComponent} from '../../src/render3/definition'; -import {bind, container, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, interpolation1, renderTemplate, setHtmlNS, setSvgNS, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, element, elementAttribute, elementClass, elementEnd, elementProperty, elementStart, elementStyle, elementStyleNamed, interpolation1, renderTemplate, setHtmlNS, setSvgNS, text, textBinding} from '../../src/render3/instructions'; import {LElementNode, LNode, NS} from '../../src/render3/interfaces/node'; import {RElement, domRendererFactory3} from '../../src/render3/interfaces/renderer'; import {TrustedString, bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl} from '../../src/sanitization/sanitization'; import {Sanitizer, SecurityContext} from '../../src/sanitization/security'; import {NgForOf} from './common_with_def'; -import {ComponentFixture, TemplateFixture} from './render_util'; +import {ComponentFixture, TemplateFixture, toHtml} from './render_util'; describe('instructions', () => { function createAnchor() { @@ -94,13 +94,12 @@ describe('instructions', () => { it('should use sanitizer function even on elements with namespaced attributes', () => { const t = new TemplateFixture(() => { - elementStart(0, 'div', [ + element(0, 'div', [ NS.FULL, 'http://www.example.com/2004/test', 'whatever', 'abc', ]); - elementEnd(); }); t.update(() => elementAttribute(0, 'title', 'javascript:true', sanitizeUrl)); @@ -451,8 +450,7 @@ describe('instructions', () => { 'title', 'abc', ]); - elementStart(2, 'circle', ['cx', '200', 'cy', '150', 'fill', '#0000ff']); - elementEnd(); + element(2, 'circle', ['cx', '200', 'cy', '150', 'fill', '#0000ff']); elementEnd(); setHtmlNS(); elementEnd(); @@ -470,7 +468,7 @@ describe('instructions', () => { it('should set an attribute with a namespace', () => { const t = new TemplateFixture(() => { - elementStart(0, 'div', [ + element(0, 'div', [ 'id', 'container', // test:title="abc" @@ -479,7 +477,6 @@ describe('instructions', () => { 'title', 'abc', ]); - elementEnd(); }); const standardHTML = '
'; @@ -490,7 +487,7 @@ describe('instructions', () => { it('should set attributes including more than one namespaced attribute', () => { const t = new TemplateFixture(() => { - elementStart(0, 'div', [ + element(0, 'div', [ 'id', 'container', @@ -516,7 +513,6 @@ describe('instructions', () => { 'shazbot', 'wocka wocka', ]); - elementEnd(); }); const standardHTML =