# microfrontends **Repository Path**: zk923852367/microfrontends ## Basic Information - **Project Name**: microfrontends - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-07-28 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Thinking in Microfrontend (微前端的那些事儿) [English](english.md) > 微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为**多个小型前端应用聚合为一的应用**。各个前端应用还可以**独立运行**、**独立开发**、**独立部署**。 同时,它们也可以在**共享组件**的同时进行并行开发——这些组件可以通过 NPM 或者 Git Tag、Git Submodule 来管理。 **注意**:这里的前端应用指的是前后端分离的单页面应用,在这基础才谈论微前端才有意义。 支持作者(纸质版): ![前端架构:从入门到微前端](imgs/9787121365348.jpg) 支持作者: - 京东:《[前端架构:从入门到微前端](https://item.jd.com/12621088.html)》 **目录** - [微前端的那些事儿](#微前端的那些事儿) - [实施微前端的六种方式](#实施微前端的六种方式) - [基础铺垫:应用分发路由 -\> 路由分发应用](#基础铺垫应用分发路由---路由分发应用) - [后端:函数调用 -\> 远程调用](#后端函数调用---远程调用) - [前端:组件调用 -\> 应用调用](#前端组件调用---应用调用) - [路由分发式微前端](#路由分发式微前端) - [使用 iFrame 创建容器](#使用-iframe-创建容器) - [自制框架兼容应用](#自制框架兼容应用) - [组合式集成:将应用微件化](#组合式集成将应用微件化) - [纯 Web Components 技术构建](#纯-web-components-技术构建) - [结合 Web Components 构建](#结合-web-components-构建) - [在 Web Components 中集成现有框架](#在-web-components-中集成现有框架) - [集成在现有框架中的 Web Components](#集成在现有框架中的-web-components) - [复合型](#复合型) - [为什么微前端开始在流行------Web 应用的聚合](#为什么微前端开始在流行web-应用的聚合) - [前端遗留系统迁移](#前端遗留系统迁移) - [后端解耦,前端聚合](#后端解耦前端聚合) - [兼容遗留系统](#兼容遗留系统) - [如何解构单体前端应用------前端应用的微服务式拆分](#如何解构单体前端应用前端应用的微服务式拆分) - [前端微服化](#前端微服化) - [独立开发](#独立开发) - [独立部署](#独立部署) - [我们真的需要技术无关吗?](#我们真的需要技术无关吗) - [不影响用户体验](#不影响用户体验) - [微前端的设计理念](#微前端的设计理念) - [设计理念一:中心化路由](#设计理念一中心化路由) - [设计理念二:标识化应用](#设计理念二标识化应用) - [设计理念三:生命周期](#设计理念三生命周期) - [设计理念四:独立部署与配置自动化](#设计理念四独立部署与配置自动化) - [实战微前端架构设计](#实战微前端架构设计) - [独立部署与配置自动化](#独立部署与配置自动化) - [应用间路由------事件](#应用间路由事件) - [大型 Angular 应用微前端的四种拆分策略](#大型-angular-应用微前端的四种拆分策略) - [前端微服务化:路由懒加载及其变体](#前端微服务化路由懒加载及其变体) - [微服务化方案:子应用模式](#微服务化方案子应用模式) - [方案对比](#方案对比) - [标准 LazyLoad](#标准-lazyload) - [LazyLoad 变体 1:构建时集成](#lazyload-变体-1构建时集成) - [LazyLoad 变体 2:构建后集成](#lazyload-变体-2构建后集成) - [前端微服务化](#前端微服务化) - [总对比](#总对比) - [前端微服务化:使用微前端框架 Mooa 开发微前端应用](#前端微服务化使用微前端框架-mooa-开发微前端应用) - [Mooa 概念](#mooa-概念) - [微前端主工程创建](#微前端主工程创建) - [Mooa 子应用创建](#mooa-子应用创建) - [导航到特定的子应用](#导航到特定的子应用) - [前端微服务化:使用特制的 iframe 微服务化 Angular 应用](#前端微服务化使用特制的-iframe-微服务化-angular-应用) - [iframe 微服务架构设计](#iframe-微服务架构设计) - [微前端框架 Mooa 的特制 iframe 模式](#微前端框架-mooa-的特制-iframe-模式) - [微前端框架 Mooa iframe 通讯机制](#微前端框架-mooa-iframe-通讯机制) - [发布主应用事件](#发布主应用事件) - [监听子应用事件](#监听子应用事件) - [示例](#示例) - [资源](#资源) 为什么微前端开始在流行——Web 应用的聚合 === > 采用新技术,更多不是因为先进,而是因为它能解决痛点。 过去,我一直有一个疑惑,人们是否真的需要微服务,是否真的需要微前端。毕竟,没有银弹。当人们考虑是否采用一种新的架构,除了考虑它带来好处之外,仍然也考量着存在的大量的风险和技术挑战。 前端遗留系统迁移 --- 自微前端框架 [Mooa](https://github.com/phodal/mooa) 及对应的《[微前端的那些事儿](https://github.com/phodal/microfrontend)》发布的两个多月以来,我陆陆续续地接收到一些微前端架构的一些咨询。过程中,我发现了一件很有趣的事:**解决遗留系统,才是人们采用微前端方案最重要的原因**。 这些咨询里,开发人员所遇到的情况,与我之前遇到的情形并相似,我的场景是:设计一个新的前端架构。他们开始考虑前端微服务化,是因为遗留系统的存在。 过去那些使用 Backbone.js、Angular.js、Vue.js 1 等等框架所编写的单页面应用,已经在线上稳定地运行着,也没有新的功能。对于这样的应用来说,我们也没有理由浪费时间和精力重写旧的应用。这里的那些使用旧的、不再使用的技术栈编写的应用,可以称为遗留系统。而,这些应用又需要结合到新应用中使用。我遇到的较多的情况是:旧的应用使用的是 Angular.js 编写,而新的应用开始采用 Angular 2+。这对于业务稳定的团队来说,是极为常见的技术栈。 在即不重写原有系统的基础之下,又可以抽出人力来开发新的业务。其不仅仅对于业务人员来说, 是一个相当吸引力的特性;对于技术人员来说,不重写旧的业务,同时还能做一些技术上的挑战,也是一件相当有挑战的事情。 后端解耦,前端聚合 --- 而前端微服务的一个卖点也在这里,去兼容不同类型的前端框架。这让我又联想到微服务的好处,及许多项目落地微服务的原因: 在初期,后台微服务的一个很大的卖点在于,可以使用不同的技术栈来开发后台应用。但是,事实上,采用微服务架构的组织和机构,一般都是中大型规模的。相较于中小型,对于框架和语言的选型要求比较严格,如在内部限定了框架,限制了语言。因此,在充分使用不同的技术栈来发挥微服务的优势这一点上,几乎是很少出现的。在这些大型组织机构里,采用微服务的原因主要还是在于,**使用微服务架构来解耦服务间依赖**。 而在前端微服务化上,则是恰恰与之相反的,人们更想要的结果是**聚合**,尤其是那些 To B(to Bussiness)的应用。 在这两三年里,移动应用出现了一种趋势,用户不想装那么多应用了。而往往一家大的商业公司,会提供一系列的应用。这些应用也从某种程度上,反应了这家公司的组织架构。然而,在用户的眼里他们就是一家公司,他们就只应该有一个产品。相似的,这种趋势也在桌面 Web 出现。**聚合**成为了一个技术趋势,体现在前端的聚合就是微服务化架构。 兼容遗留系统 --- 那么,在这个时候,我们就需要使用新的技术、新的架构,来容纳、兼容这些旧的应用。而前端微服务化,正好是契合人们想要的这个卖点罢了。 实施微前端的六种方式 === 微前端架构是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为**多个小型前端应用聚合为一的应用**。 由此带来的变化是,这些前端应用可以**独立运行**、**独立开发**、**独立部署**。以及,它们应该可以在**共享组件**的同时进行并行开发——这些组件可以通过 NPM 或者 Git Tag、Git Submodule 来管理。 **注意**:这里的前端应用指的是前后端分离的单应用页面,在这基础才谈论微前端才有意义。 结合我最近半年在[微前端](https://github.com/phodal/microfrontends)方面的实践和研究来看,微前端架构一般可以由以下几种方式进行: 1. 使用 HTTP 服务器的路由来重定向多个应用 2. 在不同的框架之上设计通讯、加载机制,诸如 [Mooa](https://github.com/phodal/mooa) 和 [Single-SPA](https://github.com/CanopyTax/single-spa) 3. 通过组合多个独立应用、组件来构建一个单体应用 4. iFrame。使用 iFrame 及自定义消息传递机制 5. 使用纯 Web Components 构建应用 6. 结合 Web Components 构建 基础铺垫:应用分发路由 -> 路由分发应用 --- 在一个单体前端、单体后端应用中,有一个典型的特征,即路由是由**框架**来分发的,框架将路由指定到对应的组件或者内部服务中。微服务在这个过程中做的事情是,将调用由**函数调用**变成了**远程调用**,诸如远程 HTTP 调用。而微前端呢,也是类似的,它是将**应用内的组件调用**变成了更细粒度的**应用间组件调用**,即原先我们只是将路由分发到应用的组件执行,现在则需要根据路由来找到对应的应用,再由应用分发到对应的组件上。 ### 后端:函数调用 -> 远程调用 在大多数的 CRUD 类型的 Web 应用中,也都存在一些极为相似的模式,即:首页 -> 列表 -> 详情: - 首页,用于面向用户展示特定的数据或页面。这些数据通常是有限个数的,并且是多种模型的。 - 列表,即数据模型的聚合,其典型特点是某一类数据的集合,可以看到尽可能多的**数据概要**(如 Google 只返回 100 页),典型见 Google、淘宝、京东的搜索结果页。 - 详情,展示一个数据的尽可能多的内容。 如下是一个 Spring 框架,用于返回首页的示例: ```java @RequestMapping(value="/") public ModelAndView homePage(){ return new ModelAndView("/WEB-INF/jsp/index.jsp"); } ``` 对于某个详情页面来说,它可能是这样的: ```java @RequestMapping(value="/detail/{detailId}") public ModelAndView detail(HttpServletRequest request, ModelMap model){ .... return new ModelAndView("/WEB-INF/jsp/detail.jsp", "detail", detail); } ``` 那么,在微服务的情况下,它则会变成这样子: ``` @RequestMapping("/name") public String name(){ String name = restTemplate.getForObject("http://account/name", String.class); return Name" + name; } ``` 而后端在这个过程中,多了一个服务发现的服务,来管理不同微服务的关系。 ### 前端:组件调用 -> 应用调用 在形式上来说,单体前端框架的路由和单体后端应用,并没有太大的区别:**依据不同的路由,来返回不同页面的模板。** ```javascritp const appRoutes: Routes = [ { path: 'index', component: IndexComponent }, { path: 'detail/:id', component: DetailComponent }, ]; ``` 而当我们将之微服务化后,则可能变成应用 A 的路由: ```javascritp const appRoutes: Routes = [ { path: 'index', component: IndexComponent }, ]; ``` 外加之应用 B 的路由: ```javascritp const appRoutes: Routes = [ { path: 'detail/:id', component: DetailComponent }, ]; ``` 而问题的关键就在于:**怎么将路由分发到这些不同的应用中去**。与此同时,还要负责管理不同的前端应用。 路由分发式微前端 --- **路由分发式微前端**,即通过路由将不同的业务**分发到不同的、独立前端应用**上。其通常可以通过 HTTP 服务器的反向代理来实现,又或者是应用框架自带的路由来解决。 就当前而言,通过路由分发式的微前端架构应该是采用最多、最易采用的 “微前端” 方案。但是这种方式看上去更像是**多个前端应用的聚合**,即我们只是将这些不同的前端应用拼凑到一起,使他们看起来像是一个完整的整体。但是它们并不是,每次用户从 A 应用到 B 应用的时候,往往需要刷新一下页面。 在几年前的一个项目里,我们当时正在进行**遗留系统重写**。我们制定了一个迁移计划: 1. 首先,使用**静态网站生成**动态生成首页 2. 其次,使用 React 计划栈重构详情页 3. 最后,替换搜索结果页 整个系统并不是一次性迁移过去,而是一步步往下进行。因此在完成不同的步骤时,我们就需要上线这个功能,于是就需要使用 Nginx 来进行路由分发。 如下是一个基于路由分发的 Nginx 配置示例: ``` http { server { listen 80; server_name www.phodal.com; location /api/ { proxy_pass http://http://172.31.25.15:8000/api; } location /web/admin { proxy_pass http://172.31.25.29/web/admin; } location /web/notifications { proxy_pass http://172.31.25.27/web/notifications; } location / { proxy_pass /; } } } ``` 在这个示例里,不同的页面的请求被分发到不同的服务器上。 随后,我们在别的项目上也使用了类似的方式,其主要原因是:**跨团队的协作**。当团队达到一定规模的时候,我们不得不面对这个问题。除此,还有 Angluar 跳崖式升级的问题。于是,在这种情况下,用户前台使用 Angular 重写,后台继续使用 Angular.js 等保持再有的技术栈。在不同的场景下,都有一些相似的技术决策。 因此在这种情况下,它适用于以下场景: - 不同技术栈之间差异比较大,难以兼容、迁移、改造 - 项目不想花费大量的时间在这个系统的改造上 - 现有的系统在未来将会被取代 - 系统功能已经很完善,基本不会有新需求 而在满足上面场景的情况下,如果为了更好的用户体验,还可以采用 iframe 的方式来解决。 使用 iFrame 创建容器 --- iFrame 作为一个非常古老的,人人都觉得普通的技术,却一直很管用。 > **HTML 内联框架元素** `` ``` Mooa 提供了两种模式,一种是基于 Single-SPA 的实验做的,在同一页面加载、渲染两个 Angular 应用;一种是基于 iFrame 来提供独立的应用容器。 解决了以下的问题: - **首页加载速度更快**,因为只需要加载首页所需要的功能,而不是所有的依赖。 - **多个团队并行开发**,每个团队里可以独立地在自己的项目里开发。 - **独立地进行模块化更新**,现在我们只需要去单独更新我们的应用,而不需要更新整个完整的应用。 但是,它仍然包含有以下的问题: - 重复加载依赖项,即我们在 A 应用中使用到的模块,在 B 应用中也会重新使用到。有一部分可以通过浏览器的缓存来自动解决。 - 第一次打开对应的应用需要时间,当然**预加载**可以解决一部分问题。 - 在非 iframe 模式下运行,会遇到难以预料的第三方依赖冲突。 于是在总结了一系列的讨论之后,我们形成了一系列的对比方案: 方案对比 --- 在这个过程中,我们做了大量的方案设计与对比,便想写一篇文章对比一下之前的结果。先看一下图: ![Angular 代码拆分对比](./imgs/angular-split-code-compare.jpg) 表格对比: x | 标准 Lazyload | 构建时集成 | 构建后集成 | 应用独立 --------|--------------|------------|-------------|------------- 开发流程 | 多个团队在同一个代码库里开发 | 多个团队在不同的代码库里开发 | 多个团队在不同的代码库里开发 | 多个团队在不同的代码库里开发 构建与发布 | 构建时只需要拿这一份代码去构建、部署 | 将不同代码库的代码整合到一起,再构建应用 | 将直接编译成各个项目模块,运行时通过懒加载合并 | 将直接编译成不同的几个应用,运行时通过主工程加载 适用场景 | 单一团队,依赖库少、业务单一 | 多团队,依赖库少、业务单一 | 多团队,依赖库少、业务单一 | 多团队,依赖库多、业务复杂 表现方式 | 开发、构建、运行一体 | 开发分离,构建时集成,运行一体| 开发分离,构建分离,运行一体 | 开发、构建、运行分离 详细的介绍如下: ### 标准 LazyLoad 开发流程:多个团队在同一个代码库里开发,构建时只需要拿这一份代码去部署。 行为:开发、构建、运行一体 适用场景:单一团队,依赖库少、业务单一 ### LazyLoad 变体 1:构建时集成 开发流程:多个团队在不同的代码库里开发,在构建时将不同代码库的代码整合到一起,再去构建这个应用。 适用场景:多团队,依赖库少、业务单一 变体-构建时集成:开发分离,构建时集成,运行一体 ### LazyLoad 变体 2:构建后集成 开发流程:多个团队在不同的代码库里开发,在构建时将编译成不同的几份代码,运行时会通过懒加载合并到一起。 适用场景:多团队,依赖库少、业务单一 变体-构建后集成:开发分离,构建分离,运行一体 ### 前端微服务化 开发流程:多个团队在不同的代码库里开发,在构建时将编译成不同的几个应用,运行时通过主工程加载。 适用场景:多团队,依赖库多、业务复杂 前端微服务化:开发、构建、运行分离 总对比 --- 总体的对比如下表所示: x | 标准 Lazyload | 构建时集成 | 构建后集成 | 应用独立 --------|--------------|------------|-------------|------------- 依赖管理 | 统一管理 | 统一管理 | 统一管理 | 各应用独立管理 部署方式 | 统一部署 | 统一部署 | 可单独部署。更新依赖时,需要全量部署 | 可完全独立部署 首屏加载 | 依赖在同一个文件,加载速度慢 | 依赖在同一个文件,加载速度慢 | 依赖在同一个文件,加载速度慢 | 依赖各自管理,首页加载快 首次加载应用、模块 | 只加载模块,速度快 | 只加载模块,速度快 | 只加载模块,速度快 | 单独加载,加载略慢 前期构建成本 | 低 | 设计构建流程| 设计构建流程 | 设计通讯机制与加载方式 维护成本 | 一个代码库不好管理 | 多个代码库不好统一 | 后期需要维护组件依赖 | 后期维护成本低 打包优化 | 可进行摇树优化、AoT 编译、删除无用代码| 可进行摇树优化、AoT 编译、删除无用代码 | 应用依赖的组件无法确定,不能删除无用代码 | 可进行摇树优化、AoT 编译、删除无用代码 前端微服务化:使用微前端框架 Mooa 开发微前端应用 === Mooa 是一个为 Angular 服务的微前端框架,它是一个基于 [single-spa](https://github.com/CanopyTax/single-spa),针对 IE 10 及 IFRAME 优化的微前端解决方案。 Mooa 概念 --- Mooa 框架与 Single-SPA 不一样的是,Mooa 采用的是 Master-Slave 架构,即主-从式设计。 对于 Web 页面来说,它可以同时存在两个到多个的 Angular 应用:其中的一个 Angular 应用作为主工程存在,剩下的则是子应用模块。 - 主工程,负责加载其它应用,及用户权限管理等核心控制功能。 - 子应用,负责不同模块的具体业务代码。 在这种模式下,则由主工程来控制整个系统的行为,子应用则做出一些对应的响应。 微前端主工程创建 --- 要创建微前端框架 Mooa 的主工程,并不需要多少修改,只需要使用 ``angular-cli`` 来生成相应的应用: ``` ng new hello-world ``` 然后添加 ``mooa`` 依赖 ``` yarn add mooa ``` 接着创建一个简单的配置文件 ``apps.json``,放在 ``assets`` 目录下: ``` [{ "name": "help", "selector": "app-help", "baseScriptUrl": "/assets/help", "styles": [ "styles.bundle.css" ], "prefix": "help", "scripts": [ "inline.bundle.js", "polyfills.bundle.js", "main.bundle.js" ] } ]] ``` 接着,在我们的 ``app.component.ts`` 中编写相应的创建应用逻辑: ``` mooa = new Mooa({ mode: 'iframe', debug: false, parentElement: 'app-home', urlPrefix: 'app', switchMode: 'coexist', preload: true, includeZone: true }); constructor(private renderer: Renderer2, http: HttpClient, private router: Router) { http.get('/assets/apps.json') .subscribe( data => { this.createApps(data); }, err => console.log(err) ); } private createApps(data: IAppOption[]) { data.map((config) => { this.mooa.registerApplication(config.name, config, mooaRouter.hashPrefix(config.prefix)); }); const that = this; this.router.events.subscribe((event) => { if (event instanceof NavigationEnd) { that.mooa.reRouter(event); } }); return mooa.start(); } ``` 再为应用创建一个对应的路由即可: ``` { path: 'app/:appName/:route', component: HomeComponent } ``` 接着,我们就可以创建 Mooa 子应用。 Mooa 子应用创建 --- Mooa 官方提供了一个子应用的模块,直接使用该模块即可: ``` git clone https://github.com/phodal/mooa-boilerplate ``` 然后执行: ``` npm install ``` 在安装完依赖后,会进行项目的初始化设置,如更改包名等操作。在这里,将我们的应用取名为 help。 然后,我们就可以完成子应用的构建。 接着,执行:``yarn build`` 就可以构建出我们的应用。 将 ``dist`` 目录一下的文件拷贝到主工程的 src/assets/help 目录下,再启动主工程即可。 导航到特定的子应用 --- 在 Mooa 中,有一个路由接口 ``mooaPlatform.navigateTo``,具体使用情况如下: ``` mooaPlatform.navigateTo({ appName: 'help', router: 'home' }); ``` 它将触发一个 ``MOOA_EVENT.ROUTING_NAVIGATE`` 事件。而在我们调用 ``mooa.start()`` 方法时,则会开发监听对应的事件: ``` window.addEventListener(MOOA_EVENT.ROUTING_NAVIGATE, function(event: CustomEvent) { if (event.detail) { navigateAppByName(event.detail) } }) ``` 它将负责将应用导向新的应用。 嗯,就是这么简单。DEMO 视频如下: Demo 地址见:[http://mooa.phodal.com/](http://mooa.phodal.com/) GitHub 示例:[https://github.com/phodal/mooa](https://github.com/phodal/mooa) 前端微服务化:使用特制的 iframe 微服务化 Angular 应用 === Angular 基于 Component 的思想,可以让其在一个页面上同时运行多个 Angular 应用;可以在一个 DOM 节点下,存在多个 Angular 应用,即类似于下面的形式: ```html