Module Federation for distributed front ends — the best of both worlds?

Exploring how we can enable independent release flow in the modern web application

Oleksandr (Sasha) Khivrych
Beamery Hacking Talent

--

Following on from our previous Beamery Engineering article, “Two years of micro-frontends: a retrospective”, we want to dig deeper into the fascinating topic of micro-frontends.

The right micro-frontends approach can provide many benefits for organisations. However, if poorly architected, this approach can lead to many critical issues, especially:

  • Overly complex architecture
  • Problems with shared libraries
  • Poor web performance

Beamery’s previous article used an example from Facebook for how to build a modern modular React app. It uses a single React DOM tree, shared context API and design system components to reduce complexity and improve runtime performance.

By using a monorepo implementation, coupled with tools such as NX or Turborepo, it is possible to split an application into different modules and create custom shared libraries, which provide better team collaboration, reduce code duplication, and enable independent development. As a result, you could achieve most of the benefits that micro-frontend architecture provides and have fewer problems.

However, it is a monolith, requiring building and releasing the whole app every time. Teams will be less independent and may have a longer delivery cycle imposed upon them.

So is it possible to have a super-fast monolith at runtime with distributed, decoupled frontends for the development and delivery?

Solution: Module Federation 🚀

Module Federation is a killer feature in Webpack 5 (from 2020), allowing you to point in modules from another webpack assembly installed on a different host.

What is Module Federation, and how does it work?

This example from the official repository shows a single React application with two routes: an orders page and a dashboard page with many widgets, including a RecentOrdersWidget

Federated React App
Federated React App

The wider application is decomposed into “federated micro-apps”. Each micro-app builds separately using an individual Webpack Config and Module Federation plugin.

In Module Federation terminology, each federated micro-app is a “container, which could be owned independently by a different team.

It’s possible to use the standard ES import (or async import) to load modules at runtime from another container. All work to load modules and shared dependencies will be handled by Module Federation internally.

In order to make it work, the following configuration must be provided to the Module Federation plugin config: unique container(app) name, remotes, exposes and shared.

Example of federated config for the Shell micro-app

Let’s look more closely at each of these additional configuration terms:

Exposes

All modules specified in this property will be accessible to the consumer container. The specific module name and path to the source file in your project must be provided. Any webpack module that can be processed in your codebase is supported, such as CSS, TS and JS.

Example of exposes config for the Order micro-app

In the example above, OrderContainer exposes OrderService (separate page) and RecentOrdersWidget(need to be rendered on the dashboard page).

Remotes

To load some remote modules from another container, you will need to specify the name of the remote container and the URL to remote-entry.js(generated by Module Federation during the build). The URL can be defined dynamically at runtime to load remote entries from localhost or any CDN domains. Once a remote entry is loaded, from this moment, it is possible to load any modules that the remote container exposes.

Example of importing remote modules from different containers

In the example above, the DashboardContainer consumes two remote modules: shell/Service and order/RecentOrdersWidget.

All remote modules are lazy loaded in parallel by Module Federation and then will be cached.

A container is referred to as a “remote” when exposing modules or a “host” when it is consuming modules. However, in some cases, it can be both “remote” and “host” simultaneously.

For example, the Shell container exposes the Service module and consumes route components from Order and Dashboard container.

Shared

The Shared property allows the specification of global libraries you wish to load only once at runtime. It is one of the most powerful features of Module Federation.

As with remote modules, Module Federation will automatically lazy-load all shared dependencies at runtime as required on the page.

Example of a shared dependencies configuration for the federated project

It is possible to specify all dependencies from your package.json as shared with the required version and specify that it is a ‘singletonversion, meaning that there should be only one version of this library at runtime

I highly recommend keeping ‘react’, ‘react-dom’ and context-based libraries such as ‘react-router’ always up to date by enabling singleton mode in the federated config.

If you specify the ‘requiredVersion option, Module Federation carries out a version check before loading the shared module. If the version is incompatible, it may throw an error, or if the ‘fallback option is enabled, it will load the required version from the current container.

The documentation has many more options that can be specified.

Using a flexible shared config reduces the complexity of managing shared dependencies because it is encapsulated in each micro-app. Moreover, it can provide a better strategy for updating dependencies.

Module Federation also has these additional benefits:

  1. Native Federation-a new version of federations, currently in beta. This will enable Module Federation for non-webpack systems, with full feature parity and built on native web standards, like ESM, import maps etc.
  2. SSR support-Module Federation support server-side rendering out of the box. Moreover, there is an official plugin for NextJS, and they are currently working on Remix support.
  3. Active community-many large companies such as Netflix, Microsoft and Amazon are already using federated implementation, and it is popular right now in the frontend community.

Useful links

  1. Module Federation Examples-amazing repo with practical examples.
  2. Webpack Module Federation Documentation
  3. Module Federation Blog-here you can find helpful information such as an MF book, blog, videos, etc.
  4. Zack Jackson-Creator of Module Federation. Follow him on Twitter to be always up to date.
  5. NX+React+Module Federation-official boilerplate code.

Conclusion

While Module Federation is an essential tool for enabling distributed web in organisations…

With great power comes great responsibility.

That is why it is always important to have a good CI/CD process, infrastructure, error handling, and observability for maintaining and receiving maximum value from distributed frontends.

So what’s next?

We at Beamery have finished the proof of concept of using this technology, and we are gathering feedback on using a federated approach with our engineering team.

Stay tuned for future updates!

Interested in joining Beamery’s Engineering, Product & Design Teams?

We’re looking for Software Engineers Front & Back Mid/Senior/Director, SRE Platform Engineers, Engineering Managers, Tech Leads, Product Operations/Managers/Designers across all levels and even more roles across London, Remote & Berlin! to join us — apply here!

--

--