Mastering Module Federation with Angular Standalone Components
Written on
Chapter 1: Introduction to Module Federation
This guide serves as the second installment in our exploration of Module Federation. Here, we delve into the practical application of Module Federation with Angular Standalone Components. The objective is to provide a comprehensive walkthrough that outlines the integration of this functionality within an Angular application. Our discussion will be based on a newly created Angular application where we will progressively implement module federation.
The examples and code showcased herein are developed using Angular version 17. You can access the source code for this application [here](#).
Step 0: Overview of Our Application
Our project revolves around a basic notes application featuring two primary tabs: Notes and Customer. In this setup, the application that hosts both tabs is referred to as the shell or host application. The Customer tab, however, operates as a separate remote application. The host application utilizes module federation to dynamically load and integrate the customer application.
- Host: http://localhost:4200
- Remote: http://localhost:4201
The host application comprises the notes functionality while consuming the customer app as a remote entity. Feel free to experiment with your applications or utilize the provided source code for a hands-on experience.
Step 1: Custom Webpack Configuration
To facilitate module federation, we employ a plugin known as ModuleFederationPlugin from Webpack, which is not included by default in Angular applications. Therefore, our first task is to configure the application to accept custom Webpack settings, allowing us to incorporate the module federation plugin.
We will utilize the custom-webpack builder to implement this customization. Begin by installing the necessary package:
npm i -D @angular-builders/custom-webpack
Next, apply the following modifications:
- Change the build's builder to custom-webpack:browser
- Change the serve's builder to custom-webpack:dev-server
- Specify the path to the custom Webpack configuration file in the options under customWebpackConfig
- Modify the "browser" key under options to "main"
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"main": "./src/main.ts",
"customWebpackConfig": {
"path": "./custom-webpack.config.js"}
}
},
"serve": {
"builder": "@angular-builders/custom-webpack:dev-server"}
}
This configuration is required for both the host and the remote applications. However, the remote application needs an additional setting to address issues that arise when both the host and remote are served simultaneously using ng serve.
"architect": {
"serve": {
"options": {
"publicHost": "http://localhost:4201"}
}
}
If the above option is not configured, the host application may experience frequent refreshes as it continuously fetches updates from the remote.
Step 2: Configuring Webpack for Remote
In this step, we will add the module federation plugin to the custom Webpack file for the remote application. While additional configuration options will be included, comments will clarify their purpose to maintain brevity.
The remote application will expose functionalities for other applications to utilize:
- name: A unique identifier for this module federation application.
- filename: The entry file generated for the remote, which the host will use to access the exposed modules.
- exposes: The modules we wish to share from this application, with the key being the name assigned to the exposed module and the value representing the module's path.
- shared: The libraries we intend to share with other applications. Initially, this can be left empty, as we will populate it later.
// custom-webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "angular-mfe-remote", // Unique name for the remote application
filename: "remoteEntry.js", //
exposes: {
"./Test": "./src/app/customer/customer.component.ts",},
library: {
type: "module",},
shared: {}
}),
],
}
Once this step is completed, executing the npm run build command will generate a remoteEntry.js file as part of the build process. This concludes the setup for the remote application, with module federation managing the underlying operations.
Step 3: Configuring Webpack for Host
Next, we will replicate the previous step for the host application so it can consume the modules exposed by the remote.
The host's configuration mirrors that of the remote, but instead of exposes, it will include remotes:
- remotes: The modules that the host intends to consume. The key is the name given by the host to the remote, and the value is the URL of the remote's entry file.
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "angular-mfe-host",
filename: "remoteEntry.js",
remotes: {
remote: "http://localhost:4201/remoteEntry.js",},
shared: {},
library: {
type: "module",},
}),
],
};
Step 4: Adapting Bootstrap Pattern
In Angular projects, the main.ts file serves as the entry point and contains the code necessary to bootstrap the application. However, we need to modify this for module federation, as shared libraries may not be available if we load and execute our main application immediately. Therefore, we will transfer the code from main.ts to a new file named bootstrap.ts. The main.ts file will then dynamically import bootstrap.ts, allowing sufficient time for the application to load the remoteEntry.js file and determine the shared libraries.
// main.ts
import('./bootstrap').catch((err) => console.error(err));
If you attempt this, you might encounter the following error:
Error: Shared module is not available for eager consumption
An alternative solution involves eagerly loading libraries in the shell and including them in the main build, although this approach is not typical and will be discussed in a future article.
Step 5: Accessing Remote from Host
With everything set up, we can now access the remote application from our host. We will utilize JavaScript's dynamic import feature to load the application, similar to how we incorporate other modules into an Angular application.
export const routes: Routes = [
// Local module
{
path: 'home',
loadComponent: () =>
import('./notes/notes.component').then((m) => m.NotesComponent),},
// Remote module
{
path: 'customer',
loadComponent: () =>
import('remote/Customer').then((m) => m.CustomerComponent),},
];
The pattern for importing the remote module is as follows:
import(${LOCAL_NAME_BY_HOST}/${REMOTE_EXPOSED_MODULE});
You will see how straightforward and seamless it is to access the remote module using module federation. However, an error may arise during the import:
Cannot find module 'remote/Customer' or its corresponding type declarations.
To resolve this, it is customary to declare a file at the root level named remotes.d.ts, where we can specify the remote types. The contents of this file would be:
declare module 'remote/Customer';
This concludes the setup for facilitating communication between the applications. Start both applications using npm start and navigate to the URL http://localhost:4200 or your application URL.
Step 6: Addressing Multiple Angular Version Issues
You may notice some console errors when you run the application. Don't worry; these are intentional to illustrate specific concepts.
The error message you may encounter in the host application is as follows:
As we are not sharing any libraries, the @angular/core library will be loaded multiple times in the browser. Since Angular does not function well with multiple versions loaded simultaneously, we must ensure that the core library is shared between applications.
To resolve this, add the following configuration to the Webpack's shared settings that we initially left empty. Restart both applications afterward.
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "^17.1.0",
},
},
And just like that, your applications will function correctly. It is also wise to share other Angular libraries to ensure stability. Thus, I will also include the router and common libraries in the shared configuration:
shared: {
"@angular/core": {
singleton: true,
strictVersion: true,
requiredVersion: "^17.1.0",
},
"@angular/common": {
singleton: true,
strictVersion: true,
requiredVersion: "^17.1.0",
},
"@angular/router": {
singleton: true,
strictVersion: true,
requiredVersion: "^17.1.0",
},
}
Thank you for taking the time to read through this extensive guide. We hope you have gained valuable insights. Please consider subscribing for more content in future articles.
Chapter 2: Practical Video Tutorials
To further enhance your understanding of Module Federation with Angular, we have included some video resources.
The first video, titled "Experimenting with Module Federation & Angular Standalone Components in Nx," provides practical examples and insights into the concepts we've discussed.
The second video, titled "Angular Module Federation Micro-FE Speed Run," offers a rapid overview of the module federation process in Angular applications.