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-federation
andngx-build-plus
to filepackage.json
under workspace root.Add command
"run:all": "node node_modules/@angular-architects/module-federation/src/server/mf-dev-server.js"
toscripts
section 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.ts
underprojects/shell/src
folder, its content was copied frommain.ts
1
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.ts
to importbootstrap.ts
(Note, when a file was imported, it was executed automatically)1
import './bootstrap';
Add file
webpack.config.js
underprojects/shell
folder,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.js
underprojects/shell
folder,1
module.exports = require('./webpack.config');
Update file
angular.json
under 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:browser
which 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.json
1
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/