3

I am developing microfrontend using Webpack 5 Module Federation.

My host app is an Angular project. My remote app is an React. Both are using Typescript.

My setup is like following:

The React (remote) project

In React project (which is the remote app), I have App.tsx under "src/".

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '../public/vite.svg'

import './App.css'

function App() {
  const [count, setCount] = useState(0)

  return (
    <>
      <div>
         ...
      </div>
    </>
  )
}

export default App

The configuration inside webpack.config.js is like this:

 output: {
    filename: "react-app.js",
    publicPath: "http://localhost:4203/",
  },
 plugins: [
    new ModuleFederationPlugin({
      name: "reactApp",
      filename: "remoteEntry.js",
      exposes: {
        // expose the App component
        "./ReactApp": "./src/App",
      },
      shared: {
        ...deps,
        react: { singleton: true, eager: true, requiredVersion: deps.react },
        "react-dom": {
          singleton: true,
          eager: true,
          requiredVersion: deps["react-dom"],
        },
        "react-router-dom": {
          singleton: true,
          eager: true,
          requiredVersion: deps["react-router-dom"],
        },
      },
    }),
    new HtmlWebpackPlugin({
      template: "./index.html",
    }),
  ],

As you can see above, in module federation plugin configuration, I have:

exposes: {
        // expose the App component
        "./ReactApp": "./src/App",
      },

to expose the App component in App.tsx.

I successfully started this React app running on localhost:4203

The Angular (host) project

In the Angular project (which is the host app), the module federation plugin configuration in webpack.config.js is like this:

plugins: [
    new ModuleFederationPlugin({
        library: { type: "module" },    

        remotes: {
            // pointing to the remote React app 
            "reactApp": "http://localhost:4203/remoteEntry.js",
        },

        shared: share({
          "@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' }, 
          "@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },

          ...sharedMappings.getDescriptors()
        })
        
    }),
    sharedMappings.getPlugin()
  ],

As you can see, I have pointed to the running React project in the remotes: configuration.

Under project src/ of this Angular project, I have also declared the React as moudle in a file called decl.d.ts :

declare module 'reactApp/ReactApp';

In the routing rule, I have async loading code like this:

{
    path: 'reactapp',
    loadChildren: () => import('reactApp/ReactApp').then(m => m.App)
  }

The above route is associated with the HTML element:

 <a [routerLink]="['reactapp']">React App</a>

I can successfully start this Angular app too, I can also see that the remote React is reachable by this Angular app in developer tool (that http://localhost:4203/remoteEntry.js is the React app):

enter image description here

But when I click on the HTML element link mentioned above, I get error:

Error: Uncaught (in promise): TypeError: fn is not a function
while loading "./ReactApp" from 2490

enter image description here

Why is that? What could be the reason?

OR if someone could guide me how to debug this would also be great! Thank you in advance!

=== Update: Please don't suggest me this ===

I know there are tutorials on internet says that on the host app's webpack configuration, I should point to the remote app by adding the name before the remote app URL like this:

remotes: {
  "reactApp": "reactApp@http://localhost:4203/remoteEntry.js",
}

I tried it, the host app can not find my remote app when doing it, the error is:

Uncaught TypeError: Failed to resolve module specifier "reactApp@http://localhost:4203/remoteEntry.js". Relative references must start with either "/", "./", or "../".

enter image description here

That's why in my code snippet in my question, I point to the remote app in host's webpack configuration without the name prefix:

remotes: {
  "reactApp": "http://localhost:4203/remoteEntry.js",
}

because it works, the host can find the remote in this way.

user842225
  • 5,445
  • 15
  • 69
  • 119
  • May I ask why you put filename: `react-app.js` as output of remote react app? I think this is the culprit here as I think remoteEntry.js is looking for a generated version of `index.js` name instead of `react-app.js`. Also you don't need to put `"reactApp": "http://localhost:4203/remoteEntry.js"` as remotes. Just `"reactApp": "reactApp"` but also in the html file, you need to add `` in the head tag at the angular app. See here in Start federating section: https://module-federation.github.io/blog/get-started – Nafis Abdullah Khan Aug 04 '23 at 05:08
  • try to import the component this way: `loadChildren: async () => { const { App } = await import('reactApp/ReactApp');` return App; } and export this this way: `export { App }`. `App` should match the name you used when you exposed the component in your remote React project's `webpack.config.js` – Ahmed Sbai Aug 15 '23 at 18:24
  • @user842225 Did you found a solution? I am facing same issue – LAXIT KUMAR Aug 30 '23 at 13:32

2 Answers2

0

You are trying to render a React component inside Angular and it can't work like that because Angular does not understand React components. In order for this to work you need to render an empty div element in the Angular app and then when the React app is loaded via Module Federation you need to inject it in the empty div using react-dom's createRoot (just like you would do in a regular HTML document). Same goes if you want to inject an Angular app inside React. Here I have the opposite example (host is React and remote is Angular) but the principle is the same - empty div in which the remote injects itself. https://github.com/alex-vukov/react-angular-federation (actually I think there is also a remote using a different React version in this repo which is also injecting itself in a div)

Alex Vukov
  • 102
  • 6
-1

You need to add the name of the exposed micro-frontend before the url.

remotes: {
            // pointing to the remote React app 
            "reactApp": "reactApp@http://localhost:4203/remoteEntry.js",
        },