Better

Ethan的博客,欢迎访问交流

微前端简单思考

约两年前就有了解这个话题,当时背景是前端的旧项目代码量比较大,开发时编译速度很慢,极大了影响开发体验,正逢技术转型以及新增产品业务线,于是拆分成了多个项目进行开发,但项目之间跳转、数据传递以及彼此交互时,就陷入了困境。

为什么

你是否需要

  • 避免巨石应用,巨石应用可能开发效率低下、构建速度变慢
  • 你是否有古老项目需要和新项目同时存在或增量升级
  • 不同的前端团队,可以独立选型、独立开发和部署他们的应用,满足自己的迭代需求

微前端核心目标

  • 多个前端子应用之间的协作与切换,不应该产生不可接受的用户体验下降
  • 运行时隔离与共享: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 应用步骤

  1. 配置微应用的打包方式
    • 修改 output 输出格式
    • 修改 devServer 设置为允许跨域,因为主应用通过 fetch api 加载子应用资源
  2. 配置微应用入口模块,提供 mount、unmount、bootstrap 钩子,用于渲染子节点和卸载子节点
  3. 主应用注册子应用:name/entry/container/activeRule
  4. 主应用启动

micro-app 启动方式更简单

  1. 子应用配置 devServer 允许跨域
  2. 主应用通过 micro-app 自定义 Element 注册子应用,完全可以把 micro-app 当做一个普通节点使用
  3. 主应用启动

核心步骤都十分简单,但通常会踩到一些坑

  • 样式冲突
  • 静态资源加载失败
  • 热更新失效

扩展:微模块

如果你是出于物理隔离代码,复杂度可控,也许可以考虑通过模块,而不是服务。

微模块与 UI 组件的区别

  • UI 组件是一种单体的封装;而微模块是一种资源的集合。
  • UI 组件为复用而生,可能在多处被实例化,通常不包含具体的业务逻辑;而微模块并不追求通用性,它包含具体的业务逻辑,通常只需要初始化一次。
  • 微模块可能包含多个 UI 组件、视图、Model 等

思考:微前端和业务组件的区别在哪里?

  • 将子应用封装成 npm 包,通过组件的方式引入,在性能和兼容性上是最优的方案,但却有一个致命的问题就是版本更新,每次版本发布需要通知接入方同步更新,管理非常困难。

扩展:system.js

single-spa 基于 system.js 实现远程加载子项目。

system.js:通用的模块加载器,在浏览器端实现对 commonjs、umd、esm 等各种模块加载,支持 import maps,原生 esm 替代品。

资料

社区已有方案

相关文章



留言