When talking about Microservices or similar integration patterns of different applications, one often limits the considerations to the backend, the frontend is often set aside.
In one of our customer projects for a large financial corporation we were facing both the challenge to split the application parts into independently deployable artifacts and to provide a mechanism to integrate the different fragments into a single homogeneous User Interface.
This approach is nowadays a commonly used and well-known concept called Micro Frontends. When we started implementing this architecture back in the beginning of 2020 frameworks supporting this strategy were scarce and not really mature. Therefore we decided to implement this solution on our own.
Now some time has passed, the application is successfully running in production and our Micro Frontends developed for this project are used even outside of the scope of our department. In the meantime, some mature and stable Micro Frontend frameworks and techniques were developed. For this reason, we decided to have a closer look at the market of available solutions with focus on the requirements we learnt are valuable, when dealing with a Micro Frontend architecture.
We will first look at the technical requirements and criteria for the evaluation. After that we describe the reviewed solutions, separated by low-level and more abstract solution. Finally, we will reach a comparison and summary of all listed solutions.
Technical Requirements / Criteria
Since we have done the analysis of Micro Frontend solutions against the backdrop of a running project, we have chosen some requirements and evaluation criteria in respect to the project’s context. Apart from that we had a screening to evaluate which solutions should be considered generally. For this we were looking at the maturity and activity on the project itself. If it is very young and/or rarely used we withdrew it from the short list of candidates.
These are the general aspects we were looking at for the solutions that were taken into consideration:
⚙️ Server-Side Rendering: Although the current implementation and UI library does not support the possibility to use SSR, we consider using it sometime in the future. Therefore, we would like to have a look at the options the solutions offer.
📦 Sandboxing: When dealing with Micro Frontends styling and scope of libraries is a very difficult aspect to deal with, due to the global scope of the DOM the applications are living in. Ideally all included components of a child application integrated in a parent application live in a kind of sandbox that cannot interfere with the resources of the parent application.
🔎 Debugging / Testing: Since Micro Frontends as child components integrated to parent applications are not runnable in standalone mode, the question how these pieces of application can be tested independently.
📖 Child Component API Document: The API of a Micro Frontend component is a very important interface for the integration of child components into parent applications. A detail view of a product for example needs to know which product it needs to render. It’s crucial that this API can be documented or even better documentation can be generated from the source code itself.
💬 Communication between Components: Components never just reside next to each other and do not care what the other one is doing. A search component must tell it’s client what results were found, or maybe that an entry was clicked. The technique how these components can communicate with each other may be very different between the solutions.
Low Level Solutions
Import Maps are a browser specification draft published 2021. The draft aims to implement native browser support for bare import specifiers like import moment from “moment” which is currently only possible with the use of ahead-of-time bundlers that resolve these imports.
To achieve the translation of bare specifiers to their corresponding URLs, a new
<script /> type “importmap” which accepts a JSON object similar to a
package.json file containing the mapping between these two is introduced.
Import Maps provide a few additional features out of the box, such as the ability to map away hashes in script filenames which improves cacheability of sub dependencies. Parent dependencies only import the sub dependency by the name they are mapped to and no longer by the concrete hash-filename, which means they don’t have to change their hash if the sub dependency changes under the hood.
Another important feature is the ability to specify scopes within the import map. Using scopes, you can allow different versions of dependencies to coexist and to be used by different parts of the codebase.
Import Maps are a very powerful tool to connect Micro Frontends. They provide a browser native way to embed Micro Frontends within each other and manage their dependencies and dependency versions with good caching capabilities. Unfortunately, the Import Maps spec is currently only supported by Chrome, Edge and Opera (last checked 06/2022 on caniuse.com) but SystemJS can be used to overcome the scarce native support.
SystemJS is not a Micro Frontend framework in the classical sense, but it offers a lot of key features which enable the developer to build a Micro Frontend upon.
It is a polyfill-like for the HTML script-tag types
importmaps (s. Import Maps), which allows the usage of native ES modules in all browsers. Although there are many standards for the module ecosystem like CommonJS or AMD, SystemJS can be configured to work with any one of them. On top of that, SystemJS supports modern modules like JSON, CSS and even WASM.
The Config API enables the modification of base URLs, the resolution of dependencies for specific modules, name mapping to ease the import of modules and much more. SystemJS further allows for synchronous and asynchronous module loading. Importing modules synchronously might have an impact on the network depending on the size and amount of the different modules used. SystemJS offers to load multiple modules, treating them like a single file, in a single network call.
While offering a set of functionalities to enable the development of a Micro Frontend, it is not recommended to use it on its own. Some of the frameworks, which will be mentioned further in this article, are based on or use SystemJS and enable the development of a full-fledged application.
Module federation is a feature of Webpack 5. To date, it probably is the best-known way of combining multiple (browser-based) applications or views into a single application.
Webpack’s module federation allows for the asynchronous loading of remotely deployed containers that expose any module, e.g., components, functions or constants. Given that a cascading as well as a bi- or multi-directional load of remote resources is possible, module federation does not impose a specific form on the resource loading mechanism.
Such flexible referencing of remote modules is allowed for by a single configuration file, Webpack’s config json – one many projects already have. The module’s source therefore is abstract to its concrete usage; it may be bundled together with the using module, it may be loaded asychronously and remotely. Furthermore, the configuration allows for the configuration of what libraries are shared, what libraries function as fallbacks, if such fallbacks are allowed etc. This ability to configure a shared dependency list is essential to performance as already locally present dependencies do not have to be loaded for each further federated module.
Webpack’s module federation is based on widely used and well-known technologies and syntaxes; developers can import and use modules as they import and use locally exported modules. The points of increased complexity can be found in the new levels of asynchronous loadings and their potential costs, and the definition and knowledge of interfaces of remotely exposed modules, i.e., demands for some further inter-team coordination.
Applications are the basic and recommended type of Single-SPA. An Application needs to export three life-cycle functions, the
unmount function, which are called by the root application (also called root config). The root config is responsible for managing the life cycle of all registered applications and for rendering the final page. The downside of applications is that they mainly depend on routes (
window.location) and only work if all applications can coexist with the other applications.
If an UI component should be used by several applications, which may not even use the same framework (like React or Angular), Parcels are the way to go.
Parcels are framework agnostic and don’t depend on predefined routes like “Applications” as they exist besides the Single-SPA life cycle management of the route config. For these reasons they need to be mounted and unmounted manually.
Where Parcels provide the ability to share UI components across multiple applications, Utility modules are there to share logic between applications. Since a module only exports a public interface of functions and variables, it may not necessarily render an UI.
To get started easily with Single-SPA, a growing number of Single-SPA projects for the most popular UI frameworks (like Angular, React, Vue, Ember…) exist. Additionally, there is also a Firefox/Chrome devtools, the “single-spa-inspector”.
The framework is well documented, and you can find many additional sections on how to add “server-side rendering” or manage global and isolated styles between Micro Frontends.
- Style Isolation
- Prefetch Assets
- Umi-Plugin support
The Luigi framework developed by SAP is split into two parts, Luigi Core and Luigi Client. Luigi Core functions as the entry point or main app while Luigi Client refers to a client library which is utilized by Micro Frontends embedded in the main app to enable parent child communication.
Per default the Luigi Core main app consists of a side navigation, top navigation and a content area which is great if you are looking for a documentation-style web page but altering this strict base design requires some work in the form of config properties and custom CSS. Unfortunately there seems to be no way to start with a blank page.
In comparison with other Micro Frontends solutions, Project Mosaic offers a bunch of libraries tailored to the many needs of a large and scalable web application.
The concept is based on so-called “Fragments”, which are composed by the Tailor layout service to create the final web page. Tailor can be integrated into any Node.js server. It uses templates with fragment placeholders to know which fragments need to be loaded. Tailor offers out of the box distributed tracing instrumentation with OpenTracing, a build-in Error-Handling, a fallback functionality for not loading fragments, live-cycle hooks and performance measurement features.
In addition to the Tailor service as the central library, the Zalando team behind the project added five more projects to Mosaic. An extendable HTTP router and reverse proxy for service composition called Skipper, Shaker - a showcase library for UI components, Quilt - a template storage solution for Tailor thats aims at making it easier for multiple teams to manage their templates and the server-side renderer Tessellate for React-Components. And finally Innkeeper - a simple route management API for Skipper, which is according to the Skipper GitHub page already deprecated.
The downside of the project is that not all libraries like Shaker und Quilt are available to everyone or are deprecated like the previously mentioned Innkeeper
Piral is a relatively young, React-based, higher-level solution to the problem of Micro Frontends resp. the remote loading of resources during runtime. It suggests a solution based on a shell-micro-frontend-pattern: In Piral a developer defines an application shell – called “Piral instance” – that asynchronously loads remote resources – called “Pilets” – during runtime from so-called “feeds”. To implement instances of these objects, Piral comes with a cli (as well as a publicly useable feed-server) that allows for initializing, configuring and publishing the “Piral instance” resp. the “Pilets”.
Probably the main strength of Piral are its mechanisms of data exchanges between modules and module usages; here, Piral offers a producer-consumer pattern resp. a registration-slot pattern which allows for a highly controllable and flexible way of including and rendering modules or sharing data between modules. Further, shared dependencies are configurable to some degree via import-maps.
With Piral a solution is offered that focuses on mechanisms of data exchange and configurable module usage; however, the impacts on several development cycles are high: To name just a few, a new cli has to be learned, the ways of deployment change, and a specific structure is imposed on the system of frontends.
The result is summarized in the following table for better comparison
|Single SPA||✔||via additional middleware||only with additional frameworks||❌||only default lifecycle methods||✔|
|Qiankun||✔||via additional middleware||✔||❌||only default lifecycle methods||✔|
|Luigi||✔||✔||conceptionally not wanted, but possible||Built-In mock library||❌||✔|
|Mosaic||✔||✔||❌||with support of “Shaker”||Only supported for React||✔|
As seen the market provides several solutions to face the problem of integrating different applications within one user interface. Some of them provide the same solution concepts while others walk a different path. One must choose carefully which concrete requirements exist for their project.
What we learned was that most solutions are based on the idea that both parent and child are using the exact same build tools. In our projects we found that this can be a tricky part to integrating different applications, that were not designed for it from the beginning.
Another important thing we find missing is a standardized interface description. Some of the above frameworks are using an approach to declare TypeScript Type Definitions to define an interface, but those are not so easy to read for businesspeople. The TypeScript approach might be also having the caveat that there could be no validation for events or properties during runtime. Defining one like Swagger for REST or WSDL for SOAP might be an interesting challenge. We already begun in cooperation with other companies to draft a specification, stay tuned for upcoming news for this topic.
One major point that is missing is the opportunity to showcase developed Micro Frontends in a showcase manner. We did not find any solution that tries to act as a registry for Micro Frontends, where possible clients can test them or find an overview of the components features. Using Storybook might be a way to fill the gap. We will dive into this topic in our next upcoming Innovation Lab.