Micro frontends - alexanderteplov/computer-science GitHub Wiki
Pros
- Smaller, more cohesive, and maintainable codebases
- More scalable organizations with decoupled, autonomous teams
- Incremental upgrades
- Independent deployment
Cons
- Payload size: Some implementations can lead to duplication of dependencies, increasing the number of bytes our users must download
- Environment differences: The dramatic increase in team autonomy can cause fragmentation in the way your teams work
- Operational and governance complexity
One approach is to publish each micro frontend as a package and have the container application include them all as library dependencies.
This approach means that we have to re-compile and release every single micro frontend in order to release a change to any individual part of the product.
One of the simplest approaches to composing applications together in the browser is the humble iframe.
By their nature, iframes make it easy to build a page out of independent sub-pages. They also offer a good degree of isolation in terms of styling and global variables not interfering with each other.
It can be difficult to build integrations between different parts of the application, so they make routing, history, and deep-linking more complicated, and they present some extra challenges to make your page fully responsive.
Each micro frontend is included on the page using a <script> tag, and upon load exposes a global function as its entry-point. The container application then determines which micro frontend should be mounted, and calls the relevant function to tell a micro frontend when and where to render itself.
<html>
<head>
<title>Feed me!</title>
</head>
<body>
<h1>Welcome to Feed me!</h1>
<!-- These scripts don't render anything immediately -->
<!-- Instead they attach entry-point functions to `window` -->
<script src="https://browse.example.com/bundle.js"></script>
<script src="https://order.example.com/bundle.js"></script>
<script src="https://profile.example.com/bundle.js"></script>
<div id="micro-frontend-root"></div>
<script type="text/javascript">
// These global functions are attached to window by the above scripts
const microFrontendsByRoute = {
'/': window.renderBrowseRestaurants,
'/order-food': window.renderOrderFood,
'/user-profile': window.renderUserProfile,
};
const renderFunction = microFrontendsByRoute[window.location.pathname];
// Having determined the entry-point function, we now call it,
// giving it the ID of the element where it should render itself
renderFunction('micro-frontend-root');
</script>
</body>
</html>Unlike with build-time integration, we can deploy each of the bundle.js files independently. And unlike with iframes, we have full flexibility to build integrations between our micro frontends however we like.
BEM, pre-processor, CSS modules, CSS-in-JS. Use any approach to avoid collisions.
Let the teams create their own components and after a while, some of them could be moved to the library. Keep there dumb components only with a UI (not business) logic. There should be someone dedicated to take care of the library.
It's better to have them communicate as little as possible. You may do it these ways:
- Custom events allow micro frontends to communicate indirectly.
- The React model of passing callbacks and data from the container application to the micro frontends.
- Use the address bar as a communication mechanism.
Avoid having any shared state.
You could implement some form of consumer-driven contracts so that each micro frontend can specify what it requires from other micro frontends.
One pattern that helps here is the BFF pattern, where each frontend application has a corresponding backend whose purpose is solely to serve the needs of that frontend.
The guiding principle here is that the team building a particular micro frontend shouldn't have to wait for other teams to build things for them. So if every new feature added to a micro frontend also requires backend changes, that's a strong case for a BFF, owned by the same team.
The obvious peculiarity is an integration testing of the various micro-frontends with the container application.
Use unit tests to cover low-level business logic and rendering logic, and functional/end-to-end tests just to validate that the page is assembled correctly.