When you’re building micro frontend applications with Angular and Webpack 5, dependency sharing becomes critical. Without it, you risk bloated bundles, redundant code, and version conflicts. Module Federation offers built-in tools to help manage this, but knowing how to use them well makes all the difference.
The Core Concepts: Host and Remote
- Host Application (Shell): Acts as the container and controls layout and routing
- Remote Application: Independent Angular apps that expose modules, components, or services to the host
Module Federation allows dynamic runtime loading of remotes by the host, making it possible for teams to build and deploy features independently.
The Power of the shared Configuration
Webpack’s ModuleFederationPlugin has a shared property that tells it which libraries should be used by both the host and remote apps. This is helpful in microfrontends because it stops the same library from being loaded more than once on the page. For example, you can make sure Angular or React is shared, so it isn’t duplicated.
Strategies for Sharing Dependencies
There are several effective strategies for managing shared dependencies in an Angular micro frontend setup. The choice of strategy often depends on the specific requirements of your project, the size of your teams, and your overall architectural goals.
Strategy | Description | Pros | Cons |
Share All | Shares all dependencies from the host with all remotes. | Easy setup, less config | Shares too much, bigger bundles |
Singleton (Granular) | It shares only key libraries like Angular core as singletons. | Prevents errors, better performance | Manual setup, more effort |
Strict Versioning | Enforces matching versions for shared packages. | Stable, catches version issues early | Can block remotes if versions clash |
Practical Example: A Shell and a Remote Micro Frontend
Let’s walk through a practical example of setting up a host (shell) application and a remote micro frontend, sharing Angular dependencies.
Project Setup
We’ll create a new Angular workspace with two applications: a shell and a mfe1 (micro frontend).
1. Create a new Angular workspace
ng new angular-mfe-example --create-application=false
cd angular-mfe-example
2. Generate the shell and remote applications
ng g application shell --routing --style=scss
ng g application mfe1 --routing --style=scss
3. Adding Module Federation
Next, we’ll add the @angular-architects/module-federation plugin to both projects.
ng add @angular-architects/module-federation --project=shell --port=5000 --type=host
ng add @angular-architects/module-federation --project=mfe1 --port=3000 --type=remote
This command will create webpack.config.js and webpack.prod.config.js files in both projects and update the angular.json to use a custom builder.
4. Configuring the Remote (mfe1)
In the webpack.config.js of the mfe1 project, we’ll expose a module (e.g., FlightsModule) and define our shared dependencies.
// mfe1/webpack.config.js
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({ name: 'mfe1', exposes: { './Module': './projects/mfe1/src/app/flights/flights.module.ts', }, shared: { ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }), },
});
In this configuration:
- name: A unique name for the remote.
- exposes: Defines which files from the remote are made available to the host.
- shared: Utilizes the shareAll helper to share all dependencies from package.json as singletons with strict version checking.
5. Configuring the Host (shell)
In the webpack.config.js of the shell project, we’ll define the remote and configure the shared dependencies.
// shell/webpack.config.js
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({ remotes: { "mfe1": "http://localhost:3000/remoteEntry.js", }, shared: { ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }), },
});
Here, the remote object maps the name of our remote (mfe1) to its remote entry file URL.
6. Lazy Loading the Remote Module
Now, in the shell application’s routing, we can lazy load the exposed module from mfe1.
// shell/src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [ { path: 'flights', loadChildren: () => import('mfe1/Module').then(m => m.FlightsModule) }
];
@NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule]
})
export class AppRoutingModule { }
The import path mfe1/Module is a virtual path that Webpack understands thanks to the Module Federation configuration.
7. Running the Applications
Finally, run both the shell and mfe1 applications.
Generated bash
ng serve shell -o
ng serve mfe1```
Conclusion
Sharing dependencies the right way can make or break your micro frontend architecture. Whether you go with a blanket approach or prefer granular control with singletons, it’s essential to understand how these configurations affect load times and stability. If your team is moving toward a distributed architecture, now’s a good time to hire Angular developers who are well-versed in Module Federation and can help architect this cleanly from the start.