Mastering Microfrontends with Webpack Module Federation
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?
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.
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
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
- 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) andcart
(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.
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.
3. We will do the same with the cart
app to display the cart information.
The cart
app will be represented with a red border.
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.
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.
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.
- Inside each of the
products
andcart
app, we will update webpack.config.js to include the exposes field that references the respective remote app code. The ModuleFederationPlugin allows thehost
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.