React Native Expo vs. CLI: Build Optimization, OTA Patch Delivery, and Output Binary Size
Understanding the Core Differences: Expo Go vs. React Native CLI
The choice between Expo’s managed workflow (Expo Go) and the React Native CLI workflow is a fundamental decision that impacts development speed, build optimization, and the ability to deliver Over-The-Air (OTA) updates. Expo Go offers a streamlined development experience by abstracting away native build complexities. It bundles your JavaScript code with a pre-built native runtime, allowing for rapid iteration and testing directly on devices or simulators without requiring Xcode or Android Studio for every change. This is achieved by running your app within the Expo Go client app. Conversely, the React Native CLI workflow gives you full control over the native projects (iOS and Android). You manage dependencies, configure native modules, and perform native builds yourself. This granular control is essential for apps requiring custom native code or specific build configurations that aren’t supported by Expo’s managed workflow.
Build Optimization Strategies
Expo Managed Workflow (Expo Go) Build Optimization
When using Expo Go, build optimization primarily revolves around managing your JavaScript bundle size and leveraging Expo’s build services. Expo’s bundler (Metro) is configured to optimize your JavaScript output. However, the native binary itself is a fixed size determined by the Expo Go client. You don’t directly control the native code optimization in this scenario, as it’s managed by Expo. The key is to keep your JavaScript dependencies lean and ensure efficient code splitting where applicable, though Expo’s managed workflow has limitations on advanced code splitting compared to bare React Native.
To inspect your JavaScript bundle size in an Expo project:
- Run
npx expo-cli --no-install --max-workers 1 --bundle-output bundle.js --platform android --dev false --minify true --entry-point index.js(orios) to generate a bundle and analyze its size. - Use tools like
react-native-bundle-visualizer(though integration with Expo’s build process requires manual steps).
React Native CLI Build Optimization
With the React Native CLI, you have direct control over native build optimizations and binary size. This involves:
- ProGuard (Android): Enables code shrinking, obfuscation, and optimization for release builds.
- R8 (Android): The successor to ProGuard, offering similar and improved optimization capabilities.
- Hermes Engine: A JavaScript engine optimized for React Native, reducing startup time and memory usage.
- Native Code Stripping: Removing unused native libraries and code.
- Asset Optimization: Compressing images and other assets.
- Build Variants: Creating different build configurations (e.g., debug, release, staging) with varying optimization levels.
To enable R8/ProGuard for Android release builds, modify your android/app/build.gradle file:
android {
// ... other configurations
buildTypes {
release {
// Enables code shrinking, obfuscation, and optimization for release builds.
minifyEnabled true
// Enables resource shrinking, which removes unused resources from the app.
shrinkResources true
// Specifies the ProGuard rules file.
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// ...
}
To enable Hermes for your React Native project, modify android/app/build.gradle:
android {
// ...
defaultConfig {
// ...
// Enable Hermes engine
enableHermes true
}
// ...
}
For iOS, optimizations are largely handled by Xcode’s build settings. Ensure you are building for “Release” configuration. The Hermes engine can also be enabled for iOS by setting hermes_enabled=true in ios/Podfile.
Over-The-Air (OTA) Patch Delivery
Expo Managed Workflow and OTA Updates
Expo’s managed workflow excels at OTA updates through its “EAS Update” service (formerly Expo Updates). This service allows you to push JavaScript and asset changes directly to your users’ devices without requiring a new app store submission. The native runtime remains the same, but your JS bundle is updated.
Key concepts:
- Channels: You can define channels (e.g.,
production,staging,beta) to manage different versions of your app. - Rollbacks: EAS Update supports rolling back to previous published updates if a new one introduces issues.
- Publishing: Updates are published using the EAS CLI.
To publish an update using EAS CLI:
# Ensure you have EAS CLI installed: npm install -g eas-cli # Log in to your Expo account: eas login # Configure your project for EAS Build and Updates: eas build:configure # Publish an update to the 'production' channel: eas update --branch production
The Expo Go app itself does not support OTA updates in the same way as a bare-built app. OTA updates are applicable to apps built using EAS Build (which generates standalone binaries) or apps ejected from Expo’s managed workflow.
React Native CLI and OTA Updates
For React Native CLI projects, OTA updates are typically handled by third-party services like CodePush (Microsoft App Center) or Sentry’s release health features. These services allow you to deploy updates to your JavaScript bundle and assets independently of app store releases.
Using CodePush:
- Installation: Install the
react-native-code-pushpackage. - Configuration: Link the package to your native projects and configure deployment keys.
- Deployment: Use the CodePush CLI to release updates.
First, install the package:
npm install --save react-native-code-push # or yarn add react-native-code-push
Then, link it (for older React Native versions or if auto-linking fails):
npx react-native link react-native-code-push
For Android, add the following to android/app/build.gradle:
apply from: "../../node_modules/react-native-code-push/android/push.gradle"
// ... inside android block
android {
// ...
defaultConfig {
// ...
// Add this line for CodePush
externalNativeBuild {
cmake {
// ...
}
}
}
// ...
}
For iOS, add the following to your ios/Podfile:
target 'YourAppName' do # ... other pods pod 'CodePush', '~> 7.0' # Use the latest version end
After setup, you can release an update:
# Replace YOUR_APP_NAME and YOUR_DEPLOYMENT_KEY appcenter codepush release-react -a YOUR_APP_NAME -d Production -k YOUR_DEPLOYMENT_KEY
Output Binary Size Comparison
Expo Managed Workflow (Expo Go)
The Expo Go client app is a single, large binary that contains a vast number of pre-compiled native modules. When you build a standalone app using EAS Build from an Expo managed project, Expo bundles your JS code with a *subset* of these modules. The resulting binary size is generally larger than a lean, custom-built React Native CLI app because it includes the Expo SDK’s runtime and a wide array of common modules. However, it’s smaller than the full Expo Go client.
Typical Size Ranges (Release Builds):
- Android: 30MB – 60MB+
- iOS: 40MB – 70MB+
This size is a trade-off for the rapid development and ease of use provided by the managed workflow.
React Native CLI
With the React Native CLI, you have fine-grained control over which native modules are included in your build. By carefully selecting dependencies and leveraging optimization techniques like R8/ProGuard and Hermes, you can achieve significantly smaller binary sizes.
Typical Size Ranges (Release Builds with Optimizations):
- Android: 15MB – 35MB+
- iOS: 20MB – 45MB+
The exact size depends heavily on the number of native dependencies, included assets, and the effectiveness of the build optimizations applied. For applications where binary size is a critical factor (e.g., for users with limited storage or slow network connections), the React Native CLI approach offers superior control.
When to Choose Which
Choose Expo Managed Workflow (Expo Go) If:
- You prioritize rapid development and iteration speed.
- Your app doesn’t require custom native modules or complex native configurations.
- You want to leverage Expo’s extensive SDK and services (like EAS Update) for easy OTA delivery.
- You are comfortable with the trade-off of potentially larger binary sizes.
- Your team is less experienced with native mobile development complexities.
Choose React Native CLI If:
- Your app requires custom native modules (e.g., specific hardware access, third-party SDKs not supported by Expo).
- You need maximum control over the native build process and optimization.
- Minimizing binary size is a critical requirement.
- You need to integrate with specific native build tools or CI/CD pipelines that are not compatible with Expo’s managed build process.
- Your team has strong expertise in native iOS and Android development.
Ultimately, the decision hinges on your project’s specific requirements, team expertise, and development priorities. Both workflows are powerful, but they cater to different needs and offer distinct advantages in build optimization and update delivery.