A progressive micro frontends framework - Fronts

2021-07-04

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 often become difficult to maintain because of tight coupling. This is why micro frontends have gained attention in front-end architecture.

Dynamic front-end application modules may become an important trend, helping teams improve codebase maintainability and delivery efficiency.

Benefits and Value of Micro Frontends

  • Independence and Autonomy

Only when each part of the application can be developed, deployed, and managed independently can a front-end project achieve real independence. This team autonomy also aligns with Conway’s Law: “Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.” In practice, micro frontends can enable a new organizational model for large front-end teams.

  • Technology Agnostic

Technology-agnostic architecture makes it easier for teams with different technology stacks to collaborate. It also makes gradual migration and technology upgrades easier for older business systems.

  • Runtime Integration

Modern front-end development often relies on build-time integration. Runtime integration can separate modules more independently. Micro frontends fit this model well by keeping modules independently delivered while still allowing dependency sharing.

  • Decoupled Modularity & Composable

Large front-end projects require strong modular decoupling, often split by business domains, technical services, or product capabilities. Composable micro frontend units can preserve consistency while allowing customization across product families, reducing duplicated business code.

In general, a well-designed micro frontend architecture can bring long-term maintainability benefits to large front-end projects.

Motivation

Among many micro frontend solutions, single-spa and Module Federation are two of the most influential.

single-spa is a micro frontend framework based on router configuration. Centralized configuration brings some limitations: nested micro frontends are harder to model, module granularity is harder to control, and module sharing still needs additional solutions.

In 2019, Zack Jackson proposed and implemented Module Federation. Module Federation is different from single-spa: it allows one JavaScript application to dynamically load code from another application. It directly addresses dependency sharing and runtime modularity. As Zack Jackson described, it is a game changer in JavaScript architecture, and it is supported by Webpack, Next.js, and Rollup.

Although Module Federation is powerful, it is not by itself a complete micro frontend framework. Fronts tries to build a more targeted framework on top of this concept.

Hotspots of Micro Frontends Framework

Based on current mainstream micro frontend frameworks and concepts, the following are the main design questions involved.

  • Should the granularity be application-level or module-level?

Module-level integration is more flexible and fine-grained, but application-level integration is still important for compatibility with older front-end projects. A practical framework should support both. If application-level runtime integration is required, Module Federation alone is not enough; we also need a runtime loader for application-level entry points.

  • Should the entry point be HTML or JavaScript?

From a modern engineering perspective, most front-end application entry points are JavaScript-based, while some older projects use HTML as the entry point. Supporting HTML entry points can be useful, but it makes the micro frontend system more complex. This capability is better suited to a standalone sub-package, while the core framework should use JavaScript entry points.

  • Is full module sharing necessary?

Module sharing is a problem that micro frontend frameworks must solve; otherwise, duplicated runtime resources reduce the value of the architecture. Module Federation provides a strong solution by handling shared dependencies at build time and resolving them dynamically at runtime.

  • 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 frontends and support for multiple containers or modes

Large front-end projects often include more than one web application. They may also include Electron applications, browser extensions, native applications, and other containers. A good micro frontend framework should support multiple containers and application types while still being compatible with traditional SPA development. Module Federation has also been used in Next.js SSR scenarios.

  • 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 frontend framework for building web applications. It is based on Webpack Module Federation, while also supporting non-module-federation mode.

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

  • Non-module-federation mode - Although Fronts is based on Module Federation concepts, it also supports non-module-federation mode.
  • Decentralized configuration - Each Fronts app uses site.json for dependency management and nested micro frontend support.
  • Cross-framework support - No framework or technology stack is required.
  • Code splitting & lazy loading - A Fronts app can be split as a module and lazy-loaded by another Fronts app as a dependency.
  • CSS isolation - Optional CSS isolation solution.
  • Lifecycle - Fronts provides a concise lifecycle for Fronts app entries.
  • Web Components & iFrame - Support for multiple frontend containers.
  • Multiple patterns - Supports both micro-frontends apps and non-micro-frontends apps.
  • Monorepo & TypeScript - Provides friendly support for Monorepo and TypeScript workflows.
  • Version control - Supports dynamic delivery patterns such as canary releases.
  • Zero hijacking - Fronts avoids runtime hijacking, preserving native behavior and reducing performance and security risks.
  • Generic communication - Fronts provides concise communication APIs that support most front-end environments.

Benefits of Fronts

Fronts is a concise and easy-to-understand micro frontend framework.

Set site.json to define a micro frontend, similar to a package.json in Node.js.

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"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 some front-end applications do not support Module Federation, they can still run as micro frontends through on-demand runtime modes. As projects are upgraded, they can gradually adopt Module Federation and eventually enable version control. By supporting multiple granularity levels, build types, module types, shared dependency types, runtime types, and communication types, Fronts can cover many micro frontend architectures.

Fronts APIs are clean and simple.

Fronts provides three loaders: useApp(), useWebComponents(), and useIframe(). It also provides a micro frontend launcher, boot(), and a Webpack configuration generator, createWebpackConfig(). These APIs make micro frontend development faster and more consistent.

Example

We will build a micro frontend project based on Fronts, where app1 is the main entry point and depends on app2.

You can follow this article(React without create-react-app Webpack 5) to quickly create app1 and app2 React projects.

Assuming you have completed these steps, let’s get a quick taste of Fronts development.

  • Install fronts-react and fronts-bundler in the projects.
1
2
3
4
5
# with NPM
npm install fronts-react fronts-bundler

# or with Yarn
yarn add fronts-react fronts-bundler
  • 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:

1
2
3
4
5
6
7
{
"name": "app1",
"exports": [],
"dependencies": {
"app2": "http://localhost:3002/remoteEntry.js"
}
}

app2 does not have any dependencies. It acts as a micro frontend, and we define ./src/bootstrap as its micro frontend entry. This entry will be used by app1.

app2/site.json:

1
2
3
4
5
{
"name": "app2",
"exports": ["./src/bootstrap"],
"dependencies": {}
}

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

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

module.exports = createWebpackConfig(originalWebpackConfig);
  • Define the default exported bootstrap function in app2/src/bootstrap.jsx and use boot() to start it.
1
2
3
4
5
6
7
8
9
10
11
12
13
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'));
  • In app1/src/App.jsx, use useApp() to load app2.
1
2
3
4
5
6
7
8
9
10
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 will be rendered as a micro frontend inside 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. For other frameworks, or no framework, use fronts.

  • Built-in Package API

Each built-in package contains three loaders: useApp(), useWebComponents(), and useIframe(). useApp() provides loose CSS isolation, useWebComponents() provides strict CSS isolation, and useIframe() provides native CSS and JavaScript isolation.

  • Version Control

Fronts does not yet provide a full version-control suite and currently only supports a self-hosted registry server.

  • Monorepo & TypeScript

Large front-end projects often involve high complexity, so Fronts is well suited to technology stacks such as Monorepo and TypeScript. You get a strong development experience for type safety, code management, and runtime integration. When each micro frontend is used as a Monorepo sub-package, you can run SPA=true yarn start to switch the micro frontend into traditional SPA development mode.

Conclusion

An architecture based on Fronts, Monorepo, and TypeScript can improve codebase management, type safety, business development, and delivery efficiency. It can also enable multiple combinations of product capabilities, higher business-code reuse, stronger consistency, and more diverse application types.

Every large front-end project has its own micro frontend requirements. Teams should analyze their own constraints and choose or build an architecture that solves their main engineering problems.

With a general module concept based on Module Federation, Fronts tries to solve micro frontend problems in a targeted and systematic way: cross-framework integration, dependency sharing, dependency management, version control, and compatibility with multiple runtime containers and patterns.

Fronts will continue evolving from real micro frontend architecture requirements into a more efficient framework.

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