Micro Frontends (MFE) are the idea that a Single Page Application (SPA) can be divided into separate specialized sections that give independent teams end-to-end ownership. This gives development teams the autonomy to complete their work independently.
Choosing Micro Frontends implementation is a decision that needs to be evaluated carefully to ensure it meets organizational needs. Some of the benefits of Micro Frontends include:
- The use of different frameworks or UI libraries for each frontend to meet the team’s needs or if the team inherited a codebase that needs to be incorporated into an existing application.
- Independent deployments as each frontend can be deployed independently of the rest of the application.
- Allows the development team(s) to scale as each team will be working independently of the rest of the team(s).
These are awesome benefits for large development teams that work on an Enterprise size application. Micro Frontends also has some limitations:
- Complexity: adding different frameworks and having the ability to share code between frontend(s) can increase the complexity for onboarding new team members.
- Operational complexity: deployment to production can be complicated as each frontend needs to be deployed independently and the container application needs to be updated and its release coordinated between different teams.
- Larger application bundles: with different frameworks and possibly code duplication between different frontend(s) this can impact the performance of the application.
Tools
In this article we are going to focus on setting up an Angular Micro Frontends project using the following tools:
- Micro Frontends and Angular work through the Module Federation feature of Webpack 5. Webpack 5 was introduced as a breaking change in Angular 12.
- The @angular-architects/module-federation by Manfred Steyer can be added to any Angular project via schematics. This package is also used within Nx tooling.
- New Nx tooling generates the configuration needed for Micro Frontends by using the @nrwl/angular:setup-mfe schematic.
Theory
Before we begin our implementation let’s define some concepts about Micro Frontends:
- host : This is the project that displays the remote(s). You want to consider the host as the shell for the application responsible for coordinating the remote(s).
- remote : This is part of the application, not the entire application, that will be displayed by the host. In theory, this can be framework agnostic. In this article we will be using Angular only. A Micro FrontEnd project will contain only one host, but multiple remotes.
- Micro FrontEnd Framework : This is the framework that sits between the host and remote(s) to coordinate loading and unloading of remotes. In our case this is Webpack 5 Module Federation. We will not be discussing the details of how Module Federation works as it is beyond the scope of this article.
Implementation
In this article we are going to show you how to setup Micro Frontends project with both Nx Workspace and Angular CLI. We will be utilizing schematics throughout the article.
Using Nx Workspace
We begin our implementation by creating a new Nx workspace and call it microFrontEnds
using the following command:
npx create-nx-workspace@latest microFrontEnds
Follow the interactive prompts and create an Angular application called shell
, which will be the host
in our application.
We will use the new schematics later to add the needed configuration for Micro Frontends.
Adding Angular Applications
At this time we need to add new Angular application(s) that will serve as the parts that we want to slice our application into, and these will be the remotes
in our setup. In our example we will create 2 applications: one is called dashboard
, which will serve as our home page. And another called admin
, which will be worked on by a different team. We will use the following command:
ng g @nrwl/angular:app --routing dashboard ng g @nrwl/angular:app --routing admin
Up to this point we have utilized typical commands and have not configured anything related to Micro Frontends. What we have at this point is a standard monorepo with 3 Angular applications.
Setup the host
At this time we need to begin configuring our shell
and set it up as our host. We can issue the following command:
ng generate @nrwl/angular:setup-mfe --appName=shell --mfeType=host --port=5000 --routing
After we run this command we will have 3 new files and updates to existing files. These changes include:
- main.ts : This file has been changed and its entire content is moved to a newly created file called
bootstrap
, which we will discuss next, and it is replaced with an import statement for the newly created bootstrap file. - bootstrap.ts : This file contains the original contents of the
main.ts
file without any changes. The reason this file is needed is to ensure the modules are exposed correctly by Webpack. The Webpack process is divided into 2 steps: one step is asynchronous while the other is synchronous. The low level details of how Webpack handles Module Federation is beyond the scope of this article. - webpack.config.js & webpack.prod.config.js : This is where the magic happens. This file brings in the packages needed and includes a modified Webpack configuration. There are 2 sections we need to be aware off.
sharedMappings
is where you can include any shared library or components.plugins
is where you listremotes
and any framework packages to share, as you can see from the code section below.
plugins: [ new ModuleFederationPlugin({ remotes: {}, shared: { '@angular/core': { singleton: true, strictVersion: true }, '@angular/common': { singleton: true, strictVersion: true }, '@angular/common/http': { singleton: true, strictVersion: true }, '@angular/router': { singleton: true, strictVersion: true }, ...sharedMappings.getDescriptors(), }, }), sharedMappings.getPlugin(), ],
- package.json : The schematic adds
@angular-architects/module-federation
package and installs it. - angular.json : The schematic makes multiple changes to this file including changing the builder on the
shell
project for both thebuild
andserve
commands, adding the appropriate Webpack config file and port to thebuild
andserve
commands, and adding a newserve-mfe
command.
At this point our shell
setup is complete and ready to accept remotes.
Setup the remote(s)
We will issue 2 very similar commands to setup our shell in the previous section. The first command we issue is to setup the admin
project to be a remote:
As you can tell from the above command we are basically using the same parameters as discussed above with the difference being appName
, mfeType
and port
. We need to ensure each host/remote runs on a different port during local development.
The schematic creates the same files and similar changes as we discussed above with few exceptions:
- The schematic does not create
serve-mfe
command for the remotes. - The schematic creates a new module called
remote-entry
in the app. In this case ofadmin
application, the module is used as the entry module for our remote and it is used in thewebpack.config.js
file.
The remote-entry module is the module we expose in the our remote configuration so it can be utilized in the shell. Example configuration looks like:
plugins: [ new ModuleFederationPlugin({ name: 'admin', filename: 'remoteEntry.js', exposes: { './Module': 'apps/admin/src/app/remote-entry/entry.module.ts', }, shared: { '@angular/core': { singleton: true, strictVersion: true }, '@angular/common': { singleton: true, strictVersion: true }, '@angular/common/http': { singleton: true, strictVersion: true }, '@angular/router': { singleton: true, strictVersion: true }, ...sharedMappings.getDescriptors(), }, }), sharedMappings.getPlugin(), ]
We need to add the remote we just created to the shell
webpack.config.js so the shell is aware of the admin
module it is responsible for loading and unloading. An example configuration looks like the following:
remotes: { // Ensure that you use the port you specified in the configuration. // Also the name must match what you declared in the admin module. admin: 'admin@http://localhost:3000/remoteEntry.js', },
Finally we need to add a route to our shell
to open the remote we created. Similar to this code snippet:
{ path: 'admin', loadChildren: () => import('admin/Module').then((m) => m.RemoteEntryModule) },
Unfortunately, Typescript does not like admin/module
since it does not exist at compile time. To solve this you can add a types.d.ts
file in the root of the src
directory in the shell
project and add declare module admin/Module;
.
The ports we defined in the commands we are executing are used for local development that allow you to run multiple Angular project simultaneously.
We will continue by setting up the dashboard
project as the next remote that we use. Thus, we issue the following command:
ng generate @nrwl/angular:setup-mfe --appName=dashboard --mfeType=remote --port=4000 --routing
This will create similar files as the admin
project, which we discussed above. We start by adding the dashboard
to the shell configuration. An example configuration looks like the following:
remotes: { // Ensure that you use the port you specified in the configuration. // Also the name must match what you declared in the admin module. dashboard: 'dashboard@http://localhost:3400/remoteEntry.js', },
We need to add a route to our shell
to open the dashboard
we created. Similar to this code snippet:
Also, ensure you add declare module 'dashboard/Module';
to the types.d.ts
file we created earlier.
Run Application
At this time we finished configuring our applications, and we are ready to try it out. To do that you would need to start all three applications. To accomplish this type the following commands into a separate terminals.
# This will start the shell using port 5000 nx serve shell # This command will start the admin using port 3000 nx serve admin # This command will start the dashboard using port 4000 nx serve dashboardOr you can combine all three commands in the array of commands under serve-mfe
in angular.json. This allows you to just use just one command nx serve-mfe
instead of 3 commands.
Another option is Concurrently, which allows you to run multiple command simultaneously.
Start our host and remotes based on the ports we configured earlier by running any of the options mentioned above. If you open a browser to the host (localhost:5000) and navigate to the the admin and/or dashboard routes you will see the content of the entry.component.ts
in each project.
Using Angular CLI
The implementation of Micro Frontends with Angular CLI is very similar to the way we implemented it with Nx Workspace. We will begin with creating an empty workspace with the Angular CLI and creating the three projects similar to what we did above. Then use ng add
schematics to add the Micro Frontends configuration for each project.