In this post, we will implement Micro-Frontends in Angular using Webpack 5 Module Federation.
Credit: This post is based on this article written by Manfred Steyer
May-12-2021 Update: Added Dockerfiles for both the projects. Please check the Running the applications section.
Table Of Contents
- Pre-Requisites
- Create Host Application
- Create Microfrontend Application with feature module
- Add Module Federation
- Webpack Config changes
- Route changes in Host
- Running the applications
- Conclusion
Pre-Requisites:
- Angular CLI: 11.2.8
- Node: 15.4.0
- Yarn: 1.22.10
We will be using yarn as package manager instead of NPM. Why? We will be using Webpack 5 Module Federation with Angular 11. Angular CLI 11 uses webpack version 4. We will be overriding the webpack version in package.json and yarn is required to override the web pack version for angular cli.
Create Host Application
Step 1: Set Yarn as package manager
ng config cli.packageManager yarn
Any ng add
or ng update
command will yarn instead of rpm to install the packages.
Step 2: Create a workspace
ng new angular-mfe-example --createApplication="false"
The above command will create a workspace with no projects.
Step 3: Create host (Shell) app
ng g applicatie host --routing --style=css
Step 4: Create home component
ng g c home --project=host
Step 5: Update Route to add path to Home and change AppComponent
Add Route to app-routing.module.ts
Clean up app.component.html
Step 6: Run the application
ng serve host
Run the host app. It should run in default port 4200
Create Microfrontend Application with feature module
We will now create another application under the same workspace. The steps to create it are the same as above.
Step 1: Create mfe1 application and home component
ng g application mfe1 --routing --style=css
ng g c home --project=mfe1
mfe1 project will get created under the main workspace
Step 2: Create a new feature module under mfe1
Create a new feature module mfefeature and a component under the feature module
ng g m mfefeature --routing --project=mfe1
ng g c mfefeature --project=mfe1
Add the route to the mfefeature component in the mfefeature-routing.module.ts
Step 3: Change App routing
Update routing module to add path to home component under mfe1.
Update routing module to add path to mfe1. The mfefeature module is lazy loaded
{
path: 'mfe1',
loadChildren: () =>
import("./mfefeature/mfefeature.module").then((m) => m.MfefeatureModule),
},
Please ensure that home component is pointing to the one under mfe1 project and not host.
Step 4: Change HomeComponent
Step 5: Change AppComponent in mfe1
Change app.component.html to include links to home and mfe1
Step 6: Run the application
ng serve mfe1
Run the mfe1 app. It should run in default port 4200.
At the end of this step, we have created 2 applications in the same workspace. The mfe1 application has a feature module. This feature module will be loaded as Microfrontend in the host application in the subsequent sections.
Add Module Federation
Angular CLI does not expose the webpack to us. We need to install custom builder to enable module federation.
Add @angular-architects/module-federation package to both the projects.
ng add @angular-architects/module-federation --project host --port 4200
ng add @angular-architects/module-federation --project mfe1 --port 5000
The above command creates web pack config files and updates angular.json.
Webpack Config changes
Step 1: Add Webpack5 to the workspace
We will now add webpack5 to the workspace. Add the below entry to package.json
"resolutions": {
"webpack": "^5.4.0",
"license-webpack-plugin": "2.3.17"
},
We need to add license-webpack-plugin@2.3.17 as Angular11 uses 2.3.11 version which gives an error when used with webpack5.
Step 2: Add Modulefederated plugin to mfe1
Locate webpack.config.js under mfe1 project and uncomment the config values under // For remotes (please adjust)
Make the following changes
name: "mfe1",
filename: "mfe1remoteEntry.js",
exposes: {
'./MfefeatureModule': './projects/mfe1/src/app/mfefeature/mfefeature.module.ts',
},
We are exposing mfefeature.module under the name MfefeatureModule. This name will be used when we are lazy loading this module in host's app-routing.module.ts
The feature module will be available in mfe1remoteEntry.js
Step 3: Add Modulefederated plugin to host
Locate webpack.config.js under host project and uncomment the lines under // For hosts (please adjust)
Make the following changes
remotes: {
"mfe1": "mfe1@http://localhost:5000/mfe1remoteEntry.js",
},
We are mapping the name 'mfe1' to the path where the remote can be found. Please note that the mfe1 project needs to run in port 5000 and we are pointing to mfe1remoteentry.js which is the name we gave in the mfe1's webpack.config.js
Route changes in Host
Step 1: Add route to mfe1 feature module
Add path to mfe1 and lazy load the mfe feature module
In host's app-routing.module.ts
{
path: 'mfe1',
loadChildren: () =>
import('mfe1/MfefeatureModule').then((m) => {
return m.MfefeatureModule;
}),
}
Note that in the import statement we are using MfeFeatureModule, which is the name of the module we gave in mfe1's webpack.config.js
Step 2: Declare MfeFeatureModule
The path mfe1/MfeFeatureModule
mentioned in the import statement does not "exist" within host project. When we compile the host project it will throw an error.
To fix the error, we will create decl.d.ts under host and declare the module
declare module 'mfe1/MfefeatureModule'
Step 3: Add route for mfe in Appcomponent
In app.component.html, make the following changes
<h1>Angular MFE Host</h1>
<a routerLink='/'>Main</a>  
<a routerLink='/mfe1'>Link to MFE</a>
<router-outlet></router-outlet>
Running the applications
Option 1: Run in terminal
Open 2 command terminals
In terminal 1 run
ng serve host
In terminal 2 run
ng serve mfe1
Open localhost:4200
you will able to navigate to the mfe1 which is actually running in localhost:5000
Option 2: Dockerize the apps and run in containers
*Step 1: * Create nginx default configuration file
Under the main folder create a folder nginx.
Inside this folder create a file "default.conf" and copy the below commands
server {
listen 80;
sendfile on;
default_type application/octet-stream;
gzip on;
gzip_http_version 1.1;
gzip_disable "MSIE [1-6]\.";
gzip_min_length 1100;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip_comp_level 9;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html =404;
}
}
This configuration is copied during the creation of the docker image.
*Step 2: * Create Dockerfile for host
In the main folder create HostDockerfile. This is in the same level as projects folder.
FROM node:15-alpine as builder
COPY package.json ./
RUN yarn install
RUN mkdir /ng-app
RUN mv ./node_modules ./ng-app
WORKDIR /ng-app
COPY . .
RUN npm run ng build --prod --project=host
FROM nginx
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /ng-app/dist/host /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
Step 3: Create Docker image for host using the below command
docker build -t host -f .\HostDockerfile
The name of the docker image is host. Please note that the name of the dockerfile is "HostDockerfile".
Step 4: Create Dockerfile for mfe1
In the main folder create MfeDockerfile. This is in the same level as projects folder.
FROM node:15-alpine as builder
COPY package.json ./
RUN yarn install
RUN mkdir /mfe-app
RUN mv ./node_modules ./mfe-app
WORKDIR /mfe-app
COPY . .
RUN npm run ng build --prod --project=mfe1
FROM nginx
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /mfe-app/dist/mfe1 /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
Step 5: Create Docker image for mfe1 using the below command
docker build -t mfe1 -f .\MfeDockerfile
The name of the docker image is mfe1. Please note that the name of the dockerfile is "MfeDockerfile".
Step 6: Create containers for host and mfe1
Run the below commands to create and run the containers
docker run -d -p 4200:80 host
docker run -d -p 5000:80 mfe1
The host expects mfe1 to run in port 5000, hence running the mfe1 container in port 5000.
Conclusion
This is a simple tutorial that demonstrates implementation of Microfrontend using Webpack Module Federation.
You can refer to my GitHub repo for the completed solution.
Top comments (12)
Hi, I read it fully and then implemented in same way of your approach,
But I run the application to got this error.
[webpack-dev-server] Live Reloading enabled.
styles.js:3322 Uncaught SyntaxError: Cannot use 'import.meta' outside a module
localhost/:1 Uncaught TypeError: Failed to resolve module specifier "mfe1@localhost:5000/mfe1remoteEntry.js". Relative references must start with either "/", "./", or "../".
Hi. I have just come up with the solution. You need to remove "mfe1@" from the URL of remotes in the webpack configuration of host. It is most probably because of newer version of angular
Hi. I have the exact same issue. Were you able to sort it out? I also followed exactly the same thing but still this is the error at the end in console
I need urgent help. I don't know how to solve this problem. I've tried creating a proxy.conf.json and still nothing.
how did u solve this?
Hi,
Thanks for this post. It is really very helpful to me.
Importing module from remote to host is clear from the above post, but how do I import component from remote to Host ? I have a requirement to show two remotes in the host on a single route. (Ex: header App & Sidenav App on router="/index" in Host application)
Any suggestion would be Appreciated. Thanks
If any one has any issue regarding importing modules/components from remote to host or vice-versa and data communication between different apps. I have got the solutions to this at:
stackoverflow.com/questions/685407...
Hey,
Interesting article. Thank you for sharing.
In your example (and also in the example of Manfred Steyer) the remote (mfe1) is a lazy loaded module defined in mfe1 project.
Would it be possible to load the mfe1 project's app module instead of the lazy loaded module?
The goal would be to:
I've tried it, but couldn't make it work correctly, because for some reason the remote's app.component.ts didn't run. What it does is that the nested routes are loaded into the router-outlet defined in the host. To fix that, I've added named router outlets but it totally messed up the routing.
Do you have any tip what could go wrong with this approach?
Hi - There can be only 1 AppModule for an angular application. Hence we are loading feature module from remote.
You can have multiple feature modules in the remote and include them in the host. The feature modules can also have routing module.
Hey,
Good explanation, I went through lot of websites, the writing was clumsy or too much info included. This wouldn't have been more clearer for beginners. Thanks, keep up the good work.
Thanks Madan Kumar
excellent tutorial