Inline Platform within RN’s Babel preset - #57848
Open
robhogan wants to merge 1 commit into
Open
Conversation
Summary:
Adds a React Native-owned Babel plugin that inlines `Platform.OS` and
`Platform.select(...)`, from
`react-native/babel-preset` at the front of the preset's plugin list (importantly, before
the preset lowers ES modules to CommonJS).
Metro already inlines `Platform`, but its matcher (`metro-transform-plugins`'
`inline-plugin` via `createInlinePlatformChecks`) runs *after* ESM has been
lowered to CommonJS. By then a genuine `import {Platform} from 'react-native'`
has become `_reactNative.Platform.OS` and the specifier proving the value came
from React Native is gone. Metro compensates by matching on the local
identifier spelling - so `Platform.OS` inlines whether or not `Platform` is
actually React Native's, and a genuine deep or RN-relative import may be missed entirely.
`Platform` inlining is in any case much more of an RN concern than a Metro one - it has to be aware of RN APIs and module names, and even the module’s path.
This plugin recognises:
- `import {Platform} from 'react-native'` (including aliased imports)
- `import * as RN from 'react-native'`, uses of `RN.Platform`
- `import P from 'react-native/Libraries/Utilities/Platform'`
- the equivalent `require` forms, plus destructuring and immutable aliases
- RN's internal relative imports, e.g. `../../Utilities/Platform`
…and deliberately does *not* recognise anything it cannot prove - a bare
`Platform.OS` global, `React.Platform.OS`, or a same-named import from another
package.
Metro's late matcher is left fully enabled and remains responsible for
those historical forms, so this change is purely additive: it inlines genuine
imports Metro was missing, and changes nothing Metro already handled. Metro’s plugin will be deprecated and removed in future.
Notable details:
- Relative RN imports are resolved lexically, with no filesystem access and no
platform-extension resolution — the identity we need is the extension-less
module `<rn-root>/Libraries/Utilities/Platform`, and Metro picks
`Platform.ios.js` / `Platform.android.js` later. The RN package root is
identified by directory name, which covers `node_modules/react-native`,
`packages/react-native` and pnpm layouts alike, and rejects
`react-native-web` and friends (who would be expected to adjust the paths in their forks)
- Imports are left in place. Removing them would change dependency collection,
so it is handled separately.
- A no-platform build (`null`, or the empty string that RN's Jest preprocessor
passes) is a no-op — inlining `Platform.OS` to `""` would be wrong.
- The plugin source is added to the preset's `getCacheKey` so edits invalidate Metro's transform cache.
Changelog:
[General][Changed] - Inline `Platform.OS` and `Platform.select(...)` for
React Native `Platform` imports during the Babel preset, covering some cases that were previously left un-inlined.
Differential Revision: D114647943
|
@robhogan has exported this pull request. If you are a Meta employee, you can view the originating Diff in D114647943. |
Contributor
|
@robhogan I guess my PR to Metro doesn't make sense at all react/metro#1732 , right ? |
retyui
reviewed
Aug 7, 2026
| // `disableImportExportTransform` is set), while the source-level import that | ||
| // proves provenance is still intact. It is a no-op when `platform` is null or | ||
| // the empty string. | ||
| extraPlugins.push([require('../inline-platform-plugin'), {platform}]); |
Contributor
There was a problem hiding this comment.
I believe you should avoid adding this plugin in tests (Jest) environment
As devs need to be able to mock Platform.OS value
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
Adds a React Native-owned Babel plugin that inlines
Platform.OSandPlatform.select(...), fromreact-native/babel-presetat the front of the preset's plugin list (importantly, beforethe preset lowers ES modules to CommonJS).
Metro already inlines
Platform, but its matcher (metro-transform-plugins'inline-pluginviacreateInlinePlatformChecks) runs after ESM has beenlowered to CommonJS. By then a genuine
import {Platform} from 'react-native'has become
_reactNative.Platform.OSand the specifier proving the value camefrom React Native is gone. Metro compensates by matching on the local
identifier spelling - so
Platform.OSinlines whether or notPlatformisactually React Native's, and a genuine deep or RN-relative import may be missed entirely.
Platforminlining is in any case much more of an RN concern than a Metro one - it has to be aware of RN APIs and module names, and even the module’s path.This plugin recognises:
import {Platform} from 'react-native'(including aliased imports)import * as RN from 'react-native', uses ofRN.Platformimport P from 'react-native/Libraries/Utilities/Platform'requireforms, plus destructuring and immutable aliases../../Utilities/Platform…and deliberately does not recognise anything it cannot prove - a bare
Platform.OSglobal,React.Platform.OS, or a same-named import from anotherpackage.
Metro's late matcher is left fully enabled and remains responsible for
those historical forms, so this change is purely additive: it inlines genuine
imports Metro was missing, and changes nothing Metro already handled. Metro’s plugin will be deprecated and removed in future.
Notable details:
platform-extension resolution — the identity we need is the extension-less
module
<rn-root>/Libraries/Utilities/Platform, and Metro picksPlatform.ios.js/Platform.android.jslater. The RN package root isidentified by directory name, which covers
node_modules/react-native,packages/react-nativeand pnpm layouts alike, and rejectsreact-native-weband friends (who would be expected to adjust the paths in their forks)so it is handled separately.
null, or the empty string that RN's Jest preprocessorpasses) is a no-op — inlining
Platform.OSto""would be wrong.getCacheKeyso edits invalidate Metro's transform cache.Changelog:
[General][Changed] - Inline
Platform.OSandPlatform.select(...)forReact Native
Platformimports during the Babel preset, covering some cases that were previously left un-inlined.Differential Revision: D114647943