Build a Microfrontend with Module Federation— A vertical application with Angular

Mattia D'Argenio
5 min readJun 14, 2024

--

Introduction

In my previous article, I wrote about what microfrontends are and the types of architectures you can use to build an application.

Today I want to show you how to use module federation to implement a simple microfrontend application using one of the most popular JS frameworks — Angular.

Webpack

First of all, we need to understand what Webpack is and how it works.

Webpack is a module bundler for Javascript applications that handles assets such as JS, HTML, CSS, images and fonts. It takes these files, combines them into one or more bundles and allows browser to serve them in a faster and optimised way.

Webpack is very popular thanks to his features:

  • Bundling: As we said before, bundling is the process of combining files into one — or one set — of files. This allows browsers to reduce the number of HTTP requests to retrieve the application’s files, improving performance.
  • Modularity: Webpack allows you to work in a modular way. Each component — a javascript module, a css file or an image — can be developed separately. This makes the code maintainable and testable.
  • Loaders: Loaders transform files before they‘re bundled. For example, babel-loader transforms ES6 code into ES5, so that files are compatible with more browsers.
  • Plugins: Plugins extend Webpack’s features. This could be optimising bundles, handling static files, generating HTML and so on. An example is the HtmlWebpackPlugin which generates an HTML file with all the imports of the generated bundles.
  • Code Splitting: Allows code to be split into different bundles that are loaded as needed. This allows you to improve the load time of the application, improving performance.
  • Hot Module Replacement (HMR): L’HMR allows you to update modules on the fly without reloading. This is very useful in development mode.

Module Federation

With Webpack 5 a new incredible features break the game, Module Federation.

This feature allows to share bundles from different builds without creating other bundles. In other words, it allows to load remote modules at runtime.

In module federations we have:

  • Container: It is an instance of ModuleFederationPlugin that can expose or load modules. There are two types of containers:
    - Exposing Container: It exposes modules
    - Consuming Container: It loads modules exposed by other containers.
  • ModuleFederationPlugin: It was used to define which modules the application should expose or load.
    Here an example of the structure:
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app',
filename: 'remoteEntry.js',
exposes: {
'./Component': './src/Component',
},
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
};

Here we have 3 importante properties:

  • Exposes: All the modules the container exposes. Every module has a public name and a local path.
  • Remotes: All the remote modules the container should load. In the configuration we have to define the remote name and the manifest URL (remoteEntry.js)
  • Shared: Here we have all the libraries that we want our containers to share. This allow us to reduce duplication of imported libs, so the applications share a compatible version of the libraries.

But now, lets do it

The first thing we need to do is create our shell application, which will be a consuming container for our microfrontends.

npx @angular/cli new shell

- or -

ng new shell

To set up Module Federation in the Angular project, we an additional dependency. In your Angular project, run:

ng add @angular-architects/module-federation

Using ng add command we can see that our project now has two new files: webpack.config.js and webpack.prod.config.js. These files contain the configurations for the ModuleFederationPlugin.

Now that our shell is set up correctly, let’s create our first microfrontend. We do the same as before to set up the project and we create a simple component

ng new angular-microfrontend

cd angular-microfrontend

ng add @angular-architects/module-federation

ng generate component hello-by-angular
@Component({
standalone: true,
imports: [CommonModule, RouterModule],
template: `
Hello {{name()}} by Angular!
`
})
export class HelloByAngularComponent {
private activatedRouter = inject(ActivatedRoute);
name = toSignal(this.activatedRouter.queryParamMap.pipe(
map(p => p.get('name'))
));
}

After that, we need to expose our component in ModuleFederationPlugin. Let’s go to webpack.config.js and edit it as follows.

module.exports = {
[...]
plugins: [
new ModuleFederationPlugin({
library: { type: "module" },

name: "angularMicrofrontend",
filename: "remoteEntry.js",
exposes: {
'./HelloByAngularComponent': './/src/app/hello-by-angular/hello-by-angular.component.ts',
},

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' },

...sharedMappings.getDescriptors()
})

}),
sharedMappings.getPlugin()
],
};

It’s time to edit the webpack.config.js of our Shell application so we can use the component of the angular-microfrontend container and we use it in our app.routes.ts

module.exports = {
[...]
plugins: [
new ModuleFederationPlugin({
library: { type: "module" },

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

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' },

...sharedMappings.getDescriptors()
})

}),
sharedMappings.getPlugin()
],
};
import {Routes} from '@angular/router';
import {loadRemoteModule} from '@angular-architects/module-federation-runtime'

export const routes: Routes = [
{
path: 'angular-hello',
loadComponent: () => import('angularMicrofrontend/HelloByAngularComponent').then(m => m.HelloByAngularComponent)
}
];

If we have the error

Cannot find module angularMicrofrontend/HelloByAngularComponent or its corresponding type declarations.

We need to create the file src/decl.d.ts and define the module

declare module 'angularMicrofrontend/HelloByAngularComponent';

Now we need to serve our applications. Let’s start to serve our microfrontend on port 4201 and our shell on port 4200.

cd angular-microfrontend
ng serve --port=4201

cd ..

cd shell
ng serve --port=4200

Navigate to http://localhost:4200/angular-hello?name=foo and you will see that our application will look like this

Application example

AWESOME!!! We created our first microfrontend vertical application with module federation.

Conclusion

I hope this article has provided you with a clear and detailed understanding of the topic. If you’re eager to dive deeper and explore the full source code, visit my GitHub repository.

Thank you for reading, and happy coding!

--

--