refactor(ivy): remove def.attributes in favor of the elementHostAttrs instruction (#28089)

Up until this point, all static attribute values (things like `title` and `id`)
defined within the `host` are of a Component/Directive definition were
generated into a `def.attributes` array and then processed at runtime.
This design decision does not lend itself well to tree-shaking and is
inconsistent with other static values such as styles and classes.

This fix ensures that all static attribute values (attributes, classes,
and styles) that exist within a host definition for components and
directives are all assigned via the `elementHostAttrs` instruction.

```
// before
defineDirective({
  ...
  attributes: ['title', 'my title']
  ...
})

//now
defineDirective({
  ...
  hostBindings: function() {
    if (create) {
      elementHostAttrs(..., ['title', 'my-title']);
    }
    ...
  }
  ...
})
```

PR Close #28089
This commit is contained in:
Matias Niemelä
2019-01-11 14:03:37 -08:00
committed by Andrew Kushnir
parent e62eeed7d4
commit 693045165c
14 changed files with 226 additions and 127 deletions

View File

@ -29,7 +29,7 @@ import {Render3ParseResult} from '../r3_template_transform';
import {prepareSyntheticListenerFunctionName, prepareSyntheticPropertyName, typeWithParameters} from '../util';
import {R3ComponentDef, R3ComponentMetadata, R3DirectiveDef, R3DirectiveMetadata, R3QueryMetadata} from './api';
import {StylingBuilder, StylingInstruction} from './styling_builder';
import {Instruction, StylingBuilder} from './styling_builder';
import {BindingScope, TemplateDefinitionBuilder, ValueConverter, prepareEventListenerParameters, renderFlagCheckIfStmt, resolveSanitizationFn} from './template';
import {CONTEXT_NAME, DefinitionMap, RENDER_FLAGS, TEMPORARY_NAME, asLiteral, conditionallyCreateMapObjectLiteral, getQueryPredicate, temporaryAllocator} from './util';
@ -101,14 +101,11 @@ function baseDirectiveFields(
}
}
// e.g. `attributes: ['role', 'listbox']`
definitionMap.set('attributes', createHostAttributesArray(allOtherAttributes));
// e.g. `hostBindings: (rf, ctx, elIndex) => { ... }
definitionMap.set(
'hostBindings',
createHostBindingsFunction(
meta, elVarExp, contextVarExp, styleBuilder, bindingParser, constantPool, hostVarsCount));
'hostBindings', createHostBindingsFunction(
meta, elVarExp, contextVarExp, allOtherAttributes, styleBuilder,
bindingParser, constantPool, hostVarsCount));
// e.g 'inputs: {a: 'a'}`
definitionMap.set('inputs', conditionallyCreateMapObjectLiteral(meta.inputs, true));
@ -504,16 +501,13 @@ function createDirectiveSelector(selector: string | null): o.Expression {
return asLiteral(core.parseSelectorToR3Selector(selector));
}
function createHostAttributesArray(attributes: any): o.Expression|null {
function convertAttributesToExpressions(attributes: any): o.Expression[] {
const values: o.Expression[] = [];
for (let key of Object.getOwnPropertyNames(attributes)) {
const value = attributes[key];
values.push(o.literal(key), o.literal(value));
}
if (values.length > 0) {
return o.literalArr(values);
}
return null;
return values;
}
// Return a contentQueries function or null if one is not necessary.
@ -649,8 +643,8 @@ function createViewQueriesFunction(
// Return a host binding function or null if one is not necessary.
function createHostBindingsFunction(
meta: R3DirectiveMetadata, elVarExp: o.ReadVarExpr, bindingContext: o.ReadVarExpr,
styleBuilder: StylingBuilder, bindingParser: BindingParser, constantPool: ConstantPool,
hostVarsCount: number): o.Expression|null {
staticAttributesAndValues: any[], styleBuilder: StylingBuilder, bindingParser: BindingParser,
constantPool: ConstantPool, hostVarsCount: number): o.Expression|null {
const createStatements: o.Statement[] = [];
const updateStatements: o.Statement[] = [];
@ -740,18 +734,20 @@ function createHostBindingsFunction(
}
}
if (styleBuilder.hasBindingsOrInitialValues()) {
// since we're dealing with directives here and directives have a hostBinding
// function, we need to generate special instructions that deal with styling
// (both bindings and initial values). The instruction below will instruct
// all initial styling (styling that is inside of a host binding within a
// directive) to be attached to the host element of the directive.
const hostAttrsInstruction =
styleBuilder.buildDirectiveHostAttrsInstruction(null, constantPool);
if (hostAttrsInstruction) {
createStatements.push(createStylingStmt(hostAttrsInstruction, bindingContext, bindingFn));
}
// since we're dealing with directives/components and both have hostBinding
// functions, we need to generate a special hostAttrs instruction that deals
// with both the assignment of styling as well as static attributes to the host
// element. The instruction below will instruct all initial styling (styling
// that is inside of a host binding within a directive/component) to be attached
// to the host element alongside any of the provided host attributes that were
// collected earlier.
const hostAttrs = convertAttributesToExpressions(staticAttributesAndValues);
const hostInstruction = styleBuilder.buildHostAttrsInstruction(null, hostAttrs, constantPool);
if (hostInstruction) {
createStatements.push(createStylingStmt(hostInstruction, bindingContext, bindingFn));
}
if (styleBuilder.hasBindingsOrInitialValues()) {
// singular style/class bindings (things like `[style.prop]` and `[class.name]`)
// MUST be registered on a given element within the component/directive
// templateFn/hostBindingsFn functions. The instruction below will figure out
@ -799,7 +795,7 @@ function createHostBindingsFunction(
}
function createStylingStmt(
instruction: StylingInstruction, bindingContext: any, bindingFn: Function): o.Statement {
instruction: Instruction, bindingContext: any, bindingFn: Function): o.Statement {
const params = instruction.buildParams(value => bindingFn(bindingContext, value).currValExpr);
return o.importExpr(instruction.reference, null, instruction.sourceSpan)
.callFn(params, instruction.sourceSpan)