From 8b4cc336670b1762a8e3919364a48b1fcf0a1127 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Wed, 16 Sep 2026 01:12:03 -0700 Subject: [PATCH] Revert preserve Platform.select initializers (#58541) Summary: Reverts https://github.com/react/react-native/pull/58350, restoring the long-standing `Platform.select` inlining behavior. Per robhogan and vonovak: Metro has inlined Platform.select() (in release builds only, not configurable) forever really - it goes back to at least 2017, before the start of the Metro repo, eg: https://github.com/react/metro/commit/a317b9da6c2a09432bf1488cb350928000d8a3fa It's always had that behaviour where it collapses side-effects of inactive branches. Recently, the plugin was copied/re-implemented into RN. Right now (RN 0.88, Metro 0.87) they both have their own version of the plugin and Metro's runs after RN's, but Metro's shouldn't find anything left to inline and should be a no-op. I couldn't remove it from Metro immediately only because it'd need a major Metro release, but I plan to remove it soon. So RN's is effectively a continuation of Metro's 8 year old behaviour - we just moved the implementation. Changelog: [General][Fixed] - Restore long-standing Platform.select inlining behavior that collapses side effects of inactive branches See discussion: https://github.com/react/react-native/pull/58442 Differential Revision: D120147924 --- .../__tests__/inline-platform-plugin-test.js | 19 -------------- .../src/inline-platform-plugin.js | 26 +++++-------------- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js b/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js index fd217bb5258d..2e182e5f763b 100644 --- a/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js +++ b/packages/react-native-babel-preset/src/__tests__/inline-platform-plugin-test.js @@ -483,25 +483,6 @@ describe('Platform.select', () => { expect(select('{ios: 1, ios: 2}')).toContain('const value=2'); }); - test('does not discard impure initializers', () => { - expectUnchanged(` - const value = require('react-native').Platform.select({ - ios: first(), - android: android(), - ios: last(), - }); - `); - }); - - test('does not mutate object methods when bailing out on impure initializers', () => { - expectUnchanged(` - const value = require('react-native').Platform.select({ - ios() { return 1; }, - android: sideEffect(), - }); - `); - }); - test('does not inline computed keys', () => { expect(select('{[key]: 1, default: 2}')).toContain('Platform.select'); }); diff --git a/packages/react-native-babel-preset/src/inline-platform-plugin.js b/packages/react-native-babel-preset/src/inline-platform-plugin.js index 2bba88342fa7..f70a24b3e95e 100644 --- a/packages/react-native-babel-preset/src/inline-platform-plugin.js +++ b/packages/react-native-babel-preset/src/inline-platform-plugin.js @@ -443,9 +443,7 @@ module.exports = function inlinePlatformPlugin( if (t.isObjectProperty(property)) { return property.value; } - // Clone: toExpression mutates in place, e.g. `ios() {}` would be - // left mutated if the purity check below bails out. - return t.toExpression(t.cloneNode(property)); + return t.toExpression(property); } } return fallback(); @@ -522,25 +520,13 @@ module.exports = function inlinePlatformPlugin( return; } - const replacement = findProperty(spec, platform, () => - findProperty(spec, 'native', () => - findProperty(spec, 'default', () => t.identifier('undefined')), + path.replaceWith( + findProperty(spec, platform, () => + findProperty(spec, 'native', () => + findProperty(spec, 'default', () => t.identifier('undefined')), + ), ), ); - // Inlining must not drop side effects from discarded property values. - // Assess the property itself: an ObjectMethod has no `.value`, so - // checking `property.value` would wrongly treat every method as - // impure and skip inlining. - if ( - spec.properties.every( - property => - (t.isObjectProperty(property) && - property.value === replacement) || - path.scope.isPure(property), - ) - ) { - path.replaceWith(replacement); - } }, }, };