阿里本地生活微应用那些事
一、什么是微前端
在介绍本地生活的微应用方案之前,先简单介绍下最近几年比较火的微前端(Micro-Frontends)。什么是微前端,简单来讲,就是一种由独立交付的多个前端应用组成整体的架构风格。具体的,就是将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以独立运行、独立开发、独立部署。 微前端本质就是将微服务的理念应用于浏览器端,来解决巨石应用维护困难的一种实现方案。如果想了解更多的微前端的内容,可以参考微前端-最容易看懂的微前端知识 。
二、商家Saas的微应用方案
1、本地生活Saas中后台的业务特点
微应用方案是为了解决我们业务场景中的巨石应用风险而提出的,那么先简单给大家介绍下本地生活商家Saas目前的业务形态。
1.1、业务特点总结
- 多功能多渠道投放,以全渠道CRM为例,不仅会投放到客如云商家中心,还会投放到口碑商家中心等多个平台,目前通过Iframe内嵌方式进行投放。
- 多团队多人协作,例如CRM中的不同模块分别是由不同的团队进行维护,最终投放时则会作为一个完整的产品进行使用。
- 多版本多权限控制,中后台系统都有比较严格的权限控制,对于商户的产品,也会有响应的版本问题,例如会针对不同的付费商户,提供不同的版本,功能集合存在差异,相同功能存在差异等情况
基于以上业务特点,决定了传统的研发模式已经不能够满足我们的需求,以多渠道投放为例,通过Iframe内嵌方式进行投放,用户体验差,系统一致性也不好。权限上没有将业务逻辑和业务控制进行有效的隔离,权限易混乱难维护。另外,商家Saas业务众多,而且还在不停增加新的业务,业务应用的代码文件越来越多,开发,构建,部署越来越慢,开发体验持续下降,维护成本也越来越高。
1.2、业务技术分析
现有工程的状况,决定了我们要实现的目标:
- 业务解耦: 将一个超大的中后台系统的业务进行解耦,按照业务领域拆分为不同的业务模块,各业务模块独立研发,独立部署。
- 集成体验: 业务系统以及业务模块之间定义投放规范,确保模块多投。在此基础上,解决Iframe集成的各类体验及性能问题,并以此契机,落地各散落系统的交互视觉体验一致性规范。
- 逻辑解耦: 隔离日常的业务逻辑和商业的版本逻辑,权限逻辑等,权限管控到页面级别。
- 灰度能力: 提供PC端场景的统一灰度能力,保障业务更稳定的推进落地
1.3、业务方案选择
本地生活内部存在多种微前端解决方案,它们在管理模型、生命周期等都存在一些差异。
IceStark提供了一套React风格的微前端方案,QianKun也提供一套简单易用的微前端方案,两者考虑的是如何将不同技术实现的业务整合在一起。而我们要考虑的是,不仅要整合在一起,而且要像一个站点一样,所有页面受到统一的管控,并配套全流程的研发发布流程,实现业务独立研发独立发布和互通互投。结合上面的业务特点,在对现有方案进行比较之后,决定建设基于singleSpa的具有更细粒度控制能力、更强扩展性、子应用可互投的微应用方案 WeApp,它主要在以下三个方面进行了加强:
- 三级模型: 除主应用,子应用外,针对全站页面进行管控,增加页面级。这样就可以在主应用中,在访问页面前,统一做前置判断。
- 扩展机制: 像上面说的前置判断、Loading、埋点等功能,都不属于某一个具体业务的逻辑,如何以不侵入的方式扩展各业务的逻辑?这就需要通过框架的扩展能力来实现。
- 沙箱容器:在业务多投、互投,以及多版本技术栈并存的场景下,对于在同一个页面的上下文中,这二者会产生的污染,主要会是:全局变量、事件监听、定时器、基础库样式等。所以,需要提供JS沙箱和UI沙箱,二者共同构成应用运行的容器。
2、微应用标准
2.1、主应用
- 子应用列表数据模型:完整JSON配置,或者配置信息URL
- 路由类型支持:Browser或Hash
- 渲染器设定:子应用配置信息制定对应的渲染器,主应用扩展对应名称的渲染器
- 子应用列表加载方式:
- 硬编码内置到父应用代码里
- 服务端输出到模板上,父应用去获取
2.2、子应用
- 子应用配置规范: 描述子应用或模块的应用注册信息,以JSON形式存储于独立配置文件app-config.json。
- 子应用注册
- 子应用注册原则,子应用以页面为单位进行注册,SPA只注册一次,多页面场景需要注册多次
- 路由规则,默认子应用名称为路由前缀,再加页面path构成完整路径,例如子应用myTestApp,页面path为/my_test_page,则页面路径为/myTestApp/my_test_page
- 子应用获取,默认子应用页面为System Module形式的组件,通过SystemJS加载
- 子应用渲染
- 通过父应用的render渲染页面,render可以自定义
- render规范,两个函数,
- 一个页面渲染函数,mount(component, container, props),传递props给子应用
- 一个页面卸载函数,unmount(container, props)
- 子应用构建规范
- 构建脚本:遵循集团统一工程构建工具 build-scripts。
- 构建产物:子应用构建产物分两类:
- 一类是各个页面对应的静态资源
- 一类是子应用配置信息对应的app-config.json文件
- 路由规范
- 前缀约定
- 子应用内跳转
- 子应用间跳转
2.3、生命周期
主应用在加载并运行子应用时,会经过多个环节(路由变化、页面卸载、资源加载、页面挂载),这些环节构成了子应用生命周期,并由主应用进行管理。
- 路由切换
- 定义:在地址栏输入URL,或者由用户操作浏览器的前进、后台,或者代码通过pushState、repalceState等API触发路由变化;
- 应用场景:可以进行404、权限等判断,也可以切换页面layout等
- 资源加载
- 定义:通过异步加载子应用及其页面静态资源
- 应用场景:资源加载统计,如耗时等
- 页面渲染/卸载
- 定义:路由命中后,子应用或页面渲染;页面切换后,上一个子应用或页面卸载
- 应用场景:页面统计,如页面埋点、渲染时长、访问时长等
- 异常处理
- 定义:生命周期异常收集与处理
- 应用场景:可以添加统一错误页面等
2.4、扩展能力
- 生命周期钩子: 针对各个生命周期节点可以添加相应的钩子,以便扩展功能,主要有:
- beforeRouting,路由切换前
- beforeLoad/afterLoad,资源加载前后
- beforeMount/afterMount,页面渲染前后
- beforeUnmount/afterUnmount,页面卸载前后
- onError,生命周期异常发生时
- 特殊页面: 添加扩展页面,如404/403/500等可以自定义页面
- 扩展作用域: 扩展工作范围,不同的扩展其工作范围不同,如404等扩展应该是全站起效,而像tabs功能,则只有部分业务场景(如报表场景)才需要。
2.5、样式隔离规范
- 开发期样式隔离: 使用 CSS Modules 方案管理样式,无论是框架应用还是子应用,直接通过 CSS Modules 的方案管理自身可控的样式,这样基本杜绝了两者样式冲突的问题;
- 开发规范: 子应用应避免产生全局样式;
- 通用样式沙箱: 样式通过沙箱机制运行,目前主要可以通过ShadowRoot来实现样式隔离,其中需要做以下的一些处理:
- 创建ShadowRoot,对渲染容器挂载Shadow DOM
- 挂载样式,通过异步获取样式内容(考虑到基础库样式需要做调整,所以异步加载),通过style标签注入到渲染容器中
- 基础组件库兼容处理,各类基础组件库中的popup、modal组件会脱离渲染容器在body中创建容器,需要基础库提供相应的方法,如antd@3中可以通过ConfigProvider指定getPopupContainer。
2.6、脚本隔离规范
- 开发规范: 子应用避免改变全局状态:比如改变全局变量 window/location 的默认行为,通过 document 操作 Layout 的 DOM,这些本身都是一些不推荐的做法;
- 沙箱机制: 脚本通过沙箱机制运行;
- 脚本沙箱: 对于需要同一基础库的不同版本的JS共同运行在一个页面上时,对于没有全局变量污染的基础库可以通过和业务代码一并打包解决,而对于会造成污染的基础库或者希望减少业务资源包大小的场景来说,则需要通过沙箱来加载JS。沙箱工作的大致流程为:
- 创建沙箱
- 获取JS内容
- 在沙箱中运行
- 返回执行结果
2.7、通信能力
- 状态共享: 一般是全局状态、数据的共享,如登录态信息等
- 事件机制:
- 事件监听响应,提供父子应用、子子应用间通讯的能力
- 命名空间支持,按命名空间创建通信客户端,提供按空间的消息管理,避免消息冲突
- 消息暂存: 由于各个应用都是异步加载执行,消息监听会后置于消息分发,所以需要对于已经分发的消息进行暂存,以便新加入的监听可以及时获得消息
3、微应用框架
为了更好的支持在全站级别对页面进行管控,框架在singleSpa的基础之上主要做了几大扩展,包括:管控模型、全局能力、生命周期及切面。同时,在使用方式上,为了适应新应用创建和老应用接入两种场景,分别提供了API和Loader两种使用模式。
3.1、三级管控模型
一般的微前端方案仅提供二级模型,即主应用(或叫基座应用)和子应用。但是,在将各个子应用组装成一个站点,尤其是中后台站点时,权限、访问控制是强诉求,所以需要扩展管控模型到三级,即主应用、子应用和页面。这样可以直接针对页面访问进行管控。大致的实现方式就是,将向singleSpa注册的应用从子应用调整为页面。
3.2、生命周期
除了管控模型的调整,同时还需要对生命周期进行扩展,因为在权限校验不通过时,需要阻止用户访问正常页面,而singleSpa是通过路由匹配的方式来命中应用的,所以需要扩展路由跳转这个生命周期。这样就形成下方的7个生命周期:
- Routing:路由跳转
- Bootstrap:应用启动
- Load/Unload:页面静态资源加载
- Mount/Unmount:页面挂载渲染
- onError: 错误处理
3.3、扩展能力
拥有了上面的生命周期,再要实现管控能力,就只需要基于生命周期进行切面,提供钩子以便定义扩展。
- 生命周期及钩子
生命周期 | 钩子 | 说明 |
---|---|---|
路由切换 | beforeRouting | 路由切换前 |
页面静态资源加载 | beforeLoad | 资源加载前 |
afterLoad | 资源加载后 | |
页面渲染 | beforeMount | 页面渲染前 |
afterMount | 页面渲染后 | |
页面卸载 | beforeUnmount | 页面卸载前 |
afterUnmount | 页面卸载后 | |
页面出错 | onError | 页面出错 |
- 定义扩展
字段 | 说明 | 是否必填 |
---|---|---|
hookName | 扩展名称 | 是 |
hookDesc | 扩展描述 | 是 |
config | 扩展配置 | 否 |
扩展名称:hookName 不能重名,内部定义的扩展有:skeleton、pageContainer、loading、404、403、500。
扩展描述:扩展描述是一个扩展的实体内容,里边包含在哪个生命周期阶段执行什么逻辑,以及定义扩展需要展示的页面和是否展示的逻辑。其中,页面可以通过config.page进行配置,配置内容和子应用的页面相同。
- 使用扩展
将自定义扩展放入主应用项目的src/hooks目录中,并在src/hooks/config.ts中引入。
也可通过通过框架提供的 configHooks/usingHooks API自行注入:
import {
...
configHooks,
...
} from '@saasfe/we-app';
...
import hookConfigs from './hooks/config';
...
// 配置扩展
// usingHooks(hookConfigs)
configHooks(hookConfigs);
复制代码
- 内置扩展
扩展名称 | 功能说明 |
---|---|
skeleton | 骨架扩展,用于动态生成站点的基础layout结构 |
pageContainer | 页面容器扩展,用于获取或创建页面容器,可以与skeleton配合使用 |
loading | 页面加载中扩展,用于在路由匹配后加载静态资源时显示loading |
404 | 未找到页面扩展,页面未找到时显示相应页面 |
403 | 无权限页面扩展,访问页面前校验权限,无权限则展示无权限页面 |
500 | 出错页面扩展,页面出错(资源加载出错、渲染出错等)时展示出错页面 |
4、微应用研发流程
微应用的研发流程是基于Wap平台的,主要包含以下几个阶段:
4.1、应用创建
- 主应用创建
- 子应用创建,并选择关联的主应用
4.2、本地开发
- 迭代创建:根据业务需求,在不同的应用上创建不同的迭代
- 开发调试:
4.3、发布阶段
- 日常
- 预发
- 灰度
- 正式
4.4、回滚
使用wap自身的回滚功能回滚至上一个版本
5、总体方案
后续会针对微应用展开各模块的详细介绍以及实现,如果大家感兴趣的话,欢迎关注!
三、作者简介
少楚,阿里本地生活-技术中心-商家SaaS技术前端研发工程师
开忻,阿里本地生活-技术中心-商家SaaS技术前端研发工程师
四、招聘信息
感兴趣的同学可将简历发送至: shihong.xsh@alibaba-inc.com(邮件主题请注明:SFE招聘)
五、参考链接
- Micro Frontends, extending the microservice idea to frontend development
- 微前端-最容易看懂的微前端知识
- 微前端的那些事儿
- Single-Spa:最早的微前端框架,兼容多种前端技术栈
- Qiankun:基于Single-Spa,阿里系开源微前端框架
- Icestark:阿里飞冰微前端框架,兼容多种前端技术栈
- TC39 proposal-shadowrealm
- AMP worker-dom
- 阿里本地生活微应用CDN型主应用