Module Federation Pipeline — Part 1

Naman Saini
Engineering @ Housing/Proptiger/Makaan
5 min readJun 7, 2023

What is Module Federation?

Module Federation is a feature in modern JavaScript module bundlers like Webpack that allows separate applications or micro-frontends to share code and resources seamlessly. It enables the development of modular and independent components that can be distributed across different applications, allowing for greater flexibility, code reuse, and improved performance.

With Module Federation, applications can dynamically load and consume modules from other applications, eliminating the need to bundle everything into a single monolithic application. This approach promotes a more scalable and maintainable architecture, as changes or updates to individual modules can be deployed independently without affecting the entire system.

Current architecture of Housing

In our current architecture, we utilize module federation exclusively on the client side. This means that any page or route we want to fetch from our server should not be a federated module, but rather a part of the host app that hosts that specific route.

For instance, when opening the homepage of housing.com at https://housing.com/, it is considered a part of our demand application. In this scenario, the demand application is responsible for fetching the container files of all other applications within our micro-frontend setup. This process allows us to obtain different modules from sources other than the demand application.

You can observe this in action in the screenshot below.

Other containers being fetched on homepage apart from demand

The container file mentioned above is generated during the build step of Webpack. However, in order for this functionality to work, we need to add a specific plugin and configure it accordingly.

To achieve this, we include a plugin called ModuleFederationPlugin in our Webpack configuration. This plugin plays a crucial role in enabling module federation. By adding the plugin and specifying its configuration, we can ensure that our applications can share code and resources seamlessly.

Implementation steps:

  1. In order to enable module federation, we need to add the ModuleFederationPlugin to the Webpack configuration of each application in our setup. It’s important to note that this configuration is specific to the client build of our application and does not apply to the server side. The reason for this is that the ModuleFederationPlugin is designed to support client-side federation only.
  2. This plugin requires some configuration, and there are various options available based on our needs. Here are a few important ones. Please refer to the sample configuration code at the end.

i. ‘name’: Specifies a unique name for the container (host) project. This name will serve as the key to add this specific container to the window object.

ii. ‘filename’: Acts as the filename for the container. When we provide a URL to fetch this container from another app, it will end with …${filename}.js.

iii. ‘exposes’: An object that specifies the modules exposed by the current project, making them accessible for remote projects. For example, in the code snippet below, this container exposes a module located at the path “./routes/demandRoutes” with the name “exposedRoutes”.

iv: ‘remotes’: Defines the remote modules consumed by the current project. It allows us to specify the remote entry points and the exposed modules. This is used for static remotes when we want to fetch remote modules statically. For instance, in the code snippet below, whenever Webpack encounters ‘supply’ in an import path, it fetches the supplyContainer from the URL mentioned in the ‘remotes’ key and extracts the respective module from this container.

v: ‘shared’: Specifies the shared modules and their versions (using the ‘requiredVersion’ key). Shared modules are loaded only once, even if multiple projects depend on them. There is also a ‘singleton’ key that, when set to true, indicates that only a single instance of this shared module will be created among different apps. Otherwise, multiple instances would be created for each federated app, but the code of this module would only be loaded once. For example, in the code snippet below, the ‘react’ version 18.2.0 is set to be a singleton.

const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'demand',
filename: `demndContainer.m.m.js`,
exposes: {
exposedRoutes: "./routes/demandRoutes"
},
remotes: {
supply: 'supply@https://supply-app.com/supplyContainer.js',
inventory: 'inventory@https://inventory-app.com/inventoryContainer.js',
}
shared: {
react: {
singleton: true,
requiredVersion: '18.2.0'
},
'react-redux': {
singleton: true,
requiredVersion: '8.0.2'
},
}),
],
};

3. If we have specified the ‘remotes’ configuration in our ModuleFederationPlugin, there is no need for any additional steps. Webpack will automatically fetch the container and its respective module when it encounters an import statement that matches our remotes.

However, if we haven’t specified ‘remotes’, it indicates that we want to dynamically fetch containers and their exposed modules. There are multiple ways to fetch an app’s container (let’s say the ‘demand’ app) based on our specific use case. In our case, we achieve this by creating a script tag with its ‘src’ attribute set to the path of the demand’s container file. As mentioned earlier, this path ends with …${filename}.js, which is defined in the demand's ModuleFederation plugin configuration. When the script tag is executed, the container is added to the window object and can be accessed using the value specified in the 'name' field of the demand's module federation plugin configuration.

4. Now, we can access the container file to obtain the required module. Each container exposes methods such as ‘init’ and ‘get’. To access a module from the container, we need to first initialize the container within the specified scope using the ‘init’ function. Only after initialization, we can asynchronously load the module from the container using the ‘get’ function. For a comprehensive understanding of how this process works, please refer to the following link: Webpack Module Federation — Dynamic Remote Containers. Take a look at the sample code snippet, which does the same.

const loadComponent = async (app, module) => {
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__('default')

const container = window[app] // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default)
const factory = await window[scope].get(module)
const Module = factory()
return Module
}

The module returned by this function includes a ‘default’ property that holds the actual module code, which can be accessed and utilized.

This concludes the current article. In the next part, I will discuss how we successfully implemented module federation for our server-side application and integrated it into our architecture.

--

--