Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions apps/expo-example-host/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# OSX
#
.DS_Store

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
**/.xcode.env.local

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
.kotlin/

# node.js
#
node_modules/
npm-debug.log
yarn-error.log

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/

**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output

# Bundle artifact
*.jsbundle

# Ruby / CocoaPods
**/Pods/
/vendor/bundle/

# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*

# testing
/coverage

# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# build artifacts
dist/

# expo
.expo/
10 changes: 10 additions & 0 deletions apps/expo-example-host/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "MFExpoExampleHost",
"slug": "MFExpoExampleHost",
"ios": {
"bundleIdentifier": "com.expo.example.host"
},
"android": {
"package": "com.expo.example.host"
}
}
3 changes: 3 additions & 0 deletions apps/expo-example-host/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['babel-preset-expo'],
};
12 changes: 12 additions & 0 deletions apps/expo-example-host/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { withAsyncStartup } from '@module-federation/metro/bootstrap';
import { registerRootComponent } from 'expo';

// create async boundry through withAsyncStartup helper
// and pass the getter function for the app component
// optionally a getter function for the fallback component
registerRootComponent(
withAsyncStartup(
() => require('./src/App'),
() => require('./src/Fallback'),
)(),
);
61 changes: 61 additions & 0 deletions apps/expo-example-host/metro.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const path = require('node:path');
const { getDefaultConfig } = require('expo/metro-config');
const { mergeConfig } = require('@react-native/metro-config');

const { withModuleFederation } = require('@module-federation/metro');

/**
* Metro configuration
* https://reactnative.dev/docs/metro
*
* @type {import('@react-native/metro-config').MetroConfig}
*/

const config = {
resolver: { useWatchman: false },
watchFolders: [
path.resolve(__dirname, '../../node_modules'),
path.resolve(__dirname, '../../packages'),
],
};

module.exports = withModuleFederation(
mergeConfig(getDefaultConfig(__dirname), config),
{
name: 'MFExpoExampleHost',
remotes: {
MFExpoExampleMini:
'MFExpoExampleMini@http://localhost:8082/mf-manifest.json',
MFExpoExampleNestedMini:
'MFExpoExampleNestedMini@http://localhost:8083/mf-manifest.json',
},
shared: {
react: {
singleton: true,
eager: true,
requiredVersion: '19.0.0',
version: '19.0.0',
},
'react-native': {
singleton: true,
eager: true,
requiredVersion: '0.79.5',
version: '0.79.5',
},
lodash: {
singleton: false,
eager: false,
requiredVersion: '4.16.6',
version: '4.16.6',
},
},
shareStrategy: 'loaded-first',
},
{
flags: {
unstable_patchHMRClient: true,
unstable_patchInitializeCore: true,
unstable_patchRuntimeRequire: true,
},
},
);
45 changes: 45 additions & 0 deletions apps/expo-example-host/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "expo-example-host",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "expo run:android",
"ios": "expo run:ios",
"android:release": "expo run:android --variant 'Release'",
"ios:release": "expo run:ios --configuration 'Release' --destination=\"simulator\"",
"lint": "eslint .",
"dev": "nodemon --config ../../nodemon.json --exec NODE_OPTIONS='--conditions=dev' pnpm start",
"start": "expo start --reset-cache",
"test": "jest"
},
"dependencies": {
"expo": "~53.0.0",
"lodash": "4.16.6",
"react": "19.0.0",
"react-native": "0.79.5"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@module-federation/metro": "workspace:*",
"@module-federation/runtime": "workspace:*",
"@react-native/babel-preset": "0.79.5",
"@react-native/eslint-config": "0.79.5",
"@react-native/metro-config": "0.79.5",
"@react-native/typescript-config": "0.79.5",
"@types/jest": "^29.5.13",
"@types/lodash": "^4",
"@types/react": "^19.0.0",
"@types/react-test-renderer": "^19.0.0",
"eslint": "^8.19.0",
"jest": "^29.6.3",
"nodemon": "^3.1.9",
"prettier": "2.8.8",
"react-test-renderer": "19.0.0",
"typescript": "5.0.4"
},
"engines": {
"node": ">=18"
}
}
112 changes: 112 additions & 0 deletions apps/expo-example-host/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { useEffect, useState } from 'react';
import {
ActivityIndicator,
Pressable,
SafeAreaView,
StyleSheet,
Text,
View,
} from 'react-native';

// @ts-ignore
import NestedMiniInfo from 'MFExpoExampleNestedMini/nestedMiniInfo';
import Card from './Card';

// @ts-ignore
const Info = React.lazy(() => import('MFExpoExampleMini/info'));

function App(): React.JSX.Element {
const [shouldLoadMini, setShouldLoadMini] = useState(false);
const [lodashVersion, setLodashVersion] = useState<string>();

useEffect(() => {
import('lodash').then((lodash) => {
setLodashVersion(lodash.VERSION);
});
}, []);

return (
<View style={styles.backgroundStyle}>
<SafeAreaView />
<View style={styles.contentContainer}>
<Card title="Host Info" description="Host app info">
<React.Suspense
fallback={
<View>
<ActivityIndicator size="large" color="#8b5cf6" />
</View>
}
>
<Info
testID="host-app-info"
sections={[
{
name: 'lodash version',
value: lodashVersion,
testID: 'host-lodash',
},
]}
/>
</React.Suspense>
</Card>
<Card title="Federated Remote" description="Dynamically loaded module">
{!shouldLoadMini ? (
<Pressable
style={styles.defaultButton}
onPress={() => setShouldLoadMini(true)}
>
<Text testID="load-mini-button" style={styles.defaultButtonText}>
Load Remote Component
</Text>
</Pressable>
) : (
<React.Suspense
fallback={
<View>
<ActivityIndicator size="large" color="#8b5cf6" />
</View>
}
>
<Info />
</React.Suspense>
)}
</Card>
<Card
title="Nested Federated Remote"
description="Dynamically loaded nested module"
>
<NestedMiniInfo />
</Card>
</View>
</View>
);
}

const styles = StyleSheet.create({
backgroundStyle: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.65)',
},
contentContainer: {
flex: 1,
paddingHorizontal: 24,
},
defaultButton: {
backgroundColor: '#000',
padding: 16,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
borderWidth: 1,
borderColor: 'rgba(255, 255, 255, 0.1)',
},
defaultButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
letterSpacing: 0.5,
},
});

export default App;
Loading
Loading