渐进式微前端框架 - Fronts
微前端
一种架构风格,其中可独立交付的前端应用程序组成一个更大的整体。
随着前端开发变得越来越复杂,传统的大型前端项目可能会因为过度耦合而变得难以维护,因此微前端在前端架构中也越来越受到关注。
前端应用模块动态化将成为前端开发的新趋势之一,将能够更彻底地解决代码库可维护性和交付效率的问题。
微前端的优势和价值
- 独立和自治
只有当应用程序开发的整体流程能够独立于代码库等进行开发、部署和管理时,前端项目才能拥有真正的独立性。而这种团队自主的可能性也符合康威定律,即“任何一个设计系统(广义定义)的组织,都会产生一个其结构是该组织沟通结构副本的设计”,从而带来一种可能的组织管理新形态。
- 技术不可知论者
技术无关性方便了不同技术栈的多个团队协同工作,技术栈的平滑迁移也为老业务系统的持续迭代和技术升级带来了更大的便利。
- 运行时集成
在现代前端开发过程中,我们最常见的是构建时集成。而之前,运行时集成恰好能够更独立地分离模块。微前端也恰好能够与这种微模块概念很好地集成,并保持这些模块的独立性和依赖共享。
- 解耦模块化和可组合性
在大型前端项目中,我们对模块化解耦的要求很高,通常会根据不同的类型进行划分,例如业务型模块化、技术服务型模块化等等。而各个微前端颗粒的可组合性,则能够跨多个交付系列实现良好的模块一致性和整体定制化差异化,并能大幅减少业务重复。
总的来说,微前端架构的正确实践将对一些大型前端项目的长期维护带来深远的价值。
动机
在众多的微前端方案中,single-spa和Module Federation是其中的佼佼者。
single-spa 是一个基于路由配置的微前端框架。配置的集中化带来了一些限制,例如难以对可嵌套的微前端进行粒度划分、模块粒度控制、模块共享等。
2019 年,Zack Jackson 提出并实现了模块联合 (Module Federation)。模块联合是一个与 Single-spa 完全不同的概念,它允许 JavaScript 应用程序动态加载来自其他应用程序的代码。它彻底解决了代码依赖共享和运行时模块化的问题。正如 Zack Jackson 的文章所述,这个理念是 JavaScript 架构的颠覆者。目前,Webpack、Next.js 和 Rollup 都支持它。
虽然 Module Federation 的概念已经非常惊艳,但是它还没有进一步提供更加完善、更加有针对性的微前端框架实现,而这正是 Fronts 正在尝试做的事情。
微前端框架热点
结合目前主流的微前端框架或概念,以下整理了其中涉及的主要热点。
- 粒度级别应该是应用程序级别还是模块级别
模块级别显然在灵活性和粒度方面更具优势,但为了兼容一些不那么现代的前端项目,支持应用级别显然更有优势,因此我们需要一个同时支持两者的框架。如果需要应用级别的运行时集成,显然仅仅使用 Module Federation 的 Webpack 是不够的,我们还需要一个运行时应用级别的粒度入口点加载器。
- 入口点文件是 HTML 还是 JavaScript
从现代工程的角度来看,大多数前端应用程序的入口点都是基于 JS 的,之前的一些前端项目甚至只使用 HTML 作为入口点。然而,构建一个以 HTML 为主要入口点的应用程序微前端系统势必会耗时更长、更复杂。这样的库更适合作为独立的子包,而整体框架则应该以 JS 文件作为入口点。
- 是否需要支持完善的模块共享
模块共享是微前端框架必须解决的问题,否则运行时资源的重复将降低微前端的价值。目前,只有支持 Module Federation 的 Webpack 能够在构建时处理此类模块共享,并在运行时动态共享依赖项。目前还没有出现比 Module Federation 更好的解决方案。
- CSS/JS 隔离权衡
CSS 隔离几乎是必需的,并且许多微前端框架都支持。我们可能需要进行各种劫持来确保安全性、性能和稳定性,同时还要考虑不同浏览器的兼容性。然而,JS 隔离的实现成本相对较高,现代前端工程化是否需要这种隔离,取决于每个微前端的实际情况。
- 通用微前端,支持多种模式的多个容器(或 SSR 等)
在大型前端项目中,通常不仅仅构建单个 Web 应用,还可能构建多个 Web 应用,甚至更多类型的前端应用,例如 Electron 应用、浏览器扩展、原生应用等。因此,一个优秀的微前端框架应该能够运行更多种类的容器并构建各种类型的应用,同时最好能够兼容构建传统的 SPA 和微前端应用。Module Federation 也初步实现了 Next.js 的 SSR 支持。
- 版本控制和依赖管理
随着快速迭代和业务的增长,各种模块的管理变得非常重要,因此当大型前端项目后期实践微前端架构时,版本控制和依赖管理将变得尤为重要,这将决定交付效率和可维护性。
为了解决这些问题,Fronts 应运而生。
什么是 Fronts
Fronts 是一个用于构建 Web 应用程序的渐进式微前端框架,它基于 Webpack 的模块联合。
仓库:https://github.com/unadlib/fronts
- 非模块联合- 尽管 Fronts 基于模块联合的概念,但它也支持
non-module-federation
模式。 - 分散配置-
site.json
在每个 Fronts 应用程序中配置依赖管理,支持嵌套微前端。 - 跨框架——不受任何框架或技术堆栈的限制。
- 代码拆分和延迟加载- 支持在 Fronts 应用程序内将代码拆分为模块,它可以作为依赖模块由其他 Fronts 应用程序延迟加载。
- CSS 隔离- 可选的 CSS 隔离解决方案。
- 生命周期- Fronts 为 Fronts 应用程序入口提供了简洁的生命周期。
- Web 组件和 iFrame - 支持多个前端容器。
- 多种模式——支持构建
micro-frontends
应用程序和non-micro-frontends
应用程序。 - Monorepo 和 TypeScript - 友好支持 Monorepo 和 TypeScript,它们是相互适用的技术栈。
- 版本控制——用于高效、动态的交付应用程序,例如金丝雀发布。
- 零劫持——Fronts 没有进行任何劫持,保持了原创性,但可能造成性能和安全性的损失。
- 通用通信- Fronts 提供简洁、通用的通信 API,支持几乎所有前端环境。
锋面的好处
Fronts 是一个简洁易懂的微前端框架。
设置site.json
以定义一个微前端,类似于package.json
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 支持多种粒度级别、构建类型、模块类型、共享类型、运行时类型和通信类型,几乎可以满足所有类型的微前端架构。
Fronts API 干净且简单。
Fronts 提供了三套加载器useApp()
、useWebComponents()
和useIframe()
。它还提供了一个微前端启动器boot()
和一个 Webpack 配置生成器createWebpackConfig()
。借助这些 API,你将能够快速高效地进行微前端开发。
例子
我们将基于 Fronts 构建一个微前端项目,其中app1
是主要入口点,它将依赖于app2
。
你可以按照这篇文章(不使用 create-react-app 的 React Webpack 5)来快速创建
app1
Reactapp2
项目。
假设您已完成这些步骤,让我们开始快速体验 Fronts 精彩的微前端开发。
fronts-react
在fronts-bundler
项目中安装。
# with NPM
npm install fronts-react fronts-bundler
# or with Yarn
yarn add fronts-react fronts-bundler
site.json
在webpack.config.js
项目中设置
我们将其定义app1
为父微前端,它依赖于app2
。
app1/site.json
:
{
"name": "app1",
"exports": [],
"dependencies": {
"app2": "http://localhost:3002/remoteEntry.js"
}
}
app2
没有任何依赖项,它充当微前端,我们将其定义为导出./src/bootstrap
为微前端条目,该条目app2
将由使用app1
。
app2/site.json
:
{
"name": "app2",
"exports": ["./src/bootstrap"],
"dependencies": {}
}
在项目中createWebpackConfig()
包装Webpack 配置。config/webpack.config.js
const { createWebpackConfig } = require('fronts-bundler');
module.exports = createWebpackConfig(originalWebpackConfig);
- 定义默认导出的引导函数
app2/src/bootstrap.jsx
并使用它boot()
来启动它。
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'));
- 加载
app1/src/App.jsx
以useApp()
导入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 />;
};
运行yarn start
,并app2
作为微前端呈现app1
。
示例代码:https://github.com/unadlib/fronts-example
笔记
- 内置包
目前主流的前端框架还是 React、Vue 和 Angular,微前端使用其中之一时,建议使用 Fronts 内置的包,例如fronts-react
、fronts-vue
和fronts-ng
,如果遇到内置包不支持或者没有框架的其他框架,则请使用fronts
。
- 内置包 API
每个内置包包含三套加载器useApp()
,,,useWebComponents()
。提供松散CSS隔离,提供严格CSS隔离,提供原生useIframe()
严格CSS隔离和JS隔离。useApp()
useWebComponents()
useIframe()
- 版本控制
Fronts没有完整的版本控制套件支持,目前仅支持自建注册中心服务器。
- Monorepo 和 TypeScript
大型前端项目通常意味着高度复杂,因此 Fronts 非常适合与 Monorepo 和 TypeScript 等技术栈结合使用。您将获得出色的类型安全、代码管理和运行时集成方面的开发体验。当每个微前端都作为 Monorepo 子包使用时,您只需运行SPA=true yarn start
并将微前端开发模式切换到传统的 SPA 开发模式即可。
结论
基于Fronts、Monorepo、TypeScript的前端架构将显著提升代码库管理、类型安全、业务开发和交付效率,并实现产品业务能力的多种组合、业务代码的高度复用和一致性以及应用类型的多样性。
每个尝试实现微前端架构的大型前端项目都有不同或相似的要求,因此通过分析自己大型前端项目的需求和需要,并以此来构建或选择自己的微前端架构,才能真正解决自己主要的工程问题。
Fronts 提出了基于模块联合的通用模块概念,试图更有针对性、系统性地解决微前端的主要问题,如跨框架、依赖共享、依赖管理、版本控制、兼容多种运行时容器和模式等。
Fronts 希望从更多的微前端架构要求发展成为一个高效的微前端框架。
仓库:https://github.com/unadlib/fronts
文章来源:https://dev.to/unadlib/a-progressive-micro-frontends-framework-fronts-3kn1