Building Micro-Frontends with Single-Spa
Introduction
Micro-services architecture is becoming trendy since it is a suitable option many projects, specially those that are complex and we want to be able spilt our business logic into different services that can be built and deliver independently. In spite of the fact that micro-services architectures are more complex, they increase the resilience of your system and increase the time-to-market delivery, which can be crucial for many companies.
But it is not all about services and backend. With the introduction of single page applications (SPAs), we can build fully-functional user interfaces that can work independently in the browser. And here we should ask ourselves the same questions as for backend applications: Is my SPA becoming a huge monolith? Would I like to deliver functional modules independently? If you using micro-services, is there a relation between my services and your UI? Do we want to have functional teams that can deliver new features (frontend+backend) independently? If your answer to most of those questions is 'yes', you might need to consider micro-frontend architectures.
What are Micro-Frontends?
Here we have one of my favourite posts about micro-frontends: https://martinfowler.com/articles/micro-frontends.html. In particular, this image summarizes quite well the importance of micro-frontends:
So, when it comes to micro-frontend good practices and principles, there is a list of points that I consider important in the design:
- Each micro-frontend should be independently developed and delivered.
- Micro-frontends should load efficiently by using lazy-loading.
- We should be able to reuse common stuff, like styles, auth, frameworks in an efficient way.
- Communication between micro-frontends should be allowed in a decoupled and async way, and it shouldn't be too chatty.
- We should be allowed to use micro-frontends with different technologies.
Single-Spa
- It allows you to build and deliver your micro-frontends independently. In fact, they recommend having separated repositories and pipelines for each of your micro-frontends.
- It supports lazy loading. So your micro-frontends will be loading as you need them, instead of having a massive loading time at the beginning.
- It allows reusability of code and styles. In order to support this, you have multiple alternatives, like utility-modules or parcels.
- Data loading for shared modules is efficient. To do this, the preference is Import-maps, but Module federation is also also supported.
- Communication between micro-frontends is supported. Here the preference is cross microfrontend imports, but other alternatives like custom events or redux are also supported.
- Single-Spa doesn't depend on any frameworks. Under the hood, it works with javascript and webpack. In addition, it offers helper libraries for the most trendy frameworks.
- Single-Spa is open source and the community support is strong. The documentation and the number of examples is quite good.
- You can mount micro-frontends dynamically with import-map-overrides. This feature is just awesome. It allows you to inject one or several micro-frontends, so you don't have to run everything locally.
- There is a CLI (create-single-spa), which makes really easy to create new micro-frontends with the initial scaffolding.
- Micro-frontends are more complex than monolith SPAs. This is not only for Single-Spa, but for every micro-frontend architecture, since distributed systems are always more complex.
- If you are used to working with one SPA and CRA, it can be challenging at the beginning and you might need to learn some concepts, specially about Webpack, since Single-Spa doesn't support CRA.
Single-Spa with Create-React-App
1) Create a new micro-frontend with create-react-app
npx create-react-app app1 --template typescript
2) Install and configure react-app-rewired
npm install react-app-rewired --save-dev
3) Configure react-app-rewired
module.exports = {
webpack(config, env) {
config.entry = "./src/index-single-spa.tsx";
config.output = {
...config.output,
filename: "@company/app1.js",
libraryTarget: "system",
};
config.plugins = config.plugins.filter(
(plugin) =>
plugin.constructor.name !== "HtmlWebpackPlugin" &&
plugin.constructor.name !== "MiniCssExtractPlugin"
);
delete config.optimization;
return config;
},
devServer(configFunction) {
return function (proxy, allowedHost) {
const config = configFunction(proxy, allowedHost);
config.disableHostCheck = true;
config.headers = config.headers || {};
config.headers["Access-Control-Allow-Origin"] = "*";
return config;
};
},
};
4) Add single spa support
npm i -D single-spa-react
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; // Note that SingleSpaContext is a react@16.3 (if available) context that provides the singleSpa props import singleSpaReact from "single-spa-react"; const reactLifecycles = singleSpaReact({ React, ReactDOM, rootComponent: App, errorBoundary(err, info, props) { // https://reactjs.org/docs/error-boundaries.html return <div>This renders when a catastrophic error occurs</div>; }, }); export const bootstrap = reactLifecycles.bootstrap; export const mount = reactLifecycles.mount; export const unmount = reactLifecycles.unmount
5) Add your micro-frontend to root application
create-single-spa --moduleType root-config
Comments
Post a Comment