From de0ebba5c43b95d527b9ff2759e699e3356e39e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 29 May 2026 02:13:13 +0000 Subject: [PATCH] Perf: Memoize regex parsing in convertConditionToConditions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 6 of the perf series. The first half of convertConditionToConditions runs two regex .match() calls on conditionName, returning four derived strings. The result depends only on the string conditionName — not on the conditionValue, controlModel, values, or controls arguments — yet the same names recur many times across element and widget instances on a page ('selected_icon[value]!' once per heading-with-icon, etc.). The user's CPU profile flagged convertConditionToConditions at 704ms self-time on a 200-element page. This adds a module-scoped Map cache keyed on conditionName with a 2000-entry safety cap. No behavior change; cache miss runs the original regex work. Not gated by an experiment flag — pure memoization with identical output, no observable semantic change. Easy to revert via git revert if needed. --- .../dev/js/editor/utils/control-conditions.js | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/assets/dev/js/editor/utils/control-conditions.js b/assets/dev/js/editor/utils/control-conditions.js index 66b0d0b26c2d..792b1ef1ae54 100644 --- a/assets/dev/js/editor/utils/control-conditions.js +++ b/assets/dev/js/editor/utils/control-conditions.js @@ -1,5 +1,36 @@ import Conditions from './conditions'; +// Module-scoped cache for parsed condition names. The regex work in +// convertConditionToConditions depends only on the string conditionName, which +// recurs many times across element/widget instances (e.g. "selected_icon[value]!" +// appears once per heading-with-icon widget). Cap entry count defensively in case +// of pathological dynamic input — in practice this holds <500 entries. +const parsedConditionNameCache = new Map(); + +function parseConditionName( conditionName ) { + let parsed = parsedConditionNameCache.get( conditionName ); + if ( parsed ) { + return parsed; + } + + const conditionNameParts = conditionName.match( /([\w-]+(?:\[[\w-]+])?)?(!?)$/i ); + const conditionRealName = conditionNameParts[ 1 ]; + const parsedControlName = conditionRealName.match( /([\w-]+)(?:\[([\w-]+)])?/ ); + + parsed = { + conditionRealName, + isNegativeCondition: !! conditionNameParts[ 2 ], + conditionNameWithoutSubKey: parsedControlName[ 1 ], + conditionSubKey: parsedControlName[ 2 ], + }; + + if ( parsedConditionNameCache.size < 2000 ) { + parsedConditionNameCache.set( conditionName, parsed ); + } + + return parsed; +} + /** * Control Conditions Class * @@ -32,18 +63,12 @@ export default class ControlConditions extends Conditions { convertConditionToConditions( conditionName, conditionValue, controlModel, values, controls ) { // The first step is to isolate the term from the negative operator if exists. For example, a condition format // can look like 'selected_icon[value]!', so we examine this term with a negative connotation. - const conditionNameParts = conditionName.match( /([\w-]+(?:\[[\w-]+])?)?(!?)$/i ), - conditionRealName = conditionNameParts[ 1 ], - isNegativeCondition = !! conditionNameParts[ 2 ]; - - const parsedControlName = conditionRealName.match( /([\w-]+)(?:\[([\w-]+)])?/ ), - // ConditionNameWithoutSubKey example: the condition key 'image[url]' will give the value of 'image'. - conditionNameWithoutSubKey = parsedControlName[ 1 ], - // ConditionSubKey example: the condition key 'image[url]' will give the value of 'url'. - conditionSubKey = parsedControlName[ 2 ], - // In some cases the control's attributes will be under the 'attributes' property, and in some - // cases they will be directly on the model object. - controlResponsiveProp = controlModel.attributes?.responsive || controlModel.responsive; + // The regex parsing depends only on conditionName, so the result is memoized across calls. + const { conditionRealName, isNegativeCondition, conditionNameWithoutSubKey, conditionSubKey } = parseConditionName( conditionName ); + + // In some cases the control's attributes will be under the 'attributes' property, and in some + // cases they will be directly on the model object. + const controlResponsiveProp = controlModel.attributes?.responsive || controlModel.responsive; let conditionNameToCheck = conditionRealName, controlValue;