Webpack Module Federation in Angular

The implementation and especially integration of microfrontends in Angular is not that satisfying. This changes with Webpack v5 where module federation is introduced.

Previously Webpack wanted to know all dependencies at compile time. With module federation dependencies can be resolved at run time. This allows us to deploy microfrontends independently while having a smooth integration. Despite all that hype and the technical possibilities, we should be careful. It is vital to think about the scenarios where module federation usage makes sense.

Let’s go through an example based on our eCommerce Playground Angular app. In this context we compare module federation to approaches using default Angular modules and additional Angular applications running separately.

Our Goal: Adding an Overview of Orders

Our application currently looks like this:

Screenshot of an eCommerce Demo application showing products and a My Orders link in the header.

It’s an online shop with e.g. product list, search box and navigation bar at the bottom. Our customers want to have an overview of their orders. That’s why we plan to integrate an orders overview page and link it in the header behind “My Orders”.

Different Variants for the Solution

There are different variants how to integrate such an orders overview. We will explore 3 different ones. In this context the “main” app is the eCommerce application and “orders” the orders overview.

1) Integrate as Standard Angular Module

The most obvious solution is just to create a separate Angular Module and lazy load it through the router. This gives you the following advantages:

  • 🧩 Smooth integration and lazy loading
  • 🖇 Easy code sharing between main and orders

But this approach also comes with some drawbacks:

  • ⏳ Main app build takes longer
  • 📦 No separate deployments
  • 🖇 More tightly coupled

2) Use a Second Angular App on a Specific URL

We can build a separate Angular app for the orders overview and just deploy it under the link behind “My Orders”. This gives us the following:

  • ⏳ Lower build time because separated
  • 📦 Standalone deployment possible

But it comes with disadvantages:

  • 🧩 The integration is not smooth
  • ⏱ Shared dependencies downloaded twice

3) Integrate by Using Module Federation

Finally, we are having a look at the new possibility. Webpack module federation allows us to integrate the orders overview like a module but still having it as a separate application. We walk through a short how-to for adding module federation and then come to the evaluation.

Setup webpack version 5

We need webpack version 5 to make use of module federation. First, we want to switch to yarn as package manager:

ng config cli.packageManager yarn

When we use yarn instead of npm, the resolutions section in the package.json forces our dependencies, e.g. the Angular CLI, to use a certain webpack version we specify. So, we can enable webpack 5 in our package.json:

"resolutions": {
    "webpack": "^5.0.0"
},

Finally run yarn install to get all the dependencies considering webpack 5:

yarn install

Create a project in the workspace

We assume you are having a nx workspace. With the following command we create a second project in that workspace named “orders”:

nx generate @nrwl/angular:app orders

There we add OrderModule with a component that shows the orders. Nothing fancy and nothing worth showing.

Adding module federation

We need some customisation for the Angular CLI to change the webpack behaviour. That’s why we install and use @angular-architects/module-federation (thanks to Manfred Steyer). Let’s configure module federation for our two projects with the following commands:

ng add @angular-architects/module-federation --project main --port 4200 
ng add @angular-architects/module-federation --project orders --port 5000

Main should run under port 4200, while orders is running on 5000.

Configure the orders app as federated module

Now we would be able to run the two apps, main and orders, but they are not really integrated yet. Let’s first add the relevant configuration to the “microfrontend”, i.e. orders app. Let’s first edit the webpack.config.js. Thanks to the module-federation package added above, the relevant config is already added to the ModuleFederationPlugin, but commented out. So, let’s enable the part for the orders app that is a “remote”:

// For remotes (please adjust)
name: "orders",
filename: "remoteEntry.js",
exposes: {
    './OrderModule': './apps/orders/src/app/order/order.module.ts',
},   
shared: {
  "@angular/core": { singleton: true, strictVersion: true }, 
  ...

We are setting a name and exposing a module. This is it for the orders app that functions as a remote in our federated module landscape. Note that libraries under “shared”, e.g. @angular/core, will only be downloaded once.
Further adjustments were done previously when installing the module-federation package. Please check for example the main.ts of the orders app.

Integrate the orders app into the main app

We have the orders app ready to be integrated as federated module. Let’s first add a route to the routing config of the main app:

{
  path: 'orders',
  loadChildren: () => import('orders/OrderModule').then(m => m.OrderModule)
},

We specify a path and a loadChildren function similar to traditional Angular modules. In the import path we specify the microfrontend “orders” and its Module. The TypeScript compiler is not happy about that because the ‘orders/Module’ is not known in the main app. To make the compiler happy again we can add a type definition by creating a decl.d.ts in the main app’s src directoy

declare module 'orders/OrderModule';

We also add an actual link to the new route in our template:

<a [routerLink]="['/orders']">My Orders</a>

As a last step we update the webpack.config also on the main app’s side. This time the “host” section is the relevant one:

// For hosts (please adjust)
remotes: {
     "orders": "orders@http://localhost:5000/remoteEntry.js",
},

We are telling webpack that the “orders” remote is found on port 5000.

Running everything

Let’s start both applications in separate terminals:

ng serve main
ng serve orders

If we now click on the “My Orders” link in the header the order list is shown:

In the network tab we see a common.js loaded that contains the OrderListComponent (but not the entire Angular package). The integration is very smooth and one doesn’t really notice that the functionality is coming from a different app.

Conclusion

Of course this was a quick walkthrough and possibly the simplest setup for webpack federated modules. But it demonstrated the idea and we can summarise the benefits as following:

  • 🧩 The integration is smooth
  • ⏳ Lower build time because separated
  • 📦 Standalone deployment possible
  • ⏱ Shared dependencies downloaded only once

My verdict on webpack module federation: something to try out!

Again, sources for above example can be found on GitHub.