DEV Community

Michael Lin
Michael Lin

Posted on

A progressive micro frontends framework - Fronts

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 and non-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 }
  }
}
Enter fullscreen mode Exit fullscreen mode

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 and app2 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 and fronts-bundler in the projects.
# with NPM
npm install fronts-react fronts-bundler

# or with Yarn
yarn add fronts-react fronts-bundler
Enter fullscreen mode Exit fullscreen mode
  • Set up site.json and webpack.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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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": {}
}
Enter fullscreen mode Exit fullscreen mode

Wrap the Webpack config with createWebpackConfig() in config/webpack.config.js in the projects.

const { createWebpackConfig } = require('fronts-bundler');

module.exports = createWebpackConfig(originalWebpackConfig);
Enter fullscreen mode Exit fullscreen mode
  • Define the default exported bootstrap function in app2/src/bootstrap.jsx and use boot() 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'));
Enter fullscreen mode Exit fullscreen mode
  • Load app1/src/App.jsx with useApp() to import app2.
import React from 'react';
import { useApp } from 'fronts-react';

export const App = () => {
  const App2 = useApp({
    name: 'app2',
    loader: () => import('app2/src/bootstrap'),
  });
  return <App2 />;
};
Enter fullscreen mode Exit fullscreen mode

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.

Repo: https://github.com/unadlib/fronts

Top comments (12)

Collapse
 
kokaneka profile image
kapeel kokane • Edited

Hey Michael! Nice work. I am experimenting with a hobby project and have a few queries around that.

  • For importing, we are using a syntax like
import('app2/src/bootstrap')
Enter fullscreen mode Exit fullscreen mode

so does it mean that all the smaller parts of the micro-frontend need to reside in the same folder structure?

  • What is the deployment process like, if I want to deploy the two projects separately?
  • Also, I am pretty new to micro-frontends so can you please explain what is the core difference between this approach and another one where two teams build a React application by maintaining boundaries defined by folder structure? For instance, team 1 works on src/cart and team 2 works on src/products.
Collapse
 
unadlib profile image
Michael Lin

We need more complete example, for example, we have a shopping application, then it depends on cart and also depends on products, so we can have two teams to develop cart and products respectively. these two teams develop and deploy completely independently.

For the more complete application shell of shopping, we do something like import('cart/src/bootstrap') and import('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() or useIframe().

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'; and import Products from 'Products/src/App';.

fronts are highly recommended for reuse as components when using the same UI framework.

Collapse
 
kokaneka profile image
kapeel kokane

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.

Thread Thread
 
unadlib profile image
Michael Lin

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.

{
  "name": "cart",
  "exports": ["./src/App"],
  "dependencies": {},
  "shared": {
    "react": { "singleton": true },
    "react-dom": { "singleton": true }
  }
}
Enter fullscreen mode Exit fullscreen mode

We also set the products/site.json file.

{
  "name": "products",
  "exports": ["./src/App"],
  "dependencies": {},
  "shared": {
    "react": { "singleton": true },
    "react-dom": { "singleton": true }
  }
}
Enter fullscreen mode Exit fullscreen mode

This would mean that they share react and react-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.

Thread Thread
 
kokaneka profile image
kapeel kokane

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:

import React from 'react';
import { useApp } from 'fronts-react';

export const App = () => {
  const App2 = useApp({
    name: 'app2',
    loader: () => import('app2/src/bootstrap'),
  });
  return <App2 />;
};
Enter fullscreen mode Exit fullscreen mode

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?

Thread Thread
 
unadlib profile image
Michael Lin

You need to set these config in app1/site.json

{
  "name": "app1",
  "dependencies": {
    "app2": "http://localhost:3002/remoteEntry.js".
  },
  "shared": {
    "react": { "singleton": true },
    "react-dom": { "singleton": true }
  }
}
Enter fullscreen mode Exit fullscreen mode

And set these config in app2/site.json.

{
  "name": "app2",
  "exports": ["./src/bootstrap"],
  "shared": {
    "react": { "singleton": true },
    "react-dom": { "singleton": true }
  }
}
Enter fullscreen mode Exit fullscreen mode

Then you need to start both app1 and app2.

This way you can use app2 on app1 and app2 is also running independently.

Thread Thread
 
kokaneka profile image
kapeel kokane • Edited

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. :)

Collapse
 
mvoloskov profile image
Miloslav 🏳️‍🌈 🦋 Voloskov

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.

Collapse
 
unadlib profile image
Michael Lin

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?

Collapse
 
kleveland profile image
Kacey Cleveland

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.

Collapse
 
unadlib profile image
Michael Lin

Yes, you can use fronts-transport. For example, globalTransport.listen('increase', (count) => count + 1) and const 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.

Collapse
 
kleveland profile image
Kacey Cleveland

No worries; it is a very cool project and I am looking forward to seeing your progress going forward!