perf: property optimizations (#10850)

This commit is contained in:
Nathan Walker
2025-09-19 14:36:19 -07:00
committed by GitHub
parent 440ae0ee7c
commit 701bfea561

View File

@ -12,9 +12,16 @@ import { calc } from '@csstools/css-calc';
export { unsetValue } from './property-shared'; export { unsetValue } from './property-shared';
const cssPropertyNames: string[] = []; const cssPropertyNames: string[] = [];
const HAS_OWN = Object.prototype.hasOwnProperty;
const symbolPropertyMap = {}; const symbolPropertyMap = {};
const cssSymbolPropertyMap = {}; const cssSymbolPropertyMap = {};
// Hoisted regex/constants for hot paths to avoid re-allocation
const CSS_VARIABLE_NAME_RE = /^--[^,\s]+?$/;
const DIP_RE = /([0-9]+(\.[0-9]+)?)dip\b/g;
const UNSET_RE = /unset/g;
const INFINITY_RE = /infinity/g;
const inheritableProperties = new Array<InheritedProperty<any, any>>(); const inheritableProperties = new Array<InheritedProperty<any, any>>();
const inheritableCssProperties = new Array<InheritedCssProperty<any, any>>(); const inheritableCssProperties = new Array<InheritedCssProperty<any, any>>();
@ -50,7 +57,7 @@ export function _getStyleProperties(): CssProperty<any, any>[] {
} }
export function isCssVariable(property: string) { export function isCssVariable(property: string) {
return /^--[^,\s]+?$/.test(property); return CSS_VARIABLE_NAME_RE.test(property);
} }
export function isCssCalcExpression(value: string) { export function isCssCalcExpression(value: string) {
@ -119,27 +126,31 @@ export function _evaluateCssCalcExpression(value: string) {
} else { } else {
return value; return value;
} }
return value;
} }
function _replaceDip(value: string) { function _replaceDip(value: string) {
return value.replace(/([0-9]+(\.[0-9]+)?)dip\b/g, '$1'); return value.replace(DIP_RE, '$1');
} }
function _replaceKeywordsWithValues(value: string) { function _replaceKeywordsWithValues(value: string) {
let cssValue = value; let cssValue = value;
if (cssValue.includes('unset')) { if (cssValue.includes('unset')) {
cssValue = cssValue.replace(/unset/g, '0'); cssValue = cssValue.replace(UNSET_RE, '0');
} }
if (cssValue.includes('infinity')) { if (cssValue.includes('infinity')) {
cssValue = cssValue.replace(/infinity/g, '999999'); cssValue = cssValue.replace(INFINITY_RE, '999999');
} }
return cssValue; return cssValue;
} }
function getPropertiesFromMap(map): Property<any, any>[] | CssProperty<any, any>[] { function getPropertiesFromMap(map): Property<any, any>[] | CssProperty<any, any>[] {
const props = []; const symbols = Object.getOwnPropertySymbols(map);
Object.getOwnPropertySymbols(map).forEach((symbol) => props.push(map[symbol])); const len = symbols.length;
const props = new Array(len);
for (let i = 0; i < len; i++) {
props[i] = map[symbols[i]];
}
return props; return props;
} }
@ -240,15 +251,13 @@ export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<
if (this._suspendedUpdates) { if (this._suspendedUpdates) {
this._suspendedUpdates[propertyName] = property; this._suspendedUpdates[propertyName] = property;
} }
} else { } else if (defaultValueKey in this) {
if (defaultValueKey in this) {
this[setNative](this[defaultValueKey]); this[setNative](this[defaultValueKey]);
delete this[defaultValueKey]; delete this[defaultValueKey];
} else { } else {
this[setNative](defaultValue); this[setNative](defaultValue);
} }
} }
}
} else { } else {
this[key] = value; this[key] = value;
if (valueChanged) { if (valueChanged) {
@ -424,15 +433,13 @@ export class CoercibleProperty<T extends ViewBase, U> extends Property<T, U> imp
if (this._suspendedUpdates) { if (this._suspendedUpdates) {
this._suspendedUpdates[propertyName] = property; this._suspendedUpdates[propertyName] = property;
} }
} else { } else if (defaultValueKey in this) {
if (defaultValueKey in this) {
this[setNative](this[defaultValueKey]); this[setNative](this[defaultValueKey]);
delete this[defaultValueKey]; delete this[defaultValueKey];
} else { } else {
this[setNative](defaultValue); this[setNative](defaultValue);
} }
} }
}
} else { } else {
this[key] = value; this[key] = value;
if (valueChanged) { if (valueChanged) {
@ -577,7 +584,10 @@ export class CssProperty<T extends Style, U> {
const propertyName = options.name; const propertyName = options.name;
this.name = propertyName; this.name = propertyName;
// Guard against undefined cssName
if (options.cssName) {
cssPropertyNames.push(options.cssName); cssPropertyNames.push(options.cssName);
}
this.cssName = `css:${options.cssName}`; this.cssName = `css:${options.cssName}`;
this.cssLocalName = options.cssName; this.cssLocalName = options.cssName;
@ -657,15 +667,13 @@ export class CssProperty<T extends Style, U> {
if (view._suspendedUpdates) { if (view._suspendedUpdates) {
view._suspendedUpdates[propertyName] = property; view._suspendedUpdates[propertyName] = property;
} }
} else { } else if (defaultValueKey in this) {
if (defaultValueKey in this) {
view[setNative](this[defaultValueKey]); view[setNative](this[defaultValueKey]);
delete this[defaultValueKey]; delete this[defaultValueKey];
} else { } else {
view[setNative](defaultValue); view[setNative](defaultValue);
} }
} }
}
} else { } else {
this[key] = value; this[key] = value;
if (valueChanged) { if (valueChanged) {
@ -743,15 +751,13 @@ export class CssProperty<T extends Style, U> {
if (view._suspendedUpdates) { if (view._suspendedUpdates) {
view._suspendedUpdates[propertyName] = property; view._suspendedUpdates[propertyName] = property;
} }
} else { } else if (defaultValueKey in this) {
if (defaultValueKey in this) {
view[setNative](this[defaultValueKey]); view[setNative](this[defaultValueKey]);
delete this[defaultValueKey]; delete this[defaultValueKey];
} else { } else {
view[setNative](defaultValue); view[setNative](defaultValue);
} }
} }
}
} else { } else {
this[key] = value; this[key] = value;
if (valueChanged) { if (valueChanged) {
@ -856,7 +862,9 @@ export class CssAnimationProperty<T extends Style, U> implements CssAnimationPro
const propertyName = options.name; const propertyName = options.name;
this.name = propertyName; this.name = propertyName;
if (options.cssName) {
cssPropertyNames.push(options.cssName); cssPropertyNames.push(options.cssName);
}
CssAnimationProperty.properties[propertyName] = this; CssAnimationProperty.properties[propertyName] = this;
if (options.cssName && options.cssName !== propertyName) { if (options.cssName && options.cssName !== propertyName) {
@ -1143,8 +1151,7 @@ export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U>
if (view._suspendedUpdates) { if (view._suspendedUpdates) {
view._suspendedUpdates[propertyName] = property; view._suspendedUpdates[propertyName] = property;
} }
} else { } else if (unsetNativeValue) {
if (unsetNativeValue) {
if (defaultValueKey in this) { if (defaultValueKey in this) {
view[setNative](this[defaultValueKey]); view[setNative](this[defaultValueKey]);
delete this[defaultValueKey]; delete this[defaultValueKey];
@ -1159,7 +1166,6 @@ export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U>
view[setNative](value); view[setNative](value);
} }
} }
}
if (this.hasListeners(eventName)) { if (this.hasListeners(eventName)) {
this.notify<PropertyChangeData>({ this.notify<PropertyChangeData>({
@ -1356,6 +1362,7 @@ export function applyPendingNativeSetters(view: ViewBase): void {
// TODO: Check what happens if a view was suspended and its value was reset, or set back to default! // TODO: Check what happens if a view was suspended and its value was reset, or set back to default!
const suspendedUpdates = view._suspendedUpdates; const suspendedUpdates = view._suspendedUpdates;
for (const propertyName in suspendedUpdates) { for (const propertyName in suspendedUpdates) {
if (!HAS_OWN.call(suspendedUpdates, propertyName)) continue;
const property = <PropertyInterface>suspendedUpdates[propertyName]; const property = <PropertyInterface>suspendedUpdates[propertyName];
const setNative = property.setNative; const setNative = property.setNative;
if (view[setNative]) { if (view[setNative]) {
@ -1523,9 +1530,11 @@ export function propagateInheritableCssProperties(parentStyle: Style, childStyle
export function getSetProperties(view: ViewBase): [string, any][] { export function getSetProperties(view: ViewBase): [string, any][] {
const result = []; const result = [];
Object.getOwnPropertyNames(view).forEach((prop) => { const ownProps = Object.getOwnPropertyNames(view);
for (let i = 0; i < ownProps.length; i++) {
const prop = ownProps[i];
result.push([prop, view[prop]]); result.push([prop, view[prop]]);
}); }
const symbols = Object.getOwnPropertySymbols(view); const symbols = Object.getOwnPropertySymbols(view);
for (const symbol of symbols) { for (const symbol of symbols) {
@ -1545,8 +1554,10 @@ export function getComputedCssValues(view: ViewBase): [string, any][] {
const result = []; const result = [];
const style = view.style; const style = view.style;
for (const prop of cssPropertyNames) { for (const prop of cssPropertyNames) {
if (prop !== undefined && prop !== null) {
result.push([prop, style[prop]]); result.push([prop, style[prop]]);
} }
}
// Add these to enable box model in chrome-devtools styles tab // Add these to enable box model in chrome-devtools styles tab
result.push(['top', 'auto']); result.push(['top', 'auto']);