Overview
TheexportJs option enables JavaScript bundling for targets that use React Native. When enabled, release builds will bundle your JavaScript code with Metro and embed the bundle/assets for offline use.
When to Use exportJs
EnableexportJs: true when your target:
- Runs React Native views (App Clip, Share Extension)
- Needs to work without a Metro connection
- Should include JavaScript in release builds
- Shares code with your main React Native app
Configuration
Enable JavaScript bundling in your target’s config file:How It Works
WhenexportJs: true is set:
Build Phase Linking
Build Phase Linking
The plugin links the main target’s “Bundle React Native code and images” build phase to your extension target. This ensures:
- Metro bundles JavaScript during release builds
- Assets are copied to the extension bundle
- Source maps are generated for debugging
- Both targets share the same bundle configuration
Development vs Release
Development vs Release
- Development builds (
Debug): Connect to Metro as usual - Release builds (
Release): Use embedded JavaScript bundle
Bundle Location
Bundle Location
The JavaScript bundle is embedded at:React Native automatically loads the embedded bundle when running in release mode.
Example: App Clip with React Native
Here’s a complete setup for an App Clip that uses React Native:1. Configure CocoaPods
Create apods.rb file in your repository root:
2. Enable exportJs
3. Detect the Current Target
Useexpo-application to determine which target is running:
4. Prebuild and Run
Example: Share Extension with React Native
Share Extensions can also use React Native to provide custom sharing UI:Bundle Size Considerations
Conditional Bundling
Include only needed code
Include only needed code
Use Metro’s This keeps the App Clip bundle small while maintaining full functionality in the main app.
require context and dynamic imports to conditionally load code:Separate entry points
Separate entry points
For maximum control, use different entry points:
Development Workflow
Testing with Metro
During development, your extension connects to Metro like the main app:Testing Release Builds
To test the embedded bundle:- Build in Release mode in Xcode
- Or use EAS Build:
eas build --profile preview - Install on device and test without Metro
Troubleshooting
Bundle not found in release build
Bundle not found in release build
Ensure:
exportJs: trueis set in your target config- You ran
npx expo prebuild --cleanafter adding it - You’re building in Release mode (not Debug)
- The “Bundle React Native code and images” build phase exists
App crashes on launch in extension
App crashes on launch in extension
Check:
- Your
pods.rbfile includes React Native dependencies - You’re detecting the bundle ID correctly with
expo-application - There are no app-specific dependencies in shared code
- The AppDelegate is properly configured for extensions
Bundle too large for App Clip
Bundle too large for App Clip
App Clips have a 15 MB limit. Reduce size by:
- Removing unused npm packages
- Enabling Hermes for better compression
- Stripping development code in production
- Using native UI instead of React Native
- Compressing images and removing unused assets
Metro connection issues in development
Metro connection issues in development
If your extension can’t connect to Metro:
- Check Info.plist has
NSAppTransportSecurityexception - Verify Metro is running on the expected port
- Ensure your device and dev machine are on the same network
- Try connecting to Metro by IP instead of localhost
Performance Tips
- Use Hermes - Smaller bundles, faster startup
- Lazy load features - Use dynamic imports for non-critical code
- Optimize assets - Compress images, use vector graphics where possible
- Profile bundle size - Use Metro’s bundle visualizer
- Test on device - Simulator performance doesn’t reflect real-world usage
Next Steps
- Set up CocoaPods for React Native targets
- Learn about shared files between targets
- Configure custom frameworks
- Read the App Clips guide for complete setup