DEV Community

Cover image for Webpack 5 Module Federation : Exchange code between applications
ishan
ishan

Posted on

Webpack 5 Module Federation : Exchange code between applications

Module Federation is one of the most exiting feature that was introduced in Webpack 5 release dated 2020-10-10.

With this there has been a huge change on how we architect our modern application we build today.

What is Module Federation(MF)?

Module Federation(MF) aims to solve the fiction sharing of modules in a distributed system. To simplify things we can bring in modules of our code across multiple applications.

The code can contain :: state of the application, library dependencies that will be made available within a common registry managed by the Webpack container.

Module Federation allows a JavaScript application to dynamically load code from another application and  in the process, share dependencies.
Zack Jackson (Creator of Webpack)

It’s important to note that this system is designed so that each completely standalone build/app can be in its own repository, deployed independently, and run as its own independent SPA.

 💡 This is often known as Micro-Frontends, but is not limited to that.

Setting up Module Federation

We will take a look into how can we use MF in our demo React application. We will just start with create-react-app for simplicity.

First React App

mkdir applicaion01
cd applicaion01
npm init -y
npm install react react-dom --save
npm install @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader html-webpack-plugin webpack webpack-cli webpack-dev-server --save-dev
Enter fullscreen mode Exit fullscreen mode

With the above commands in place we can make our first change inside of package.json file

"scripts": {
  "build": "webpack",
  "start": "webpack serve --watch-files ./src"
}
Enter fullscreen mode Exit fullscreen mode

Make sure you are in your current project directory. We will need a change the babel presets as below

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Lets start with our webpack.config.js file and past the code below

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

module.exports = {
  mode: "development",
  resolve: {
    extensions: [".css", ".js", ".jsx"],
  },
  module: {
    rules: [
      {
        test: /\.s?css$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              url: {
                filter: (url) => {
                  if (url.startsWith("data:")) {
                    return false;
                  }
                  return true;
                },
              },
            },
          }
        ],
      },
      {
        test: /\.jsx?$/,
        use: ["babel-loader"],
        exclude: /node_modules/,
      },
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: "asset/resource",
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public", "index.html"),
    }),
    new ModuleFederationPlugin({
      name: "APP_ONE",
      filename: "remoteEntry.js",
      exposes: {
        "./app": "./src/components/App",
      },
    }),
  ],
};
Enter fullscreen mode Exit fullscreen mode

In the code above we set up couple of loaders needed for our application. We named our (first) application application01 because we will be sharing code across multiple application/projects.

Let's continue with editing our new file app.js file.

import React from 'react';
import "./styles.css";

export default function App({ onChange }) {
  return (
    <>
      <input onChange={onChange} type="text" placeholder="Enter your address" />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

The above component App is the main microfrontend we will be exposing in our project.

We can add more components and expose them using webpack.config.js file which we will dive into

Lets edit our main file main.js where we will be importing the file we created before app.js into this file.

import {useState} from 'react';
import App from './app';
import "./styles.css";

export default function MainApp() {
  const [address, setAddress] = useState('');
  return (
    <>
      <h3>{ address ? <p>Your address {address}</p> : null }</h3>
      <App onChange={(e) => setAddress(e.target.value)} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

With this in place we can see a input file where we could type our address inside and see the binding state changed.

Creating second React app

Lets create another project and name it application02

Second React App

mkdir applicaion02
cd applicaion02
npm init -y
npm install react react-dom --save
npm install @babel/core @babel/preset-env @babel/preset-react babel-loader css-loader html-webpack-plugin webpack webpack-cli webpack-dev-server --save-dev
Enter fullscreen mode Exit fullscreen mode

We will also change our package.json file as we did before something like

"scripts": {
  "build": "webpack",
  "start": "webpack serve --watch-files ./src"
}
Enter fullscreen mode Exit fullscreen mode

Also create a babel.config.json file alike config below

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Create our webpack.config.js file and paste the code below

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

module.exports = {
  mode: "development",
  resolve: {
    extensions: [".css", ".js", ".jsx"],
  },
  module: {
    rules: [
      {
        test: /\.s?css$/,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              url: {
                filter: (url) => {
                  if (url.startsWith("data:")) {
                    return false;
                  }
                  return true;
                },
              },
            },
          }
        ],
      },
      {
        test: /\.jsx?$/,
        use: ["babel-loader"],
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "public", "index.html"),
    }),
    new ModuleFederationPlugin({
      name: "applicaion02",
      remotes: {
        APP_ONE: "applicaion01@http://localhost:8080/remoteEntry.js",
      },
    }),
  ],
};
Enter fullscreen mode Exit fullscreen mode

We have done the same process adding needful loaders to our application and setup our app remote as applicaion01

Let's go ahead to edit our app.js file inside of our src folder. Below are some changes

import { lazy, Suspense } from "react";
import "./styles.css";

const AppOne = lazy(() => import("APP_ONE/app"));

const App = () => {
  const [address, setAddress] = useState('');

  return (
    <>
      <h3>{ address ? <p>Your address {address}</p> : null }</h3>
      <div>
        <Suspense fallback={<span>Loading...</span>}>
          <AppOne onChange={(e) => 
 setAddress(e.target.value)} />
        </Suspense>
      </div>
    </>
  );
};

export default App;

Enter fullscreen mode Exit fullscreen mode

We are making use of react lazy loading to load parts of our application. We are also importing suspense so that we could wrap our remote React micro app we created.

Where we have alike micro-frontend architecture going on.

We have successfully to this point imported modules from another app and used it inside of another application. We have implemented something basic, we just scratched the surface.

The awesome thing is this also works well with Server Side Rendering(SSR) means we can use this inside of any application/framework unless its using Webpack as a module bundler your code chunks.

Conclusion

We used Webpack 5 Module Federation to consume and share micro-frontend components. This brings a huge gap of possibilities using Federated Modules.

Snowboarding

Reference readings

https://webpack.js.org/concepts/module-federation/

https://medium.com/swlh/webpack-5-module-federation-a-game-changer-to-javascript-architecture-bcdd30e02669

https://dev.to/marais/webpack-5-and-module-federation-4j1i

https://javascript.plainenglish.io/next-js-11-module-federation-and-ssr-a-whole-new-world-6da7641a25b4

Top comments (0)