@levinzhang
2022-09-26T22:32:48.000000Z
字数 5796
阅读 442
微前端是一个越来越流行的概念,它通过将大型应用拆分为更多较小的应用,提升了应用开发和部署的效率,但这也会带来一定的复杂性,本文介绍了微前端的基本概念和适用场景,并通过Angular实现了一个简单的样例。
本文最初发表于作者的博客网站,将作者Ahmed Bouhlel许可由InfoQ中文站翻译分享。
现代Web应用正在变得越来越庞大和复杂,有时候这样的应用会由不同的团队来管理。应用可能会包含不同团队开发的特性,在交付整个应用之前,我们可能希望只将某些特定的功能发布到生产环境中。如果整个应用只有一个仓库(repo),那我们该如何管理不同的团队和不同的发布周期呢?
这些复杂的应用大多位于客户端,使其更加难以维护。这种单体式的臃肿应用还有一些其他的问题。在本文中,我将会讨论微前端的优势、劣势、实现方式以及其他的内容。
微前端是一些小型的应用,大多会根据子域或功能进行划分,它们互相协作来交付一个更大的应用。在深入介绍微前端的实现之前,我们将会阐述什么是微前端以及为什么要使用它。
通常,项目都有不同的规模和不同的需求。如果你的项目非常简单,只有两三个页面,那么根本没有必要考虑微前端。你可以直接使用自己选择的任意框架来实现,比如Angular、React或Vuejs。
但是,事实并非总是如此。有时候,你的前端应用是另一个大型应用的一小部分,或者你的应用有很多的区域和特性组成,它们由不同的团队进行开发,又或者你的应用要按特性依次发布到生产环境中。如果你正在面临这样的场景,那么就需要考虑一下微前端了。我们看一下这张图片。
如上图所示,我们有6个前端应用互相协作来交付一个更大的应用。这些应用之间的通信可以借助事件总线、window对象或发布/订阅方法来实现。每个应用都可以由不同的团队和任意框架实现。每个应用都可以独立地与其后端或端点进行交互。这里有一个bootstrap/launch应用,它会负责加载所有其他的应用,并根据用户的交互或路由在DOM中挂载或卸载它们。
这种微前端架构主要有如下的优势:
我们看一下如何将大型应用拆分为微前端。在这方面,没有拆分应用的具体标准,我们可以根据自己的需要以多种方式进行拆分。我们会看到各种拆分应用的方式。
这是最常见的方法,因为我们可以很容易地划分应用的功能。例如,如果应用有三个特性,分别是Dashboard、Profile和Views,我们可以将每个特性作为一个单独的应用,并在Launch.js的辅助下在DOM中挂载和卸载它们。这个Launch.js可以是一个独立的应用,也可以只是一个简单的JavaScript应用。
在有些应用中,每个区域都有很多功能,例如,在coinbase、Gmail中。在这种情况下,我们可以将每个区域作为一个新的应用来实现。
有些应用的功能是按页面划分的。每个页面都有一些独立的功能。我们可以通过页面来划分应用。在下图中,我们有四个页面,可以分别创建四个应用。
基于域来拆分应用也是最常见的方式之一。
我们有很多实现微前端的方式,我发现最常用的是如下6种:
微前端出现至少已经有两年了,但它依然是一个新兴领域。你可能会问有没有相关的框架或库帮助我们实现这种架构,从而减轻我们工作。答案是肯定的,目前已经有一些相关的库或框架了。
single-spa是一个用于前端微服务的JavaScript框架,可以用最流行的三个框架/库来实现,即Angular、React和Vue.js。它可以根据需要懒加载应用,请查阅他们的网站以了解更多信息。
frint.js是一个模块化的JavaScript框架,用于构建可扩展和反应式的应用。目前,它不支持Angular,但支持React。如果你要从头开始构建一个反应式应用,而且刚刚开始的话,这个框架会特别适合你。请参阅他们的网站以了解更多信息。
有了这些基础知识之后,我们在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;
}
)
我知道微前端是一个很时尚的东西,但你不应该在每个应用中都使用它。如果你的应用程序很小,就没有必要这样做,不要把事情复杂化。这种方式的目的是让我们的整个过程更加顺畅,而不是增加复杂性。所以在使用该方式之前,先要进行必要的判断。