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
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"
}
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"
]
}
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",
},
}),
],
};
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" />
</>
);
}
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)} />
</>
);
}
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
We will also change our package.json file as we did before something like
"scripts": {
"build": "webpack",
"start": "webpack serve --watch-files ./src"
}
Also create a babel.config.json file alike config below
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
]
}
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",
},
}),
],
};
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;
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.
Reference readings
https://webpack.js.org/concepts/module-federation/
Top comments (0)