module-federation / core

Module Federation is a concept that allows developers to share code and resources across multiple JavaScript applications
https://module-federation.io/
MIT License
1.27k stars 182 forks source link

Can't create Error Fallback when any Remote fails. #2672

Open oytuncoban opened 6 days ago

oytuncoban commented 6 days ago

Describe the bug

Hey, I was using the old Module Federation ('webpack/lib/container/ModuleFederationPlugin'), and decided to migrate to the new Module Federation 2.0.

However, in the previous MF version, I had to implement some ErrorBoundary components:

ErrorBoundary:

import React from 'react';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    this.setState({
      error: error,
      errorInfo: errorInfo,
    });
    console.error('ErrorBoundary caught an error', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Something went wrong.</h1>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo?.componentStack}
          </details>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

DynamicImport:

import React, { Suspense } from 'react';
import i18n from '../../helpers/i18n';
import queryClient from '../../queryClient';
import Loading from '../Loading';
import ErrorBoundary from './ErrorBoundary'; 

const sharedDeps = {
  i18n,
  queryClient,
};

const DynamicImport = ({ ImportRemoteApp, LoadingComponent = Loading }) => {
  return (
    <ErrorBoundary>
      <Suspense fallback={<LoadingComponent />}>
        <ImportRemoteApp {...sharedDeps} />
      </Suspense>
    </ErrorBoundary>
  );
};

export default DynamicImport;

Usage of DynamicImport:

import React from 'react';
import DynamicImport from '../components/DynamicImportComponent/DynamicImportComponent';

const #RemoteComponentName#> = React.lazy(() =>
  import('#RemoteModuleName#/#RemoteComponentName#').catch((error) => {
    console.error(`Failed to load module: ${'#RemoteModuleName#/#RemoteComponentName#'}`);
    console.error(error);
    return { default: <div>
      <h1>Something went wrong.</h1>
      <pre>{error?.message}</pre>
    </div> };
  })
);

const Component = () => {
  return <DynamicImport ImportRemoteApp={#RemoteComponentName#} />;
};

export default Component;

With this method, I was able to show Error UI when one of the remote is failing and has errors, or unavailable.

With Module Federation 2.0 I now get this error thrown by @module-federation/enhanced:

[ Federation Runtime ]: 
      Unable to use the **remote_name**'s 'http://localhost:3001/remoteEntry.js' URL with **remote_name**'s globalName to get remoteEntry exports.
      Possible reasons could be:

      1. 'http://localhost:3001/remoteEntry.js' is not the correct URL, or the remoteEntry resource or name is incorrect.

      2. muhakemat_davalar cannot be used to get remoteEntry exports in the window object.
    at error (http://localhost:3000/main.js:2176:11)
    at Object.assert (http://localhost:3000/main.js:2168:9)
    at http://localhost:3000/main.js:196:15

The versions of used packages: webpack: ^5.57.1, @module-federation/enhanced: ^0.2.1,

Example remotes object that I use:

{
remote_app1: "RemoteApp1Name@http://remote-url-1.domain.com/remoteEntry.js",
remote_app2: "RemoteApp2Name@http://remote-url-2.domain.com/remoteEntry.js"
}

Issue is that, webpack throws error and I cannot use my website as expected. Main goal is to keep Host(Shell) app to be continue working even if a remote fails. Users should be able to use other remotes and shell app layout.

I tried to implement the Dynamic System Host example. Here again, if I don't start one of the remotes, when user tries to load the failing remote, it immediately throws error and breaks the shell.

Wouldn't it be convenient that we can catch the Remote load errors and provide a fallback UI to the user?

Reproduction

https://github.com/module-federation/module-federation-examples/tree/master/dynamic-system-host

Used Package Manager

npm

System Info

System:
    OS: macOS 15.0
    CPU: (8) arm64 Apple M3
    Memory: 145.58 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 18.12.1 - ~/.nvm/versions/node/v18.12.1/bin/node
    Yarn: 4.0.2 - ~/.nvm/versions/node/v18.12.1/bin/yarn
    npm: 8.19.2 - ~/.nvm/versions/node/v18.12.1/bin/npm
    pnpm: 8.15.5 - ~/.nvm/versions/node/v18.12.1/bin/pnpm
  Browsers:
    Brave Browser: 108.1.46.144
    Chrome: 126.0.6478.116
    Edge: 126.0.2592.68
    Safari: 18.0

Validations

ScriptedAlchemy commented 6 days ago

You can use a runtime plugin to handle such cases, since the v1 way or catching errors was unreliable and did not work for import from ''

https://github.com/module-federation/module-federation-examples/blob/master/runtime-plugins/offline-remote/app1/offline-remote.js

https://module-federation.io/plugin/dev/index.html

danieloprado commented 1 day ago

The runtime plugin doesn't catch this kind of error, I'm stuck on version 0.1.6 until it get fixed 😢

Captura de ecrã 2024-07-01, às 15 55 50