Introduction
This article will introduce Angular Module Federation, a new feature in Webpack 5 that allows you to share code between Angular applications.
Module Federation is a new feature in Webpack 5 that allows you to share code between applications. It enables your application to load remote modules at runtime from another application and share dependencies between applications. This feature is particularly useful for micro-frontends, where you have multiple applications that need to share code.
Steps
Create an empty angular workspace
Option --no-create-application is used to create an empty workspace without an initial application.
1 | ng new angular-module-federation --no-create-application |
Generate shell application shell
1 | cd angular-module-federation |
Generate remote application mfe1
1 | ng generate application mfe1 |
Add plugin @angular-architects/module-federation to shell app
1 | ng add @angular-architects/module-federation --project shell --port 4200 --type host |
This command will do the following:
Add
@angular-architects/module-federationandngx-build-plusto filepackage.jsonunder workspace root.Add command
"run:all": "node node_modules/@angular-architects/module-federation/src/server/mf-dev-server.js"toscriptssection in filepackage.json. This command will start the module federation development server, which will run app shell and app mfe1 at the same time.Add file
bootstrap.tsunderprojects/shell/srcfolder, its content was copied frommain.ts1
2
3
4
5
6import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));Update file
projects/shell/src/main.tsto importbootstrap.ts(Note, when a file was imported, it was executed automatically)1
import './bootstrap';
Add file
webpack.config.jsunderprojects/shellfolder,1
2
3
4
5
6
7
8
9const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
remotes: {
"mfe1": "http://localhost:4200/remoteEntry.js",
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});Add file
webpack.prod.config.jsunderprojects/shellfolder,1
module.exports = require('./webpack.config');
Update file
angular.jsonunder workspace root.- projects/shell/architect/build/builder:
"@angular-devkit/build-angular:application"—>ngx-build-plus:browser- The reason for this change is that module federation is a feature provided byWebPack, but"@angular-devkit/build-angular:application"is for ESBuild, so we need to change it tongx-build-plus:browserwhich is forWebPack. see here for details. - projects/shell/architect/build/builder/extraWebpackConfig:
"projects/shell/webpack.config.js" - projects/shell/architect/build/configurations/production/extraWebpackConfig:
"projects/shell/webpack.prod.config.js" - projects/shell/architect/serve/configurations/extraWebpackConfig:
"projects/shell/webpack.prod.config.js" - projects/shell/architect/serve/options:
1
2
3
4
5"options": {
"port": 4200,
"publicHost": "http://localhost:4200",
"extraWebpackConfig": "projects/shell/webpack.config.js"
}
- projects/shell/architect/build/builder:
Note that: extraWebpackConfig is an option from ngx-build-plus, it allows you to add additional webpack configuration to the project.
Add plugin @angular-architects/module-federation to mfe1 app
1 | ng add @angular-architects/module-federation --project mfe1 --port 4201 --type remote |
This command will do the same thing as the previous command, but for the mfe1 app with a little difference.
- webpack.config.js
1
2
3
4
5
6
7
8
9
10
11const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'mfe1',
exposes: {
'./Component': './projects/mfe1/src/app/app.component.ts',
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
}); - Serve options in
angular.json1
2
3
4
5"options": {
"port": 4201,
"publicHost": "http://localhost:4201",
"extraWebpackConfig": "projects/mfe1/webpack.config.js"
}
Add home component to shell app
1 | ng generate component home --project=shell |
Add product component to mfe1 app
app mfe1 will expose a component product to shell app.
1 | ng generate component product --project=mfe1 |
Update webpack.config.js in app mfe1
We’ll expose the product component to the shell app.
1 | const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack'); |
Update app.routers.ts in app mfe1
1 | export const routes: Routes = [ |
Update webpack.config.js in app shell
Note the generated port in webpack.config.js, its value is 4200, we should change it to 4201 since mfe1 runs on this port.
1 | const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack'); |
Update app.routes.ts in app shell
Add a route path product to load the product component remotely from mfe1.
1 | export const routes: Routes = [ |
Add typescript declaration file decl.d.ts under projects/shell/src
If you don’t add this file, you will get an error when you import the remote module mfe1 in the shell app.
1 | declare module 'mfe1/Component'; |
Workspace structure
Here is the file structure of the workspace till now.
1 | angular-module-federation/ |
Start the development server
You can run shell and mef1 app separately by running the following command.
1 | ng serve shell - o |
Or run them at the same time by running the following command.
1 | npm run run:all |
Change the shell app url in browser to localhost:4200/product, you will see the product component from mfe1 app.
Remote entry file
If you look into the webpack.config.js file in the shell app, you will see the following configuration.
1 | remotes: { |
Here we use remoteEntry.js as the entry point of the remote expose module, this is the default file name, However, if you want to change it, you can update the configuration in the webpack.config.js file in the mfe1 app.
1 | module.exports = withModuleFederationPlugin({ |
and then update the configuration in the webpack.config.js file in the shell app.
1 | module.exports = withModuleFederationPlugin({ |
namedChunk
When you run the shell app, switch to network tab in the browser’s developer tool, you will see the response of the javascript file name as a random string, that’s because Webpack generates a random name for the chunk file, you can change it to a named chunk by adding the following configuration in the angular.json file in the mfe1 app.(under architect | build | configuration | development)
1 | module.exports = withModuleFederationPlugin({ |
Dynamic loading
You can remove the remotes part in app shell’s webpack.config.js file, and update the app.routes.ts file in the shell app to load the remote module dynamically.
1 | { |
You can found the options of withModuleFederationPlugin in WebPack’s official documentation here, unfortunately, some property was even not mentioned in this document, you can found them in the source code of the plugin(schema file) from here
loadRemoteEntry vs loadRemoteModule
loadRemoteEntry is used to load the remote entry file, and loadRemoteModule is used to load the remote module.
loadRemoteEntry is not required, but nice to have, here is the description from github
If somehow possible, load the remoteEntry upfront. This allows Module Federation to take the remote’s metadata in consideration when negotiating the versions of the shared libraries.
For this, you could call loadRemoteEntry BEFORE bootstrapping Angular:
1 | // main.ts |
References
- Mono repo with Angular CLI: https://angular.dev/reference/configs/file-structure#multiple-projects
- Module federation with Angular: https://module-federation.io/practice/frameworks/angular/angular-cli.html
- https://github.com/manfredsteyer/ngx-build-plus/