Skip to content

Inline Platform within RN’s Babel preset - #57848

Open
robhogan wants to merge 1 commit into
mainfrom
export-D114647943
Open

Inline Platform within RN’s Babel preset#57848
robhogan wants to merge 1 commit into
mainfrom
export-D114647943

Conversation

@robhogan

@robhogan robhogan commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

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

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
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 6, 2026
@meta-codesync

meta-codesync Bot commented Aug 6, 2026

Copy link
Copy Markdown

@robhogan has exported this pull request. If you are a Meta employee, you can view the originating Diff in D114647943.

@retyui

retyui commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@robhogan I guess my PR to Metro doesn't make sense at all react/metro#1732 , right ?

// `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}]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you should avoid adding this plugin in tests (Jest) environment

As devs need to be able to mock Platform.OS value

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported p: Facebook Partner: Facebook Partner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants