An approach to micro-frontends

[Versión en castellano]This link opens in a popup window

Some months ago, I talk to you about how we use modern web frameworks within a PortalThis link opens in a popup window. I wrote about some ideas like orchestration and modularization to name a few. In this post I’d like to retake the same concepts, but I’m going to tell you how we are using them in a different approach. I dare say, in a more modern way. A micro-frontend one, even though I’m not extremely fond on that name. As a matter of fact, a portal could also be seen as a micro-frontend orchestrator.

First and foremost, what are micro-frontends? In short, it’s a way to divide your frontend in a series of theoretically independent elements, which are going to be developed also independently. You shouldn’t be confused with other frontend techniques that also divide the code in different fragments, fundamentally lazy loading as it is done, for example, in React. Some people will tell you that the main difference is that micro-frontends provide you with the tremendous advantage to develop each element with a different technology. From my point of view, the actual difference is the need to orchestrate. Obviously, you will load these micro-frontends lazily and you can also develop them with your preferred technology, but the main point is when, how and why they are going to be deployed on screen.

image00-portadaimage00-portada

Hence, how can we achieve this orchestration? Unfortunately, there are no silver bullets. A solution which could fit me, probably won’t be the best answer in a different situation. Nevertheless, should you know how we tackle this problem, you’ll be able to understand challenges better and provide your own solutions.

In my opinion, building this architecture involves deciding how micro-frontends are going to be created, how they are going to be loaded, creating the main frontend application – the orchestrator – and, finally, solving a series of important issues that appear when you don’t have a monolithic frontend. Let’s see it.

Creating the micro-frontends

As I told you previously, each micro-frontend is, in fact, an independent element. That means that it can have it owns dependencies and it could work on its own. However, that doesn’t mean chaos, you need to provide a common environment to make easier these services development.

In our case, we are using several techniques. Namely, a folder used as a common service-place for the project, yarn workspaces to avoid downloading each dependency hundredths of times a generator which helps us to create new services and a customized bundler focused not only on micro-frontend "transpilation" but also on preparing this micro-frontend to be used from the orchestrator engine.

imagen04-gradleimagen04-gradle

Loading the micro-frontends

To tell the truth, loading the micro-frontends is not related to deploy them on the screen, but to know the micro-frontends that exist within your application, and provide mechanism to read their code from the frontend orchestrator.

From my point of view, this is not a frontend task but a backend one’s. I mean to say, provided that we are developing a modular application, we can add new modules. Moreover, we are committed to avoid frontend orchestrator modification when adding these new services. Thereby, the knowledge of the different modules should be stored in a "server", a standard backend, database, serverless, whatsoever.

To tackle this challenge, we are applying complementary approaches. Particularly, we have a watchdog on folder structure to detect both new and modified services, which is also responsible for analysing service structure and deploying it on a database, a mechanism which allows code downloading from the frontend orchestrator and a security layer which ensures that the code can only be downloaded by authorized users.

imagen01-watchdogimagen01-watchdog

Note that we need to expose internal micro-frontend code, at least a piece of it, as to be called from the orchestrator. This can be done using webpack capabilities by publishing transpiled code as a library.

imagen05-webpackimagen05-webpack

The orchestrator

Building a full orchestrator is complex. Just imagine, that you want to configure in a WYSIWYG approach where your elements are going to be shown on the screen, or if there are different layouts per route, to name a few. To solve this problem, we should go for a Portal. But in this case, I’m interested in a simpler solution, only a menu which provides access to the different micro-frontends. In this case, we only need to store the order of the elements and that could be done within the micro-frontend boundaries.

imagen03-imagen03-

Apart from this, we need to implement the lazy-loading approach. That’s a quite easy and common task in JavaScript,

imagen06-lazyloadimagen06-lazyload

But, in this case, I wanted to go one step further. So, I decided to wrap this script and style loading in a Web ComponentThis link opens in a popup window. This approach is quite interesting, since it would allow us to use the Shadow DOM and to make simpler the service loading from the orchestrator point of view.

imagen02-webcomponent00imagen02-webcomponent00

Take into account that when you use the Shadow DOM you could have problems with styling. But also, there are certain issues such as opening a dialog from the micro-frontend. In my opinion it’s worth giving it a try, but I prefer, for my use case, not shadowing the micro-frontend.

Some challenges when micro-frontending

There are other challenges when taking this micro-frontend approach. Had I to select one of them I would pick up the following ones.

  • Network access. When you have different services that are rendered independently from the main rendering engine, you don’t have access to context or other variables. This could be challenging if you need to consider global logout issues when you receive either a 401 or 403 status header, etc. Solution could be declaring global variables, I don’t like this idea, though. Or better you could override global fetch provider, not a wrapper but an actual overriding.

imagen07-fetchimagen07-fetch

  • Routing issues. You have two different routing engines involved, the orchestrator and the micro-frontend one’s. I’ve followed a solution where I use browser normal URIS for orchestrator but within each micro-frontend a hash based one.
  • Exposing shared libraries to microfrontends. Just imagine that all your microfrontends are developed with React, hence you could be tempted to export globally react, react-dom, redux to name a few, as to minimize micro-frontend bundle size. In my opinion, you should be careful when using this approach since you need to maintain version consistency across orchestrator and micro-frontends’ dependencies to avoid problems. As a general rule, I wouldn’t recommend going this way.
  • Sharing information between micro-frontends. This is a complex task, and I haven't approached to it in the current example. You could try using events or even storing information in shared IndexedDB database, you must be careful with sensitive information though.