Skip to content

Breaking: Remove Platform inlining, now done by React Native - #2195

Open
robhogan wants to merge 1 commit into
mainfrom
pr2195
Open

robhogan wants to merge 1 commit into
mainfrom
pr2195

Conversation

@robhogan

@robhogan robhogan commented Nov 7, 2026

Copy link
Copy Markdown
Collaborator

Since React Native 0.88, @react-native/babel-preset inlines Platform.OS and Platform.select itself (react/react-native#57848), whenever Metro sets caller.inlinePlatform:

https://github.com/react/react-native/blob/v0.89.0-rc.0/packages/react-native-babel-preset/src/configs/main.js#L165-L167

Metro's own implementation in inline-plugin runs after ESM has been lowered to CommonJS, so it can only match on names. It inlines .OS on any top-level binding called Platform, whether or not it's React Native's, and misses some genuine imports. RN's plugin tracks bindings back to React Native's Platform module, so Metro's has been redundant since 0.88, and occasionally wrong.

This removes platform inlining from inline-plugin, which still inlines __DEV__ and process.env.NODE_ENV, and narrows its options to {dev}. Metro still sets the inlinePlatform transform option and passes it through to Babel - that's what enables RN's plugin.

Projects on React Native < 0.88 with this Metro will lose platform inlining, so this is breaking. RN and Expo both pin Metro, so in practice that only affects projects overriding Metro's version. Expo's fork of the transform worker already skips inlinePlugin.

Changelog:

 - **[Breaking]**: Remove Metro's `Platform.OS` / `Platform.select` inlining, which `@react-native/babel-preset` performs since React Native 0.88

Test plan:
Built a React Native 0.89.0-rc.0 app (@react-native/metro-config and @react-native/babel-preset from npm) with this branch's transform worker, for iOS and Android, dev and prod, unminified, and compared against main. The entry point reads Platform.OS through a named import, a namespace import, a deep import of Libraries/Utilities/Platform and require('react-native').Platform, and calls Platform.select. All are inlined on both.

The bundles, including all of React Native's own Platform uses, are byte-identical except for one line in a local module that declares an unrelated const Platform = {OS: ...}, which main wrongly inlined:

-  var os = exports.os = "ios";
+  var os = exports.os = Platform.OS;

Since React Native 0.88, `@react-native/babel-preset` inlines `Platform.OS` and `Platform.select` itself (react/react-native#57848), whenever Metro sets `caller.inlinePlatform`:

https://github.com/react/react-native/blob/v0.89.0-rc.0/packages/react-native-babel-preset/src/configs/main.js#L165-L167

Metro's own implementation in `inline-plugin` runs after ESM has been lowered to CommonJS, so it can only match on names. It inlines `.OS` on any top-level binding called `Platform`, whether or not it's React Native's, and misses some genuine imports. RN's plugin tracks bindings back to React Native's `Platform` module, so Metro's has been redundant since 0.88, and occasionally wrong.

This removes platform inlining from `inline-plugin`, which still inlines `__DEV__` and `process.env.NODE_ENV`, and narrows its options to `{dev}`. Metro still sets the `inlinePlatform` transform option and passes it through to Babel - that's what enables RN's plugin.

Projects on React Native < 0.88 with this Metro will lose platform inlining, so this is breaking. RN and Expo both pin Metro, so in practice that only affects projects overriding Metro's version. Expo's fork of the transform worker already skips `inlinePlugin`.

Changelog:
```
 - **[Breaking]**: Remove Metro's `Platform.OS` / `Platform.select` inlining, which `@react-native/babel-preset` performs since React Native 0.88
```

Test plan:
Built a React Native 0.89.0-rc.0 app (`@react-native/metro-config` and `@react-native/babel-preset` from npm) with this branch's transform worker, for iOS and Android, dev and prod, unminified, and compared against `main`. The entry point reads `Platform.OS` through a named import, a namespace import, a deep import of `Libraries/Utilities/Platform` and `require('react-native').Platform`, and calls `Platform.select`. All are inlined on both.

The bundles, including all of React Native's own `Platform` uses, are byte-identical except for one line in a local module that declares an unrelated `const Platform = {OS: ...}`, which `main` wrongly inlined:

```diff
-  var os = exports.os = "ios";
+  var os = exports.os = Platform.OS;
```
@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 Nov 7, 2026
@github-actions

github-actions Bot commented Nov 7, 2026

Copy link
Copy Markdown

Metro review: Looks good

Risk: medium · Commit: dca390b · 7 commands run

This PR removes Platform.OS and Platform.select inlining from inline-plugin, leaving it to @react-native/babel-preset, which turns it on through caller.inlinePlatform. It also narrows the plugin's options to {dev}. The published @react-native/babel-preset@0.88.1 does include inline-platform-plugin, gated on caller.inlinePlatform, and metro-babel-transformer still passes both inlinePlatform and platform. Tests, Flow, lint and API snapshots all pass. The cache key already covers inline-plugin.js, and the deleted helper was never part of it. So I found no problems. The cost is the documented break for React Native versions before 0.88.

No findings.

Verified
  • ✅ yarn jest --no-watchman packages/metro-transform-plugins packages/metro-transform-worker — 9 suites, 197 tests, including the new test for process.env.NODE_ENV write targets and the new 'doesn't replace Platform' test
  • ✅ yarn jest --no-watchman packages/metro/src/integration_tests packages/metro/src/DeltaBundler/__tests__ — 29 suites, 544 tests
  • ✅ flow check (yarn typecheck equivalent, custom temp dir) — No errors
  • ✅ yarn lint packages/metro-transform-plugins/src packages/metro-transform-worker/src
  • ✅ yarn verify-api-snapshots — InlinePluginOptions isn't in any public API snapshot; the metro-transform-plugins snapshot only exposes typeof inlinePlugin
  • ✅ npm pack @react-native/babel-preset@0.88 and grep src/configs/main.js and src/inline-platform-plugin.js — 0.88.1 ships inline-platform-plugin, which is enabled by caller.inlinePlatform (or options.inlinePlatform) and resolves Platform by binding provenance, which backs the description's claim
  • ✅ grep for remaining users of createInlinePlatformChecks, isWrapped and requireName in the inline plugin's options — No other users in the repo. The helper wasn't exported from the package index or listed in getTransformPluginCacheKeyFiles.

Not run: I didn't repeat the author's end-to-end test (comparing React Native 0.89.0-rc.0 app bundles against main). I did check the preset code it depends on. I didn't benchmark: the change only removes visitor work from the per-file transform worker and claims no performance gain. Nothing here is Windows-specific, so I didn't run anything on Windows.

Automated review by Claude. Advisory only: it never approves or blocks, and a maintainer makes the call.

This branch has not been deployed

No deployments
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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant