reactjsangularwebpacknx-monorepo

Load React mfe into Angular host with Nx and Angular Architects


I have created a monorepo with Nx, featuring an Angular host and an Angular remote, and this works perfectly. I also have another monorepo with a React host and a React remote, which also work.

This is the complete APP

The issue arises when I try to load the remote React microfrontend into the Angular host.

I believe the problem lies within the React microfrontend because if I use the demo URL from Angular Architects URL, it works fine, but it doesn't work with my own URL that is running on a Live Server.

My app.routes.ts

import { Route } from '@angular/router';
import { loadRemoteModule } from '@nx/angular/mf';
import {
  WebComponentWrapper,
  WebComponentWrapperOptions,
} from '@angular-architects/module-federation-tools';
import { NotFoundError } from 'rxjs';
import { HomeComponent } from './home/home.component';

export const appRoutes: Route[] = [
  {
    path: '',
    component: HomeComponent,
  },
  {
    path: 'microfront-angular',
    loadChildren: () =>
      loadRemoteModule('microfront-angular', './Module').then(
        (m) => m.RemoteEntryMfNg
      ),
  },
  {
    path: 'microfront-react',
    component: WebComponentWrapper,
    data: {
      // type: 'module',
      remoteEntry:
        'http://localhost:4301/remoteEntry.js',
      remoteName: 'microfront-react',
      elementName: 'microfront-react',
      exposedModule: './Module',
    } as WebComponentWrapperOptions,
  },
  {
    path: 'react',
    component: WebComponentWrapper,
    data: {
      remoteEntry:
        'https://witty-wave-0a695f710.azurestaticapps.net/remoteEntry.js',
      remoteName: 'react',
      elementName: 'react-element',
      exposedModule: './web-components',
    } as WebComponentWrapperOptions,
  },
  {
    path: 'vue',
    component: WebComponentWrapper,
    data: {
      remoteEntry:
        'https://mango-field-0d0778c10.azurestaticapps.net/remoteEntry.js',
      remoteName: 'vue',
      exposedModule: './web-components',
      elementName: 'vue-element',
    } as WebComponentWrapperOptions,
  },
  {
    path: '**',
    component: NotFoundError,
  },
];

microfront-react dont'works but react works

This happens when try to access to microfront-react

microfront-react

But I still see remoteEntry.js from my build on Network...

remoteEntry

The command to create my React microfront was:

nx g @nx/react:host host-react --remotes=microfront-react --style=scss

My apps/microfront-react/src/bootstrap.tsx

import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';

import App from './app/app';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

class MfeReact extends HTMLElement {
  connectedCallback() {
    console.log('web-component: bootstrap.tsx');

    root.render(
      <StrictMode>
        <App />
      </StrictMode>
    );
  }
}

customElements.define('microfront-react', MfeReact);

I think that maybe the fail is in the webpack.config.js and module-federation.config.js from my microfront-react

webpack.config.js

const { composePlugins, withNx } = require('@nx/webpack');
const { withReact } = require('@nx/react');
const { withModuleFederation } = require('@nx/react/module-federation');

const baseConfig = require('./module-federation.config');

const config = {
  ...baseConfig,
};

// Nx plugins for webpack to build config object from Nx options and context.
module.exports = composePlugins(
  withNx(),
  withReact(),
  withModuleFederation(config)
);

module-federation.config.js

module.exports = {
  name: 'microfront-react',
  filename: 'remoteEntry.js',
  exposes: {
    './web-components': './src/remote-entry.ts',
  }
};

With that lines is enought to package correctly the remoteEntry.js?

Hope this helps to undertand my problem and thanks a lot to all people!!!

Source and thx:

Angular-architects Lerna

Nx


Solution

  • I solved that!

    Need to create a customElements on our bootstrap.tsx

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    import App from './app/app';
    
    class Mfe4Element extends HTMLElement {
      connectedCallback() {
        console.log('http-mfe-react-element connectedCallback from DOM');
    
        window.React = React;
        ReactDOM.render(<App />, this);
      }
    
      disconnectedCallback() {
        console.log('http-mfe-react-element disconnectedCallback from DOM');
      }
    }
    
    customElements.define('http-mfe-react-element', Mfe4Element);
    

    And create a proper config on webpack.config.js

    const { composePlugins, withNx } = require('@nx/webpack');
    const { withReact } = require('@nx/react');
    
    const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
    
    const path = require('path');
    
    const webpackEntry = [
      path.resolve(__dirname, './src/index.html'),
      path.resolve(__dirname, './src/main.tsx'),
    ];
    
    const webpackOutput = {
      publicPath: 'auto',
      path: path.resolve(__dirname, '../../dist/apps/http-mfe-react'),
    };
    
    const webpackModuleFederationPlugin = new ModuleFederationPlugin({
      name: 'http_mfe_react',
      library: { type: 'var', name: 'http_mfe_react' },
      filename: 'remoteEntry.js',
      exposes: {
        './web-components': path.resolve(__dirname, './src/bootstrap.tsx'),
      },
      shared: ['react', 'react-dom'],
    });
    
    const ruleForTsx = {
      test: /\.tsx$/,
      exclude: /node_modules/,
      use: [
        {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true,
            presets: ['@babel/react', '@babel/env'],
          },
        },
      ],
    };
    const ruleForMisc = {
      test: /\.(png|jpe?g|gif|woff|svg|eot|ttf)$/i,
      use: ['file-loader'],
    };
    const ruleForHtml = {
      test: /\.html$/,
      use: ['file-loader?name=[name].[ext]'],
    };
    const ruleForStyles = {
      test: /\.(s[ac]ss|\.css)$/,
      use: ['style-loader', 'css-loader', 'postcss-loader'],
    };
    
    const webpackRules = [ruleForTsx, ruleForMisc, ruleForHtml, ruleForStyles];
    
    const webpackExtensions = ['.tsx', '.ts', '.js'];
    
    // Nx plugins for webpack.
    module.exports = composePlugins(withNx(), withReact(), (config) => {
      // Update the webpack config as needed here.
      // e.g. `config.plugins.push(new MyPlugin())`
    
      config.entry = webpackEntry;
      config.output = webpackOutput;
      config.plugins.push(webpackModuleFederationPlugin);
      config.optimization.runtimeChunk = false; // Only needed to bypass a temporary bug
      config.module.rules = webpackRules;
      config.resolve.extensions = webpackExtensions;
    
      return config;
    });
    
    

    Hope it helps someone.