Inline Platform within RN’s Babel preset (#57848) - #57848
Conversation
|
@robhogan has exported this pull request. If you are a Meta employee, you can view the originating Diff in D114647943. |
|
@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}]); |
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
There was a problem hiding this comment.
Thanks - I hadn't considered that and worth checking, but I think we're safe. The inline plugin is a no-op unless caller.platform is a non-empty string - i.e, it only inlines if platform is explicitly set at transform time.
The RN jest preset does not set platform - it uses babel-jest with no overrides
react-native/packages/jest-preset/jest-preset.js
Lines 31 to 32 in 2dc5e3a
That picks up Babel configs, but we don't encourage setting platform there either, and the template doesn't:
expo-jest does set a platform, but Expo use their own preset entirely (I've added a line exporting this plugin so they can reuse it)
This is intended to replace Metro's plugin, but all credit to you for identifying the problem here, it was reviewing that PR that led me to conclude that inlining living where it does/did in Metro is just unsalvageable. There are no good options that late in the transform pipeline (dealing with Here, we can safely couple to RN, and OOT forks can modify their fork of the plugin if they need. |
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 provenance is obscured and fragile to track. 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.
Reviewed By: huntie
Differential Revision: D114647943
Platform within RN’s Babel presetPlatform within RN’s Babel preset (#57848)
2dc5e3a to
a802f54
Compare
|
This pull request has been merged in 40c0612. |
…lt on via `@react-native/metro-babel-transformer`) (#57973) Summary: `react-native/babel-preset` inlines `Platform.OS` and `Platform.select(...)` whenever it is handed a `platform` (since yesterday - #57848 ), but this is over-zealous - `platform` is a pre-existing transform option whose intent is to set the transform target. In some cases (eg, to allow `Platform` mocking from tests), we don't want `Platform` inlining, even though `platform` may already be passed for other reasons. Gate the inlining behind a separate `inlinePlatform` opt-in, resolved as `options.inlinePlatform ?? babel.caller(...) ?? false`, mirroring how `platform` and `unstable_transformProfile` are already resolved. Metro already models this as a distinct transform option, so both Babel transformers pass it straight through. The caller channel covers the case where the preset is named in a `babel.config.js` and so receives no preset options at all. Bundling is unaffected: Metro sets `inlinePlatform` on every transform, so `Platform` continues to be inlined through `react-native/metro-babel-transformer`. Changelog: [General][Changed] - `Platform.OS` and `Platform.select(...)` inlining in `react-native/babel-preset` now requires the `inlinePlatform` option in addition to `platform` Reviewed By: GijsWeterings Differential Revision: D116281656
…lt on via `@react-native/metro-babel-transformer`) Summary: X-link: react/react-native#57973 `react-native/babel-preset` inlines `Platform.OS` and `Platform.select(...)` whenever it is handed a `platform` (since yesterday - react/react-native#57848 ), but this is over-zealous - `platform` is a pre-existing transform option whose intent is to set the transform target. In some cases (eg, to allow `Platform` mocking from tests), we don't want `Platform` inlining, even though `platform` may already be passed for other reasons. Gate the inlining behind a separate `inlinePlatform` opt-in, resolved as `options.inlinePlatform ?? babel.caller(...) ?? false`, mirroring how `platform` and `unstable_transformProfile` are already resolved. Metro already models this as a distinct transform option, so both Babel transformers pass it straight through. The caller channel covers the case where the preset is named in a `babel.config.js` and so receives no preset options at all. Bundling is unaffected: Metro sets `inlinePlatform` on every transform, so `Platform` continues to be inlined through `react-native/metro-babel-transformer`. Changelog: [General][Changed] - `Platform.OS` and `Platform.select(...)` inlining in `react-native/babel-preset` now requires the `inlinePlatform` option in addition to `platform` Reviewed By: GijsWeterings Differential Revision: D116281656 fbshipit-source-id: 0230cd74f38661862396391da62cf81b34773b85
…lt on via `@react-native/metro-babel-transformer`) (#57973) Summary: Pull Request resolved: #57973 `react-native/babel-preset` inlines `Platform.OS` and `Platform.select(...)` whenever it is handed a `platform` (since yesterday - #57848 ), but this is over-zealous - `platform` is a pre-existing transform option whose intent is to set the transform target. In some cases (eg, to allow `Platform` mocking from tests), we don't want `Platform` inlining, even though `platform` may already be passed for other reasons. Gate the inlining behind a separate `inlinePlatform` opt-in, resolved as `options.inlinePlatform ?? babel.caller(...) ?? false`, mirroring how `platform` and `unstable_transformProfile` are already resolved. Metro already models this as a distinct transform option, so both Babel transformers pass it straight through. The caller channel covers the case where the preset is named in a `babel.config.js` and so receives no preset options at all. Bundling is unaffected: Metro sets `inlinePlatform` on every transform, so `Platform` continues to be inlined through `react-native/metro-babel-transformer`. Changelog: [General][Changed] - `Platform.OS` and `Platform.select(...)` inlining in `react-native/babel-preset` now requires the `inlinePlatform` option in addition to `platform` Reviewed By: GijsWeterings Differential Revision: D116281656 fbshipit-source-id: 0230cd74f38661862396391da62cf81b34773b85
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 provenance is obscured and fragile to track. Metro compensates by matching on the localidentifier 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.Reviewed By: huntie
Differential Revision: D114647943