Mastering Microfrontends: Sharing Dependencies Across Apps

Eric Bach
AMA Technology Blog
5 min readNov 17, 2022

Previously we saw how we can build our single-page application using microfrontends in my previous blog post. In this post, we will explore how dependencies work within a microfrontend host and remote apps. We will look at how packages get loaded with Webpack Module Federation, how we can share the same versions of packages, and how we can use specific versions of each package in our host and remote apps.

With microfrontends, there is the ability to run each remote app in isolation and also as part of the host app. Running in isolation has the benefit of allowing each team to easily develop and test their remote app before integrating it with the rest of the application within the host app.

The products app running in isolation (left) and within the host app (right)

When each app is run in isolation it is no different than any monolithic SPA that we are familiar with. Further inspecting the Network tab in the browser debugger tools, we can see each application’s code is bundled into a main.js file and each dependency (@faker-js/faker, react, react-dom) is being loaded individually.

Photo by Markus Spiske on Unsplash

As discrete standalone applications, this behaviour is expected and nothing out of the ordinary. But what does this look like when we inspect the host app?

JavaScript files for each of the products (top) and cart (bottom) apps

When we take a closer look at the host app, we see that it is also loading two identical copies of @faker-js/faker (remember we have been using the same version in each remote app). Why is this happening? Let’s explore in the next section.

The source code for this project can be found in my GitHub repository here.

How The Host App Loads Codes

To understand why this is happening we should first review how the Webpack Module Federation plugin is fetching code from each app.

The host app contains the remotes entries for each of the products and cart remote apps. This is the file that further instructs Webpack where to fetch the code for each of the remote apps.

webpack.config.js of the host app

This is performed in a sequence of steps:

  • The host app fetches the products remoteEntry.js file
  • The remoteEntry.js file requires @faker-js/faker so it is fetched
  • The host app fetches the cart remoteEntry.js file
  • Again, the remoteEntry.js file requires @faker-js/faker so it is fetched once more

As a result, we end up seeing that the host app ends up fetching two identical copies of @faker-js/faker. But how come it does not fetch two copies of react or even react-dom? And how can we only fetch one copy of @faker-js/faker?

Sharing Dependencies Between Apps

The answer to both of the previous questions can be answered in one configuration within the Webpack Module Federation plugin configuration.

When we used the create-mf-app template to generate our microfrontend apps, it scaffolds a lot of code in the webpack.config.js file. The Webpack Module Federation plugin configuration includes a shared section that allows us to define which dependencies should be shared across apps.

By default, this template shares all the dependencies in the package.json file. The only exception is the react and react-dom libraries that are configured to be singletons in order to prevent multiple copies of react and react-dom to be loaded in the browser which would cause all sorts of errors.

webpack.config.js of the host app

Now to allow us to share only one instance of @faker-js/faker, we can simply add the devDepdencies from package.json to this list.

webpack.config.js of the products remote app

Now if we restart the products and cart remote apps and re-load the host app we can see that there is indeed only one copy of @faker-js/faker being loaded now. As a result, the overall package size of the JavaScript files being loaded has been reduced from 11.8 MB to 7.5 MB (a 36% improvement before even minifying our code)!

Specifying Versions in Remote Apps

Within our microfrontend application, each of the remote apps may be managed by different teams. As we have seen before, each of these teams may prefer or have a reason to use different technologies or even different package versions.

Suppose the team that is responsible for the cart remote app is using an older version of @faker-js/faker, suppose version 6.3.1 instead of 7.4.0 in products. We no longer want to share all the dependencies in the package.json file or Webpack will by default use the higher version available.

package.json with an older version of @faker-js/faker

To do this we can remove the devDependencies from the webpack.config.js in each of our remote apps. This tells the Webpack Module Federation plugin not to load one copy of the dependencies for all of the remote apps.

webpack.config.js of the remote apps

Once we restart our apps, we can inspect again to see that we are indeed getting two different versions of @faker-js/faker for each of the remote apps. Of course, our bundle size naturally has also increased from 7.5 MB to 10.5 MB. This is to be expected because we require multiple copies of the package to be loaded.

Two different versions of @faker-js/faker are loaded

Wrapping Up

Now that we have seen how the Webpack Module Federation plugin helps manage package dependencies within a microfrontend app, the next series of posts will explore how to handle communication within a microfrontend application.

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