[关闭]
@levinzhang 2022-09-26T14:32:48.000000Z 字数 5796 阅读 227

基于Angular的微前端理念与实践

摘要

微前端是一个越来越流行的概念,它通过将大型应用拆分为更多较小的应用,提升了应用开发和部署的效率,但这也会带来一定的复杂性,本文介绍了微前端的基本概念和适用场景,并通过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

single-spa是一个用于前端微服务的JavaScript框架,可以用最流行的三个框架/库来实现,即Angular、React和Vue.js。它可以根据需要懒加载应用,请查阅他们的网站以了解更多信息。

frint.js

frint.js是一个模块化的JavaScript框架,用于构建可扩展和反应式的应用。目前,它不支持Angular,但支持React。如果你要从头开始构建一个反应式应用,而且刚刚开始的话,这个框架会特别适合你。请参阅他们的网站以了解更多信息。

使用Angular的微前端项目实例

有了这些基础知识之后,我们在single-spa框架的协助下构建一个Angular项目的样例,我希望构建一个简单的应用以便于演示。

我们将按下图所示,把这个应用分成多个组成部分。我们一共要实现4个应用,分别是HeaderApp、DashboardApp、FooterApp和根应用。

如下是四个应用的代码仓库,你可以在自己的机器上分别克隆并运行它们。

  1. // root app runs on port 4200
  2. git clone https://github.com/ahmedbhl/micro-root.git
  3. npm install
  4. npm start
  5. // micro header runs on port 4300
  6. git clone https://github.com/ahmedbhl/micro-header.git
  7. npm install
  8. npm start
  9. // micro dashboard runs on port 4202
  10. git clone https://github.com/ahmedbhl/micro-dashboard.git
  11. npm install
  12. npm start
  13. // micro footer runs on port 4201
  14. git clone https://github.com/ahmedbhl/micro-footer.git
  15. npm install
  16. npm start

然后,可以在http://localhost:4200/ 上访问整个应用程序

如下是根应用的index HTML文件。我们在第10行导入了这三个应用,并以适当的名称和位置注册了这些应用。由于我们在页面加载时加载了所有的应用程序,所以没有定义任何特定的上下文路径。

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <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';">
  5. <meta charset="utf-8">
  6. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  7. <title>Your application</title>
  8. <meta name="viewport" content="width=device-width, initial-scale=1">
  9. <meta name="importmap-type" content="systemjs-importmap">
  10. <script type="systemjs-importmap">
  11. {
  12. "imports": {
  13. "footer": "http://localhost:4201/main.js",
  14. "dashboard": "http://localhost:4202/main.js",
  15. "header": "http://localhost:4300/main.js",
  16. "single-spa": "https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.5/system/single-spa.min.js"
  17. }
  18. }
  19. </script>
  20. <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.5/system/single-spa.min.js" as="script" crossorigin="anonymous" />
  21. <script src='https://unpkg.com/core-js-bundle@3.1.4/minified.js'></script>
  22. <script src="https://unpkg.com/zone.js"></script>
  23. <script src="https://unpkg.com/import-map-overrides@1.6.0/dist/import-map-overrides.js"></script>
  24. <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/system.min.js"></script>
  25. <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/amd.min.js"></script>
  26. <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/named-exports.js"></script>
  27. <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/4.0.0/extras/named-register.min.js"></script>
  28. <style>
  29. </style>
  30. </head>
  31. <body>
  32. <script>
  33. System.import('single-spa').then(function (singleSpa) {
  34. singleSpa.registerApplication(
  35. 'header',
  36. function () {
  37. return System.import('header');
  38. },
  39. function (location) {
  40. return true;
  41. }
  42. )
  43. singleSpa.registerApplication(
  44. 'dashboard',
  45. function () {
  46. return System.import('dashboard');
  47. },
  48. function (location) {
  49. // return location.pathname.startsWith('/app2');
  50. return true;
  51. }
  52. )
  53. singleSpa.registerApplication(
  54. 'footer',
  55. function () {
  56. return System.import('footer');
  57. },
  58. function (location) {
  59. // return location.pathname.startsWith('/app1');
  60. return true;
  61. }
  62. );
  63. singleSpa.start();
  64. })
  65. </script>
  66. <import-map-overrides-full></import-map-overrides-full>
  67. </body>
  68. </html>

我们可以设置“/header”的位置路径,这样当浏览器的URL导航到“/header”时就会加载header。我们来测试一下。

  1. <script>
  2. System.import('single-spa').then(function (singleSpa) {
  3. singleSpa.registerApplication(
  4. 'header',
  5. function () {
  6. return System.import('header');
  7. },
  8. function (location) {
  9. return location.pathname.startsWith('/header');
  10. // return true;
  11. }
  12. )

总结

我知道微前端是一个很时尚的东西,但你不应该在每个应用中都使用它。如果你的应用程序很小,就没有必要这样做,不要把事情复杂化。这种方式的目的是让我们的整个过程更加顺畅,而不是增加复杂性。所以在使用该方式之前,先要进行必要的判断。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注