Skip to content

Commit 32759ef

Browse files
sammy-SCfacebook-github-bot
authored andcommitted
Optimize processBoxShadow with pre-compiled regex patterns (facebook#55825)
Summary: 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
1 parent f66d201 commit 32759ef

3 files changed

Lines changed: 61 additions & 4 deletions

File tree

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
@@ -990,6 +990,17 @@ const definitions: FeatureFlagDefinitions = {
990990
},
991991
ossReleaseStage: 'none',
992992
},
993+
enableOptimizedBoxShadowParsing: {
994+
defaultValue: false,
995+
metadata: {
996+
dateAdded: '2026-02-26',
997+
description:
998+
'Hoists regex patterns to module scope and optimizes parseLength in processBoxShadow for improved performance.',
999+
expectedReleaseValue: true,
1000+
purpose: 'experimentation',
1001+
},
1002+
ossReleaseStage: 'none',
1003+
},
9931004
externalElementInspectionEnabled: {
9941005
defaultValue: true,
9951006
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<<fece18f7b3b23de103a64eca1a76a4c6>>
7+
* @generated SignedSource<<0d5cf6a975687f5bf540b16a997809a7>>
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>,
@@ -158,6 +159,11 @@ export const deferFlatListFocusChangeRenderUpdate: Getter<boolean> = createJavaS
158159
*/
159160
export const disableMaintainVisibleContentPosition: Getter<boolean> = createJavaScriptFlagGetter('disableMaintainVisibleContentPosition', false);
160161

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

0 commit comments

Comments
 (0)