Build SuperApps in React Native

You might have gone through numerous apps which consists of mini/child apps inside it and without updates from app/play store these apps can receive Ui and functionality changes which are within the guidelines of PlayStore & AppStore

Lets understand how can we builds such SuperApps in React Native with use of Repack with Module Federation
What’s Repack?
Re.Pack is what makes Webpack and Webpack-produced bundle usable in React Native application.
What’s Module Federation?
Module Federation is an architecture, which splits the application into multiple pieces. These pieces are called containers. Similarly to micro-services, Module Federation splits application into a distributed frontends, sometimes referred to as micro-frontends.

Module Federation can help you:
- Reduce code duplication
- Improve code maintainability
- Reduce the overall size of applications
- Improve application performance
Module Federation has three main components:
- Exposed Module (Remote App)
- Host Module (Shell App)
- Shared dependencies
shared: {
react: {
singleton: true,
eager: STANDALONE,
requiredVersion: '18.2.0',
},
'react-native': {
singleton: true,
eager: STANDALONE,
requiredVersion: '0.72.3',
}
}
API Definition for Shared Dependencies
- shared (object | [string]): An object or Array of strings containing a list of dependencies that can be shared and consumed by other federated apps.
- eager (boolean): If true, the dependency will be eagerly loaded and made available to other federated apps as soon as the host application starts. If false, the dependency will be lazily loaded when it is first requested by a federated app.
- singleton (boolean): If true, the dependency will be treated as a singleton and only a single instance of it will be shared among all federated apps.
- requiredVersion (string): Specifies the required version of the dependency. If a federated app tries to load an incompatible version of the dependency, two copies will be loaded.
Code Splitting
Code Splitting is a technique that splits the code into multiple files, which can be loaded on demand and in parallel. These chunks are remote by default. We can specify which would be local and which should be remotely fetched in runtime to decrease our delivery bundle size.
ScriptManager is responsible to resolve these local and remote chunks and make it available for app to run smoothly.
Assets Handling
Asset handling for mini-apps needs to be carefully decided. Here the best suggested way to have remote assets as we have in webapps. But still if all assets needs to be local (for eg: splash, noInternet assets) can be added with inline: true
option to the Assets loader.
This will add image as base64 and would be included in bundle which will eventually increase the bundle size.
App Archiecture

Run the projects:
Clone the project from Github Repo
In the root project you can find the below scripts in package.json
- yarn bootstrap — This would download all the dependencies for your host/mini apps in one go
- yarn start — This would start your apps. Host on port 8081 and miniapp on 9000 on localhost.
Run Android/iOS host app
- yarn run:host-app:android
- yarn run:host-app:ios
Run Android/iOS mini app independently
- yarn mini-app:android
- yarn run:mini-app:ios
Create bundle/async chunks for miniapp
- yarn bundle:mini-app
All remote chunks are stored under <projectRoot>/build/output/<platform>/remotes
by default.
For example if button.chunk.bundle
is a remote chunk, it will be stored under: <projectRoot>/build/output/ios/remotes/button.chunk.bundle
for iOS.
Remote Hosting of Async Chunks
You can use any kind of cloud storage. I have used GCS. We would keep the generated chunks in the below hierarchy. Upload both android and ios folders from <projectRoot>/build/output
The update.json would be used to check for latest version of app and then the specific bundle can be fetched by host app.
{
"app_name": "MiniApp",
"latest_version": "1.0.1",
"versions": {
"1.0.1": {
"buildVersion": 2,
"release_date": "2024-09-29",
"is_mandatory": false,
"release_notes": "Bug fixes and stability improvements."
},
"1.0.0": {
"buildVersion": 1,
"release_date": "2024-09-10",
"is_mandatory": false,
"release_notes": "New features added."
}
}
}

Caching and Versioning
The caching mechanism in Re.Pack prevents scripts over-fetching, which helps reducing bandwidth. Providing storage
options to ScriptManager.shared.setStorage
will enable caching of downloaded script.
import { ScriptManager, Script } from '@callstack/repack/client';
ScriptManager.shared.setStorage(AsyncStorage);
ScriptManager.shared.addResolver(async (scriptId) => {
const { version } = await getRemoteConfig();
return {
url: Script.getRemoteURL(`http://my-domain.dev/v${version}/${scriptId}`),
};
});
CodeSigning
A code signing plugin can be used to sign chunks so that their integrity can be verified before execution. You should consider code-signing your chunks when you are using CodeSplitting or ModuleFederation and want to deliver parts of your code remotely to the end-user. Compatible with both JS and Hermes-bytecode bundles.
webpack.config.js
import * as Repack from '@callstack/repack';
new Repack.plugins.CodeSigningPlugin({
enabled: mode === 'production',
privateKeyPath: './code-signing.pem',
excludeChunks: /local/,
});