= Setting Up Module Federation in an Angular CLI Application

The integration of Module Federation with Angular CLI facilitates a modular development approach, allowing for dynamic code loading and sharing across different Angular projects. This guide dives deep into the steps required for this integration, aided by the `@angular-architects/module-federation` plugin.

==  Prerequisites

- **Angular CLI**: Ensure that you have Angular CLI version 10 or later installed in your development environment.
- **Plugin Installation**: Having the `@angular-architects/module-federation` plugin installed in your project is crucial as it extends the Angular CLI to support Module Federation.

==  Installation

=== 1. Installing the Plugin:

The initial step towards leveraging Module Federation is instructing the Angular CLI to employ Module Federation during the build process. However, given the CLI's encapsulation of Webpack, a custom builder is necessitated to unveil the power of Module Federation.

The `@angular-architects/module-federation` package graciously provides this custom builder, simplifying the integration process. To initiate, simply employ the ng add command to integrate it into your projects:

[source, bash]
----
ng add @angular-architects/module-federation --project shell --port 4200 --type host
ng add @angular-architects/module-federation --project mfe1 --port 4201 --type remote
----

For those utilizing Nx, the procedure deviates slightly. First, execute an npm install to acquire the library, followed by invoking the init schematic:

[source, bash]
----
npm i @angular-architects/module-federation -D 
ng g @angular-architects/module-federation:init --project shell --port 4200 --type host
ng g @angular-architects/module-federation:init --project mfe1 --port 4201 --type remote
----

NOTE: The --type argument, introduced in version 14.3, which meticulously ensures that only the requisite configuration is generated.

In our representation, the project labeled 'shell' encapsulates the code for the shell, whereas 'mfe1' denotes Micro Frontend 1. The execution of the aforementioned commands orchestrates several pivotal tasks:

*Skeleton Generation:*
A rudimentary webpack.config.js skeleton is crafted to facilitate Module Federation integration.

*Custom Builder Installation:*
A custom builder is installed, empowering Webpack within the CLI to utilize the generated webpack.config.js.

*Port Assignment:*
A distinct port is allocated for the ng serve command, enabling simultaneous serving of multiple projects.

It's imperative to acknowledge that the generated `webpack.config.js` is a partial configuration solely dedicated to governing Module Federation. The remaining configurations are autonomously generated by the CLI, maintaining the usual workflow intact.

This setup lays down a robust foundation for harnessing Module Federation, enabling seamless code sharing and dynamic loading across your Angular CLI projects.

== The Shell (Host) Configuration

In the realm of Module Federation, the Shell, also referred to as the Host, serves as a pivotal point of integration. This segment delineates the configuration of the Shell to support the lazy-loading of a `FlightModule` through routing.

== Routing Configuration

Commence by defining the application routes, specifying a lazy-loaded `FlightModule` via a virtual path:

[source, javascript]
----
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, as it doesn't physically reside within the Shell. It's a pointer towards a module in a separate project.

=== TypeScript Typing

To appease the TypeScript compiler, create a type definition for the virtual path:

[source, typescript]
----
// decl.d.ts
declare module 'mfe1/Module';
----

This declaration guides the TypeScript compiler in understanding the virtual path, easing the import process.

== Webpack Configuration

Further, instruct Webpack to resolve all paths prefixed with `mfe1` to a remote project. This is achieved within the `webpack.config.js` file, generated earlier:

[source, javascript]
----
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, specifically to its remote entry point. This remote entry, generated by Webpack, holds essential information for interaction with the micro-frontend.

Although hardcoding the remote entry's URL suffices for development, a dynamic approach is necessary for production. The topic of xref:core-features/dynamic-remotes.adoc[Dynamic Remotes] is explored in a dedicated documentation page.

- The `shared` property outlines the npm packages to be shared between the Shell and the micro-frontend(s). Utilizing the `shareAll` helper method, all dependencies found in your `package.json` are shared. While this expedites a functional setup, it might result in excessive shared dependencies, a concern to be addressed later.

- The duo of `singleton: true` and `strictVersion: true` instructs Webpack to raise a runtime error when version incompatibility is detected between the Shell and the micro-frontend(s). Altering `strictVersion` to `false` would merely trigger a runtime warning.

- The `requiredVersion: 'auto'` setting, a provision of the `@angular-architects/module-federation` plugin, auto-resolves the version from your `package.json`, averting potential issues.

== Configuring the Remote

The Micro-frontend, termed as the Remote in the context of Module Federation, essentially mirrors a typical Angular application setup. It comprises defined routes within the `AppModule` and encapsulates a `FlightsModule` to manage flight-related functionalities. This section outlines the configuration steps to ensure the seamless loading of the `FlightsModule` into the Shell (Host).

=== Route Definition

Start by defining the basic routes within the `AppModule`:

[source, typescript]
----
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

Proceed to create a `FlightsModule` to handle flight-related operations:

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

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

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

=== Exposing Modules via Webpack Configuration

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

[source, javascript]
----
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.

== Testing the Configuration

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.

=== Running the Applications

==== 1. Starting the Shell and Micro-frontend:

Kickstart both the Shell and micro-frontend using the following commands:

[source, bash]
----
ng serve shell -o
ng serve mfe1 -o
----

Upon executing these commands, the Shell and Micro-frontend will be served, and the respective applications will open in your default web browser.

==== 2. Loading the Micro-frontend:

Navigate to the Flights section in the Shell, and observe the Micro-frontend being dynamically loaded.

//TODO: Add Image

//![Shell Interface with Flights Section](screenshot)

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

[source, bash]
----
npm run run:all
----

For serving selected applications, append their names as command line arguments:

[source, bash]
----
npm run run:all shell mfe1
----

=== A Closer Look at Main.ts

Delving into the `main.ts` file, you might notice a slight deviation from the usual:

[source, typescript]
----
import('./bootstrap')
    .catch(err => console.error(err));
----

The typical code in `main.ts` has been relocated to a new `bootstrap.ts` file, a modification orchestrated by the `@angular-architects/module-federation` plugin. This change facilitates Module Federation's library version decision-making process during runtime. The asynchronous nature of dynamic imports, as seen above, allows Module Federation to ascertain and load the appropriate version of shared libraries.

=== Optimizing Dependency Sharing

The initial setup using `shareAll` provides a quick, working configuration but might result in excessive shared bundles. To optimize this aspect, consider transitioning from `shareAll` to the `share` helper for more granular control over shared dependencies:

[source, javascript]
----
// 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 a potential reduction in the load times.
