# 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**
```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
```
**Nx Cli**
For Nx users, the procedure is slightly different.
```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
```
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:
```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, 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:
```typescript
// 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:
```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's entry point. This entry point, generated by Webpack, contains essential information for interacting with the micro-frontend.
## 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).
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.
### Route Definition
Establish the basic routes within the `AppModule`:
```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
Create a `FlightsModule` to handle flight-related operations:
```typescript
@NgModule({
imports: [
CommonModule,
RouterModule.forChild(FLIGHTS_ROUTES)
],
declarations: [
FlightsSearchComponent
]
})
export class FlightsModule { }
```
This module contains a route to a `FlightsSearchComponent` defined as follows:
```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, expose it through the Remote's Webpack configuration:
```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' }),
},
});
```
## 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:
```bash
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:
```bash
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:
```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 potentially reducing load times.
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.