No ios/ or android/ folders
How ReadyNative keeps native projects generated instead of committed - where native config lives, how to add a native library, how SDK upgrades stay a version bump, and how that pairs with over-the-air updates.
Hey - open the repo and you won't find ios/ or android/. That's on purpose. ReadyNative uses
Expo's Continuous Native Generation
(CNG): the Xcode and Gradle projects are build output, generated from your config every time you
build, the same way dist/ is generated from src/.
Why it matters
- Upgrades are a version bump. Moving to the next Expo SDK is
bunx expo install expo@latest --fixand a rebuild - there's no hand-merged Podfile, AppDelegate orbuild.gradleto reconcile. - Native config is reviewable. Bundle ids, permissions, entitlements and plugins are a few lines
of TypeScript in a pull request, not a 3,000-line
project.pbxprojdiff. - Modules can't drift. Picking
--widgets expo-widgetsadds a widget extension target, an App Group andNSSupportsLiveActivities; pickingnoneremoves them. Nobody edits Xcode by hand, so nothing is left behind. - No Mac required. EAS generates and builds the native projects in the cloud.
Where native config lives
| What | Where |
|---|---|
| App name, bundle id, scheme, brand colours, store ids | readynative.config.ts - the one file you edit |
| Everything else in the Expo config | app.config.ts, which reads readynative.config.ts |
| What each module needs (plugins, permissions, privacy) | the module's app patch, merged by setup into .readynative.json and applied by app.config.ts |
| Anything the Expo config can't express | a config plugin in plugins/ (e.g. with-scene-lifecycle.js) |
| Apple privacy manifest | composed from every selected module's privacy block - see Privacy |
Want to see the result? Generate the projects locally and look - they're gitignored:
bunx expo prebuild --cleanbun run ios / bun run android (and every eas build) do that for you before compiling.
Adding a native library
- Install it with
bunx expo install <package>so you get the version that matches the SDK. - If it ships a config plugin, add it to
pluginsinapp.config.tswith its options. - Rebuild the dev build (
bun run ios) - JavaScript reloads can't add native code.
That's the whole flow; there's no pod install to run or Xcode setting to flip.
Pairs with over-the-air updates
Because the native layer is generated from config, it's clear what needs a store release and what
doesn't. runtimeVersion follows the app version (policy: "appVersion"), and every EAS build
profile publishes to its own channel (development, preview, production in eas.json), so:
- JavaScript, assets, translations, screens - ship with
eas update; installed apps show the built-in Update ready banner and restart into it. Your first update walks through it. - Anything in the table above, or a new native library - bump the version and ship a store build; an update can never reach a binary it doesn't match.
When you'd commit native folders
Rarely. If you must hand-edit native code that no config plugin covers, you can run
bunx expo prebuild, commit ios/ and android/, and maintain them yourself from then on - but
you give up regenerated upgrades and module switching. Writing a small config plugin in plugins/
is almost always the better trade.