refactor(compiler): generate less code for bindings to DOM elements

Detailed changes:
- remove `UNINITIALIZED`, initialize change detection fields with `undefined`.
  * we use `view.numberOfChecks === 0` now everywhere
    as indicator whether we are in the first change detection cycle
    (previously we used this only in a couple of places).
  * we keep the initialization itself as change detection get slower without it.
- remove passing around `throwOnChange` in various generated calls,
  and store it on the view as property instead.
- change generated code for bindings to DOM elements as follows:
  Before:
  ```
  var currVal_10 = self.context.bgColor;
  if (jit_checkBinding15(self.throwOnChange,self._expr_10,currVal_10)) {
    self.renderer.setElementStyle(self._el_0,'backgroundColor',((self.viewUtils.sanitizer.sanitize(jit_21,currVal_10) == null)? null: self.viewUtils.sanitizer.sanitize(jit_21,currVal_10).toString()));
    self._expr_10 = currVal_10;
  }
  var currVal_11 = jit_inlineInterpolate16(1,' ',self.context.data.value,' ');
  if (jit_checkBinding15(self.throwOnChange,self._expr_11,currVal_11)) {
    self.renderer.setText(self._text_1,currVal_11);
    self._expr_11 = currVal_11;
  }
  ```,
  After:
  ```
  var currVal_10 = self.context.bgColor;
  jit_checkRenderStyle14(self,self._el_0,'backgroundColor',null,self._expr_10,self._expr_10=currVal_10,false,jit_21);
  var currVal_11 = jit_inlineInterpolate15(1,' ',self.context.data.value,' ');
  jit_checkRenderText16(self,self._text_1,self._expr_11,self._expr_11=currVal_11,false);
  ```

Performance impact:
- None seen (checked against internal latency lab)

Part of #13651
This commit is contained in:
Tobias Bosch
2016-12-29 15:03:55 -08:00
committed by Igor Minar
parent 8ed92d75b0
commit db49d422f2
23 changed files with 346 additions and 286 deletions

View File

@ -21,28 +21,14 @@ export function createCheckBindingField(builder: ClassBuilder): CheckBindingFiel
const fieldExpr = createBindFieldExpr(bindingId);
// private is fine here as no child view will reference the cached value...
builder.fields.push(new o.ClassField(fieldExpr.name, null, [o.StmtModifier.Private]));
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name)
.set(o.importExpr(createIdentifier(Identifiers.UNINITIALIZED)))
.toStmt());
builder.ctorStmts.push(o.THIS_EXPR.prop(fieldExpr.name).set(o.literal(undefined)).toStmt());
return new CheckBindingField(fieldExpr, bindingId);
}
export function createCheckBindingStmt(
evalResult: ConvertPropertyBindingResult, fieldExpr: o.ReadPropExpr,
throwOnChangeVar: o.Expression, actions: o.Statement[]): o.Statement[] {
let condition: o.Expression = o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
throwOnChangeVar, fieldExpr, evalResult.currValExpr
]);
if (evalResult.forceUpdate) {
condition = evalResult.forceUpdate.or(condition);
}
return [
...evalResult.stmts, new o.IfStmt(condition, actions.concat([
<o.Statement>o.THIS_EXPR.prop(fieldExpr.name).set(evalResult.currValExpr).toStmt()
]))
];
}
function createBindFieldExpr(bindingId: string): o.ReadPropExpr {
return o.THIS_EXPR.prop(`_expr_${bindingId}`);
}
export function isFirstViewCheck(view: o.Expression): o.Expression {
return o.not(view.prop('numberOfChecks'));
}

View File

@ -13,69 +13,64 @@ import * as o from '../output/output_ast';
import {EMPTY_STATE as EMPTY_ANIMATION_STATE} from '../private_import_core';
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType} from '../template_parser/template_ast';
import {isFirstViewCheck} from './binding_util';
import {ConvertPropertyBindingResult} from './expression_converter';
import {createEnumExpression} from './identifier_util';
export function writeToRenderer(
view: o.Expression, boundProp: BoundElementPropertyAst, renderElement: o.Expression,
renderValue: o.Expression, logBindingUpdate: boolean,
export function createCheckRenderBindingStmt(
view: o.Expression, renderElement: o.Expression, boundProp: BoundElementPropertyAst,
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult,
securityContextExpression?: o.Expression): o.Statement[] {
const updateStmts: o.Statement[] = [];
const renderer = view.prop('renderer');
renderValue = sanitizedValue(view, boundProp, renderValue, securityContextExpression);
const checkStmts: o.Statement[] = [...evalResult.stmts];
const securityContext = calcSecurityContext(boundProp, securityContextExpression);
switch (boundProp.type) {
case PropertyBindingType.Property:
if (logBindingUpdate) {
updateStmts.push(
o.importExpr(createIdentifier(Identifiers.setBindingDebugInfo))
.callFn([renderer, renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
}
updateStmts.push(
renderer
.callMethod(
'setElementProperty', [renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderProperty))
.callFn([
view, renderElement, o.literal(boundProp.name), oldValue,
oldValue.set(evalResult.currValExpr),
evalResult.forceUpdate || o.literal(false), securityContext
])
.toStmt());
break;
case PropertyBindingType.Attribute:
renderValue =
renderValue.isBlank().conditional(o.NULL_EXPR, renderValue.callMethod('toString', []));
updateStmts.push(
renderer
.callMethod(
'setElementAttribute', [renderElement, o.literal(boundProp.name), renderValue])
.toStmt());
checkStmts.push(o.importExpr(createIdentifier(Identifiers.checkRenderAttribute))
.callFn([
view, renderElement, o.literal(boundProp.name), oldValue,
oldValue.set(evalResult.currValExpr),
evalResult.forceUpdate || o.literal(false), securityContext
])
.toStmt());
break;
case PropertyBindingType.Class:
updateStmts.push(
renderer
.callMethod(
'setElementClass', [renderElement, o.literal(boundProp.name), renderValue])
checkStmts.push(
o.importExpr(createIdentifier(Identifiers.checkRenderClass))
.callFn([
view, renderElement, o.literal(boundProp.name), oldValue,
oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false)
])
.toStmt());
break;
case PropertyBindingType.Style:
let strValue: o.Expression = renderValue.callMethod('toString', []);
if (isPresent(boundProp.unit)) {
strValue = strValue.plus(o.literal(boundProp.unit));
}
renderValue = renderValue.isBlank().conditional(o.NULL_EXPR, strValue);
updateStmts.push(
renderer
.callMethod(
'setElementStyle', [renderElement, o.literal(boundProp.name), renderValue])
checkStmts.push(
o.importExpr(createIdentifier(Identifiers.checkRenderStyle))
.callFn([
view, renderElement, o.literal(boundProp.name), o.literal(boundProp.unit), oldValue,
oldValue.set(evalResult.currValExpr), evalResult.forceUpdate || o.literal(false),
securityContext
])
.toStmt());
break;
case PropertyBindingType.Animation:
throw new Error('Illegal state: Should not come here!');
}
return updateStmts;
return checkStmts;
}
function sanitizedValue(
view: o.Expression, boundProp: BoundElementPropertyAst, renderValue: o.Expression,
securityContextExpression?: o.Expression): o.Expression {
function calcSecurityContext(
boundProp: BoundElementPropertyAst, securityContextExpression?: o.Expression): o.Expression {
if (boundProp.securityContext === SecurityContext.NONE) {
return renderValue; // No sanitization needed.
return o.NULL_EXPR; // No sanitization needed.
}
if (!boundProp.needsRuntimeSecurityContext) {
securityContextExpression =
@ -84,15 +79,13 @@ function sanitizedValue(
if (!securityContextExpression) {
throw new Error(`internal error, no SecurityContext given ${boundProp.name}`);
}
const ctx = view.prop('viewUtils').prop('sanitizer');
const args = [securityContextExpression, renderValue];
return ctx.callMethod('sanitize', args);
return securityContextExpression;
}
export function triggerAnimation(
export function createCheckAnimationBindingStmts(
view: o.Expression, componentView: o.Expression, boundProp: BoundElementPropertyAst,
boundOutputs: BoundEventAst[], eventListener: o.Expression, renderElement: o.Expression,
renderValue: o.Expression, lastRenderValue: o.Expression) {
oldValue: o.ReadPropExpr, evalResult: ConvertPropertyBindingResult) {
const detachStmts: o.Statement[] = [];
const updateStmts: o.Statement[] = [];
@ -104,22 +97,21 @@ export function triggerAnimation(
// it's important to normalize the void value as `void` explicitly
// so that the styles data can be obtained from the stringmap
const emptyStateValue = o.literal(EMPTY_ANIMATION_STATE);
const unitializedValue = o.importExpr(createIdentifier(Identifiers.UNINITIALIZED));
const animationTransitionVar = o.variable('animationTransition_' + animationName);
updateStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([
view, renderElement,
lastRenderValue.equals(unitializedValue).conditional(emptyStateValue, lastRenderValue),
renderValue.equals(unitializedValue).conditional(emptyStateValue, renderValue)
view, renderElement, isFirstViewCheck(view).conditional(emptyStateValue, oldValue),
evalResult.currValExpr
]))
.toDeclStmt());
updateStmts.push(oldValue.set(evalResult.currValExpr).toStmt());
detachStmts.push(
animationTransitionVar
.set(animationFnExpr.callFn([view, renderElement, lastRenderValue, emptyStateValue]))
.toDeclStmt());
detachStmts.push(animationTransitionVar
.set(animationFnExpr.callFn(
[view, renderElement, evalResult.currValExpr, emptyStateValue]))
.toDeclStmt());
const registerStmts: o.Statement[] = [];
const animationStartMethodExists = boundOutputs.find(
@ -151,5 +143,14 @@ export function triggerAnimation(
updateStmts.push(...registerStmts);
detachStmts.push(...registerStmts);
return {updateStmts, detachStmts};
const checkUpdateStmts: o.Statement[] = [
...evalResult.stmts,
new o.IfStmt(
o.importExpr(createIdentifier(Identifiers.checkBinding)).callFn([
view, oldValue, evalResult.currValExpr, evalResult.forceUpdate || o.literal(false)
]),
updateStmts)
];
const checkDetachStmts: o.Statement[] = [...evalResult.stmts, ...detachStmts];
return {checkUpdateStmts, checkDetachStmts};
}