reactjswebpackwebpack-dev-serverwebpack-5webpack-module-federation

React Module Federation - Uncaught Error: Shared module is not available for eager consumption: webpack/sharing/consume/default/react/react


I am trying react module federation. I have created two react apps(modulefederation1, modulefederation2) using create-react-app command. I am getting 'Uncaught Error: Shared module is not available for eager consumption: webpack/sharing/consume/default/react/react' error when i run the application.

Node version: v16.14.2

Below is my code.

modulefederation1:

Package.json:

{
  "name": "modulefederation1",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^13.2.0",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.1",
    "@types/node": "^16.11.36",
    "@types/react": "^18.0.9",
    "@types/react-dom": "^18.0.4",
    "html-webpack-plugin": "^5.5.0",
    "react": "^18.1.0",
    "react-dom": "^18.1.0",
    "react-scripts": "5.0.1",
    "serve": "^13.0.2",
    "ts-loader": "^9.3.0",
    "typescript": "^4.6.4",
    "web-vitals": "^2.1.4",
    "webpack": "^5.72.1",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.9.0"
  },
  "scripts": {
    "start": "webpack serve --open",
    "build": "webpack --config webpack.prod.js",
    "serve": "serve dist -p 3002",
    "clean": "rm -rf dist"
},
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack'); // only add this if you don't have yet
const { ModuleFederationPlugin } = webpack.container;
const deps = require('./package.json').dependencies;

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  console.log({ isProduction });
  return {
    entry: './src/index.tsx',
    mode: process.env.NODE_ENV || 'development',
    devServer: {
      port: 3000,
      open: true,
    },
    resolve: {
      extensions: ['.ts', '.tsx', '.js'],
    },
    module: {
      rules: [
        {
          test: /\.(js|jsx|tsx|ts)$/,
          loader: 'ts-loader',
          exclude: /node_modules/,
        },
      ],
    },

    plugins: [
      new ModuleFederationPlugin({
        name: 'container',
        remotes: {
          app1: 'app1@http://localhost:3001/remoteEntry.js'
        },
        shared: {
          ...deps,
          react: { singleton: true, eager: true, requiredVersion: deps.react },
          'react-dom': {
            singleton: true,
            eager: true,
            requiredVersion: deps['react-dom'],
          },
        },
      }),
      new HtmlWebpackPlugin({
        template: './public/index.html',
      }),
    ],
  };
};

public/index.html:

<html>
  <head>
    <title>CONTAINER</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

src/index.tsx:

import './bootstrap';

src/bootstrap.tsx:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

src/App.tsx:

import React from 'react';

const ModuleFederationTwo = React.lazy(() => import('app1/ModuleFederationTwo'));

function App() {
  return (
    <div >
     Container application
    </div>
  );
}

export default App;

modulefederation2:

Pacakge.json file is same as modulefederation1 except "name": "modulefederation2"

webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
const deps = require('./package.json').dependencies;

module.exports = {
  entry: './src/index.tsx',
  mode: 'development',
  devServer: {
    port: 3001,
    open: true,
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js'],
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx|tsx|ts)$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      exposes: {
        // expose each component
        './ModuleFederationTwo': './src/App',
      },
      shared: {
        ...deps,
        react: { singleton: true, eager: true, requiredVersion: deps.react },
        'react-dom': {
          singleton: true,
          eager: true,
          requiredVersion: deps['react-dom'],
        },
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

src/index.tsx and src/bootstrap.tsx file is same as modulefederation1

src/App.tsx:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      MFE application One
    </div>
  );
}

export default App;

I run both modulefederation1, modulefederation2 using yarn start.

I get main.js:1312 Uncaught Error: Shared module is not available for eager consumption: webpack/sharing/consume/default/react/react error when i run modulefederation1 application.

When i browsed everyone say that index code should be moved to bootstrap which i have done already. Do i have any other solution?

Network:

Network log


Solution

  • I was able to make above code work by changing the below code.

    modulefederation1:

    public/index.html:- add script tag <script src="http://localhost:3001/remoteEntry.js"></script>

    <html>
      <head>
        <script src="http://localhost:3001/remoteEntry.js"></script>
        <title>CONTAINER</title>
      </head>
      <body>
        <div id="root"></div>
      </body>
    </html>
    

    webpack.config.js: Change remotes app1 value

    remotes: {
              app1: 'app1'
            },