约两年前就有了解这个话题,当时背景是前端的旧项目代码量比较大,开发时编译速度很慢,极大了影响开发体验,正逢技术转型以及新增产品业务线,于是拆分成了多个项目进行开发,但项目之间跳转、数据传递以及彼此交互时,就陷入了困境。
为什么
你是否需要
- 避免巨石应用,巨石应用可能开发效率低下、构建速度变慢
- 你是否有古老项目需要和新项目同时存在或增量升级
- 不同的前端团队,可以独立选型、独立开发和部署他们的应用,满足自己的迭代需求
微前端核心目标
- 多个前端子应用之间的协作与切换,不应该产生不可接受的用户体验下降
- 运行时隔离与共享:js 和 css 互相隔离不受影响,同时又有同新机制保证应用之间能够互相通信或数据共享
可能存在的问题
- 可能是个伪需求:你应该统一技术栈
- 用户体验下降:加载重复的资源,应用跳转体验下降
- 跨系统数据共享需求并不常见,这是假定有多个前端服务的前提,然而你甚至可能无须拆成多个服务
- 需要在开发中运行许多不同的应用程序来测试完整的体验
- 从本质上说,我们是在用单一前端的简单性来换取整个系统的复杂性
或许,解决遗留系统,才是人们采用微前端方案最重要的原因。
微前端不同于微服务
- 微服务对于后端而言是一次架构升级,但微前端不是这样,而是当你的确有具体难题时,没有法子的办法。如果非往上边靠,就相当于没有困难创造困难也要上。
- 对于微服务来说,模块分开解藕基本就完事了,但是微前端不一样,前端应用在运行时却是一个整体,需要聚合,需要隔离,甚至还需要交互,通信。
常见方案
较为成熟的方案有
- single-spa、qiankun
- micro-app:基于 WebComponent
- EMP:基于 webpack5 module federation
- tencent/wujie:基于 WebComponent + iframe
single-spa、qiankun
- qiankun 是基于 single-spa 的二次开发,single spa 通过监听 url change 时间,在路由变化时匹配到渲染的子应用并进行渲染
- single-spa 要求子应用修改渲染逻辑并暴露出三个方法:bootstrap、mount、unmount,因此需要对入口文件做修改
- 需要对 webpack 打包进行一些修改
micro-app 借鉴了 WebComponent 的思想,通过 CustomElement 结合自定义的 ShadowDom 将微前端封装成一个类 WebComponent 组件,从而实现微前端的组件化渲染。由于自定义 ShadowDom 的隔离特性,micro-app 不需要像 single-spa 和 qiankun 一样要求子应用修改渲染逻辑并暴露出方法,也不需要修改 webpack 配置
微前端架构解决方案大概分为两类场景:
- 单实例:同一时刻,只有一个子应用被展示,子应用具备一个完整的应用生命周期。通常基于 url 的变化来做子应用的切换。
- 多实例:同一时刻可展示多个子应用。通常使用 Web Components 方案来做子应用封装,子应用更像是一个业务组件而不是应用。
基本改造思路
- 只有一个不包含子项目资源的主项目,子项目通过主项目来按需加载,子系统间切换不再刷新
- 菜单栏、登录、退出等功能都从子项目剥离,写在主项目里,包括错误监控、埋点等行为
- 子项目原本需要加载的公共部分(第三方依赖),全部由主项目调度,配合 webpack 的 externals 功能通过外链的方式按需加载,这样一来每个子项目的 dist 文件里就只有子项目自己的业务代码
简单实践
实践下 qiankun 和 micro-app 方案。
qiankun 应用步骤
- 配置微应用的打包方式
- 修改 output 输出格式
- 修改 devServer 设置为允许跨域,因为主应用通过 fetch api 加载子应用资源
- 配置微应用入口模块,提供 mount、unmount、bootstrap 钩子,用于渲染子节点和卸载子节点
- 主应用注册子应用:name/entry/container/activeRule
- 主应用启动
micro-app 启动方式更简单
- 子应用配置 devServer 允许跨域
- 主应用通过 micro-app 自定义 Element 注册子应用,完全可以把 micro-app 当做一个普通节点使用
- 主应用启动
核心步骤都十分简单,但通常会踩到一些坑
- 样式冲突
- 静态资源加载失败
- 热更新失效
扩展:微模块
如果你是出于物理隔离代码,复杂度可控,也许可以考虑通过模块,而不是服务。
微模块与 UI 组件的区别
- UI 组件是一种单体的封装;而微模块是一种资源的集合。
- UI 组件为复用而生,可能在多处被实例化,通常不包含具体的业务逻辑;而微模块并不追求通用性,它包含具体的业务逻辑,通常只需要初始化一次。
- 微模块可能包含多个 UI 组件、视图、Model 等
思考:微前端和业务组件的区别在哪里?
- 将子应用封装成 npm 包,通过组件的方式引入,在性能和兼容性上是最优的方案,但却有一个致命的问题就是版本更新,每次版本发布需要通知接入方同步更新,管理非常困难。
扩展:system.js
single-spa 基于 system.js 实现远程加载子项目。
system.js:通用的模块加载器,在浏览器端实现对 commonjs、umd、esm 等各种模块加载,支持 import maps,原生 esm 替代品。
资料
社区已有方案
相关文章