现代Web应用正在变得越来越庞大和复杂,有时候这样的应用会由不同的团队来管理。应用可能会包含不同团队开发的特性,在交付整个应用之前,我们可能希望只将某些特定的功能发布到生产环境中。如果整个应用只有一个仓库(repo),那我们该如何管理不同的团队和不同的发布周期呢?
这些复杂的应用大多位于客户端,使其更加难以维护。这种单体式的臃肿应用还有一些其他的问题。在本文中,我将会讨论微前端的优势、劣势、实现方式以及其他的内容。
简介
微前端是一些小型的应用,大多会根据子域或功能进行划分,它们互相协作来交付一个更大的应用。在深入介绍微前端的实现之前,我们将会阐述什么是微前端以及为什么要使用它。
通常,项目都有不同的规模和不同的需求。如果你的项目非常简单,只有两三个页面,那么根本没有必要考虑微前端。你可以直接使用自己选择的任意框架来实现,比如Angular、React 或 Vuejs。
但是,事实并非总是如此。有时候,你的前端应用是另一个大型应用的一小部分,或者你的应用有很多的区域和特性组成,它们由不同的团队进行开发,又或者你的应用要按特性依次发布到生产环境中。如果你正在面临这样的场景,那么就需要考虑一下微前端了。我们看一下这张图片。
如上图所示,我们有 6 个前端应用互相协作来交付一个更大的应用。这些应用之间的通信可以借助事件总线、window 对象或发布/订阅方法来实现。每个应用都可以由不同的团队和任意框架实现。每个应用都可以独立地与其后端或端点进行交互。这里有一个 bootstrap/launch 应用,它会负责加载所有其他的应用,并根据用户的交互或路由在 DOM 中挂载或卸载它们。
这种微前端架构主要有如下的优势。
应用会很小:显然,当大的应用按照区域、页面或特性进行拆分后,每个应用都会变得很小。
应用是独立的:由于所有的应用都是单独拆分和开发的,所以它们是相互独立的。
应用更易于理解:因为每个应用更小,由单一团队进行开发,所以更易于理解。
应用更易于开发和部署:由于这些应用本身都很小,都由单一的团队进行开发,所以很易于开发和部署。我们甚至可以独立部署它们。
应用更易于测试:我们必须为大型的应用编写成千上万的单元测试,并且需要一直运行。这会拖慢我们的部署过程。在实现微前端之后,每个应用都有数量更少的单元测试,并且可以独立运行自己的单元测试。
应用的开发会更迅速:因为应用都有独立的团队,所以整个开发会更迅速、更容易。
CI/CD 会更简单:每个应用都可以单独集成和部署,这使得 CI/CD 过程会变得更加容易。当我们修复某个应用或者引入新的特性时,不用考虑整个应用的情况,因为所有的特性都是独立的。
独立的技术栈和版本:我们可以为每个应用选择自己的技术栈,只不过这种情况不太多见。但是,我们可以使用相同技术栈的不同版本。例如,有些团队可能有足够的灵活性和时间来引入和测试同一技术栈的较新版本。
没有共享的代码:在大型的应用中,我们倾向于跨特性共享代码,但是,这并不能很好地进行扩展,而且随着应用越来越大,会引入很多缺陷和相互依赖。微前端中则没有这样的问题,因为我们不会共享代码,除非它是一个哑(dumb)组件。
能够很容易地在不影响旧有架构的情况下变更架构:有时候,我们必须要扩展旧的架构,但是可能没有足够的开发人员来实现或扩展架构。借助微前端的方式,我们可以使用最新的技术栈开发新特性,并独立进行交付。
微前端的特点
每个前端应用代表整个应用的一个特定功能或子域。
每个前端应用都可以由一个独立的团队来实现。
每个前端应用可以采用不同的技术来实现。
它们之间不能共享逻辑,而且相互独立。
每个前端应用都由一个团队来负责。
如何拆分应用
我们看一下如何将大型应用拆分为微前端。在这方面,没有拆分应用的具体标准,我们可以根据自己的需要以多种方式进行拆分。我们会看到各种拆分应用的方式。
按照特性
这是最常见的方法,因为我们可以很容易地划分应用的功能。例如,如果应用有三个特性,分别是 Dashboard、Profile 和 Views,我们可以将每个特性作为一个单独的应用,并在 Launch.js 的辅助下在 DOM 中挂载和卸载它们。这个 Launch.js 可以是一个独立的应用,也可以只是一个简单的 JavaScript 应用。
按照区域
在有些应用中,每个区域都有很多功能,例如,在 coinbase、Gmail 中。在这种情况下,我们可以将每个区域作为一个新的应用来实现。
按页面
有些应用的功能是按页面划分的。每个页面都有一些独立的功能。我们可以通过页面来划分应用。在下图中,我们有四个页面,可以分别创建四个应用。
按照域
基于域来拆分应用也是最常见的方式之一。
微前端的不同实现方式
我们有很多实现微前端的方式,我发现最常用的是如下 6 种:
Iframes
借助 NGINX
Web Component/Angular 元素
Angular 库
Monorepos
定制化的编排器
微前端框架
微前端出现至少已经有两年了,但它依然是一个新兴领域。你可能会问有没有相关的框架或库帮助我们实现这种架构,从而减轻我们工作。答案是肯定的,目前已经有一些相关的库或框架了。
single-spa
single-spa 是一个用于前端微服务的 JavaScript 框架,可以用最流行的三个框架/库来实现,即 Angular、React 和 Vue.js。它可以根据需要懒加载应用,请查阅他们的网站以了解更多信息。
frint.js
frint.js 是一个模块化的 JavaScript 框架,用于构建可扩展和反应式的应用。目前,它不支持 Angular,但支持 React。如果你要从头开始构建一个反应式应用,而且刚刚开始的话,这个框架会特别适合你。请参阅他们的网站以了解更多信息。
使用 Angular 的微前端项目实例
有了这些基础知识之后,我们在single-spa框架的协助下构建一个 Angular 项目的样例,我希望构建一个简单的应用以便于演示。
我们将按下图所示,把这个应用分成多个组成部分。我们一共要实现 4 个应用,分别是 HeaderApp、DashboardApp、FooterApp 和根应用。
如下是四个应用的代码仓库,你可以在自己的机器上分别克隆并运行它们。
// root app runs on port 4200
git clone https://github.com/ahmedbhl/micro-root.git
npm install
npm start
// micro header runs on port 4300
git clone https://github.com/ahmedbhl/micro-header.git
npm install
npm start
// micro dashboard runs on port 4202
git clone https://github.com/ahmedbhl/micro-dashboard.git
npm install
npm start
// micro footer runs on port 4201
git clone https://github.com/ahmedbhl/micro-footer.git
npm install
npm start
复制代码
然后,可以在 http://localhost:4200/ 上访问整个应用程序
如下是根应用的 index HTML 文件。我们在第 10 行导入了这三个应用,并以适当的名称和位置注册了这些应用。由于我们在页面加载时加载了所有的应用程序,所以没有定义任何特定的上下文路径。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src * data: blob: 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Your application</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="importmap-type" content="systemjs-importmap">
<script type="systemjs-importmap">
{
"imports": {
"footer": "http://localhost:4201/main.js",
"dashboard": "http://localhost:4202/main.js",
"header": "http://localhost:4300/main.js",
"single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.5/system/single-spa.min.js"
}
}
</script>
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.5/system/single-spa.min.js" as="script" crossorigin="anonymous" />
<script src='https://unpkg.com/core-js-bundle@3.1.4/minified.js'></script>
<script src="https://unpkg.com/zone.js"></script>
<script src="https://unpkg.com/import-map-overrides@1.6.0/dist/import-map-overrides.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/system.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/amd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/named-exports.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/named-register.min.js"></script>
<style>
</style>
</head>
<body>
<script>
System.import('single-spa').then(function (singleSpa) {
singleSpa.registerApplication(
'header',
function () {
return System.import('header');
},
function (location) {
return true;
}
)
singleSpa.registerApplication(
'dashboard',
function () {
return System.import('dashboard');
},
function (location) {
// return location.pathname.startsWith('/app2');
return true;
}
)
singleSpa.registerApplication(
'footer',
function () {
return System.import('footer');
},
function (location) {
// return location.pathname.startsWith('/app1');
return true;
}
);
singleSpa.start();
})
</script>
<import-map-overrides-full></import-map-overrides-full>
</body>
</html>
复制代码
我们可以设置“/header”的位置路径,这样当浏览器的 URL 导航到“/header”时就会加载 header。我们来测试一下。
<script>
System.import('single-spa').then(function (singleSpa) {
singleSpa.registerApplication(
'header',
function () {
return System.import('header');
},
function (location) {
return location.pathname.startsWith('/header');
// return true;
}
)
复制代码
总结
我知道微前端是一个很时尚的东西,但你不应该在每个应用中都使用它。如果你的应用程序很小,就没有必要这样做,不要把事情复杂化。这种方式的目的是让我们的整个过程更加顺畅,而不是增加复杂性。所以在使用该方式之前,先要进行必要的判断。
原文链接:
https://blog.devgenius.io/angular-micro-frontend-4dad619c4277
相关阅读:
微前端如何改变 Angular 的未来?
Angular 13 发布:全面弃用 View Engine
Angular、React 和 Vue 三大框架,Web 开发该如何选择?
评论