Build Monorepo Micro-Frontend Using Angular And Webpack 5 Module Federation

Angular is one of the popular Single Page Application framework used to build Single Page Applications. Webpack is a bundling tool that Angular uses to build and deploy modules. Micro-frontend is a pattern that allows you to build the frontend applications as individual applications(remote) that can be integrated within a shell(host) application. Module federation plugin of the Webpack allows you to load these micro-frontend applications into a shell application.

This tutorial helps you to build Monorepo Micro-frontends using Angular and Webpack Module Federation.

Prerequisites

  • Angular CLI: Version 14.0.0 (use exact version)
  • Module Federation library for Angular (@angular-architects/module-federation): 14.3.0 (compatible to Angular 14.0.0)
  • Node 16.x or later
  • Visual Studio Code

Building the angular workspace and creating remote(MFE) and host(shell) projects

1) To create an angular workspace, open the command terminal and run the following command.

ng new bookstore-ws --create-application false --skip-tests

2) Switch to the workspace folder and add two micro-frontend projects and a shell application to it.

cd bookstore-ws
ng g app book-mfe --skip-tests --routing
ng g app auth-mfe --skip-tests --routing
ng g app bookstore-shell --skip-tests --routing

3) We can also install the bootstrap and jquery libraries for the projects. Install the bootstrap and jquery to the workspace by running the following command.

npm install --save [email protected] jquery

4) Open the angular.json file and add register the bootstrap and jquery in the styles and scripts sections of all 3 projects. Below is the sample configuration for bookstore-shell project.

"styles": [
    "node_modules/bootstrap/dist/css/bootstrap.min.css",
    "projects/bookstore-shell/src/styles.css"
],
"scripts": [
    "node_modules/jquery/dist/jquery.min.js",
    "node_modules/bootstrap/dist/js/bootstrap.min.js"
]

Configure the Book-MFE project

1) Add a home component to the book-mfe project. Also, add a feature module that can be lazily loaded.

ng g c --project book-mfe components/home
ng g module --project book-mfe book --routing

2) Add a component BookListComponent to the book module in the book-mfe project.

ng g c --project book-mfe --module book book/components/book-list

3) Update the routing file in the book module to add the route for loading the book-list component. Open the book-routing.module.ts file and update the route with the following.

const routes: Routes = [{
    path: 'list',
    component: BookListComponent
}];

4) Now, we need to update the main routing file -app-routing.module.ts. Add the following routes to it.

const routes: Routes = [{
    path: '',
    component: HomeComponent,
    pathMatch: 'full'
}, {
    path: 'books',
    loadChildren: () => import('./book/book.module').then(m => m.BookModule)
}];

5) Open the app.component.html file and add the following HTML code to it.

<nav class="navbar navbar-expand-lg bg-primary bg-body-tertiary" data-bs-theme="dark">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Books MFE</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" routerLink="">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="books/list">Books</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>

Configure the Auth-MFE project

1) We also need to add a HomeComponent and a feature module to the auth-mfe project.

ng g c --project auth-mfe components/home
ng g module --project auth-mfe auth --routing

2) Add a component LoginComponent to the auth module in the auth-mfe project.

ng g c --project auth-mfe --module auth auth/components/login

3) Update the routing file in the auth module to add the route for loading the login component. Open the auth-routing.module.ts file and update the route with the following.

const routes: Routes = [{
    path: 'login',
    component: LoginComponent
}];

4) Now, we need to update the main routing file -app-routing.module.ts. Add the following routes to it.

const routes: Routes = [{
    path: '',
    component: HomeComponent,
    pathMatch: 'full'
}, {
    path: 'auth',
    loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule)
}];

5) Open the app.component.html file and add the following HTML code to it.

<nav class="navbar navbar-expand-lg bg-primary bg-body-tertiary" data-bs-theme="dark">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Auth MFE</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" routerLink="">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="auth/login">Login</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>

Configure the BookStore-Shell project

1) Add a home component to the bookstore-shell project.

ng g c --project bookstore-shell components/home

2) Open the app-routing.module.ts file and add the route for loading the HomeComponent.

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

3) Update the app.component.html file with the following code.

<nav class="navbar navbar-expand-lg bg-primary bg-body-tertiary" data-bs-theme="dark">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Book Store</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" routerLink="">Home</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>

Run and test the projects

Run the following commands in different terminals for running projects.

ng serve auth-mfe -o --port 4200
ng serve book-mfe -o --port 4300    
ng serve bookstore-shell -o --port 5000

Configure the MFE and Shell apps with Module federation

1) Add the module federation to book-mfe project by running the following command.

ng add @angular-architects/[email protected] --project book-mfe --type remote --port 4300

2) Open the webpack.config.js file in the book-mfe project folder and update the configuration with the following.

const {
    shareAll,
    withModuleFederationPlugin
} = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
    name: 'book-mfe',
    exposes: {
        './Module': './projects/book-mfe/src/app/book/book.module.ts',
    },
    shared: {
        ...shareAll({
            singleton: true,
            strictVersion: true,
            requiredVersion: 'auto'
        }),
    },
});

3) Now, we can add the module federation to auth-mfe project by running the following command.

ng add @angular-architects/[email protected] --project auth-mfe --type remote --port 4200

4) Open the webpack.config.js file in the auth-mfe project folder and update the configuration with the following.

const {
    shareAll,
    withModuleFederationPlugin
} = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
    name: 'auth-mfe',
    exposes: {
        './Module': './projects/auth-mfe/src/app/auth/auth.module.ts',
    },
    shared: {
        ...shareAll({
            singleton: true,
            strictVersion: true,
            requiredVersion: 'auto'
        }),
    },
});

5) Finally, add the module federation to the shell project by running the following command.

ng add @angular-architects/[email protected] --project bookstore-shell --type host --port 5000

6) Update the webpack.config.js with the following code. Make sure the ports are configured correctly.

const {
    shareAll,
    withModuleFederationPlugin
} = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
    remotes: {
        "bookMfe": "http://localhost:4300/remoteEntry.js",
        "authMfe": "http://localhost:4200/remoteEntry.js",
    },
    shared: {
        ...shareAll({
            singleton: true,
            strictVersion: true,
            requiredVersion: 'auto'
        }),
    },
});

7) Open the app-routing.module.ts file in the bookstore-shell project and update the routes with the following code.

const routes: Routes = [{
    path: '',
    component: HomeComponent,
    pathMatch: 'full'
}, {
    path: 'books',
    loadChildren: () => import('bookMfe/Module').then(m => m.BookModule)
}, {
    path: 'auth',
    loadChildren: () => import('authMfe/Module').then(m => m.AuthModule)
}];

8) Create a file with the name decl.d.ts inside the src folder of the bookstore-shell project and add the following lines to it.

declare module 'bookMfe/Module';
declare module 'authMfe/Module';

9) No, we can update the navigation URLs in the app.component.html file of the bookstore-shell project.

<nav class="navbar navbar-expand-lg bg-primary bg-body-tertiary" data-bs-theme="dark">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Book Store</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" routerLink="">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="auth/login">Login</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" routerLink="books/list">Books</a>
        </li>
      </ul>
    </div>
  </div>
</nav>
<div class="container">
  <router-outlet></router-outlet>
</div>

10) Run the following commands in different terminals for running projects.

ng serve auth-mfe -o --port 4200 
ng serve book-mfe -o --port 4300
ng serve bookstore-shell -o --port 5000

11) Open the bookstore-shell project output in the browser and navigate to the Login and Books hyperlinks.