The Microfrontend Revolution: Module Federation in Webpack 5

Module Federation allows loading separately compiled program parts. This makes it an official solution for implementing microfrontends.

This article is part of a series:


Until now, when implementing microfrontends, you had to dig a little into the bag of tricks. One reason is surely that current build tools and frameworks do not know this concept. Webpack 5, which is currently in BETA, will initiate a change of course here.

It allows an approach implemented by the webpack contributor Zack Jackson. It's called Module Federation and allows referencing program parts not yet known at compile time. These can be self-compiled microfrontends. In addition, the individual program parts can share libraries with each other, so that the individual bundles do not contain any duplicates.

In this article, I will show how to use Module Federation using a simple example. The source code can be found here.


The example used here consists of a shell, which is able to load individual, separately provided microfrontends if required:

Shell with microfrontend

The shell is represented here by the black navigation bar. The micro front end through the framed area shown below. Also, the microfrontend can also be started without a shell

Microfrontends in standalone mode

This is necessary to enable separate development and testing. It can also be advantageous for weaker clients, such as mobile devices, to only have to load the required program part.

Concepts of Module Federation

In the past, the implementation of scenarios like the one shown here was difficult, especially since tools like Webpack assume that the entire program code is available when compiling. Lazy loading is possible, but only from areas that were split off during compilation.

With microfrontend architectures, in particular, one would like to compile and provide the individual program parts separately. In addition, mutual referencing via the respective URL is necessary. Hence, constructs like this would be desirable:


Since this is not possible for the reasons mentioned, one had to resort to approaches such as externals and manual script loading. Fortunately, this will change with the Federation module in Webpack 5.

The idea behind it is simple: A so-called host references a remote using a configured name. What this name refers to is not known at compile time:

The host accesses the remote using a configured name

This reference is only resolved at runtime by loading a so-called remote entry point. It is a minimal script that provides the actual external url for such a configured name.

Implementation of the Host

The host is a JavaScript application that loads a remote when needed. A dynamic import is used for this.

The following host loads the component mfe1/component in this way -- mfe1 is the name of a configured remote and component the name of a file (an EcmaScript module) it provides.

const rxjs = await import('rxjs');

const container = document.getElementById('container');
const flightsLink = document.getElementById('flights');

rxjs.fromEvent(flightsLink, 'click').subscribe(async _ => {
    const module = await import('mfe1/component');
    const elm = document.createElement(module.elementName);

Webpack would normally take this reference into account when compiling and split off a separate bundle for it. To prevent this, the ModuleFederationPlugin is used:

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");


plugins: [
  new ModuleFederationPlugin({
    name: "shell",
    library: { type: "var", name: "shell" },
    remotes: {
      mfe1: "mfe1"
    shared: ["rxjs"]

With its help, the remote mfe1 (Microfrontend 1) is defined. The configuration shown here maps the internal application name mfe1 to the same official name. Webpack does not include any import that now relates to mfe1 in the bundles generated at compile time.

Libraries that the host should share with the remotes are mentioned in the shared array. In the case shown, this is rxjs. This means that the entire application only needs to load this library once. Without this specification, rxjs would end up in the bundles of the host as well as those of all remotes.

For this to work without problems, the host and remote must agree on a common version. In addition, the host must load such libraries via a dynamic import. Static imports, such as

import * as rxjs from 'rxjs';

were not supported by plugin when this article was written.

Implementation of the Remote

The remote is also a standalone application. In the case considered here, it is based on Web Components:

class Microfrontend1 extends HTMLElement {

    constructor() {
        this.attachShadow({ mode: 'open' });

    async connectedCallback() {
        this.shadowRoot.innerHTML = `[…]`;

const elementName = 'microfrontend-one';
customElements.define(elementName, Microfrontend1);

export { elementName };

Instead of web components, any JavaScript constructs or components based on frameworks can also be used. In this case, the frameworks can be shared between the remotes and the host as shown.

The webpack configuration of the remote, which also uses the ` ModuleFederationPlugin '', exports this component with the property exposes under the name component:

 output: {
      publicPath: "http://localhost:3000/",
 plugins: [
    new ModuleFederationPlugin({
      name: "mfe1",
      library: { type: "var", name: "mfe1" },
      filename: "remoteEntry.js",
      exposes: {
        component: "./mfe1/component"
      shared: ["rxjs"]

The name component refers to the corresponding file. In addition, this configuration defines the name mfe1 for the remote. To access the remote, the host uses a path that consists of the two configured names, mfe1 and component. This results in the instruction shown above:


However, the host must know the URL at which it finds mfe1. The next section shows how this can be accomplished.

Connect Host to Remote

To give the host the option to resolve the name mfe1, the host must load a remote entry point. This is a script that the ModuleFederationPlugin generates when the remote is compiled.

The name of this script can be found in the filename property shown in the previous section. The url of the microfrontend is taken from the publicPath property. This means that the url of the remote must already be known when it is compiled. Fortunately, there is already a PR which removed this need.

Now this script is only to be integrated into the host:

<script src="http://localhost:3000/remoteEntry.js"></script>

At runtime it can now be observed that the instruction


causes the host to load the remote from its own url (which is localhost:3000 in our case):

Laden des Remotes von anderer Url

Conclusion and Outlook

The Module Federation integrated in Webpack 5 fills a large gap for microfrentends. Finally, separately compiled and provided program parts can be reloaded and already loaded libraries can be reused.

However, the teams involved in developing such applications must manually ensure that the individual parts interact. This also means that you have to define and comply with contracts between the individual microfrontends, but also that you have to agree on a version for each of the shared libraries.

At the time this article was written, Webpack 5 was still BETA and therefore not ready for production use. This means that you still have to rely on the tricks mentioned at the beginning. In the medium to long run, however, the Module Federation could become the standard solution for micro front ends in the JavaScript environment.

Don't Miss Anything!

Subscribe to our newsletter to get all the information about Angular.

* By subscribing to our newsletter, you agree with our privacy policy.

Unsere Angular-Schulungen

Aktuelle Blog-Artikel

Nur einen Schritt entfernt!

Stellen Sie noch heute Ihre Anfrage,
wir beraten Sie gerne!

Jetzt anfragen!