Mastering Microfrontends with Webpack Module Federation

Eric Bach
AMA Technology Blog
6 min readOct 10, 2022

Just like a typical monolithic application over time, a single-page application can quickly become difficult to scale, maintain, or even understand for a new developer on the team. Traditional monolithic frontends pose many challenges:

  • How do we limit the blast radius of errors in our application so we do not bring down the whole system?
  • How do we accelerate and scale autonomous feature development across teams?
  • How can we limit the risk of framework changes or leverage different frameworks altogether?
Curtain wall by Héctor J. Rivas on Unsplash

Microfrontends aim to solve these challenges by extending the backend microservices pattern to the frontend. If a single team manages the entire frontend that talks to multiple backend services, they would need to understand this entire span of scope to maintain the frontend. Each of these backend services may also be built using different technologies and be responsible for very different business domains.

Evolution to vertical domains with microfrontends

Using microfrontends, we can align our web application to each of our business domains. Each vertical business domain can be owned by independent teams that may have their preferred architectural standards. This approach allows teams to be cross-functional, developing features end-to-end, from the user interface all the way to the backend database.

Webpack Module Federation

Module Federation is a plugin introduced in Webpack 5 that offers the ability to create multiple separate builds without dependencies between each other so they can be developed and deployed individually. Regardless of the environment (React, Vue, SolidJS, etc) used, any application bundled with Webpack 5 or greater can be dynamically loaded or share code and dependencies with each other at runtime.

This feature makes it incredibly simple to set up a microfrontend application as we will see in the next example.

A Simple Real-World Example

Suppose we have an e-commerce application consisting of a catalogue of products and a checkout cart. Following Domain Driven Design, we can think of the products and cart as separate domains and individual microfrontend remote apps. There is no direct communication between them and no direct integration between them.

They can be maintained by different teams that can make changes independently and choose radically different architectural decisions. One team can use ReactJS to build the product catalogue, and one can use VueJS for the cart. Each team has the flexibility and autonomy to use their preferred style without impacting other teams.

For all of this to work, a single lightweight host app is required to coordinate the loading and displaying of each remote app. Each remote app can run in isolation or within the host app.

To demonstrate how this works, we will create 3 JavaScript applications with the create-mf-app helper: one for the host app and two for each of the remote apps — products and cart.

  • The host app will be built using SolidJS
  • The products app will be built using ReactJS
  • The cart app will be built using VueJS
Example MFE App

This example will not go over the difference between each of these libraries/frameworks, as they are mainly meant to illustrate how different environments can be used. The sample code can be found on my GitHub repository here.

Create Microfrontend Apps

We will first use the create-mf-app helper to create the host, products, and cart apps

  1. The host app (solid-js, port 8080) will be created to manage when and where to show each remote app. Each of the remote apps, products (react, port 8081) and cart (vue3, port 8082), can be created the same way.
$ npx create-mf-app? Pick the name of your app: host
? Project Type: Application
? Port number: 8080
? Framework: solid-js
? Language: typescript
? CSS: CSS

2. First we will work in the products app where we will install @faker-js/faker to generate some placeholder data.

App.tsx for products app

To represent the boundary of the products app, we will also apply a blue border. This will be evident when we hook up the host app.

Products app displaying a list of products

3. We will do the same with the cart app to display the cart information.

App.tsx for cart app

The cart app will be represented with a red border.

Cart app displaying the number of items

4. The host app will contain a couple of div with ids matching those that we will create in each of our remote apps so it can load each app once we set up the microfrontend communication.

host app — App.tsx

The host app will just contain a heading represented by a green border. The borders illustrate the boundaries of each of the host and remote apps in the future.

Host app

Microfrontend Communication

Once we have our host and remote apps setup, we can configure the microfrontend communication. The host app will need to know how and where to load each of the remote apps, so we will start with setting up the Webpack configuration first.

  1. Inside each of the products and cart app, we will update webpack.config.js to include the exposes field that references the respective remote app code. The ModuleFederationPlugin allows the host app to load each of our remote app’s bundled code.

2. Next, we will update the webpack.config.js in the host app with the correct path of where the remote app code can be found.

3. For the host app to display each of the remote apps we need to add the two divs with the id tags matching the ids in each of the remote apps.

Putting It All Together

If we now start the host app and each of the remote apps, and navigate to the host app URL, http://localhost:8080, we will find that it is indeed loading each of our remote app sources in the Network tab of the browser debugger.

The host app now displays our products and cart apps with the same coloured borders illustrate this for us again.

Solving The Initial Challenges

Now we have seen how Webpack Module Federation makes building microfrontends much simpler.

By aligning each of our apps, products and cart, vertically to each business domain independent teams can be responsible for each application with the flexibility to leverage their preferred technology of choice (React, Vue, SolidJS, etc). Not only does this reduce the risk of errors in one remote app impacting the entire application but it allows teams to work autonomously and massively scale as the application grows.

In the upcoming series of posts, we’ll tackle further microfrontend concepts including sharing dependencies between remote apps, managing different package versions, and microfrontend communication.

References

Microfrontends with React: A Complete Developer’s Guide
https://www.udemy.com/course/microfrontend-course/

Eric Bach is a Senior Software Developer @ Alberta Motor Association who enjoys learning, reading, and writing about leadership principles, event-driven microservices, and all things AWS.

--

--

AMA Technology Blog
AMA Technology Blog

Published in AMA Technology Blog

Sharing stories on how we use technology to empower nearly one million members in Alberta

Eric Bach
Eric Bach

Written by Eric Bach

Senior Software Developer @ amaabca | AWS Certified x 2 | Domain Driven Design | Event Driven Architecture | CQRS | Microservices