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:

committed by
Andrew Kushnir

parent
e62eeed7d4
commit
693045165c
@ -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)
|
||||
|
@ -20,7 +20,7 @@ import {ValueConverter} from './template';
|
||||
/**
|
||||
* A styling expression summary that is to be processed by the compiler
|
||||
*/
|
||||
export interface StylingInstruction {
|
||||
export interface Instruction {
|
||||
sourceSpan: ParseSourceSpan|null;
|
||||
reference: o.ExternalReference;
|
||||
buildParams(convertFn: (value: any) => o.Expression): o.Expression[];
|
||||
@ -225,17 +225,17 @@ export class StylingBuilder {
|
||||
* Builds an instruction with all the expressions and parameters for `elementHostAttrs`.
|
||||
*
|
||||
* The instruction generation code below is used for producing the AOT statement code which is
|
||||
* responsible for registering initial styles (within a directive hostBindings' creation block)
|
||||
* to the directive host element.
|
||||
* responsible for registering initial styles (within a directive hostBindings' creation block),
|
||||
* as well as any of the provided attribute values, to the directive host element.
|
||||
*/
|
||||
buildDirectiveHostAttrsInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
StylingInstruction|null {
|
||||
if (this._hasInitialValues && this._directiveExpr) {
|
||||
buildHostAttrsInstruction(
|
||||
sourceSpan: ParseSourceSpan|null, attrs: o.Expression[],
|
||||
constantPool: ConstantPool): Instruction|null {
|
||||
if (this._directiveExpr && (attrs.length || this._hasInitialValues)) {
|
||||
return {
|
||||
sourceSpan,
|
||||
reference: R3.elementHostAttrs,
|
||||
buildParams: () => {
|
||||
const attrs: o.Expression[] = [];
|
||||
this.populateInitialStylingAttrs(attrs);
|
||||
return [this._directiveExpr !, getConstantLiteralFromArray(constantPool, attrs)];
|
||||
}
|
||||
@ -251,7 +251,7 @@ export class StylingBuilder {
|
||||
* responsible for registering style/class bindings to an element.
|
||||
*/
|
||||
buildElementStylingInstruction(sourceSpan: ParseSourceSpan|null, constantPool: ConstantPool):
|
||||
StylingInstruction|null {
|
||||
Instruction|null {
|
||||
if (this._hasBindings) {
|
||||
return {
|
||||
sourceSpan,
|
||||
@ -312,7 +312,7 @@ export class StylingBuilder {
|
||||
* which include the `[style]` and `[class]` expression params (if they exist) as well as
|
||||
* the sanitizer and directive reference expression.
|
||||
*/
|
||||
buildElementStylingMapInstruction(valueConverter: ValueConverter): StylingInstruction|null {
|
||||
buildElementStylingMapInstruction(valueConverter: ValueConverter): Instruction|null {
|
||||
if (this._classMapInput || this._styleMapInput) {
|
||||
const stylingInput = this._classMapInput ! || this._styleMapInput !;
|
||||
|
||||
@ -355,7 +355,7 @@ export class StylingBuilder {
|
||||
|
||||
private _buildSingleInputs(
|
||||
reference: o.ExternalReference, inputs: BoundStylingEntry[], mapIndex: Map<string, number>,
|
||||
allowUnits: boolean, valueConverter: ValueConverter): StylingInstruction[] {
|
||||
allowUnits: boolean, valueConverter: ValueConverter): Instruction[] {
|
||||
return inputs.map(input => {
|
||||
const bindingIndex: number = mapIndex.get(input.name) !;
|
||||
const value = input.value.visit(valueConverter);
|
||||
@ -381,7 +381,7 @@ export class StylingBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
private _buildClassInputs(valueConverter: ValueConverter): StylingInstruction[] {
|
||||
private _buildClassInputs(valueConverter: ValueConverter): Instruction[] {
|
||||
if (this._singleClassInputs) {
|
||||
return this._buildSingleInputs(
|
||||
R3.elementClassProp, this._singleClassInputs, this._classesIndex, false, valueConverter);
|
||||
@ -389,7 +389,7 @@ export class StylingBuilder {
|
||||
return [];
|
||||
}
|
||||
|
||||
private _buildStyleInputs(valueConverter: ValueConverter): StylingInstruction[] {
|
||||
private _buildStyleInputs(valueConverter: ValueConverter): Instruction[] {
|
||||
if (this._singleStyleInputs) {
|
||||
return this._buildSingleInputs(
|
||||
R3.elementStyleProp, this._singleStyleInputs, this._stylesIndex, true, valueConverter);
|
||||
@ -397,7 +397,7 @@ export class StylingBuilder {
|
||||
return [];
|
||||
}
|
||||
|
||||
private _buildApplyFn(): StylingInstruction {
|
||||
private _buildApplyFn(): Instruction {
|
||||
return {
|
||||
sourceSpan: this._lastStylingInput ? this._lastStylingInput.sourceSpan : null,
|
||||
reference: R3.elementStylingApply,
|
||||
@ -416,7 +416,7 @@ export class StylingBuilder {
|
||||
* into the update block of a template function or a directive hostBindings function.
|
||||
*/
|
||||
buildUpdateLevelInstructions(valueConverter: ValueConverter) {
|
||||
const instructions: StylingInstruction[] = [];
|
||||
const instructions: Instruction[] = [];
|
||||
if (this._hasBindings) {
|
||||
const mapInstruction = this.buildElementStylingMapInstruction(valueConverter);
|
||||
if (mapInstruction) {
|
||||
|
@ -36,7 +36,7 @@ import {I18nContext} from './i18n/context';
|
||||
import {I18nMetaVisitor} from './i18n/meta';
|
||||
import {getSerializedI18nContent} from './i18n/serializer';
|
||||
import {I18N_ICU_MAPPING_PREFIX, assembleBoundTextPlaceholders, assembleI18nBoundString, formatI18nPlaceholderName, getTranslationConstPrefix, getTranslationDeclStmts, icuFromI18nMessage, isI18nRootNode, isSingleI18nIcu, metaFromI18nMessage, placeholdersToParams, wrapI18nPlaceholder} from './i18n/util';
|
||||
import {StylingBuilder, StylingInstruction} from './styling_builder';
|
||||
import {Instruction, StylingBuilder} from './styling_builder';
|
||||
import {CONTEXT_NAME, IMPLICIT_REFERENCE, NON_BINDABLE_ATTR, REFERENCE_PREFIX, RENDER_FLAGS, asLiteral, getAttrsForDirectiveMatching, invalid, trimTrailingNulls, unsupported} from './util';
|
||||
|
||||
// Default selector used by `<ng-content>` if none specified
|
||||
@ -689,7 +689,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
if (input.type === BindingType.Animation) {
|
||||
const value = input.value.visit(this._valueConverter);
|
||||
// animation bindings can be presented in the following formats:
|
||||
// 1j [@binding]="fooExp"
|
||||
// 1. [@binding]="fooExp"
|
||||
// 2. [@binding]="{value:fooExp, params:{...}}"
|
||||
// 3. [@binding]
|
||||
// 4. @binding
|
||||
@ -936,7 +936,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
}
|
||||
|
||||
private processStylingInstruction(
|
||||
implicit: any, instruction: StylingInstruction|null, createMode: boolean) {
|
||||
implicit: any, instruction: Instruction|null, createMode: boolean) {
|
||||
if (instruction) {
|
||||
const paramsFn = () =>
|
||||
instruction.buildParams(value => this.convertPropertyBinding(implicit, value, true));
|
||||
|
Reference in New Issue
Block a user