Load React mfe into Angular host with Nx and Angular Architects

955 Views Asked by At

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

2

There are 2 best solutions below

0
On BEST ANSWER

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.

2
On

You are adding a remote entry in your Angular application of type WebComponentWrapper. Maybe your React microfrontend needs to be packed as a web component?