Repository navigation
fix: support Expo modules in React Native CLI apps - #487
Conversation
The CLI ran `expo prebuild` whenever it found Expo and an app.json. In a React Native CLI app that installed Expo modules, prebuild regenerated the committed android/ and ios/ directories and deleted the brownfield library module, so packaging failed. Only run prebuild when the native directory is missing or gitignored (CNG). Also document the manual iOS step for these apps: Expo modules only register through the Expo host runtime, which the config plugin enables in Expo apps.
…Expo Expo modules only register through the Expo host runtime, which the config plugin enables in Expo apps. create-react-native-brownfield now does the same when the app's package.json depends on expo: the Podfile framework target gets the Expo host env and the EXPO_SDK_GTE_55 define, and the framework interface gets ensureExpoModulesProvider(). Re-running it after adding Expo switches an existing framework target over. Only the app's own package.json counts, so a hoisted expo in a monorepo does not turn the Expo host on. The Podfile hook no longer assumes Expo's podfile_properties exists.
Three things broke packaging with precompiled Expo modules outside Expo apps:
- `--use-prebuilt-expo` only picked which XCFrameworks to copy. It never told
CocoaPods to use precompiled modules, which only Expo-generated Podfiles turn
on, so a bare app failed with "XCFramework not found". The CLI now sets
EXPO_USE_PRECOMPILED_MODULES from the flag.
- Older React Native versions wrote SWIFT_ENABLE_EXPLICIT_MODULES = NO into app
projects. With precompiled Expo the React Native pods are static libraries,
and with explicit modules off Xcode can't find their module maps when it
verifies the framework's Swift interface ("cannot load underlying module").
The CLI turns explicit modules on for these builds.
- Expo's precompiled modules are built against the React Native version the SDK
targets, and crash at runtime on another minor version. The CLI now defaults
to building Expo from source in that case and warns if it's forced.
With precompiled Expo, CocoaPods symlinks expo-image's dependency XCFrameworks into node_modules. The package step copied the links, so the output broke when moved to another machine, and stripping signatures modified the files in node_modules.
- warn when git can't tell whether the native dir is gitignored - make the Expo host Podfile edit handle CRLF, comments and fail loudly - respect Podfile.properties.json and an exported opt-out for precompiled Expo - keep a user-provided SWIFT_ENABLE_EXPLICIT_MODULES value - add tests for the new defaults
CocoaPods renames the framework target's Sources phase comment to "Sources", so the idempotency check missed it and a re-run added a second phase, which broke the next pod install.
|
|
||
| On iOS, Expo modules only work when React Native starts through Brownfield's Expo runtime. You set this up once in your React Native project, then update your native iOS app. | ||
|
|
||
| ### In your React Native project |
There was a problem hiding this comment.
Is there a possibility to have the manual steps collapsed into brownfield CLI? For example, using spm instead of manual linking the frameworks?
There was a problem hiding this comment.
Thanks Hur, good call! The setup command now raises the framework target to iOS 16.4, and the package:ios script it generates builds a local Swift package by default, so the host app adds one package instead of dragging in every XCFramework. I tested it with expo-image in a fresh Xcode app.
The host app still needs a one-time step to add that package in Xcode (plus the ensureExpoModulesProvider() call Expo apps already make). I didn't want to make the CLI edit the host app's Xcode project in this PR. It's doable, but I'm worried it'd be hard to get right for every setup. We could give it a try as a separate PR.
…-bare-expo # Conflicts: # docs/docs/docs/guides/_meta.json
Also recommend the local Swift package in the Expo modules guide, so host apps don't add every Expo XCFramework by hand.
The host app then adds one local Swift package instead of every XCFramework. Existing package:ios scripts are left as they are.
Summary
Expo modules didn't work in bare RN apps packaged with brownfield. Issues found and fixed:
expo prebuildwiped the brownfield module. The CLI ran prebuild for any project with Expo and anapp.json, which regenerated the committedandroid/andios/dirs without the library module. It now skips prebuild when the native dir is committed.create-react-native-brownfieldnow sets it up when the app depends onexpo, also when re-run after adding Expo. On a re-run it also raises an existing framework target to iOS 16.4.--use-prebuilt-expodidn't work in bare apps. It never setEXPO_USE_PRECOMPILED_MODULES, sopod installbuilt Expo from source and packaging failed with "XCFramework not found". The CLI now sets it from the flag, and without the flag it respects an exportedEXPO_USE_PRECOMPILED_MODULES=0orPodfile.properties.jsonturning it off.SWIFT_ENABLE_EXPLICIT_MODULES = NOthat RN'spod installwrites into the project. The CLI now passesSWIFT_ENABLE_EXPLICIT_MODULES=YESfor these builds.node_modules, and the CLI copied the links instead of the files. The package broke when moved off the build machine (e.g. committed to the host app's repo), and stripping signatures edited files innode_modules. Found while testing this, present since Expo 56 support (feat: add Expo 56 support #360). The CLI now copies the real files.pod installbroke the nextpod install. CocoaPods renames the framework's Sources phase, so the duplicate check missed it and added a second one ("no parent for object BrownfieldLib.swift"). This was on main already, but our docs now tell users to re-run the command after adding Expo. The check now matches any Sources phase on the target.Docs: a new guide, Expo Modules in React Native CLI Apps, with the steps for these apps. The RN CLI setup guide and the iOS guide link to it, so apps without Expo can skip it. The
package:iosscript thatcreate-react-native-brownfieldgenerates now passes--add-spm-package, so the host app adds one local Swift package instead of every XCFramework (existing scripts aren't changed).Behavior change
Prebuild used to run for every Expo project. Now it only runs when
ios/orandroid/is missing or gitignored. Two cases get no automatic prebuild anymore:npx expo prebuildthemselves after changingapp.jsonor plugins..git) with native dirs already present.Nothing fails in either case, but the package is built from native dirs that weren't regenerated. If that's a concern, we could add a flag to force prebuild. Happy to add it if you think committed CNG dirs are common.
Testing
node_modules/expo-imagehidden, still builds and runs.--add-spm-package) with Expo: a host Xcode app that only adds the package builds and renders expo-image.package:iosscript produces the Swift package, and a host Xcode app that only adds it renders the RN screen.strip-framework-binary.test.tsfails onmaintoo).🤖 Generated with Claude Code