Webpack Module Federation: Micro-Frontend Powered by Modern Technologies (Part 2) — Deep Dive

Ilya Isupov
netcracker
Published in
8 min readDec 15, 2021

Based on the questions from the previous article, we decided to write Part 2 and tell you what else we have successfully accomplished.

Agenda:

  • Host application routing (React/Vue Routing inside Angular)
  • Fully-featured adapters for remote plugins
  • Using service modules at runtime

Routing

In the first part of the article, we created a pet project, in which we used an Angular host and remote Angular plugins. In such a combination, the routing of the host application covers all options for the native use of the application. For example, there is the /plugin route in the host application, and the plugin contains two routers: /first and /second. As a result, we will get /plugin/first and /plugin/second in the finished application. Such a system will work perfectly at any nesting level.

Let’s consider an example of a child routing for an Angular plugin.

Consider the following task: in an Angular application, use a plugin written in a different framework — React.

This usually poses the question: why are you doing this? And why not write everything in one framework? Let’s digress from techniques and explain.

Indeed, it is worth doing on projects of simple and medium complexity, if it is possible to use the same technologies by all teams. The task to combine several frameworks is relevant for complex projects, where independent teams from different domains (microservices) develop their components, in their release cycles, using different technologies. Further, these components should be used in the same interface. Our experience shows that the world becomes more complicated, therefore, IT systems become more complicated as well, and such complex projects grow in number.

As an example, let’s take the workplace of a contact center agent. On this screen, we see the financial information developed by the Billing team that is highly reliable and rarely changes. And here is a widget with the trendy Next Best Action, written by a dynamic team of hipsters who are constantly experimenting. There are also chatbots, scripting, marketing promotions, wizards for solving technical problems, knowledge bases, and much more. Under each component, there is a large amount of business logic as well as large teams developing it.

You must admit that it is wrong to impose using certain technologies on a team as it would contradict the idea of microservice architecture.

Let’s go back to techniques.

If we simply create an Angular route /react in our host application and inject the React plugin with routing under it, then everything will work, but not exactly as expected.

Create routing in the React plugin.

In this implementation, when switching to /react, we will see that the URL in the browser changes to <host_url>/first. This is not what we expect. Solve the problem using basename. Idea: we need to forward the route from the host application, under which the React plugin is stored, to the plugin itself in order to initialize the BrowserRouter with the correct basename. In a few words, the basename is the URL against which the React routing will be built.

Thus, forward it on the host side. Obviously, we need the routePath value directly from the plugin configuration.

Accept it on the plugin side.

We get a ready full-featured solution Angular Router + React Browser Router.

Super Native Adapters for Micro-Frontend

Let’s consider:

  • Angular Adapter
  • React Adapter
  • Vue Adapter

Initializing some plugin with a set of props (properties) is easy, but this solves only half the problem. What if we want to use a remote plugin as part of our application, as a dumb component, just as a component that sometimes needs props from the host or another plugin?

Create a Change Detector for our plugins. We already have a detector. Now let’s learn to pass props to plugins:)

The props scheme should be clear to everyone, but just in case, let’s draw a picture.

Assumption. On the plugin side, you can get the updated props only in the setter, because we are routing them manually, and not by the standard change detection cycle.

Angular Adapter

Components

We have slightly updated/rewritten our Angular adapter, and we have ended up with the following code.

The new features include the option to use any component from any module from any remote repository with the necessary providers. This means that if we have a module in the plugin repository with a component that uses a service, which provides the same module, we can export the entire module with its service providers without worrying that some service gets lost.

How to implement it:

On the plugin side, we should provide the module and component we want to use. One module is not sufficient, because all component factory of this module will not get into the bundle (tree-shakable components) at build -prod, and there remains one module with the injector.

It looks as follows:

On the side that receives the plugin, there must be an advanced configuration, like the following one:

These four properties framed in red ensure that the module and plugin components can be loaded.

We can load the plugin in the following way:

Here is the code:

This code gives us free rein! Now we can retrieve any component from any module :)

And what about services? We also can!

Why use shared services from remote modules? We have a set of backend services that provide us with a REST API. Each service supports a specific business. When two plugins need to access the same backend, each plugin must create a GraphQL query, add a GraphQL module implementation to the repository, and remember about the data models. (╯°□°)╯︵ ┻━┻.

The idea is to create master UI services for backends. Then each backend has one Singleton service for the entire application (for the host and all plugins). We will use them at runtime.

Let’s provide the service in the remote module like this:

This is necessary so that to retrieve it from the injector using the following string:

Let’s be honest: the way we can use this service is not ideal, but at least now we can do it. Everything is so complicated because we’re pulling out of the injector not the service only, but the service with all of its dependencies, if any. And there is no need to provide anything additionally on the consumer side — everything will automatically start working!

React Adapter

The Angular-React adapter will be a bit simpler. Since it is much easier to expose from React as there is no need to divide components into smart and dumb, we will not include this in the adapter implementation either.

We should be able to:

  • Render a component under the route;
  • Render a component anywhere in the Angular component using the adapter.

Under the route, we already know how to implement the following code:

And now let’s learn how to render a component at a randomly selected point. Conceptually, it will be the same as for the Angular plugin. You should simply insert the React adapter for the plugin into the template, and then add the plugin configuration and props to the template.

The adapter renders the plugin:

And installs the props upon receiving them:

Remember to execute unmount after the component is destroyed:

Success :)

Vue Adapter

The adapter code for Vue is structurally the same as for the React and Angular adapters. The main difference is only in the code of the renderer, component, or application itself.

We get the component and, accordingly, “mount” it on the adapter host element.

We can also write a self-invoking adapter, which can be used in the case when the logic of binding settings, routing configuration, and libraries connection is assigned to a remote plugin.

And already in the Vue project, export the bootstrap function, for example:

in which we simply use the forwarded element and attach the component (the application) to it.

Since Module Federation allows exposing any javascript, the Webpack configuration will not change much.

All the code discussed can be found on github.

P.S. Thanks Anton (telegram: @poetique) and Ivan for help in implementing Vue adapter and React child routing.

--

--