Micro Frontend with Webpack Module Federation

Micro Frontend with Webpack Module Federation

·

3 min read

Many of us have heard of Microservice, an architecture pattern to break a big application backend service (monolith) into many small services. Each service handles a single concern of business domain or product functionality. As a result, we hope to develop, deploy, and maintain our applications individually.

That's for the backend. We could also do the same with frontend by using a Webpack Module Federation Plugin. It was introduced in Webpack version 5 release (2020-10-10) and considered the best solution for micro-frontend implementation.

Module Federations lets you combine code and dependencies of separated applications to form a single application. This enables you to develop and deploy modules independently as an application and combine them at runtime.

Before we jump to the code, let's see some Module Federation terminology:

  1. Module federation: Loading codes and dependencies from another federated application (another app that also uses Module Federation)

  2. Host: An application that hosts federated remote modules.

  3. Remote: An address of an external federated module.

  4. Expose: A module that is exposed to an external federated application

  5. Module: A shareable module (could be a single file component)

  6. Bi-directional host: Federated application can be a host or a remote. Either consuming other applications or being consumed by others at runtime

Minimum Prerequisites to use Module Federation

  1. Webpack version 5

  2. React version 17

  3. Node.js 10.13.0 (LTS)

Demo Scenario

We are going to have two application

  • myApp1

  • myApp2

Each application will import a button component from the other.

  • myApp1 import button component from myApp2

  • myApp2 import button component from myApp1

Implementation in myApp1

In our webpack config (webpack.config.js)

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  mode: 'development',
  devServer: {
    port: 8083,
  },
  module: {
  plugins: [
    new ModuleFederationPlugin(
      {
        name: 'myApp1',
        filename:
          'remoteEntry.js',
        exposes: {
          './Button': 
            './src/Button',
        },
        remotes: {
          myApp2:
            'myApp2@http://localhost:8082/remoteEntry.js',
        }
      }
    ),
    // ....
  ],
 // ....
};

In our page component

import React from 'react';

const MyApp2_Button = React.lazy(
  () => import('myApp2/Button')
);


function App() {
  return (
    <div>
      <h1>This is myApp1 importing button from myApp2</h1>
      <React.Suspense fallback='Loading Button'>
        <MyApp2_Button/>
      </React.Suspense>
    </div>
  );
}

export default App;

Implementation in myApp2

In our webpack config (webpack.config.js)

const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
  mode: 'development',
  devServer: {
    port: 8082,
  },
  module: {
  plugins: [
    new ModuleFederationPlugin(
      {
        name: 'myApp2',
        filename:
          'remoteEntry.js', 
        exposes: {
          './Button': 
            './src/Button',
        },
        remotes: {
          myApp1:
            'myApp1@http://localhost:8083/remoteEntry.js',
        },
      }
    ),
    // ...
  ],
  // ....
};

In our page component

import React from 'react';
const MyApp1_Button = React.lazy(
  () => import('myApp1/Button')
);

function App() {
  return (
    <div>
      <h1>This is myApp2 importing button from myApp1</h1>
      <React.Suspense fallback='Loading Button'>
        <MyApp1_Button />
      </React.Suspense>
    </div>
  );
}

export default App;

myApp1 and myApp2 UI

Bellow how the myApp1 and myApp2 UI looks like

myApp1

myApp2

Thank you so much for reading this article. I hope you like it. Cheers