Micro Frontends
An architectural style where independently deliverable frontend applications are composed into a greater whole.
As front-end development becomes increasingly complex, traditional large front-end projects should likely end up being difficult to maintain due to over-coupling, and therefore Micro Frontends is also gaining attention in front-end architectures.
Front-end application modules dynamic will become one of the new trends in front-end development, and it will be possible to solve the problem of code base maintainability and delivery efficiency more thoroughly.
Benefits and Value of Micro Frontends
- Independence and Autonomy
Only if the overall process of application development can be developed, deployed and managed independently of the code base, etc., then the front-end project can have true independence guaranteed. And this possibility of team autonomy is also in line with Conway's Law, which states that "Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure", thus bringing about a possible new form of organizational management.
- Technology Agnostic
Technology agnostic facilitates the collaboration of multiple teams with different technology stacks. The smooth migration of technology stacks also brings greater convenience to the continuous iteration and technology upgrade of the older business system.
- Runtime Integration
In modern front-end development processes, we most often see build-time integration. Whereas before, runtime integration happened to separate modules more independently. Micro frontends also happen to integrate well with such micro module concepts and keep such modules independent and dependency sharing.
- Decoupled Modularity & Composable
In large front-end projects, we have high requirements for modular decoupling, often based on different types of divisions, such as business type modularity, technical service type modularity, and so on. The composable of individual micro frontends particles in turn allows for good module consistency and overall customization differentiation across multiple deliverable families, and can greatly reduce business duplication.
In general, the proper practice of micro frontends architecture will bring far-reaching value to the long-term maintenance of some large front-end projects.
Motivation
Among the many micro frontends solutions, single-spa and Module Federation are the best of them.
single-spa is a micro frontends framework based on router configuration. The centralization of configuration brings some limitations, such as it is difficult to granulate nestable micro frontends, module granularity control, module sharing, and so on.
In 2019, Zack Jackson proposed and implemented Module Federation. Module Federation is a completely different concept from single-spa, and allows a JavaScript application to dynamically load code from another application. It completely solves the problem of code dependency sharing and runtime modularity. The idea is true - A game-changer in JavaScript architecture as mentioned in Zack Jackson's article. And it's currently supported by Webpack, Next.js, and Rollup.
Although the Module Federation concept is so amazing, it has not yet gone further to provide a more complete and fully targeted micro frontends framework implementation, and this is what Fronts is trying to do.
Hotspots of Micro Frontends Framework
Based on the current mainstream micro frontends frameworks or concepts, the following is a compilation of the main hotspots involved.
- Should the granularity level be application level or module level
Module level is obviously more advantageous in terms of flexibility and granularity, but there is clearly an advantage to supporting application level in order to be compatible with some not so modern front-end projects, so we need a framework that supports both. If application-level runtime integration is required, it is clear that just using Module Federation's Webpack is not enough, we also need a runtime application-level granular entry point loader.
- Whether the entry point file is HTML or JavaScript
From a modern engineering perspective, most front-end application entry points are JS-based, and some previous front-end projects have used HTML alone as the entry point. The trade-off is that building a micro frontends system for applications where HTML is the main entry point is bound to be a longer and more complex of processes. Such a library would be better suited as a standalone sub-package, while the overall framework should take JS files as the entry point.
- Is it necessary to support perfect module sharing
Module sharing is a problem that must be solved by micro frontends frameworks, otherwise the duplication of resources at runtime will make micro frontends less valuable. Currently, only Webpack with Module Federation allows such module sharing to be handled at build time, with dynamic dependency sharing at runtime. No better solution than Module Federation has yet emerged.
- CSS/JS isolation trade-off
Isolation of CSS is almost required and is supported by many micro frontends frameworks. We may have to do all kinds of hijacking to ensure the security, performance and stability, and also consider the compatibility of different browsers. However, JS isolation is relatively expensive to implement, and the fact that such isolation is required for modern front-end engineering depends on the actual situation of each micro frontend.
- Generic micro frontend and support multiple containers with multiple modes (Or SSR, etc.)
In large front-end projects, it is often not just about building a single web application, but possibly multiple web applications, or even more front-end application types, such as Electron applications, browser extensions, native applications, etc. So a good micro frontends framework should be able to run more kinds of containers and build a variety of application types, but also preferably compatible with building traditional SPA and micro frontends applications. Module Federation also initially implemented in the next.js implementation of SSR support.
- Version control and dependency management
With rapid iteration and business growth, various module management becomes very important, so when a large front-end project practices micro frontends architecture at a later stage, version control and dependency management will become especially important, which will determine the delivery efficiency and maintainability.
To solve these problems, Fronts was created.
What's Fronts
Fronts is a progressive micro frontends framework for building Web applications, and it's based on the module federation of Webpack.
Repo: https://github.com/unadlib/fronts
-
Non-module-federation - Although Fronts is based on the concept of module federation, it also supports
non-module-federation
mode. -
Decentralized configuration - Configure
site.json
for dependency management in each Fronts app, support for nested micro frontends. - Cross frameworks - No framework or technology stack is restricted.
- Code splitting & lazy loading - Support code splitting within the Fronts app as a module, it can be lazy loaded by other Fronts app as a dependent module.
- CSS isolation - Optional CSS isolation solution.
- Lifecycle - Fronts provide concise lifecycle for Fronts app entry.
- Web Components & iFrame - Support for multiple frontend containers.
-
Multiple patterns - Support for building
micro-frontends
app andnon-micro-frontends
app. - Monorepo & TypeScript - Friendly support for Monorepo and TypeScript, which are mutually appropriate technology stack.
- Version control - It's used for efficient and dynamic delivery apps such as canary release.
- Zero hijacking - Fronts didn't do any hijacking, maintaining originality and possible loss of performance and security.
- Generic Communication - Fronts provides concise and generic communication APIs, which supports almost all frontend environments.
Benefits of Fronts
Fronts is a concise and easy to understand micro frontends framework.
Set site.json
to define a micro frontend, similar to a package.json
in Node.js.
{
"name": "app1",
"exports": ["./src/bootstrap"],
"dependencies": {
// If version control is enabled,
// here it looks like: `"app2": "1.0.0"`
"app2": "http://localhost:3002/remoteEntry.js"
},
"shared": {
"react": { "singleton": true },
"react-dom": { "singleton": true }
}
}
Fronts is progressive.
If every front-end application does not support Module Federation, it will still work well as a micro frontend, with on-demand runtime modes, and as projects are upgraded, they can gradually be made to support Module Federation and eventually version control can be enabled. With support for multiple granularity levels, build types, module types, shared types, runtime types, and communication types, Fronts can almost meet all kinds of micro frontends architectures.
Fronts APIs are clean and simple.
Fronts provides three sets of loaders useApp()
, useWebComponents()
and useIframe()
. It also provides an micro frontend launcher boot()
and a Webpack configuration generator createWebpackConfig()
. With these APIs, you will be able to do micro frontends development quickly and efficiently.
Example
We will build a micro frontends project based on Fronts, where app1
is the main entry point and it will depend on app2
.
You can follow this article(React without create-react-app Webpack 5) to quickly create
app1
andapp2
React projects.
Assuming you've completed these steps, let's get started with a quick taste of the wonderful micro frontends development of Fronts.
- Install
fronts-react
andfronts-bundler
in the projects.
# with NPM
npm install fronts-react fronts-bundler
# or with Yarn
yarn add fronts-react fronts-bundler
- Set up
site.json
andwebpack.config.js
in the projects
We define app1
as a parent micro frontend and it depends on app2
.
app1/site.json
:
{
"name": "app1",
"exports": [],
"dependencies": {
"app2": "http://localhost:3002/remoteEntry.js"
}
}
app2
doesn't have any dependencies, it acts as a micro frontend and we define it to export ./src/bootstrap
as a micro frontends entry, this entry of app2
end will be used by app1
.
app2/site.json
:
{
"name": "app2",
"exports": ["./src/bootstrap"],
"dependencies": {}
}
Wrap the Webpack config with createWebpackConfig()
in config/webpack.config.js
in the projects.
const { createWebpackConfig } = require('fronts-bundler');
module.exports = createWebpackConfig(originalWebpackConfig);
- Define the default exported bootstrap function in
app2/src/bootstrap.jsx
and useboot()
to get it booted.
import React from 'react';
import ReactDOM from 'react-dom';
import { boot } from 'fronts-react';
import App from './App';
export default function render(element) {
ReactDOM.render(<App />, element);
return () => {
ReactDOM.unmountComponentAtNode(element);
};
}
boot(render, document.getElementById('root'));
- Load
app1/src/App.jsx
withuseApp()
to importapp2
.
import React from 'react';
import { useApp } from 'fronts-react';
export const App = () => {
const App2 = useApp({
name: 'app2',
loader: () => import('app2/src/bootstrap'),
});
return <App2 />;
};
Run yarn start
, and app2
is rendered as a micro frontend on app1
.
Example repo๏ผhttps://github.com/unadlib/fronts-example
Notes
- Built-in packages
The mainstream front-end frameworks are still React, Vue and Angular. When a micro frontend uses one of them, it is recommended to use Fronts' built-in packages, such as fronts-react
, fronts-vue
and fronts-ng
, and when it comes to other frameworks not supported by the built-in packages or no framework, then please use fronts
.
- Built-in Package API
Each built-in package contains three sets of loaders useApp()
, useWebComponents()
, useIframe()
. useApp()
provides loose CSS isolation, useWebComponents()
provides strict CSS isolation, and useIframe()
provides native strict CSS isolation and JS isolation.
- Version Control
Fronts does not have full version control suite support and currently only supports self-built registry server.
- Monorepo & TypeScript
Large front-end projects often mean a high level of complexity, so Fronts are well suited for use in a combination of technology stacks like Monorepo and TypeScript. You get a great development experience in type safety, code management and runtime integration. when each micro frontend is used as a Monorepo sub-package, you just run SPA=true yarn start
and switch the micro frontends development mode to the traditional SPA development mode.
Conclusion
The front-end architecture based on Fronts, Monorepo, and TypeScript will significantly improve codebase management, type safety, business development and delivery efficiency, and enable multiple combination of product business capabilities, high reuse and consistency of business code, and diversity of application types.
Every large front-end project that tries to implement micro frontends architecture has different or similar requirements, so by analyzing the demands and needs of their own large front-end projects and using them to build or choose their own micro front-end architecture, they can really solve their own main engineering problems.
With a general module concept based on Module Federation, Fronts tries to solve the main problems of micro frontends in a more targeted and systematic way, such as cross-framework, dependency sharing, dependency management, version control, compatibility with multiple runtime containers and patterns, etc.
Fronts wants to evolve from more micro frontends architecture requirements to an efficient micro frontends framework.
Top comments (12)
Hey Michael! Nice work. I am experimenting with a hobby project and have a few queries around that.
so does it mean that all the smaller parts of the micro-frontend need to reside in the same folder structure?
src/cart
and team 2 works onsrc/products
.We need more complete example, for example, we have a
shopping
application, then it depends oncart
and also depends onproducts
, so we can have two teams to developcart
andproducts
respectively. these two teams develop and deploy completely independently.For the more complete application shell of
shopping
, we do something likeimport('cart/src/bootstrap')
andimport('products/src/bootstrap')
to render them both in the shopping application.We can choose the rendering containers in the shopping app for these two sub-applications,
useApp()
,useWebComponents()
oruseIframe()
.And the most important, it also supports components of the same UI framework. So the three teams, which can choose to use React , and we can merge two sub-applications in the shopping using
import Cart from 'cart/src/App';
andimport Products from 'Products/src/App';
.fronts are highly recommended for reuse as components when using the same UI framework.
Hey Michael, thanks a lot for the reply!
These questions are regarding an article that I'm writing comparing singleSPA, webpack module federation and fronts.
I also checked out the example that is linked on the official fronts github. Planning to convert it into an ecom example with a better UX.
I have a few follow up questions:
In the above example that you provided, does the shopping app, cart and products code need to be in the same code base? If yes, then what is the advantage over a conventional codebase that depends on folder structure boundaries? Is the ability to build them separately the only major advantage?
Also, these component apps be deployed separately and what would that deployment process look like?
And lastly, does fronts optimize the shared common dependencies among these code bases? If yes, where do we define the shared libraries?
We can even connect for some time if you're free.
In the above example, shopping app, cart and products does not need to be in a codebase at all (If necessary in fact monorepo is a good practice to manage large frontend project).
Its biggest advantage is that it is completely independent, lazy loading, and it is fully dynamic. Compared to traditional codebases, it doesn't need to be rebuilt in its entirety every time. For example, we add a new feature to cart. We simply rebuild cart and deploy it so that the shopping application loads the latest cart code. For large and complex front-end projects, this is a huge advantage. It means it's a very efficient delivery. Imagine how flexible it will become when many teams are working together to deliver large projects, with each team just being responsible for its own part independently. (It has very many other benefits, and if you want to learn more about it, you can read this article. martinfowler.com/articles/micro-fr...)
The deployment process of fronts is like this. For example, cart has been made by A and B feature, when the new feature C has been completed, then we can directly build and upload the same directory location files to overwrite the old file on host, then once the user refresh the page or open the new shopping page, the user will get A, B and C these three features. (Also fronts supports version control, which will mean that the administrator can control the loaded version of the application, and any Canary release, A/B testing, etc. delivery becomes very convenient.)
Regarding dependency sharing, fronts follow exactly the settings of the module federation, for example.
We set up the
cart/site.json
file.We also set the
products/site.json
file.This would mean that they share
react
andreact-dom
.Finally, thank you for the invitation. I'm not a native English speaker. If you don't mind my broken English speaking. We can schedule a meeting via Zoom. I live in China and there should be some time to discuss this topic from 7:00-9:00 PM Beijing time.
Hey Michael. Thanks a lot for taking out so much time and replying in such detail. That previous comment cleared out a lot of doubts.
I just have 1 last question:
How would the following code present in app1 look like in case of a non-monolith app:
Let's say that app1 and app2 are separate Github repositories with example URLs like these:
github.com/kokanek/app1
github.com/kokanek/app2
In that case, how can I use a button exposed from app2 inside of app1 with the help of Fronts.js?
You need to set these config in
app1/site.json
And set these config in
app2/site.json
.Then you need to start both app1 and app2.
This way you can use app2 on app1 and app2 is also running independently.
Awesome! That clears almost all the doubts that I had about this. I forked the fronts-example repo and created a more visually appealing example of the same by creating a dummy ecom site powered by fronts.
Here's the repo if you'd like to check it out. Would love to know your feedback. :)
Nice work!
I never understood why the entry point is always JS no matter where I look. Conceptually, the webpage lifecycle always starts with your browser receiving HTML. Also, HTML modules could include CSS and JS, and we should be able to parse it into an AST just like we parse JS. Then we tree-shake it and it'll be done!
Too bad I only managed to found some pre-alpha or straight up abandoned solutions which achieve this.
Right.
But we should look at things differently. Most modern front-end projects are packaged into a JS bundle or or several JS chunks, while HTML and CSS are injected by JS.
If we really need HTML as an entry point, then that means that we have to fetch the HTML file asynchronously and parse the HTML file so that the CSS/JS/HTML are parsed out, we have to parse it completely, and for the parsed elements to be strictly enforced by the Web's rendering standards, which is not easy, it has a high cost, and otherwise there can easily be some problems otherwise there could easily be some problems.
So what we need to think about is do we really need HTML files as entry points in most cases?
How does communication work between micro-frontends using fronts? If I have a parent frontend that has state management and I want all of my child frontends to pull in some of the parent's state, is there a way to handle this with fronts? First thing that comes to mind is user authentication and storing a user's state in the parent state with the child frontends pulling the user's session in from the parent state.
Yes, you can use
fronts-transport
. For example,globalTransport.listen('increase', (count) => count + 1)
andconst result = await globalTransport.emit('increase', 1)
, and the result should be 2.fronts-transport
can be used in any iframe or non-iframe micro frontend, they can communicate as long as they have a dependency, either indirect or direct.So sorry, the documentation for fronts is still in progress.
No worries; it is a very cool project and I am looking forward to seeing your progress going forward!