Microfrontend: the basics of module federation

Steps to implement module federation
  • Designate one app as the Host (ex: Container) and others as the Remotes (ex: Cart and Products)
  • In the Remote, decide which modules (files) you want to make available to other projects (example: src/index.js)
  • Set up Module Federation plugin to expose those files
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  mode: 'development',
  devServer: {
    port: 8082,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'cart',
      filename: 'remoteEntry.js',
      exposes: {
        './CartShow': './src/index',
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};
  • In the Host, decide which files you want to get from the remote
  • Set up Module Federation plugin in Host to fetch those files
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  mode: 'development',
  devServer: {
    port: 8080,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        products: 'products@http://localhost:8081/remoteEntry.js',
        cart: 'cart@http://localhost:8082/remoteEntry.js',
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
};
  • In the Host, refactor the entry point to load information asynchronously. This asynchronous behavior of dynamic imports with import() is beneficial for loading modules on-demand, especially in scenarios where certain modules are not required immediately or are conditionally needed during runtime.
bootstrap.js // Load information

import 'products/ProductsIndex';
import 'cart/CartShow';

index.js
 // import function call loads the imports asynchronously
// If you don't use import function, then you get an error cause webpack can't find it.
// You need to get the dependencies before running the bootstrap
import('./bootstrap');
  • In the Host, import whatever files you need from the remote. Example imports in index.js
import 'products/ProductsIndex';
import 'cart/CartShow';

index.html has the id’s to show the information from other microfrontends.

<html>
  <head></head>
  <body>
    <div id="dev-products"></div>
    <div id="cart-dev"></div>
  </body>
</html>

products/src/index.js adds the info into dev-products

document.querySelector('#dev-products').innerHTML = cartText;

Files Webpack generates from the Remote file (ex: index.js)

  • Normal bundling process generates main.js and we still can run products as Standalone.
  • Module Federation Plugin generates
    • remoteEntry.js – contains list of files that are available from this project + directions on how to load them
    • src_index.js – version of src/index.js that can be safely loaded into the browser
    • faker.js – version of faker rhat can be safely loaded into the browser. (faker is a external library used in test)

Files Webpack generates from the Host

  • Container has
    • index.js from the Remote – import(‘./bootstrap.js’)
    • bootstrap.js from the Remote – import ‘products/ProductIndex’
  • Webpack generates
    • main.js which contains content only from the index.js
    • bootstrap.js – contains bootstrap.js. Webpack knows it has to fetch comething from products before running this file!

What happens when running Container

  • main.js loaded and executed
  • we need to load and execute bootstrap.js
  • bootstrap needs a file from Products! Fetch remoteEntry.js to figure out how to fetch that
  • Now we see that we need src_index.js, and that needs faker.js
  • After getting them both, bootstrap.js can be now executed.

Module Federation Plugin parameters

Parameters in Host:

  • name – Used only in Remote. For Host you can add it only for clarity
  • remotes – list of projects that the Host can search to get additional code
  • remotes.products – load the file at the listed UrlL if anyting in Host has an import like: import smth from ‘products’
  • ‘products@http://localhost:8081/remoteEntry.js – where products is related to the name property in the Products webpack config file and after the @ sign is URL to the remoteEntry file
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        products: 'products@http://localhost:8081/remoteEntry.js',
        cart: 'cart@http://localhost:8082/remoteEntry.js',
      },
    }),

Parameters in Remote:

  • name – has to be same as the Host remotes url is using
  • filename – sets the name of the manifest file. Leave it as ‘remoteEntry.js’ unless you’ve got a good reason to change it
  • exposes has Aliases filenames. Alias should be a good name to understand what kind of data does it have.
    new ModuleFederationPlugin({
      name: 'products',
      filename: 'remoteEntry.js',
      exposes: {
        './ProductIndex': './src/index',
      },
    }),
In Remotes Index.js files are used only in development process to make the component visible in the browser!