mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(compiler): Extract host binding static attributes to hostAttrs (#51498)
Host bindings can apply static attributes. These will be extracted to a `hostAttrs` field on the host binding function's metadata. In order to achieve this, we add an `attributes` field to the host binding job. Then, we peform attribute exraction on host bindings. We finally populate the `attributes` field directly, instead of relying on a `consts` array. PR Close #51498
This commit is contained in:
parent
8071ed36c5
commit
0df7828637
7 changed files with 77 additions and 40 deletions
|
|
@ -70,8 +70,7 @@
|
|||
"host_attributes.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"skipForTemplatePipeline": true
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support host attributes together with host classes and styles",
|
||||
|
|
|
|||
|
|
@ -580,6 +580,7 @@ function createHostBindingsFunction(
|
|||
componentName: name,
|
||||
properties: bindings,
|
||||
events: eventBindings,
|
||||
attributes: hostBindingsMetadata.attributes,
|
||||
},
|
||||
bindingParser, constantPool);
|
||||
transformHostBinding(hostJob);
|
||||
|
|
@ -588,6 +589,7 @@ function createHostBindingsFunction(
|
|||
if (varCount !== null && varCount > 0) {
|
||||
definitionMap.set('hostVars', o.literal(varCount));
|
||||
}
|
||||
definitionMap.set('hostAttrs', hostJob.root.attributes);
|
||||
|
||||
return emitHostBindingFunction(hostJob);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ export function transformHostBinding(job: HostBindingCompilationJob): void {
|
|||
phaseHostStylePropertyParsing(job);
|
||||
phaseStyleBindingSpecialization(job);
|
||||
phaseBindingSpecialization(job);
|
||||
phaseAttributeExtraction(job);
|
||||
phasePureLiteralStructures(job);
|
||||
phaseConstCollection(job);
|
||||
phaseNullishCoalescing(job);
|
||||
phaseExpandSafeReads(job);
|
||||
phaseTemporaryVariables(job);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export function ingestComponent(
|
|||
export interface HostBindingInput {
|
||||
componentName: string;
|
||||
properties: e.ParsedProperty[]|null;
|
||||
attributes: {[key: string]: o.Expression};
|
||||
events: e.ParsedEvent[]|null;
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +52,9 @@ export function ingestHostBinding(
|
|||
for (const property of input.properties ?? []) {
|
||||
ingestHostProperty(job, property, false);
|
||||
}
|
||||
for (const [name, expr] of Object.entries(input.attributes) ?? []) {
|
||||
ingestHostAttribute(job, name, expr);
|
||||
}
|
||||
for (const event of input.events ?? []) {
|
||||
ingestHostEvent(job, event);
|
||||
}
|
||||
|
|
@ -82,6 +86,14 @@ export function ingestHostProperty(
|
|||
isTextAttribute, false, property.sourceSpan));
|
||||
}
|
||||
|
||||
export function ingestHostAttribute(
|
||||
job: HostBindingCompilationJob, name: string, value: o.Expression): void {
|
||||
const attrBinding = ir.createBindingOp(
|
||||
job.root.xref, ir.BindingKind.Attribute, name, value, null, SecurityContext.NONE, true, false,
|
||||
/* TODO: host attribute source spans */ null!);
|
||||
job.root.update.push(attrBinding);
|
||||
}
|
||||
|
||||
export function ingestHostEvent(job: HostBindingCompilationJob, event: e.ParsedEvent) {}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@
|
|||
|
||||
|
||||
import * as ir from '../../ir';
|
||||
import {ComponentCompilationJob, ViewCompilationUnit} from '../compilation';
|
||||
import {HostBindingCompilationJob, type CompilationJob, type CompilationUnit} from '../compilation';
|
||||
import {getElementsByXrefId} from '../util/elements';
|
||||
|
||||
/**
|
||||
* Find all extractable attribute and binding ops, and create ExtractedAttributeOps for them.
|
||||
* In cases where no instruction needs to be generated for the attribute or binding, it is removed.
|
||||
*/
|
||||
export function phaseAttributeExtraction(cpl: ComponentCompilationJob): void {
|
||||
export function phaseAttributeExtraction(cpl: CompilationJob): void {
|
||||
for (const unit of cpl.units) {
|
||||
const elements = getElementsByXrefId(unit);
|
||||
for (const op of unit.ops()) {
|
||||
|
|
@ -72,15 +72,13 @@ function lookupElement(
|
|||
* Extracts an attribute binding.
|
||||
*/
|
||||
function extractAttributeOp(
|
||||
view: ViewCompilationUnit, op: ir.AttributeOp,
|
||||
elements: Map<ir.XrefId, ir.ElementOrContainerOps>) {
|
||||
unit: CompilationUnit, op: ir.AttributeOp, elements: Map<ir.XrefId, ir.ElementOrContainerOps>) {
|
||||
if (op.expression instanceof ir.Interpolation) {
|
||||
return;
|
||||
}
|
||||
const ownerOp = lookupElement(elements, op.target);
|
||||
|
||||
let extractable = op.expression.isConstant();
|
||||
if (view.job.compatibility === ir.CompatibilityMode.TemplateDefinitionBuilder) {
|
||||
if (unit.job.compatibility === ir.CompatibilityMode.TemplateDefinitionBuilder) {
|
||||
// TemplateDefinitionBuilder only extracted attributes that were string literals.
|
||||
extractable = ir.isStringLiteral(op.expression);
|
||||
if (op.name === 'style' || op.name === 'class') {
|
||||
|
|
@ -92,11 +90,17 @@ function extractAttributeOp(
|
|||
}
|
||||
|
||||
if (extractable) {
|
||||
ir.OpList.insertBefore<ir.CreateOp>(
|
||||
ir.createExtractedAttributeOp(
|
||||
op.target, op.isTemplate ? ir.BindingKind.Template : ir.BindingKind.Attribute, op.name,
|
||||
op.expression),
|
||||
ownerOp);
|
||||
const extractedAttributeOp = ir.createExtractedAttributeOp(
|
||||
op.target, op.isTemplate ? ir.BindingKind.Template : ir.BindingKind.Attribute, op.name,
|
||||
op.expression);
|
||||
if (unit.job instanceof HostBindingCompilationJob) {
|
||||
// This attribute will apply to the enclosing host binding compilation unit, so order doesn't
|
||||
// matter.
|
||||
unit.create.push(extractedAttributeOp);
|
||||
} else {
|
||||
const ownerOp = lookupElement(elements, op.target);
|
||||
ir.OpList.insertBefore<ir.CreateOp>(extractedAttributeOp, ownerOp);
|
||||
}
|
||||
ir.OpList.remove<ir.UpdateOp>(op);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,29 +10,32 @@ import * as core from '../../../../core';
|
|||
import {splitNsName} from '../../../../ml_parser/tags';
|
||||
import * as o from '../../../../output/output_ast';
|
||||
import * as ir from '../../ir';
|
||||
import {ComponentCompilationJob} from '../compilation';
|
||||
import {HostBindingCompilationJob, type CompilationJob, ComponentCompilationJob} from '../compilation';
|
||||
import {element} from '../instruction';
|
||||
|
||||
/**
|
||||
* Converts the semantic attributes of element-like operations (elements, templates) into constant
|
||||
* array expressions, and lifts them into the overall component `consts`.
|
||||
*/
|
||||
export function phaseConstCollection(job: ComponentCompilationJob): void {
|
||||
// Serialize the extracted messages into the const array.
|
||||
const messageConstIndices: {[id: ir.XrefId]: ir.ConstIndex} = {};
|
||||
for (const unit of job.units) {
|
||||
for (const op of unit.create) {
|
||||
if (op.kind === ir.OpKind.ExtractedMessage) {
|
||||
messageConstIndices[op.owner] = job.addConst(op.expression, op.statements);
|
||||
ir.OpList.remove<ir.CreateOp>(op);
|
||||
export function phaseConstCollection(job: CompilationJob): void {
|
||||
if (job instanceof ComponentCompilationJob) {
|
||||
// Serialize the extracted messages into the const array.
|
||||
const messageConstIndices: {[id: ir.XrefId]: ir.ConstIndex} = {};
|
||||
for (const unit of job.units) {
|
||||
for (const op of unit.create) {
|
||||
if (op.kind === ir.OpKind.ExtractedMessage) {
|
||||
messageConstIndices[op.owner] = job.addConst(op.expression, op.statements);
|
||||
ir.OpList.remove<ir.CreateOp>(op);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign const index to i18n ops that messages were extracted from.
|
||||
for (const unit of job.units) {
|
||||
for (const op of unit.create) {
|
||||
if (op.kind === ir.OpKind.I18nStart && messageConstIndices[op.xref] !== undefined) {
|
||||
op.messageIndex = messageConstIndices[op.xref];
|
||||
// Assign const index to i18n ops that messages were extracted from.
|
||||
for (const unit of job.units) {
|
||||
for (const op of unit.create) {
|
||||
if (op.kind === ir.OpKind.I18nStart && messageConstIndices[op.xref] !== undefined) {
|
||||
op.messageIndex = messageConstIndices[op.xref];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,19 +54,34 @@ export function phaseConstCollection(job: ComponentCompilationJob): void {
|
|||
}
|
||||
|
||||
// Serialize the extracted attributes into the const array.
|
||||
for (const unit of job.units) {
|
||||
for (const op of unit.create) {
|
||||
if (op.kind === ir.OpKind.Element || op.kind === ir.OpKind.ElementStart ||
|
||||
op.kind === ir.OpKind.Template) {
|
||||
const attributes = elementAttributes.get(op.xref);
|
||||
if (attributes !== undefined) {
|
||||
const attrArray = serializeAttributes(attributes);
|
||||
if (attrArray.entries.length > 0) {
|
||||
op.attributes = job.addConst(attrArray);
|
||||
if (job instanceof ComponentCompilationJob) {
|
||||
for (const unit of job.units) {
|
||||
for (const op of unit.create) {
|
||||
if (op.kind === ir.OpKind.Element || op.kind === ir.OpKind.ElementStart ||
|
||||
op.kind === ir.OpKind.Template) {
|
||||
const attributes = elementAttributes.get(op.xref);
|
||||
if (attributes !== undefined) {
|
||||
const attrArray = serializeAttributes(attributes);
|
||||
if (attrArray.entries.length > 0) {
|
||||
op.attributes = job.addConst(attrArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (job instanceof HostBindingCompilationJob) {
|
||||
// TODO: If the host binding case further diverges, we may want to split it into its own
|
||||
// phase.
|
||||
for (const [xref, attributes] of elementAttributes.entries()) {
|
||||
if (xref !== job.root.xref) {
|
||||
throw new Error(
|
||||
`An attribute would be const collected into the host binding's template function, but is not associated with the root xref.`);
|
||||
}
|
||||
const attrArray = serializeAttributes(attributes);
|
||||
if (attrArray.entries.length > 0) {
|
||||
job.root.attributes = attrArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@
|
|||
*/
|
||||
|
||||
import * as ir from '../../ir';
|
||||
import {CompilationUnit} from '../compilation';
|
||||
import type {CompilationUnit} from '../compilation';
|
||||
|
||||
/**
|
||||
* Gets a map of all elements in the given view by their xref id.
|
||||
*/
|
||||
export function getElementsByXrefId(view: CompilationUnit) {
|
||||
export function getElementsByXrefId(unit: CompilationUnit) {
|
||||
const elements = new Map<ir.XrefId, ir.ElementOrContainerOps>();
|
||||
for (const op of view.create) {
|
||||
for (const op of unit.create) {
|
||||
if (!ir.isElementOrContainerOp(op)) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue