概念
首先我们要明确一个问题,微前端到底是什么。微前端源自于微服务,而微服务的概念出现是为了处理庞大的后端结构的臃肿问题。微前端是处理前端项目的臃肿问题,
特点
将庞大的整体拆成可控的小块,并明确它们之间的依赖关系。关键优势在于:
代码库更小,更内聚、可维护性更高
松耦合、自治的团队可扩展性更好
代码的迭代更加简单,甚至可以重写部分功能很少甚至不会对彼此造成影响。
为什么之前的代码难改,‘祖传’代码多,关系复杂,牵一发而动全身。技术栈落后,但是引用太多,配置太多不敢改。改以部分功能,难度不亚于重写。
而微前端不一样,可以独立部署。 A 是 A , B 是 B ,彼此不会影响,大家只能算是邻居,家庭内部矛盾,自己解决,关上门解决问题。打开门一起 Happy 。开发人员和项目代码全都是独立的,甚至可以请不同的团队开发不同的代码。谁好了谁先上,节约成本。
实现方案
多 Bundle 集成
微前端架构中一般会有个容器应用(container application)将各子应用集成起来,职责如下:
渲染公共的页面元素,比如 header、footer
解决横切关注点(cross-cutting concerns),如身份验证和导航
将各个微前端整合到一个页面上,并控制微前端的渲染区域和时机
集成方式分为 3 类:
服务端集成:如 SSR 拼装模板
构建时集成:如 Code Splitting
运行时集成:如通过 iframe、JS、Web Components 等方式
构建时集成
常见的构建时集成方式是将子应用发布成独立的 npm 包,共同作为主应用的依赖项,构建生成一个供部署的 JS Bundle
然而,构建时集成最大的问题是会在发布阶段造成耦合,任何一个子应用有变更,都要整个重新编译,意味着对于产品局部的小改动也要发布一个新版本,因此,不推荐这种方式
运行时集成
将集成时机从构建时推迟到运行时,就能避免发布阶段的耦合。常见的运行时集成方式有:
iframe
JS:比如前端路由
Web Components
虽然直觉上用 iframe 好像不太好(性能、通信成本等),但在这里确实是个合理选项,因为 iframe 无疑是最简单的方式,还天然支持样式隔离以及全局变量隔离
但这种原生的隔离性,意味着很难把应用的各个部分联系到一起,路由控制、历史栈管理、深度链接(deep-linking)、响应式布局等都变得异常复杂,因而限制了 iframe 方案的灵活性
另一种最常见的方式是前端路由,每个子应用暴露出渲染函数,主应用在启动时加载各个子应用的独立 Bundle,之后根据路由规则渲染相应的子应用。目前看来,是最灵活的方式
还有一种类似的方式是Web Components,将每个子应用封装成自定义 HTML 元素(而不是前端路由方案中的渲染函数),以获得Shadow DOM带来的样式隔离等好处
影响隔离
子应用之间,以及子应用与主应用间的样式、作用域隔离是必须要考虑的问题,常见解决方案如下:
样式隔离:开发规范(如BEM)、CSS 预处理(如SASS)、模块定义(如CSS Module)、用 JS 来写(CSS-in-JS)、以及shadow DOM特性
作用域隔离:各种模块定义(如ES Module、AMD、Common Module、UMD)
资源复用
资源复用对于 UI 一致性和代码复用有重要意义,但并非所有的可复用资源(如组件)都必须在一开始就提出来复用,建议的做法是前期允许一定程度的冗余,各个 Bundle 在各自的代码库中创建组件,直到形成相对明确的组件 API 时再建立可供复用的公共组件
另一方面,资源分为以下 3 类:
基础资源:完全不含逻辑功能的图标、标签、按钮等
UI 组件:含有一定 UI 逻辑的搜索框(如自动完成)、表格(如排序、筛选、分页)等
业务组件:含有业务逻辑
其中,不建议跨子应用复用业务组件,因为会造成高度耦合,增加变更成本
对于公共资源的归属和管理,一般有两种模式:
公共资源归属于所有人,即没有明确归属
公共资源归集中管理,由专人负责
从实践经验来看,前者很容易衍变成没有明确规范,且背离技术愿景的大杂烩,而后者会造成资源创建和使用的脱节,比较推荐的模式是开源软件的管理模式:所有人都能补充公共资源,但要有人(或一个团队)负责监管,以保证质量、一致性以及正确性
应用间通信
通过自定义事件间接通信是一种避免直接耦合的常用方式,此外,React 的单向数据流模型也能让依赖关系更加明确,对应到微前端中,从容器应用向子应用传递数据与回调函数
另外,路由参数除了能用于分享、书签等场景外,也可以作为一种通信手段,并且具有诸多优势:
其结构遵从定义明确的开放标准
页面级共享,能够全局访问
长度限制促使只传递必要的少量数据
面向用户的,有助于依照领域建模
声明式的,语义上更通用
迫使子应用之间间接通信,而不直接依赖对方
但原则上,无论采用哪种方式,都应该尽可能减少子应用间的通信,以避免大量弱依赖造成的强耦合
测试
每个子应用都应该有自己的全套测试方案,特殊之处在于,除单元测试、功能测试外,还要有集成测试:
集成测试:保证子应用间集成的正确性,比如跨子应用的交互操作
功能测试:保证页面组装的正确性
单元测试:保证底层业务逻辑和渲染逻辑的正确性
自下而上形成一个金字塔结构,每一层只需验证在其下层覆盖不到的部分即可
缺点
当然,这种架构模式并非百益而无一害,一些问题也随之而来:
导致依赖项冗余,增加用户的流量负担
团队自治程度的增加,可能会破坏协作
文档出处:https://zhuanlan.zhihu.com/p/96464401