mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
perf(core): move property remapping for dom properties to compiler (#62421)
Since we know that DOM properties won't go to an inputs, we can move the remapping logic to the compiler, saving us some processing on the client. PR Close #62421
This commit is contained in:
committed by
Jessica Janiuk
parent
c30f4eebc1
commit
8fbe558cbc
+1
-1
@@ -12,7 +12,7 @@ template: function MyComponent_Template(rf, ctx) {
|
||||
$r3$.ɵɵadvance(3);
|
||||
$r3$.ɵɵattribute("aria-label", ctx.value1)("aria-label", ctx.value2);
|
||||
$r3$.ɵɵadvance();
|
||||
$r3$.ɵɵdomProperty("tabindex", ctx.value1)("tabindex", ctx.value2);
|
||||
$r3$.ɵɵdomProperty("tabIndex", ctx.value1)("tabIndex", ctx.value2);
|
||||
$r3$.ɵɵadvance();
|
||||
$r3$.ɵɵclassMap(ctx.value2);
|
||||
$r3$.ɵɵadvance();
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
hostBindings: function MyDirective_HostBindings(rf, ctx) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵdomProperty("tabindex", 1);
|
||||
$r3$.ɵɵdomProperty("tabIndex", 1);
|
||||
$r3$.ɵɵattribute("title", "my title")("id", "my-id");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
hostBindings: function MyDirective_HostBindings(rf, ctx) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵdomProperty("title", ctx.myTitle)("tabindex", 1)("id", ctx.myId);
|
||||
$r3$.ɵɵdomProperty("title", ctx.myTitle)("tabIndex", 1)("id", ctx.myId);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
hostBindings: function MyDirective_HostBindings(rf, ctx) {
|
||||
…
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵdomProperty("tabindex", 1)("title", ctx.myTitle)("id", ctx.myId);
|
||||
$r3$.ɵɵdomProperty("tabIndex", 1)("title", ctx.myTitle)("id", ctx.myId);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,13 +1,13 @@
|
||||
hostBindings: function HostBindingDir_HostBindings(rf, ctx) {
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵdomProperty("innerHtml", ctx.evil, $r3$.ɵɵsanitizeHtml)("href", ctx.evil, $r3$.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.evil, $r3$.ɵɵsanitizeUrlOrResourceUrl)("sandbox", ctx.evil, $r3$.ɵɵvalidateIframeAttribute);
|
||||
$r3$.ɵɵdomProperty("innerHTML", ctx.evil, $r3$.ɵɵsanitizeHtml)("href", ctx.evil, $r3$.ɵɵsanitizeUrlOrResourceUrl)("src", ctx.evil, $r3$.ɵɵsanitizeUrlOrResourceUrl)("sandbox", ctx.evil, $r3$.ɵɵvalidateIframeAttribute);
|
||||
$r3$.ɵɵattribute("style", ctx.evil, $r3$.ɵɵsanitizeStyle);
|
||||
}
|
||||
}
|
||||
…
|
||||
hostBindings: function HostBindingDir2_HostBindings(rf, ctx) {
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵdomProperty("innerHtml", ctx.evil, $r3$.ɵɵsanitizeHtml)("href", ctx.evil, $r3$.ɵɵsanitizeUrl)("src", ctx.evil)("sandbox", ctx.evil, $r3$.ɵɵvalidateIframeAttribute);
|
||||
$r3$.ɵɵdomProperty("innerHTML", ctx.evil, $r3$.ɵɵsanitizeHtml)("href", ctx.evil, $r3$.ɵɵsanitizeUrl)("src", ctx.evil)("sandbox", ctx.evil, $r3$.ɵɵvalidateIframeAttribute);
|
||||
$r3$.ɵɵattribute("style", ctx.evil, $r3$.ɵɵsanitizeStyle);
|
||||
}
|
||||
}
|
||||
|
||||
+34
-7
@@ -166,7 +166,7 @@ export declare class MyModule {
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: special_property_remapping.js
|
||||
* PARTIAL FILE: special_property_remapping_property.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
@@ -176,15 +176,13 @@ export class MyComponent {
|
||||
}
|
||||
}
|
||||
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: false, selector: "my-component", ngImport: i0, template: `
|
||||
<label [for]="forValue"></label>`, isInline: true });
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: false, selector: "my-component", ngImport: i0, template: `<label [for]="forValue"></label>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-component',
|
||||
template: `
|
||||
<label [for]="forValue"></label>`,
|
||||
standalone: false
|
||||
template: `<label [for]="forValue"></label>`,
|
||||
standalone: false,
|
||||
}]
|
||||
}] });
|
||||
export class MyModule {
|
||||
@@ -198,7 +196,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: special_property_remapping.d.ts
|
||||
* PARTIAL FILE: special_property_remapping_property.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyComponent {
|
||||
@@ -212,6 +210,35 @@ export declare class MyModule {
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: special_property_remapping_dom_property.js
|
||||
****************************************************************************************************/
|
||||
import { Component } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyComponent {
|
||||
constructor() {
|
||||
this.forValue = 'some-input';
|
||||
}
|
||||
}
|
||||
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "ng-component", ngImport: i0, template: `<label [for]="forValue"></label>`, isInline: true });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `<label [for]="forValue"></label>`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: special_property_remapping_dom_property.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyComponent {
|
||||
forValue: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "ng-component", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: temporary_variables.js
|
||||
****************************************************************************************************/
|
||||
|
||||
+17
-3
@@ -44,15 +44,29 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should not remap property names whose names do not correspond to their attribute names",
|
||||
"description": "should not remap special property names when outputting property instructions",
|
||||
"inputFiles": [
|
||||
"special_property_remapping.ts"
|
||||
"special_property_remapping_property.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect template",
|
||||
"files": [
|
||||
"special_property_remapping.js"
|
||||
"special_property_remapping_property.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should remap special property names when outputting domProperty instructions",
|
||||
"inputFiles": [
|
||||
"special_property_remapping_dom_property.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect template",
|
||||
"files": [
|
||||
"special_property_remapping_dom_property.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ template: function MyComponent_Template(rf, ctx) {
|
||||
$r3$.ɵɵdomElement(0, "div", 0)(1, "link", 1)(2, "div")(3, "img", 2)(4, "iframe", 3)(5, "a", 1)(6, "div");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵdomProperty("innerHtml", ctx.evil, $r3$.ɵɵsanitizeHtml);
|
||||
$r3$.ɵɵdomProperty("innerHTML", ctx.evil, $r3$.ɵɵsanitizeHtml);
|
||||
$r3$.ɵɵadvance();
|
||||
$r3$.ɵɵdomProperty("href", ctx.evil, $r3$.ɵɵsanitizeResourceUrl);
|
||||
$r3$.ɵɵadvance();
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
|
||||
consts: [[__AttributeMarker.Bindings__, "for"]]
|
||||
|
||||
…
|
||||
|
||||
function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵɵdomElement(0, "label", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵɵdomProperty("htmlFor", ctx.forValue);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `<label [for]="forValue"></label>`,
|
||||
})
|
||||
export class MyComponent {
|
||||
forValue = 'some-input';
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
|
||||
consts: [[__AttributeMarker.Bindings__, "for"]]
|
||||
|
||||
// ...
|
||||
…
|
||||
|
||||
function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
@@ -10,4 +10,4 @@ function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 2) {
|
||||
$i0$.ɵɵproperty("for", ctx.forValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -1,15 +1,13 @@
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: `
|
||||
<label [for]="forValue"></label>`,
|
||||
standalone: false
|
||||
selector: 'my-component',
|
||||
template: `<label [for]="forValue"></label>`,
|
||||
standalone: false,
|
||||
})
|
||||
export class MyComponent {
|
||||
forValue = 'some-input';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {
|
||||
}
|
||||
export class MyModule {}
|
||||
+1
-1
@@ -22,7 +22,7 @@ consts: () => {
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵadvance(3);
|
||||
$r3$.ɵɵdomProperty("tabindex", ctx.tabIndex);
|
||||
$r3$.ɵɵdomProperty("tabIndex", ctx.tabIndex);
|
||||
$r3$.ɵɵadvance();
|
||||
$r3$.ɵɵdomProperty("ngIf", ctx.cond);
|
||||
$r3$.ɵɵadvance(2);
|
||||
|
||||
@@ -26,6 +26,19 @@ const GLOBAL_TARGET_RESOLVERS = new Map<string, o.ExternalReference>([
|
||||
['body', Identifiers.resolveBody],
|
||||
]);
|
||||
|
||||
/**
|
||||
* DOM properties that need to be remapped on the compiler side.
|
||||
* Note: this mapping has to be kept in sync with the equally named mapping in the runtime.
|
||||
*/
|
||||
const DOM_PROPERTY_REMAPPING = new Map([
|
||||
['class', 'className'],
|
||||
['for', 'htmlFor'],
|
||||
['formaction', 'formAction'],
|
||||
['innerHtml', 'innerHTML'],
|
||||
['readonly', 'readOnly'],
|
||||
['tabindex', 'tabIndex'],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Compiles semantic operations across all views and generates output `o.Statement`s with actual
|
||||
* runtime calls in their place.
|
||||
@@ -549,7 +562,12 @@ function reifyUpdateOperations(unit: CompilationUnit, ops: ir.OpList<ir.UpdateOp
|
||||
ir.OpList.replace(
|
||||
op,
|
||||
unit.job.mode === TemplateCompilationMode.DomOnly && !op.isLegacyAnimationTrigger
|
||||
? ng.domProperty(op.name, op.expression, op.sanitizer, op.sourceSpan)
|
||||
? ng.domProperty(
|
||||
DOM_PROPERTY_REMAPPING.get(op.name) ?? op.name,
|
||||
op.expression,
|
||||
op.sanitizer,
|
||||
op.sourceSpan,
|
||||
)
|
||||
: ng.property(op.name, op.expression, op.sanitizer, op.sourceSpan),
|
||||
);
|
||||
break;
|
||||
@@ -598,7 +616,12 @@ function reifyUpdateOperations(unit: CompilationUnit, ops: ir.OpList<ir.UpdateOp
|
||||
} else {
|
||||
ir.OpList.replace(
|
||||
op,
|
||||
ng.domProperty(op.name, op.expression, op.sanitizer, op.sourceSpan),
|
||||
ng.domProperty(
|
||||
DOM_PROPERTY_REMAPPING.get(op.name) ?? op.name,
|
||||
op.expression,
|
||||
op.sanitizer,
|
||||
op.sourceSpan,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,8 +234,7 @@ export function enableApplyRootElementTransformImpl() {
|
||||
* object lookup) for performance reasons - the series of `if` checks seems to be the fastest way of
|
||||
* mapping property names. Do NOT change without benchmarking.
|
||||
*
|
||||
* Note: this mapping has to be kept in sync with the equally named mapping in the template
|
||||
* type-checking machinery of ngtsc.
|
||||
* Note: this mapping has to be kept in sync with the equivalent mappings in the compiler.
|
||||
*/
|
||||
function mapPropName(name: string): string {
|
||||
if (name === 'class') return 'className';
|
||||
@@ -265,6 +264,11 @@ export function setPropertyAndInputs<T>(
|
||||
return; // Stop propcessing if we've matched at least one input.
|
||||
}
|
||||
|
||||
// If the property is going to a DOM node, we have to remap it.
|
||||
if (tNode.type & TNodeType.AnyRNode) {
|
||||
propName = mapPropName(propName);
|
||||
}
|
||||
|
||||
setDomProperty(tNode, lView, propName, value, renderer, sanitizer);
|
||||
}
|
||||
|
||||
@@ -287,7 +291,6 @@ export function setDomProperty<T>(
|
||||
) {
|
||||
if (tNode.type & TNodeType.AnyRNode) {
|
||||
const element = getNativeByTNode(tNode, lView) as RElement | RComment;
|
||||
propName = mapPropName(propName);
|
||||
|
||||
if (ngDevMode) {
|
||||
validateAgainstEventProperties(propName);
|
||||
|
||||
@@ -76,13 +76,11 @@ describe('property bindings', () => {
|
||||
it('should bind to properties whose names do not correspond to their attribute names', () => {
|
||||
@Component({
|
||||
template: '<label [for]="forValue"></label>',
|
||||
standalone: false,
|
||||
})
|
||||
class MyComp {
|
||||
forValue?: string;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp]});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
const labelNode = fixture.debugElement.query(By.css('label'));
|
||||
|
||||
@@ -104,7 +102,6 @@ describe('property bindings', () => {
|
||||
@Component({
|
||||
template: '',
|
||||
selector: 'my-comp',
|
||||
standalone: false,
|
||||
})
|
||||
class MyComp {
|
||||
@Input() for!: string;
|
||||
@@ -112,13 +109,12 @@ describe('property bindings', () => {
|
||||
|
||||
@Component({
|
||||
template: '<my-comp [for]="forValue"></my-comp>',
|
||||
standalone: false,
|
||||
imports: [MyComp],
|
||||
})
|
||||
class App {
|
||||
forValue?: string;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [App, MyComp]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const myCompNode = fixture.debugElement.query(By.directive(MyComp));
|
||||
fixture.componentInstance.forValue = 'hello';
|
||||
|
||||
Reference in New Issue
Block a user