微前端(Micro Front-end)

微前端(Micro Front-end)

这篇文章其实已经准备了11个月了,因为虽然我们年初就开始使用 Angular 的微前端架构,但是产品一直没有正式发布,无法通过生产环境实践验证可行性,11月16日我们的产品正式灰度发布,所以是时候分享一下我们在使用 Angular 微前端这条路上的心得(踩过的坑)了额,希望和 Angular 社区一起成长一起进步,如果你对微前端有一定的了解并且已经在项目中尝试了可以忽略前面的章节。

什么是微前端

微前端这个词这两年很频繁的出现在大家的视野中,最早提出这个概念的应该是在 ThoughtWork 的技术雷达,主要是把微服务的概念引入到了前端,让前端的多个模块或者应用解耦,做到让前端的子模块独立仓储,独立运行,独立部署。

那么微前端和微服务到底有什么区别呢?

下面这张图是微服务的示意图,微服务主要是业务模块按照一定的规则拆分,独立开发,独立部署,部署后通过 Nginx 做路由转发,微服务的难点是需要考虑多个模块之间如何调用的问题,以及鉴权,日志,甚至加入网关层

对于微服务来说,模块分开解藕基本就完事了,但是微前端不一样,前端应用在运行时却是一个整体,需要聚合,甚至还需要交互,通信。

为什么需要微前端(Micro Front-end)

  1. 系统模块增多,单体应用变得臃肿,开发效率低下,构建速度变慢;
  2. 人员扩大,需要多个前端团队独立开发,独立部署,如果都在一个仓储中开发会带来一些列问题;
  3. 解决遗留系统,新模块需要使用最新的框架和技术,旧系统还继续使用。

微前端的几种方案对比

方式 描述 优点 缺点 难度系数
路由转发 路由转发严格意义上不属于微前端,多个子模块之间共享一个导航即可 简单,易实现 体验不好,切换应用整个页面刷新 ??
嵌套 iframe 每个子应用一个 iframe 嵌套 应用之间自带沙箱隔离 重复加载脚本和样式 ????
构建时组合 独立仓储,独立开发,构建时整体打包,合并应用 方便依赖管理,抽取公共模块 无法独立部署,技术栈,依赖版本必须统一 ????
运行时组合 每个子应用独立构建,运行时由主应用负责应用管理,加载,启动,卸载,通信机制 良好的体验,真正的独立开发,独立部署 复杂,需要设计加载,通信机制,无法做到彻底隔离,需要解决依赖冲突,样式冲突问题 ??????
Web Components 每个子应用需要使用 Web Components 技术编写组件或者使用框架生成 面向未来 不成熟,需要踩坑 ??????

上述只是简单列举了几种实现方式的对比,当然这些方案也不是互斥的,选择哪种方案取决你的业务场景是什么,以下几个前提条件对于技术选型至关重要:

  • 是否为 SPA 单体应用?
  • 技术栈是否统一,需要支持跨框架调用吗?
  • 是否需要应用间彻底隔离?

我们是做企业级 SaaS 平台的,肯定是 SPA 单体应用,技术栈都是 Angular,应用之间不需要彻底隔离,反而需要共享通用样式和组件,避免重复加载。

所以选择的是: 运行时组合  方案。

Worktile 的微前端技术选型之路

目前市面上的微前端解决方案并不多,关注度和成熟度最高的应该就是 single-spa

国内也有很多团队都有自己的微前端框架,比如开源了的基于 single-spa 的 qiankun - 可能是你见过最完善的微前端解决方案 , 还有 phodal 的 mooa 以及无数内部的解决方案(最近阿里飞冰也开源 了面向大型工作台的微前端解决方案 icestark,只支持 React 和 Vue)

我们在做技术选型的时候首要考虑的就是  single-spa  和  mooa ,  single-spa  成熟度应该最高,示例文档很完善, mooa  为 Angular 打造的主从结构的微前端框架,和我们的业务和技术符合度最高,研究一段时间后最终我们还是选择了自研一套符合自己的微前端库(因为比较简单,不敢称之为框架),主要是因为我们的业务有以下几个需求在以上的框架中不满足或者说很难满足, 甚至需要高度定制。

  • 产品是主从结构的,Portal 包含左侧导航,消息通知以及子应用管理
  • 需要在多个子应用之间通信,主应用或者某个子应用需要打开其他子应用的详情页或者路由跳转
  • 子应用A的某个页面中可能会加载子应用B的某个组件
  • 基于以上2个特性,所以需要提供并存模式,即当前显示的虽然是 B 应用,但是要保证 A 应用正常可以调用,如果销毁了就无法被其他应用调用
  • 需要提供预加载功能
  • 子应用的样式也需要独立加载
  • 路由,不管是在主应用还是子应用,路由体验要和单体应用一致

我运行了  single-spa  和  mooa  的示例,主要是一些简单的渲染展示,一旦需要满足以上一些特性还是需要修改很多东西, mooa  实现应该还是比较全面也比较适合我们的,但是它的示例中路由有一些问题,页面跳转了但是路由没有变,打包已经抛弃了 Angular CLI,代码层面参考了  single-spa  的很多东西,API 可以再度简化,既然是为 Angular 定制的,我觉得应该以 Angular 的方式实现更符合,当然不排除作者想要后期支持 React 和 Vue,不可否认的是  phodal  本人对于微前端的理解的确很深,写的很多不错的微前端的文章 microfrontends, 甚至出过唯一一本微前端的书《前端架构 - 从入门到微前端》,我在实现微前端的时候也借鉴参考了它的很多思想和实现方式。

使用 Angular 打造微前端应用

使用 Angular 实现微前端其实比 React 和 Vue 更加困难,因为 Angular 包含 AOT 编译,Module,Zone.js ,Service 共享等等问题,React 和 Vue 直接子应用 JS 加载渲染页面某个区域即可。

选择动态加载模块后编译还是加载整个应用

在 Angular 单体应用中,必须有一个根模块 AppModule,然后是每个特性模块 FeatureModule,每个特性模块可以有自己的路由,当然可以使用路由的惰性加载这些特性模块,但是在微前端架构中,每个子模块都是独立仓储的,如何在运行时把子模块加载到根模块就是一个技术选择难点。

  1. 第一种方案就是把每个子模块当作一个特性模块,然后在打包的时候随着主应用一起打包编译,这样是最简单的,但是这个无法做到独立部署,而且每次部署都是全量更新
  2. 第二种方案还是把子模块当作一个特性模块,在主应用通过 SystemJsNgModuleLoader 加载子模块,然后编译运行,(注:SystemJsNgModuleLoader 在新版本已经遗弃)
  3. 第三种方案就是每个子模块是一个独立的应用,和主应用一样,有自己的 AppModule, 路由,选择这种方案就需要处理多个应用路由同步的问题,还有就是 Angular 目前的依赖库是无法直接运行时使用的,需要每个子应用一起编译,无法做到公共依赖库抽取(可能有其他方案)
  4. 第四种方案就是把所有的子模块编译成 Web Components 使用,我暂时没有深入研究过,选择这种方案直接使用组件肯定没有问题,但是使用 Web Components 后路由如何处理我不知道。

我们最终选择了最复杂的第三种方案,因为新的 Ivy 渲染引擎正式发布后会解决第三方依赖库运行时直接使用的问题,至于 Web Components 没有深入研究,因为目前第三种方案运行挺好的。

应用注册,加载,销毁机制

这个是所有微前端应用的基础和核心,但是我觉得反而是最简单容易实现的,主要要做的就是:

  • 提供静态资源动态加载功能
  • 配置好子应用的规则,包含:应用名称,路由前缀,静态资源文件

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    this.planet.registerApps([

      {

          name: ‘app1‘,

          hostParent‘#app-host-container‘,

          routerPathPrefix‘/app1‘,

          selector: ‘app1-root‘,

          scripts: [‘/static/app1/main.js‘],

          styles: [‘/static/app1/styles.css‘]

      },

      // ...

    ]);

  • 应用加载:根据当前页面的 URL 找到对应的子应用,然后加载应用的静态资源,调用预定义好的启动函数直接启动应用即可,在 Angular 中就是启动根模块  platformBrowserDynamic().bootstrapModule(AppModule) 。
  • 应用的预加载:当前应用渲染完毕会预加载其他应用,并启动,并不会显示
  • 销毁应用使用  appModuleRef.destroy();

按照上述的步骤处理简单的场景基本就足够了,但是如果希望应用共存就不一样了,我们的做法是把  bootstrapped  状态隐藏起来,而不是销毁,只有  Active  状态的应用才会显示在当前页面中。

路由

因为选择了每个子应用是独立的 Angular 应用,同时还可以共存多个子应用,那么多个应用的路由同步,跳转就成了难题,而且还要支持应用之间路由跳转,应用之间通信,组件渲染等场景。我认为路由是我们在使用微前端架构中遇到的最复杂的问题。

目前我们的做法是主应用的路由中把所有子应用的路由都配置上,组件设置成  EmptyComponent  , 这样在切换到子应用路由的时候,主应用会匹配空路由状态,不会报错,每个子应用需要添加一个通用的空路由  EmptyComponent


1

2

3

4

{

        path: ‘**‘,

        component: EmptyComponent

}

除此之外还需要在切换路由的时候同步更新其他应用的路由,否则会造成每个应用的当前路由状态不一致,切换的时候会有跳转不成功的问题。

  • 主应用路由切换时,找到所有当前启动的子应用,使用  router.navigateByUrl  同步跳转
  • 子应用路由切换时,同步主应用路由,同时同步其他启动状态的子路由

我看了很多微前端框架包括  single-spa ,基本上路由这一块没有处理,完全交给开发者自己去填坑, single-spa  的 Angular 示例基本就是切换就销毁了 Angular 应用,因为没有并存,所以也就不需要处理多个应用路由的问题了,当然它作为和框架无关的微前端解决方案,也只能做到这一步了吧。

这个等 Ivy 渲染引擎正式发布后,可以把子应用编译成直接可以运行的模块,整个应用如果只有一个路由会简化很多。

共享全局服务

对于一些全局的数据我们一般会存储在服务中,然后子应用可以直接共享,比如: 当前登录用户 , 多语言服务 等,简单的数据共享可以直接挂载在 window 上即可,为了让每个子应用使用全局服务和模块内服务一致,我们通过在主应用中实例化这些服务,但后在每个子应用的 AppModule 中使用 provide 重新设置主应用的 value,当然这些不需要子应用的业务开发人员自己设置,已经封装到业务组件库中全局配置好了。


1

2

3

4

{

  provide: AppContext,

  useValue: window.portalAppContext

}

应用间通信

应用间通信有很多中方式,我们底层使用浏览器的  CustomEvent  ,在这之上封装了  GlobalEventDispatcher  服务做通信(当然你也可以使用在 window 对象上挂载全局对象实现),场景就是某个子应用要打开另外一个子应用的详情页


1

2

3

4

5

6

7

// App1

globalEventDispatcher.dispatch(‘open-task-detail‘, { taskId‘xxx‘ });

// App2

globalEventDispatcher.register(‘open-task-detail‘).subscribe((payload) => {

    // open dialog of task detail

});

应用间组件互相调用

在我们的 敏捷开发 子产品中,一个用户故事的详情页,需要显示 测试管理 应用的关联的测试用例和测试执行情况,那么这个测试用例列表组件放在  测试管理  子应用是最合适的,那么用户故事详情页肯定在 敏捷开发 应用中,如何加载 测试管理 应用的某个组件就是一个问题。

这一块使用了  Angular CDK 中的 DomPortalOutlet  动态创建组件,并指定渲染在某个容器中,这样保证了这个动态组件的创建还是  测试管理  模块的,只是渲染在了其他应用中而已。


1

2

3

const portalOutlet = new DomPortalOutlet(container, componentFactoryResolver, appRef, injector);

const testCasesPortalComponent = new ComponentPortal(TestCasesComponent, null);

portalOutlet.attachComponentPortal(testCasesPortalComponent);

工程化

使用微前端开发应用不仅仅要解决 Angular 的技术问题,还有一些开发,协作,部署等工程化的问题需要解决,比如:

  • 公共依赖库抽取
  • 本地如何启动开发
  • 如何打包部署,生成的 hash 资源文件如何通知主应用

应用公共依赖库抽取避免类库重复打包,减少打包体积,这就需要自定义 Webpack Config 实现,起初我们是完全自定义 Webpack 打包 Angular 应用,一旦这么做就会失去很多 CLI 提供的方便功能,偶尔发现了一个类库 angular-builders ,他的作用其实就是在 Angular CLI 生成的 Webpack Config 中合并自定义的 Webpack Config,这样就做到了只需要写少量的自定义配置,其余的还是完全使用 CLI 的打包功能,差一点就要自己写一个类似的工具了。
在主应用中把需要公共依赖包放入  scripts  中,然后在子应用中配置  externals ,比如: moment   lodash   rxjs  这样的类库。


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

const webpackExtraConfig = {

    optimization: {

        runtimeChunk: false // 子应用一定要设置 false,否则会报错

    },

    externals: {

        moment: ‘moment‘,

        lodash: ‘_‘,

        rxjs: ‘rxjs‘,

       ‘rxjs/operators‘‘rxjs.operators‘,

        highcharts: ‘Highcharts‘

    },

    devtool: options.isDev ‘eval-source-map‘ ‘‘,

    plugins: [new WebpackAssetsManifest()]

};

return webpackExtraConfig;

WebpackAssetsManifest 主要作用是生成  manifest.json  文件,目的就是让生成的 Hash 文文件的对应关系,让主应用加载正确的资源文件。

本地开发配置  proxy.conf.js  代理访问每个子应用的资源文件,同时包括 API 调用。

基于 Angular 的微前端库 ngx-planet

以上是我们在使用 Angular 打造微前端应用遇到的一些技术难点和我们的解决方案,调研后最终选择自研一套符合我们业务场景的,同时只为 Angular 量身打造的微前端库。

Github 仓储地址:ngx-planet
在线 Demo:http://planet.ngnice.com

不敢说 “你见过最完善的微前端解决方案” ,但至少是 Angular 社区目前我见过完全可用于生产环境的方案,API 符合 Angular Style ,国内很多大厂做微前端方案基本都忽略了 Angular 这个框架的存在,Worktile 四个研发子产品完全基于  ngx-planet  打造开发,经过接近一年的踩坑和实践,基本完全可用。

希望 Angular 社区可以多一些微前端的解决方案,一起进步,我们的方案肯定也存在很多问题,也欢迎大家提出改进的建议和吐槽,我们也将继续在 Angular 微前端的路上继续深耕下去,如果你正在寻找 Angular 的微前端类库,不妨试试 ngx-planet。

将来会调研在 Ivy 渲染引擎下的优化和改进方案。

原文地址:https://www.cnblogs.com/Leo_wl/p/12256515.html

时间: 2024-10-08 15:47:22

微前端(Micro Front-end)的相关文章

可能是你见过的最完善的微前端解决方案

前言 目前社区有很多关于微前端架构的介绍,但大多停留在概念介绍的阶段.而本文会就某一个具体的类型场景,着重介绍微前端架构可以带来什么价值以及具体实践过程中需要关注的技术决策,并辅以具体代码,从而能真正意义上帮助你构建一个生产可用的微前端架构系统. 而对于微前端的概念感兴趣或不熟悉的同学,可以通过搜索引擎来获取更多信息,如 知乎上的相关内容,本文不再做过多介绍. 两个月前 Twitter 曾爆发过关于微前端的“热烈”讨论,参与大佬众多(Dan.Larkin 等),对“事件”本身我们今天不做过多评论

HeadFirst 解析Alibaba 成熟的微前端framework qiankun乾坤源码图文

感谢平台分享-http://bjbsair.com/2020-04-10/tech-info/53345.html 转发链接:https://github.com/a1029563229/blogs/blob/master/Source-Code/qiankun/1.md 本文将针对微前端框架 qiankun 的源码进行深入解析,在源码讲解之前,我们先来了解一下什么是 微前端. 微前端 是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将单页面前端应用由单一的单体应用转变为多个小型前端

微服务 Micro services

微服务 (Microservices) 是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模组化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic) 的 API 集相互通讯.微服务架构运用于软件架构风格的其中一项概念是甘露运算 (Dew Computing),意指由许多的小露水 (代表微服务的功能元件) 汇集而成的运算能力. 微服务的起源是

微前端-前端项目拆解简述

1.起因 1.系统庞大,涉及多个角色,角色互串,导致功能与功能之间的关系不清晰 2.角色对应的页面划分不清晰,系统和系统之间混杂在一起 3.前端项目过大,前端性能低下 4.组员多,项目管理难 2.拆解后的预期 1.优势 1.角色清晰.角色所属功能清晰 2.项目解耦,便于维护管理 3.项目上线,完全不会影响现有功能 4.性能进一步提升 5.拆解颗粒度足够细时,将不在限制于技术框架,对优秀人才的需求可以多元化 2.劣势 1.现有共用功能,难以实现共用,导致了代码冗余(持续迭代则是需要这么做) 2.第

微前端基座本地调试方式研究

基座在本地开发的时候,打开的是基座项目,基座里面什么都没有,无法看到真实场景,因此我们现在就要想办法解决. 由于基座项目是一个单独的项目,它主要作用是通过 systemimport.js 引入其他项目的路由和接口,然后通过路由引入对应的模块 JS 文件.由于 systemimport.js 引用的是不同域名下的 js 文件,例如:dev.songshu.com.test.songshu.com... 由于 webapck 在打包构建的时候,如果 publicPath 写的是 "/" 分

如何实现前端微服务化?

译者按: 微服务在后端开发中大行其道,其实对于越来越复杂的前端应用来说,微服务也是一种不错的选择. 原文: Micro frontends-a microservice approach to front-end web development 译者: Fundebug 为了保证可读性,本文采用意译而非直译.另外,本文版权归原作者所有,翻译仅用于学习. 对于网页应用,现代的开发方法使得前端部分变得越来越大,与之对应的后端反而变小.我们的网站Weld的代码中90%都是前端相关.我可以想象大多数现代

前端单页应用微服务化解决方案2 - Single-SPA

技术选型 经过各种技术调研我们最终选择的方案是基于 Single-SPA 来实现我们的前端微服务化. Single-SPA 一个用于前端微服务化的JavaScript前端解决方案 使用Single-SPA之后,你可以这样做: (兼容各种技术栈)在同一个页面中使用多种技术框架(React, Vue, AngularJS, Angular, Ember等任意技术框架),并且不需要刷新页面. (无需重构现有代码)使用新的技术框架编写代码,现有项目中的代码无需重构. (更优的性能)每个独立模块的代码可做

从最新的技术趋势看前端的未来

新一期的ThoughtWorks技术雷达有点出乎意料,使用new标签的框架.工具.技术.语言等等超过了一半——Vue.js.ES2017上榜,Three.js凭着VR的火又上榜了,还有熟悉的Electron,以及微前端的概念. 让我们先看看一些技术亮点~~. 前端在可见的未来 在那篇<最流行的编程语言JavaScript能做什么?>的文章里,我们看到了JavaScript在各个领域的应用.在这一期里,仍然有很多亮点(new): Vue.js Vue.js,如果你在使用Vue.js,那么你更应该

【转】微服务与SOA之间差了一个ESB

本文来自 dockone 编辑:yan 微服务只是最近提出的概念,实际上很多巨头公司(FB.Twitter.AWS等)已经在亲身实践.微服务并不是银弹,但是我们可以参考它的思想来解决自己遇到的问题.对于已经找准市场,业务即将或者马上就要急剧发展的创业公司,适合使用基于微服务的软件架构. 今天阅读了两篇关于微服务的文章,总结一些笔记,简单翻译了一篇文章.说明:并没有严格按照原文一字语句翻译,有部分自己的理解,还有部分是意译. 微服务(micro services)这个概念不是新概念,很多公司已经在