Module Federation is a powerful tool that allows you to share code between applications. It enables you to build applications that are more modular, maintainable, and scalable. In this guide, we will explore the benefits of using Module Federation in a monorepo.
Polyrepos (multiple repositories) have long since been the de facto choice for developing micro-frontends, to combat organizational issues, however, they introduce more organizational challenges around code sharing, dependency management, versioning and team autonomy.
Enabling team autonomy via Micro Frontends inadvertently pushed teams towards a polyrepo structure. Each team would have their own repository and would be responsible for maintaining their own codebase. This approach led to a lack of collaboration and a lack of transparency among other challenges.
You can get all the benefits of Module Federation without the hassle of managing multiple repositories when you choose a monorepo, including some additional benefits around code sharing and dependency management. This guide will help you understand the benefits of using Module Federation in a monorepo and how they address organizational challenges.
Total isolation can be very desirable for teams but it comes with its own set of caveats and issues, especially with Module Federation. It's easy to forget that when working with a Module Federation system, the end result that the user experiences is a single application. As such, the total isolation and autonomy over tech decisions for polyrepos that comprise portions of the single application can actually lead to inconsistencies in the final experience for the user.
Let's take a look at some of the other challenges that arise with the polyrepo approach.
The polyrepo approach can lead to a lack of transparency and feedback loops. This can be especially problematic when working with a Module Federation system.
When one team makes a change to their portion of the application, it can be difficult to understand how it will affect the other parts of the application. Notification of runtime issues and breaking changes cannot be found until after a deployment has occurred.
Even if there is a quality assurance process in place to ensure that changes are safe before reaching production, the feedback loop is slow and can be disruptive, forcing teams to either roll back changes or quickly fix issues that arise instead of adding new features.
Sharing code between polyrepos is difficult. It tends to involve creating and maintaining a private package registry of some form. Shared code gets built and deployed to this registry and consumed by the other polyrepos. If a change is made to the shared code, it must be released to the registry.
This can be time-consuming and error-prone! It introduces the risk of blockers to feature work if any of the teams rely on the PR for the shared code to be released to the registry.
It also circles back to the feedback loops issue, as teams must wait for the registry to be updated before they can see the impact of their changes, if they ever see the impact at all because of the isolated teams. They could be inadvertently introducing breaking changes that affect other teams.
The teams consuming the shared code must also actively update the versions of the shared packages they are using to ensure compatibility across the module federated system, or risk breaking changes at runtime.
Polyrepos can also lead to dependency management issues. When teams are working in isolation in their own repository, they may have different dependencies or different versions of dependencies. This can lead to conflicts, versioning issues or worse, increased bundle sizes because of duplicated dependencies.
For example, if one team is using React 18 and another team decides they want to use React 19 so they update their dependency, then once the changes are deployed to production there is a high risk of incompatible code and runtime errors, especially if other portions of the application do not support React 19.
In a polyrepo setup, the update of React 19 does not immediately inform the team that by doing so they will be breaking the other teams. This can lead to a lot of frustration and rolling back of changes.
When teams are working in isolation, they may not have the same testing strategies or tools. This can lead to inconsistent testing across the system, which can be a problem for the overall quality of the application.
It also adds difficulty to the QA process. Either they test each micro-frontend separately or they test all of them together.
Testing individual micro-frontends does not provide the same level of confidence in the overall quality of the application because it does not test the integration points nor any shared context between them.
It also means having to build some kind of test framework that can mock certain layers of the full application which introduces a maintenance tax that must be paid to ensure that the test framework stays up to date with the other portions of the application.
Testing the full application will provide a higher level of confidence overall, however, in a polyrepo setup it requires setting up configurable test environments that can deploy the latest versions of each micro-frontend. This is time consuming, costs money on cloud resources and means that feedback on issues that arise during testing is delayed, reducing overall iteration speed.
A monorepo is a single repository that contains all the code for a project. It is a single source of truth for the entire project. Let's take a closer look at each of the challenges above and how a monorepo can address them.
In a monorepo, the feedback loop is simplified because all the code is in the same repository. This means that changes that are made by individual teams will present immediate feedback on how they impact the other teams and micro-frontends. This eliminates the need for manual releases and reduces the risk of breaking changes.
It informs the developer of the impact of their change at the time of making it, allowing them to make the necessary changes to ensure that the impact is minimal.
With monorepo tooling such as Nx, teams can share code without the need for a central package registry. This eliminates the need for teams to manually release changes to the registry and reduces the risk of conflicts and versioning issues.
It also greatly improves the speed of development as it cuts out the process of updating a separate repo of shared code, waiting for a new release to the central registry and then installing the new version in the consumers.
Any changes to shared code can be made in the same repository as the code that consumes it. This increases collaboration and transparency across teams while improving quality (changes will provide immediate feedback) and decreasing the risk of breaking changes.
Observability into what is using the shared code is also increased with help from dependency graphs and IDE support. This can help developers make informed decision about how impactful their changes will be.
In a monorepo single version bumps of major dependencies such as React or Angular are applied to all consumers. This ensures maximum compatibility across the system and reduces the risk of breaking changes.
It does require teams to collaborate and align on when to perform these upgrades, but it results in more confidence and compatibility for the end user.
Teams can still choose to use their own versions of other minor dependencies such as date-fns
or lodash
if they choose to do so by defining those dependencies in their own package.json
, or by aliasing them in the root package.json
.
Testing strategies are also simplified in a monorepo. This is because all parts of the system are in the same repo, so testing is easier and more consistent. Coordinating local serves to run automated e2e and integration tests against the system is easier because the repository will have the latest from each micro-frontend at all times.
These automated tests can be run in a CI/CD pipeline, which can be triggered by changes to the monorepo. This ensures that the full system is always tested and that any issues that were previously difficult to identify (integration points between micro-frontends) are caught early.
The test suites can also be written closer to how the user would actually use the final deployed application improving the quality of the final product.
Nx is a powerful open-source build system that provides tools and techniques for enhancing developer productivity, optimizing CI performance, and maintaining code quality It works especially well with monorepos and comes with built-in support for Module Federation for Angular and React projects.
Learn more about why Nx is the best choice for Module Federation tooling