Skip to content

Commit b5c815c

Browse files
sammy-SCmeta-codesync[bot]
authored andcommitted
Optimize processBoxShadow with pre-compiled regex patterns (#55825)
Summary: Pull Request resolved: #55825 changelog: [internal] Hoist regex patterns to module-level constants to avoid recompiling them on every function call. This optimization targets a hotspot identified via JS sampling profiler. Benchmark results show 6-7% improvement in "views with large props and styles" tests: - render 100 views with large props/styles: 9.48ms → 8.89ms (-6.2%) - render 1500 views with large props/styles: 137.2ms → 127.5ms (-7.0%) Reviewed By: javache, NickGerleman Differential Revision: D92153667 fbshipit-source-id: d4cd98d34e968b2cea5a491ede4ed727273708cd
1 parent 67db89d commit b5c815c

File tree

3 files changed

+61
-4
lines changed

3 files changed

+61
-4
lines changed

packages/react-native/Libraries/StyleSheet/processBoxShadow.js

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,15 @@
1111
import type {ProcessedColorValue} from './processColor';
1212
import type {BoxShadowValue} from './StyleSheetTypes';
1313

14+
import {enableOptimizedBoxShadowParsing} from '../../src/private/featureflags/ReactNativeFeatureFlags';
1415
import processColor from './processColor';
1516

17+
// Pre-compiled regex patterns for performance - avoids regex compilation on each call
18+
const COMMA_SPLIT_REGEX = /,(?![^()]*\))/;
19+
const WHITESPACE_SPLIT_REGEX = /\s+(?![^(]*\))/;
20+
const LENGTH_PARSE_REGEX = /^([+-]?\d*\.?\d+)(px)?$/;
21+
const NEWLINE_REGEX = /\n/g;
22+
1623
export type ParsedBoxShadow = {
1724
offsetX: number,
1825
offsetY: number,
@@ -32,7 +39,12 @@ export default function processBoxShadow(
3239

3340
const boxShadowList =
3441
typeof rawBoxShadows === 'string'
35-
? parseBoxShadowString(rawBoxShadows.replace(/\n/g, ' '))
42+
? parseBoxShadowString(
43+
rawBoxShadows.replace(
44+
enableOptimizedBoxShadowParsing() ? NEWLINE_REGEX : /\n/g,
45+
' ',
46+
),
47+
)
3648
: rawBoxShadows;
3749

3850
for (const rawBoxShadow of boxShadowList) {
@@ -109,7 +121,9 @@ function parseBoxShadowString(rawBoxShadows: string): Array<BoxShadowValue> {
109121
let result: Array<BoxShadowValue> = [];
110122

111123
for (const rawBoxShadow of rawBoxShadows
112-
.split(/,(?![^()]*\))/) // split by comma that is not in parenthesis
124+
.split(
125+
enableOptimizedBoxShadowParsing() ? COMMA_SPLIT_REGEX : /,(?![^()]*\))/,
126+
) // split by comma that is not in parenthesis
113127
.map(bS => bS.trim())
114128
.filter(bS => bS !== '')) {
115129
const boxShadow: BoxShadowValue = {
@@ -123,7 +137,11 @@ function parseBoxShadowString(rawBoxShadows: string): Array<BoxShadowValue> {
123137
let lengthCount = 0;
124138

125139
// split rawBoxShadow string by all whitespaces that are not in parenthesis
126-
const args = rawBoxShadow.split(/\s+(?![^(]*\))/);
140+
const args = rawBoxShadow.split(
141+
enableOptimizedBoxShadowParsing()
142+
? WHITESPACE_SPLIT_REGEX
143+
: /\s+(?![^(]*\))/,
144+
);
127145
for (const arg of args) {
128146
const processedColor = processColor(arg);
129147
if (processedColor != null) {
@@ -192,6 +210,28 @@ function parseBoxShadowString(rawBoxShadows: string): Array<BoxShadowValue> {
192210
}
193211

194212
function parseLength(length: string): ?number {
213+
if (enableOptimizedBoxShadowParsing()) {
214+
// Use pre-compiled regex for performance
215+
const match = LENGTH_PARSE_REGEX.exec(length);
216+
217+
if (!match) {
218+
return null;
219+
}
220+
221+
const value = parseFloat(match[1]);
222+
if (Number.isNaN(value)) {
223+
return null;
224+
}
225+
226+
// match[2] is 'px' or undefined
227+
// If no unit and value is not 0, return null
228+
if (match[2] == null && value !== 0) {
229+
return null;
230+
}
231+
232+
return value;
233+
}
234+
195235
// matches on args with units like "1.5 5% -80deg"
196236
const argsWithUnitsRegex = /([+-]?\d*(\.\d+)?)([\w\W]+)?/g;
197237
const match = argsWithUnitsRegex.exec(length);

packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,17 @@ const definitions: FeatureFlagDefinitions = {
10011001
},
10021002
ossReleaseStage: 'none',
10031003
},
1004+
enableOptimizedBoxShadowParsing: {
1005+
defaultValue: false,
1006+
metadata: {
1007+
dateAdded: '2026-02-26',
1008+
description:
1009+
'Hoists regex patterns to module scope and optimizes parseLength in processBoxShadow for improved performance.',
1010+
expectedReleaseValue: true,
1011+
purpose: 'experimentation',
1012+
},
1013+
ossReleaseStage: 'none',
1014+
},
10041015
externalElementInspectionEnabled: {
10051016
defaultValue: true,
10061017
metadata: {

packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<8cd3151384989b1a4e8ec204943e2435>>
7+
* @generated SignedSource<<03688450419694f6d3f4fc709df4de9a>>
88
* @flow strict
99
* @noformat
1010
*/
@@ -33,6 +33,7 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
3333
animatedShouldUseSingleOp: Getter<boolean>,
3434
deferFlatListFocusChangeRenderUpdate: Getter<boolean>,
3535
disableMaintainVisibleContentPosition: Getter<boolean>,
36+
enableOptimizedBoxShadowParsing: Getter<boolean>,
3637
externalElementInspectionEnabled: Getter<boolean>,
3738
fixImageSrcDimensionPropagation: Getter<boolean>,
3839
fixVirtualizeListCollapseWindowSize: Getter<boolean>,
@@ -159,6 +160,11 @@ export const deferFlatListFocusChangeRenderUpdate: Getter<boolean> = createJavaS
159160
*/
160161
export const disableMaintainVisibleContentPosition: Getter<boolean> = createJavaScriptFlagGetter('disableMaintainVisibleContentPosition', false);
161162

163+
/**
164+
* Hoists regex patterns to module scope and optimizes parseLength in processBoxShadow for improved performance.
165+
*/
166+
export const enableOptimizedBoxShadowParsing: Getter<boolean> = createJavaScriptFlagGetter('enableOptimizedBoxShadowParsing', false);
167+
162168
/**
163169
* Enable the external inspection API for DevTools to communicate with the Inspector overlay.
164170
*/

0 commit comments

Comments
 (0)