sharing react between microfrontend bundles without CDN

Viewed 851

I am building an app using a microfrontends architecture with the Single-Spa framework.

I have:

  • A root config web app: Defines the layout of the apps and includes each microfrontend as an NPM dependency
  • N Microfrontends: React apps which are loaded into the root config app.

Because each microfrontend is using React, I don't want to bundle it everytime. Instead I want to have the root config app specify React as a dependency and then it can be loaded by each microfrontend.

I have declared react and react-dom as webpack externals so they are not bundled but I think this will only work if I include React from CDN in my index.html of the root config app. Is there a way to configure webpack so that it will include React in the root config and make it available to every microfrontend? I can't use public CDNs in my firm.

Thanks!

3 Answers

In the "Getting Started: Quick start" guide, we cover how to add shared dependencies, and the example specifically shows adding React and ReactDOM to the import map.

You should not try to bundle React into the root-config, since modules bundled into an application by Webpack are namespaced and not shared globally. Instead, per your requirement, you can host those React files in your own CDN or even serve them locally though the root-config because ultimately there are static files. The location of where those files are loaded from is not relevant to SystemJS as long as it can load them. This means you can go as simple as copying those files locally and serving them from the root-config server, to as complicated as setting up your own unpkg server.

Yes, we can do that not just for the React Package but with all the dependencies and share them between all the micro-frontend apps.

Webpack 5, provides the support of ModuleFederationPlugin in which you can define the list of packages. Example:-

Import Package.json in the Webpack configuration and add following plugin

const packageJson = require('../package.json');

{
   plugins: [
    new ModuleFederationPlugin({
            name: <AppName>,
            filename: 'remoteEntry.js',
            exposes: {
                './App': <PathToFile>,
            },
            shared: {
                ...packageJson.dependencies,
            }
        }),
  ],
}

You can explicitly define only the packages name which you want to share.

Check out more about ModuleFederationPlugin

If you want to have flexibility in micro frontend, that is, if you want each micro frontend to be deployed independently of each other, you can use client-side intgration.

React

You can use a CDN for static files. (image, fonts, pdf, doc) Webpack module federation can be used for client-side integration.

Configuring an example webpack and module federation.

We divide the webpack config files into 3 parts. for common, development, production

webpack.common.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-react', '@babel/preset-env'],
            plugins: ['@babel/plugin-transform-runtime'],
          },
        },
      },
    ],
  },
};

webpack.dev.js

const { merge } = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const commonConfig = require('./webpack.common');
const packageJson = require('../package.json');

const devConfig = {
  mode: 'development',
  output: {
    publicPath: 'http://localhost:8082/',
  },
  devServer: {
    port: 8082,
    historyApiFallback: {
      index: 'index.html',
    },
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'auth',
      filename: 'remoteEntry.js',
      exposes: {
        './AuthApp': './src/bootstrap',
      },
      shared: packageJson.dependencies,
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};

module.exports = merge(commonConfig, devConfig);
Related