How to redownload bundle failed and update the screen
Description
I use react-native 0.77.2 and @callstack/repack 5.1.0 and federation
This is the case: When i load bundle failed for some reason (no internet, link revoke, bundle invalid), i load the fallback-component to replace for handler crash app. And when the internet is back I want the user to be able to click to redownload that bundle.
This is my rs-pack rspack.config.mjs
import pkg from '@btaskee/sdk';
import * as Repack from '@callstack/repack';
import { ReanimatedPlugin } from '@callstack/repack-plugin-reanimated';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const { getLocalIP, getSharedDependencies } = pkg;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Rspack configuration enhanced with Re.Pack defaults for React Native.
*
* Learn about Rspack configuration: https://rspack.dev/config/
* Learn about Re.Pack configuration: https://re-pack.dev/docs/guides/configuration
*/
export default (env) => {
const { mode, platform = process.env.PLATFORM } = env;
const hostIP = getLocalIP();
return {
mode,
context: __dirname,
entry: './index.js',
experiments: {
incremental: mode === 'development',
},
resolve: {
...Repack.getResolveOptions(),
alias: {
'@navigation': path.resolve(__dirname, './src/navigation'),
'@screens': path.resolve(__dirname, './src/screens'),
'@app': path.resolve(__dirname, './src/App.tsx'),
'@hooks': path.resolve(__dirname, './src/hooks'),
'@images': path.resolve(__dirname, './src/assets/images'),
'@lottie': path.resolve(__dirname, './src/assets/lottie'),
'@components': path.resolve(__dirname, './src/components'),
'@utils': path.resolve(__dirname, './src/utils'),
'@types': path.resolve(__dirname, './src/types'),
'@stores': path.resolve(__dirname, './src/stores'),
'@providers': path.resolve(__dirname, './src/providers'),
},
},
output: {
uniqueName: 'sas-host',
path: path.resolve(__dirname, 'dist', platform),
},
module: {
rules: [
...Repack.getJsTransformRules(),
...Repack.getAssetTransformRules(),
],
},
plugins: [
new Repack.RepackPlugin(),
new ReanimatedPlugin(),
new Repack.plugins.ModuleFederationPluginV2({
name: 'host',
dts: false,
remotes: {
error: `error@https://error-447.firebaseapp.com/error.container.js.bundle`,
account: `account@https://account-247.firebaseapp.com/account.container.js.bundle`,
},
shared: getSharedDependencies({ eager: true }),
runtimePlugins: [path.resolve(__dirname, 'remote-fallback-plugin.ts')],
}),
],
};
};
remote-fallback-plugin.ts
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
const remoteFallbackPlugin = (): FederationRuntimePlugin => {
return {
name: 'remote-fallback-plugin',
async errorLoadRemote(args: any) {
const FallbackComponent = await import('error/FallbackComponent');
return () => FallbackComponent;
},
};
};
export default remoteFallbackPlugin;
Account
import React from 'react';
import { useRoute } from '@react-navigation/native';
import { ErrorBoundary, LoadingMiniApp } from '@components';
const Account = React.lazy(() => import('account/AccountTab'));
export function TabAccountScreen(): React.JSX.Element {
const route = useRoute();
return (
<ErrorBoundary name="AccountScreen">
<React.Suspense fallback={<LoadingMiniApp />}>
<Account {...route.params} />
</React.Suspense>
</ErrorBoundary>
);
}
Because ErrorBoundary can't catch crash app when no have internet, so i use errorLoadRemote to reassign account bundle to error/FallbackComponent (Of course error/FallbackComponent has been preloaded and cached)
I want to implement button retry in the FallbackComponent, When user click, it will redownload bundle and replace bundle error/FallbackComponent to valid bundle.
But now i only can restart App, Is there any other solution?
Suggested solution
No response
Additional context
No response
I have the same problem, but I'm working with version "@callstack/repack": "^4.4.1". I currently have 18 microfronts published and in the process of being updated to the latest versions. To solve these very specific cases, especially when users have a very poor internet connection, I had to implement different solutions. One of these was implementing a lazyRetry, which allows me to retry the microfront download, capture the microfront that presents the error, and if it fails despite the retries, since the app closes automatically because the ErrorBoundary doesn't correctly capture the error (something I still need to review), upon reopening, I identify which microfront caused the error and change its URL, specifically the version, even without changing the publication. This is to force the download again, since invalidating the scripts wasn't working correctly despite the attempts. The way I handle the microfront version is simply a configuration in nginx so that, regardless of the version in the URL, the bundles are returned.
I have the same problem, but I'm working with version "@callstack/repack": "^4.4.1". I currently have 18 microfronts published and in the process of being updated to the latest versions. To solve these very specific cases, especially when users have a very poor internet connection, I had to implement different solutions. One of these was implementing a lazyRetry, which allows me to retry the microfront download, capture the microfront that presents the error, and if it fails despite the retries, since the app closes automatically because the ErrorBoundary doesn't correctly capture the error (something I still need to review), upon reopening, I identify which microfront caused the error and change its URL, specifically the version, even without changing the publication. This is to force the download again, since invalidating the scripts wasn't working correctly despite the attempts. The way I handle the microfront version is simply a configuration in nginx so that, regardless of the version in the URL, the bundles are returned.
The app still crashes, doesn’t it? Can you share your lazyRetry?
It's still failing, but I can prevent it from closing completely. I display a screen indicating a poor connection with a button that resets the navigation to reload everything. If it's just an intermittent issue, retries should fix it. But as I mentioned, I had to force a URL change to reload the microfront.
It's not the best implementation, but it works for me.
export const lazyRetry = (
componentImport,
namebundle: string,
retries = 3,
delay = 3000,
) => {
function attempt(remaining) {
return componentImport()
.then(resolve => {
AsyncStorage.removeItem('SHOW_ERROR_BOUNDARY');
removeUrlMicroFromBundle(namebundle);
return resolve;
})
.catch(error => {
console.warn('*** Error loading component:', error);
if (remaining <= 1) {
AsyncStorage.setItem('SHOW_ERROR_BOUNDARY', 'true').then(() => {
throw error;
});
}
return new Promise(res => setTimeout(res, delay)).then(() => {
console.warn('*** Retrying component load:', remaining);
return attempt(remaining - 1);
});
});
}
return attempt(retries);
};
// component
const AppUserAccount = React.lazy(() =>
lazyRetry(
() =>
Federated.importModule('user-account', './UserAccount').then(mod =>
mod && mod.default ? {default: mod.default} : {default: mod},
),
'account-bundle',
),
);
const UserAccount = () => {
return (
<ErrorBoundary>
<Suspense fallback={<Loading />}>
<AppUserAccount />
</Suspense>
</ErrorBoundary>
);
};
It's still failing, but I can prevent it from closing completely. I display a screen indicating a poor connection with a button that resets the navigation to reload everything. If it's just an intermittent issue, retries should fix it. But as I mentioned, I had to force a URL change to reload the microfront.
It's not the best implementation, but it works for me.
export const lazyRetry = ( componentImport, namebundle: string, retries = 3, delay = 3000, ) => { function attempt(remaining) { return componentImport() .then(resolve => { AsyncStorage.removeItem('SHOW_ERROR_BOUNDARY'); removeUrlMicroFromBundle(namebundle); return resolve; }) .catch(error => { console.warn('*** Error loading component:', error);
if (remaining <= 1) { AsyncStorage.setItem('SHOW_ERROR_BOUNDARY', 'true').then(() => { throw error; }); } return new Promise(res => setTimeout(res, delay)).then(() => { console.warn('*** Retrying component load:', remaining); return attempt(remaining - 1); }); });} return attempt(retries); };
// component
const AppUserAccount = React.lazy(() => lazyRetry( () => Federated.importModule('user-account', './UserAccount').then(mod => mod && mod.default ? {default: mod.default} : {default: mod}, ), 'account-bundle', ), );
const UserAccount = () => { return ( <ErrorBoundary> <Suspense fallback={<Loading />}> <AppUserAccount /> </Suspense> </ErrorBoundary> ); }; Hi. Can i ask how to prevent crash if deps loaded fail with MF1