repack
repack copied to clipboard
how to handle unavailability of micro App in the host application
Description
Currently there's no way to reload the mini app if fetching fails due to network failure or any other type of failure
My Mini App
const TvNav = React.lazy(() =>
Federated.importModule('Tv', './Tv'),
);
const Tv = ({ route }: any) => {
return (
<ErrorBoundary name="Tv">
<React.Suspense fallback={<FallbackComponent />}>
<TvNav params={route?.params} />
</React.Suspense>
</ErrorBoundary>
);
};
export default Tv;
My Error Boundary
import { globalRed100 } from '@/style-dictionary-dist/momoStyle';
import { gpsh } from '@/utils/parseTokenStyle';
import { Box, Button, Icon, SafeAreaContainer, Text } from '@atom';
import { ScriptManager } from '@callstack/repack/client';
import { BackHeadingX } from '@molecule';
import React from 'react';
import { StyleSheet } from 'react-native';
type Props = {
children: React.ReactNode;
name: string;
};
type State = {
hasError: boolean;
};
class ErrorBoundary extends React.Component<Props, State> {
name: string;
constructor(props: Props) {
super(props);
this.name = props.name;
this.state = {hasError: false};
}
static getDerivedStateFromError() {
return {hasError: true};
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<SafeAreaContainer>
<BackHeadingX title="" noCancel />
<Box style={styles.container}>
<Icon size={96} color={globalRed100} name="WarningIcon" />
<Text
color={'red100'}
variant={'bold18'}>{`Failed to load ${this.name}`}</Text>
<Text color={'red100'} variant={'medium16'}>
try again later
</Text>
<Button
label="go back"
// variant='secondary'
bStyle={{
marginTop: gpsh(10),
}}
onPress={async () => {
try {
const ass = await ScriptManager.shared.invalidateScripts([
'Startimes',
]);
console.log('ass', ass)
} catch (err) {
console.log('err', err);
}
}}
/>
</Box>
</SafeAreaContainer>
);
}
return this.props.children;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
marginBottom: gpsh(150),
},
});
export default ErrorBoundary;
In my error boundary, There's a button that calls ScriptManager.shared.invalidateScripts(['Startimes',])
onPress = async () => {
try {
const scriptId = await ScriptManager.shared.invalidateScripts([
"Startimes",
]);
console.log("scriptId", scriptId);
} catch (error) {}
};
But it does not do anything.... Have tried unmounting and remounting, it's the same thing. Currently the only way to reload the micro app is to reopen the application and it gets invalidated but if I enable caching, and I reopen the app, I'd be stuck on the Error Boundary screen. So this is the only blocker for enabling caching for me.
There's also a closed issue on this - https://github.com/callstack/repack/issues/526
Also when I logged the ScriptId, I got values like
node_modules_babel_runtime_helpers_asyncToGenerator_js-node_modules_babel_runtime_helpers_sli-1b78c7
vendors-node_modules_react-native_Libraries_TurboModule_TurboModuleRegistry_js
also I get the scriptId for the mini app - "Startimes"
Suggested solution
No response
Additional context
No response
Hi, this is the final step in enabling caching with Repack. If this issue persists, the user will remain stuck on the error screen as it also gets cached too
Hey @vlack-coder,
this is a very valid case, I currently have no capacity to handle this but we will look into this in the future for sure. Do you care do some tinkering on your own? I'm happy to help and answer some questions along the way to unblock you if needed
@jbroma thanks for your response. I'd also like to contribute to this discourse and more issues but can you recommend resources that'd give me more insights on RePack and Webpack in relation to module federation and react native, Thanks.
For Re.Pack, we got our docs at re-pack.dev, but they might be slightly outdated in some places.
For the Module Federation stuff, there is the official docs but thats mostly for MF 2 - but it will be the only supported version from V5 forward anyways. There are also multiple articles from Zack Jackson (the creator of MF) available here.
All in all, it's always down to reading the codebase itself I'm afraid 😅
This issue has been marked as stale because it has been inactive for 30 days. Please update this issue or it will be automatically closed in 14 days.
This issue has been marked as stale because it has been inactive for 30 days. Please update this issue or it will be automatically closed in 14 days.
This issue has been automatically closed because it has been inactive for more than 14 days. Please reopen if you want to add more context.
hey @vlack-coder @vlak-koder
have you solved the problem of reloading the module? i am in the same situation and this method invalidateScripts does not do anything and i have to close and reopen the application in order for the module to reload?
@jbroma Is there any way to do this without restarting the app?
Hi @krzysztofzaluski, No, I was not able to resolve this. tbvh, It's a not-so-great UX for users. The company I worked for used it regardless but for me it's kinda a big deal production-wise
@vlack-coder @krzysztofzaluski you can try approach like below for adding support for retry. this should allow u to retry to load miniapp when it fails to load bundles initially.
import { loadRemote } from '@module-federation/enhanced/runtime';
import React, { ComponentType, useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, ActivityIndicator, StyleSheet } from 'react-native';
export interface MicroAppContext {
userData: unknown;
}
export const MicroAppComponent = ({
module,
context,
}: {
module: ComponentType<{ context: MicroAppContext }>;
context: MicroAppContext;
}) => {
if (!module) {
return null;
}
const Component = module;
return <Component context={context} />;
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 24,
textAlign: 'center',
},
});
export const withRetry = (moduleName: string) => () => {
return (props: { context: MicroAppContext }) => {
const [module, setModule] = useState<ComponentType<{ context: MicroAppContext }> | null>(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
const [retryCount, setRetryCount] = useState(0);
const loadModule = () => {
setLoading(true);
loadRemote<{ default: ComponentType<{ context: MicroAppContext }> }>(moduleName)
.then((m) => {
console.log('Loaded module:', m);
if (m) {
setModule(() => m.default);
setLoading(false);
setError(null);
}
})
.catch((err) => {
console.error(`Error loading ${moduleName}:`, err);
setError(err);
setLoading(false);
});
};
useEffect(() => {
loadModule();
}, [retryCount]);
const onRetry = () => {
setRetryCount(retryCount + 1);
};
if (loading) {
return (
<View style={{ justifyContent: 'center', alignItems: 'center', flex: 1 }}>
<ActivityIndicator color={'black'} size="large" />
</View>
);
}
if (error || !module) {
return (
<View style={styles.container}>
<Text style={styles.text}>Error loading {moduleName}.</Text>
<TouchableOpacity onPress={onRetry}>
<Text>Retry</Text>
</TouchableOpacity>
</View>
);
}
return <MicroAppComponent {...props} module={module} />;
};
};
Hi @mlakmal , thanks for your solution. Though I've also implemented on my end — It's somewhat in the example in the repo. What is your versioning and caching strategy?
@jbroma thank you for adding MF2 and I like that we can register the remote anywhere in the app. Just one thing as regards RSPack.
-
I am not too sure but the app startup time seems longer than with metro or even webpack - 14 secs on a slow Android phone though (and it's not even a cold start)
-
The JS Bundle for RSPack is almost double the size of either of metro or webpack which is not favourable for codepush update
-
Lastly, I was able to create a bundle for webpack and run the app in debug mode but when I create release app it crashes and gives this error
TypeError: undefined is not a function
This error is located at:
at AppContainer (address at index.android.bundle:1:2002929), js engine: hermes
This is the command I use to bundle my code
"bundleAndroid:rspack": "find ./CodePush/Android -mindepth 1 -delete && react-native bundle --platform android --entry-file index.js --bundle-output ./CodePush/Android/index.android.bundle --assets-dest ./CodePush/Android --dev false",
Is there any flag I can add to optimize the bundle
Hi @mlakmal , thanks for your solution. Though I've also implemented on my end — It's somewhat in the example in the repo. What is your versioning and caching strategy?
@vlack-coder Bundles are located in versioned folders ex: domain/miniapps/auth/v1/* or domain/miniapps/auth/v2/*
We just use script manager caching and it works without any custom solution...