Module Federation has been a game-changer in the micro-frontends landscape. When combined with Angular, it offers a robust and scalable solution for distributed front-end architecture. This guide goes beyond the basics and explores a practical example, detailing how to configure the Webpack ModuleFederationPlugin
in an Angular shell and a remote application.
Before diving into the setup, ensure you meet the following prerequisites:
Angular CLI projects often come pre-configured with Webpack, but to ensure that Module Federation is fully supported, you need to opt-in to Webpack 5.
Open your package.json
and add a resolutions
key to force the use of Webpack 5:
NOTE: The resolutions
key is not natively supported by npm. It's advisable to use Yarn as your package manager. Alternatively, you can try using the npm-force-resolutions
package, although it hasn't been extensively tested for this setup.
If you opt to use Yarn, you'll need to inform the Angular CLI to use it as the default package manager.
In your angular.json
file, add the following configuration:
While Angular does use Webpack internally, it doesn't expose the Webpack configuration for customization by default.
You have a couple of options for exposing the Webpack configuration, such as using Ngx-build-plus
or @angular-builders/custom-webpack
. In this example, we'll use the latter.
First, install the package:
Then, update your angular.json
file to use this custom builder for both the build and serve commands:
NOTE: The custom Webpack configuration will be merged with Angular's default configuration, allowing you to specify only the changes needed for Module Federation.
Webpack uses the name from the package.json
by default. However, to avoid conflicts, especially in monorepos, it's recommended to manually define a unique name.
In your webpack.config.ts
, set the uniqueName
of the output configuration:
NOTE: If you're not using a monorepo and your package.json
already has unique names, you can skip this step.
Due to a current bug, setting the runtimeChunk
optimization to false
is essential; otherwise, the Module Federation setup will break.
In your webpack.config.ts
, disable the runtimeChunk
optimization:
The ModuleFederationPlugin
is crucial for defining how modules from remote applications will be consumed in the shell application.
In your webpack.config.ts
, add the ModuleFederationPlugin
to the plugins array:
Here, in the remotes
object, we map remote module names to their respective locations. The key ('mf1' in this example) is the name used to import the module in the shell application. The value specifies the location of the remote file, which in this example is http://localhost:4300/mf1.js
.
The shared
section in the Webpack configuration plays a pivotal role in defining modules that are common between the shell and the remote module. Doing so can significantly reduce the bundle size, enhancing the user experience.
Webpack will emit runtime errors if there are major version incompatibilities between the shell and remote apps. Harmonizing versioning across development teams is essential to prevent such issues.
Webpack adheres to semantic versioning when resolving shared dependencies. It’s advisable to allow some flexibility in version selection using operators like ^
or >=
. This ensures that only the necessary versions are loaded, minimizing the risk of loading multiple conflicting versions of a library.
Similar to the shell application, define a unique output name and disable the runtimeChunk
optimization:
Configure the ModuleFederationPlugin
as follows:
Here, the filename
and name
properties specify the JavaScript file's name and the namespace for the module container in the global window object. These are the exact values used by the shell application when loading the remote module.
The exposes
object specifies the modules to be exported. In this example:
./Contact
exports an Angular NgModule
with child routes../Clock
exports an Angular component for runtime rendering.Before you can use the remote modules, you need to inform TypeScript about their existence as they will be loaded dynamically at runtime.
Create a new TypeScript definition file, remote-modules.d.ts
, next to your routing module:
Just like you would with native lazy-loaded modules, you can now import remote modules into your Angular routing configuration.
Modify your route configuration as follows:
Creating components dynamically from remote modules offers a more advanced level of integration. This involves setting up a service and a directive to handle the dynamic rendering.
This service is responsible for dynamically loading remote modules and resolving component factories.
This structural directive dynamically creates components within its own view container using the component factory obtained from the Remote Module Loader Service.
==== Usage in View
In your Angular view, you can use the directive as follows:
== Summary
This guide has walked you through the dynamic integration of remote modules in an Angular application leveraging Webpack's Module Federation. Specifically, you've learned:
For a production-ready setup, additional steps are necessary, which will be covered in a future guide. Feel free to reach out to us via our social networks with any questions you may have on this technique.