Angular CLI Setup

This guide explains how to integrate Module Federation with Angular CLI. The @angular-architects/module-federation plugin is used to assist with this integration.

Prerequisites

  • Angular CLI: Version 10 or higher.
  • Plugin Installation: Install the @angular-architects/module-federation plugin.

Installation

To start, configure the Angular CLI to use Module Federation during the build phase. A custom builder is needed to unlock Module Federation's potential.

The @angular-architects/module-federation package provides this custom builder. Use the ng add command to incorporate it into your projects:

Angular CLI
Nx Cli
ng add @angular-architects/module-federation --project shell --port 4200 --type host
ng add @angular-architects/module-federation --project mfe1 --port 4201 --type remote

The --type argument, introduced in version 14.3, ensures that only the necessary configuration is generated.

Shell (Host) Configuration

The Shell (Host) is crucial for Module Federation integration. This section configures the Shell to support lazy-loading of a FlightModule through routing.

Routing Configuration

Start by defining the application routes, specifying a lazy-loaded FlightModule using a virtual path:

export const APP_ROUTES: Routes = [
  {
    path: '',
    component: HomeComponent,
    pathMatch: 'full'
  },
  {
    path: 'flights',
    loadChildren: () => import('mfe1/Module').then(m => m.FlightsModule)
  },
];

In this configuration, the path 'mfe1/Module' is a virtual representation, indicating it doesn't physically exist within the Shell application. Instead, it's a reference to a module in a separate project.

TypeScript Typing

Create a type definition for the virtual path:

// decl.d.ts
declare module 'mfe1/Module';

This helps the TypeScript compiler understand the virtual path.

Webpack Configuration

Instruct Webpack to resolve all paths prefixed with mfe1 to a remote project. This is done in the webpack.config.js file:

const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({

   remotes: {
     "mfe1": "http://localhost:4201/remoteEntry.js",
   },

   shared: {
     ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
   },

});

In the remotes section, the path mfe1 is mapped to the remote micro-frontend's entry point. This entry point, generated by Webpack, contains essential information for interacting with the micro-frontend.

For development, hardcoding the remote entry's URL is enough. However, a dynamic approach is necessary for production. The concept of dynamic remotes is further explored in a dedicated documentation page on Dynamic Remotes.

  • The shared property specifies the npm packages to be shared between the Shell and the micro-frontend(s). Using the shareAll helper method, all dependencies listed in your package.json are shared. While this facilitates a quick setup, it may lead to an excessive number of shared dependencies, which could be a concern for optimization.
  • The combination of singleton: true and strictVersion: true settings instructs Webpack to throw a runtime error if there is a version mismatch between the Shell and the micro-frontend(s). Changing strictVersion to false would instead result in a runtime warning.
  • The requiredVersion: 'auto' option, provided by the @angular-architects/module-federation plugin, automatically determines the version from your package.json, helping to prevent version-related issues.

Configuring the Remote

The Micro-frontend, also known as the Remote in Module Federation, has a structure similar to a standard Angular app. It has specific routes in the AppModule and a FlightsModule for flight-related tasks. This section explains how to smoothly load the FlightsModule into the Shell (Host).

Route Definition

Establish the basic routes within the AppModule:

export const APP_ROUTES: Routes = [
     { path: '', component: HomeComponent, pathMatch: 'full'}
 ];

This simple routing setup navigates to a HomeComponent when the application is accessed.

Module Creation

Create a FlightsModule to handle flight-related operations:

@NgModule({
   imports: [
     CommonModule,
     RouterModule.forChild(FLIGHTS_ROUTES)
   ],
   declarations: [
     FlightsSearchComponent
   ]
 })
 export class FlightsModule { }

This module contains a route to a FlightsSearchComponent defined as follows:

export const FLIGHTS_ROUTES: Routes = [
     {
       path: 'flights-search',
       component: FlightsSearchComponent
     }
 ];

Exposing Modules via Webpack Configuration

To enable the loading of FlightsModule into the Shell, expose it through the Remote's Webpack configuration:

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:

  • The name property identifies the micro-frontend as mfe1.
  • The exposes property signifies the exposure of FlightsModule under the public name Module, allowing its consumption by the Shell.
  • The shared property lists the libraries to be shared with the Shell, using the shareAll method to share all dependencies found in your package.json. The singleton: true and strictVersion: true properties ensure that a single version of shared libraries is used, and a runtime error is triggered in case of version incompatibility, respectively.

Starting the Applications

Having set up the Shell (Host) and Micro-frontend (Remote), it's time to test the configuration to ensure the seamless integration of Module Federation.

To start the Shell and Micro-frontend, use the following commands:

ng serve shell -o
ng serve mfe1 -o

Navigate to the Flights section in the Shell to see the Micro-frontend being dynamically loaded.

TIP

The plugin installs an npm script run:all during the ng-add and init schematics, allowing for simultaneous serving of all applications:

npm run run:all
# or
npm run run:all shell mfe1

Optimizing Dependency Sharing

The initial setup with shareAll is simple and functional, but it can result in the creation of unnecessarily large shared bundles.

To manage shared dependencies more effectively, consider switching from shareAll to using the share helper. This provides finer control over which dependencies are shared:

// Replace shareAll with share:
const { share, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');

module.exports = withModuleFederationPlugin({
    // Specify the packages to share:
    shared: share({
        "@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
        "@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
        "@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
        "@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
    })
});

In this configuration, the share helper allows for explicit sharing of selected packages, enabling a more optimized bundle sharing and potentially reducing load times.