From ef9cb6a03464a06572ba86ad70965ac9f531c3cc Mon Sep 17 00:00:00 2001 From: Olivier Combe Date: Tue, 25 Jun 2019 17:14:07 +0200 Subject: [PATCH] perf(ivy): chain multiple `i18nExp` calls (#31258) Implement function chaining for `i18nExp` to reduce the output size. FW-1391 #resolve PR Close #31258 --- .../compliance/r3_view_compiler_i18n_spec.ts | 67 ++++++------------- .../compiler/src/render3/view/template.ts | 54 ++++++++------- packages/core/src/render3/i18n.ts | 7 +- tools/public_api_guard/core/core.d.ts | 2 +- 4 files changed, 54 insertions(+), 76 deletions(-) diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts index f6538077e4..c4d612240e 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts @@ -464,14 +464,10 @@ describe('i18n support in the view compiler', () => { $r3$.ɵɵelementEnd(); } if (rf & 2) { - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 6, ctx.valueA)); - $r3$.ɵɵi18nExp(ctx.valueB); + $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 6, ctx.valueA))(ctx.valueB); $r3$.ɵɵi18nApply(2); $r3$.ɵɵselect(3); - $r3$.ɵɵi18nExp(ctx.valueA); - $r3$.ɵɵi18nExp(ctx.valueB); - $r3$.ɵɵi18nExp(ctx.valueA + ctx.valueB); - $r3$.ɵɵi18nExp(ctx.valueC); + $r3$.ɵɵi18nExp(ctx.valueA)(ctx.valueB)(ctx.valueA + ctx.valueB)(ctx.valueC); $r3$.ɵɵi18nApply(4); } } @@ -690,14 +686,10 @@ describe('i18n support in the view compiler', () => { $r3$.ɵɵelementEnd(); } if (rf & 2) { - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 6, ctx.valueA)); - $r3$.ɵɵi18nExp(ctx.valueB); + $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(1, 6, ctx.valueA))(ctx.valueB); $r3$.ɵɵi18nApply(2); $r3$.ɵɵselect(3); - $r3$.ɵɵi18nExp(ctx.valueA); - $r3$.ɵɵi18nExp(ctx.valueB); - $r3$.ɵɵi18nExp(ctx.valueA + ctx.valueB); - $r3$.ɵɵi18nExp(ctx.valueC); + $r3$.ɵɵi18nExp(ctx.valueA)(ctx.valueB)(ctx.valueA + ctx.valueB)(ctx.valueC); $r3$.ɵɵi18nApply(4); } } @@ -1054,8 +1046,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(2, 2, ctx.valueA)); - $r3$.ɵɵi18nExp(ctx.valueA == null ? null : ctx.valueA.a == null ? null : ctx.valueA.a.b); + $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(2, 2, ctx.valueA))(ctx.valueA == null ? null : ctx.valueA.a == null ? null : ctx.valueA.a.b); $r3$.ɵɵi18nApply(1); } } @@ -1224,8 +1215,7 @@ describe('i18n support in the view compiler', () => { $r3$.ɵɵi18nExp(ctx.one); $r3$.ɵɵi18nApply(1); $r3$.ɵɵselect(4); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(5, 3, ctx.two)); - $r3$.ɵɵi18nExp(ctx.nestedInBlockTwo); + $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(5, 3, ctx.two))(ctx.nestedInBlockTwo); $r3$.ɵɵi18nApply(4); } } @@ -1335,8 +1325,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(2); - $r3$.ɵɵi18nExp(ctx.valueB); - $r3$.ɵɵi18nExp(ctx.valueC); + $r3$.ɵɵi18nExp(ctx.valueB)(ctx.valueC); $r3$.ɵɵi18nApply(3); $r3$.ɵɵi18nExp(ctx.valueA); $r3$.ɵɵi18nApply(1); @@ -1402,8 +1391,7 @@ describe('i18n support in the view compiler', () => { if (rf & 2) { const $ctx_r0$ = $r3$.ɵɵnextContext(); $r3$.ɵɵselect(2); - $r3$.ɵɵi18nExp($ctx_r0$.valueA); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(4, 2, $ctx_r0$.valueB)); + $r3$.ɵɵi18nExp($ctx_r0$.valueA)($r3$.ɵɵpipeBind1(4, 2, $ctx_r0$.valueB)); $r3$.ɵɵi18nApply(2); } } @@ -1527,8 +1515,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $ctx_r2$ = $r3$.ɵɵnextContext(2); - $r3$.ɵɵi18nExp($ctx_r2$.valueC); - $r3$.ɵɵi18nExp($ctx_r2$.valueD); + $r3$.ɵɵi18nExp($ctx_r2$.valueC)($ctx_r2$.valueD); $r3$.ɵɵi18nApply(0); } } @@ -1547,8 +1534,7 @@ describe('i18n support in the view compiler', () => { const $ctx_r0$ = $r3$.ɵɵnextContext(); $r3$.ɵɵselect(4); $r3$.ɵɵproperty("ngIf", $ctx_r0$.exists); - $r3$.ɵɵi18nExp($ctx_r0$.valueA); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(3, 3, $ctx_r0$.valueB)); + $r3$.ɵɵi18nExp($ctx_r0$.valueA)($r3$.ɵɵpipeBind1(3, 3, $ctx_r0$.valueB)); $r3$.ɵɵi18nApply(0); } } @@ -1596,8 +1582,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $ctx_r1$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵi18nExp($ctx_r1$.valueE + $ctx_r1$.valueF); - $r3$.ɵɵi18nExp($r3$.ɵɵpipeBind1(3, 2, $ctx_r1$.valueG)); + $r3$.ɵɵi18nExp($ctx_r1$.valueE + $ctx_r1$.valueF)($r3$.ɵɵpipeBind1(3, 2, $ctx_r1$.valueG)); $r3$.ɵɵi18nApply(0); } } @@ -2554,8 +2539,7 @@ describe('i18n support in the view compiler', () => { if (rf & 2) { const $ctx_r1$ = $r3$.ɵɵnextContext(); $r3$.ɵɵselect(2); - $r3$.ɵɵi18nExp($ctx_r1$.count); - $r3$.ɵɵi18nExp($ctx_r1$.count); + $r3$.ɵɵi18nExp($ctx_r1$.count)($ctx_r1$.count); $r3$.ɵɵi18nApply(2); } } @@ -2615,8 +2599,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵi18nExp(ctx.age); - $r3$.ɵɵi18nExp(ctx.other); + $r3$.ɵɵi18nExp(ctx.age)(ctx.other); $r3$.ɵɵi18nApply(1); } } @@ -2742,8 +2725,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nExp(ctx.ageA + ctx.ageB + ctx.ageC); + $r3$.ɵɵi18nExp(ctx.gender)(ctx.ageA + ctx.ageB + ctx.ageC); $r3$.ɵɵi18nApply(1); } } @@ -2808,8 +2790,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nExp(ctx.age); + $r3$.ɵɵi18nExp(ctx.gender)(ctx.age); $r3$.ɵɵi18nApply(1); } } @@ -2914,8 +2895,7 @@ describe('i18n support in the view compiler', () => { if (rf & 2) { $r3$.ɵɵselect(3); $r3$.ɵɵproperty("ngIf", ctx.visible); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nExp(ctx.gender); + $r3$.ɵɵi18nExp(ctx.gender)(ctx.gender); $r3$.ɵɵi18nApply(1); } } @@ -2962,8 +2942,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵi18nExp(ctx.age); - $r3$.ɵɵi18nExp(ctx.gender); + $r3$.ɵɵi18nExp(ctx.age)(ctx.gender); $r3$.ɵɵi18nApply(1); } } @@ -3132,8 +3111,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { const $ctx_r0$ = $r3$.ɵɵnextContext(); - $r3$.ɵɵi18nExp($ctx_r0$.age); - $r3$.ɵɵi18nExp($ctx_r0$.otherAge); + $r3$.ɵɵi18nExp($ctx_r0$.age)($ctx_r0$.otherAge); $r3$.ɵɵi18nApply(0); } } @@ -3151,9 +3129,7 @@ describe('i18n support in the view compiler', () => { if (rf & 2) { $r3$.ɵɵselect(2); $r3$.ɵɵproperty("ngIf", ctx.ageVisible); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nExp(ctx.weight); - $r3$.ɵɵi18nExp(ctx.height); + $r3$.ɵɵi18nExp(ctx.gender)(ctx.weight)(ctx.height); $r3$.ɵɵi18nApply(1); } } @@ -3204,10 +3180,7 @@ describe('i18n support in the view compiler', () => { } if (rf & 2) { $r3$.ɵɵselect(1); - $r3$.ɵɵi18nExp(ctx.gender); - $r3$.ɵɵi18nExp(ctx.weight); - $r3$.ɵɵi18nExp(ctx.height); - $r3$.ɵɵi18nExp(ctx.age); + $r3$.ɵɵi18nExp(ctx.gender)(ctx.weight)(ctx.height)(ctx.age); $r3$.ɵɵi18nApply(1); } } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index e93726cd91..bb8354335f 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -456,10 +456,14 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // setup accumulated bindings const {index, bindings} = this.i18n; if (bindings.size) { + const chainBindings: ChainableBindingInstruction[] = []; bindings.forEach(binding => { - this.updateInstruction( - index, span, R3.i18nExp, () => [this.convertPropertyBinding(binding)]); + chainBindings.push({ + sourceSpan: span, + value: () => this.convertPropertyBinding(binding) + }); }); + this.updateInstructionChain(index, R3.i18nExp, chainBindings); this.updateInstruction(index, span, R3.i18nApply, [o.literal(index)]); } if (!selfClosing) { @@ -641,6 +645,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (i18nAttrs.length) { let hasBindings: boolean = false; const i18nAttrArgs: o.Expression[] = []; + const bindings: ChainableBindingInstruction[] = []; i18nAttrs.forEach(attr => { const message = attr.i18n !as i18n.Message; if (attr instanceof t.TextAttribute) { @@ -654,13 +659,17 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver i18nAttrArgs.push(o.literal(attr.name), this.i18nTranslate(message, params)); converted.expressions.forEach(expression => { hasBindings = true; - this.updateInstruction( - elementIndex, element.sourceSpan, R3.i18nExp, - () => [this.convertExpressionBinding(expression)]); + bindings.push({ + sourceSpan: element.sourceSpan, + value: () => this.convertExpressionBinding(expression) + }); }); } } }); + if (bindings.length) { + this.updateInstructionChain(elementIndex, R3.i18nExp, bindings); + } if (i18nAttrArgs.length) { const index: o.Expression = o.literal(this.allocateDataSlot()); const args = this.constantPool.getConstLiteral(o.literalArr(i18nAttrArgs), true); @@ -733,7 +742,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver propertyBindings.push({ name: prepareSyntheticPropertyName(input.name), - input, + sourceSpan: input.sourceSpan, value: () => hasValue ? this.convertPropertyBinding(value) : emptyValueBindInstruction }); } else { @@ -771,7 +780,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // [prop]="value" // Collect all the properties so that we can chain into a single function at the end. propertyBindings.push( - {name: attrName, input, value: () => this.convertPropertyBinding(value), params}); + {name: attrName, sourceSpan: input.sourceSpan, value: () => this.convertPropertyBinding(value), params}); } } else if (inputType === BindingType.Attribute) { if (value instanceof Interpolation && getInterpolationArgsLength(value) > 1) { @@ -785,7 +794,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Collect the attribute bindings so that they can be chained at the end. attributeBindings.push({ name: attrName, - input, + sourceSpan: input.sourceSpan, value: () => this.convertPropertyBinding(boundValue), params }); } @@ -830,18 +839,6 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver } } - /** - * Adds an update instruction for a bound property or attribute, such as `[prop]="value"` or - * `[attr.title]="value"` - */ - boundUpdateInstruction( - instruction: o.ExternalReference, elementIndex: number, attrName: string, - input: t.BoundAttribute, value: any, params: any[]) { - this.updateInstruction(elementIndex, input.sourceSpan, instruction, () => { - return [o.literal(attrName), this.convertPropertyBinding(value), ...params]; - }); - } - /** * Adds an update instruction for an interpolated property or attribute, such as * `prop="{{value}}"` or `attr.title="{{value}}"` @@ -1042,7 +1039,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (value !== undefined) { this.allocateBindingSlots(value); propertyBindings.push( - {name: input.name, input, value: () => this.convertPropertyBinding(value)}); + {name: input.name, sourceSpan: input.sourceSpan, value: () => this.convertPropertyBinding(value)}); } } }); @@ -1093,12 +1090,17 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver private updateInstructionChain( nodeIndex: number, reference: o.ExternalReference, bindings: ChainableBindingInstruction[]) { - const span = bindings.length ? bindings[0].input.sourceSpan : null; + const span = bindings.length ? bindings[0].sourceSpan : null; this.addSelectInstructionIfNecessary(nodeIndex, span); this._updateCodeFns.push(() => { - const calls = bindings.map( - property => [o.literal(property.name), property.value(), ...(property.params || [])]); + const calls = bindings.map(property => { + const fnParams = [property.value(), ...(property.params || [])]; + if (property.name) { + fnParams.unshift(o.literal(property.name)); + } + return fnParams; + }); return chainedInstruction(span, reference, calls).toStmt(); }); @@ -2027,8 +2029,8 @@ function hasTextChildrenOnly(children: t.Node[]): boolean { } interface ChainableBindingInstruction { - name: string; - input: t.BoundAttribute; + name?: string; + sourceSpan: ParseSourceSpan|null; value: () => o.Expression; params?: any[]; } diff --git a/packages/core/src/render3/i18n.ts b/packages/core/src/render3/i18n.ts index 96ed5ffc13..5467796939 100644 --- a/packages/core/src/render3/i18n.ts +++ b/packages/core/src/render3/i18n.ts @@ -16,7 +16,7 @@ import {assertDataInRange, assertDefined, assertEqual, assertGreaterThan} from ' import {attachPatchData} from './context_discovery'; import {bind, setDelayProjection, ɵɵload} from './instructions/all'; import {attachI18nOpCodesDebug} from './instructions/lview_debug'; -import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, textBindingInternal} from './instructions/shared'; +import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, textBindingInternal, TsickleIssue1009} from './instructions/shared'; import {LContainer, NATIVE} from './interfaces/container'; import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, IcuType, TI18n, TIcu} from './interfaces/i18n'; import {TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from './interfaces/node'; @@ -1012,16 +1012,19 @@ let shiftsCounter = 0; * update the translated nodes. * * @param value The binding's value + * @returns This function returns itself so that it may be chained + * (e.g. `i18nExp(ctx.name)(ctx.title)`) * * @codeGenApi */ -export function ɵɵi18nExp(value: T): void { +export function ɵɵi18nExp(value: T): TsickleIssue1009 { const lView = getLView(); const expression = bind(lView, value); if (expression !== NO_CHANGE) { changeMask = changeMask | (1 << shiftsCounter); } shiftsCounter++; + return ɵɵi18nExp; } /** diff --git a/tools/public_api_guard/core/core.d.ts b/tools/public_api_guard/core/core.d.ts index 5048d8a5f6..718b609ede 100644 --- a/tools/public_api_guard/core/core.d.ts +++ b/tools/public_api_guard/core/core.d.ts @@ -851,7 +851,7 @@ export declare function ɵɵi18nAttributes(index: number, values: string[]): voi export declare function ɵɵi18nEnd(): void; -export declare function ɵɵi18nExp(value: T): void; +export declare function ɵɵi18nExp(value: T): TsickleIssue1009; /** @deprecated */ export declare function ɵɵi18nLocalize(input: string, placeholders?: {