ICE 在微前端的探索
ICE 在微前端的探索
天下大事,分久必合,合久必分。盘古开天之时,还未有程序猿这一物种;待到秦始皇一统天下,一己之力完成一个应用,也无前后之分;辗转至三国乱世,群雄逐鹿,前后端彼此剑拔弩张,常叹“***要是起来了,还要你们干嘛?”。然而,沧海桑田,终归尘土。
那么前后端分离模式下,到底有哪些痛点?笔者就自己浅薄的经历,尝试进行一些解读,如有偏颇,还望指正。
痛点
SPA
作为当下流行的前端技术架构,SPA(Single page application) 以更好的用户体验,更强的前端自主控制能力,以及完善的基础设施(ice-scripts
、create react app
、next
、umi
等),深受前端开发者的喜爱。
我们从一个实际场景出发,简述下中后台场景下可能遇到的问题:一年前临时接到一个任务,要求完成一个酒店商家订单管理系统,这里以飞猪 EBK 系统为例,大致包含四个二级菜单,主要是列表展示、详情页展示以及搜索和确认入住等简单的交互处理。我们快速通过 (SPA
+ ice-sctipts 1.x
+ react 15.x
+ fusion 0.x
)配套方案完成开发。最终效果如下:
项目能基本满足商家的交易管理需求,稳定运行了 5 个月。随着商家入驻数量的不断增加,新需求大量涌入:商家希望房价和房态有更多自主控制权、官方新增了“信用住”的爆点功能、部分商家有多个店铺需要快捷切换、需要支持国际化等等。然而,此时项目初创团队有别的高级别业务缠身,不能确保 100% 投入,同时因为整体技术体系的敲定,申请了外包资源协助开发;商家营销类功能、数据分析等以往都是另一个团队负责维护;但整体项目迭代要求一个月完成开发上线。此时,就需要跨团队协作。
SPA 架构就必须让所有协作团队在一个代码仓库下开发。因此就需要建立一系列制度进行保障:新建分支规范、代码合并规范、commit 信息填写规范、code Review 规范等等。同时,fusion 1.x
、react 16.x
等也在进行迭代了,因为项目时间紧、稳定性要求高等原因,没有哪个独立小组敢尝试进行基础组件等的升级。而且每次 utils
、request
等公共工具的修改,都需要对全链路进行测试回归。
虽然折腾,但经过一系列的快速迭代,终于让整个项目如期上线了。此时效果示意图如下:
时光飞逝,又过了一年多,此时 ice-scripts
已经升级了 2.x
、react 16.x
已经趋于主流、hooks
横空出世、fusion 1.x
也成为了官方主推版本。系统经过一系列修修补补,项目主创人员早已不再经手,因为稳定性和经手人员熟悉度等等原因,项目仍保持最初的技术体系,同时如下代码随处可见:
// I don‘t know why, but it works
if (isVipOne) {
if (!isVVip) {}
} else if (isVipTwo) {
if (!isVVip) {}
} else {
//...
}
终于,整个系统一步步沦为一个又老又旧的项目,稳定运行却让新接手人怨言不断。
SPA/MPA 混合
那么,如果我们尝试换一种技术架构,当我们预计到项目可能涉及跨团队协作时,用 SPA + SPA/MPA 混合架构时会如何?
同样以上面的事例为例,在二次迭代时,我们选择跨团队多应用模式进行开发,不同应用使用不同的 git 仓库,走独立的构建、部署流程,公共的 Header
采用统一的 npm
包进行处理,Menu
涉及跨应用跳转统一采用 <a />
标签进行强制跳转。项目结构如下。
此时,不同应用之间可以做到业务解耦,同时新应用开发可以时时紧随技术体系的迭代,甚至可以支持 vue
和 react
跨技术栈“共存”。但同时,也会有如下问题:
- 跳转体验差,本质是多个看起来类似的前端应用,每次跨应用跳转都会引起全页面刷新,体验瞬间回到了后端路由时代
- 体验一致性成本高,以
vue
+element
和react
+fusion
实现为例,完全用element
体系实现 跟fusion
体系一模一样的视觉风格、操作体感,兼容改造成本较高 - 跨应用通信成本高,万一遇到需要做数据通信场景,前端只能依赖 url 等进行简单的数据通信,或者依赖后端接口
- 公共内容迭代成本高,以
npm
包迭代为例,针对需要在Header
层增加新入口的需求,示意图如下。需要先完成Header
基础组件升级,接着通知所有关联应用进行升级、构建、发布等流程,此时又涉及到跨团队等问题,同时随着不断迭代,关联应用数量不断增加,整体步调一致性的成本也较高
iframe
因为多应用模式下的问题,主要集中在公共内容的维护和整体跳转体验上。因此,公共内容统一管理,子应用通过 iframe 方案加载渲染的方案就很容易进入我们的视线。
iframe 架构示意图
iframe 是纯天然的能实现 100% 隔离的容器,同时保留了混合应用架构的业务解耦、独立开发部署、技术栈无关等优势,又通过公共应用解决了公共内容(Header
、Menu
)等的维护问题,在应用切换时,能保障公共内容不会重复渲染。然而,iframe 本身也引入了不少新的问题:
- 性能问题,iframe 复用链接池机制,容易在切换应用时给人页面卡顿的感受
- 因为完全的运行环境隔离,给数据同步增加了一些成本,通常用 postMessage 解决复杂通信的问题,然而对路由时时同步(保障页面刷新时内嵌应用正常定位)、内容高度上报(解决双滚动条问题)、登陆信息同步、弹窗遮罩等问题的解决都需要较大成本
- 都 9102 年了,还在用 iframe ?
研发流程
HTML 之殇
前后端分离的结构下, HTML 的部署却是一个让人纠结的问题。SPA 架构下,HTML 自身已非常轻量,作用基本如下:
- 确定域名
- 生成一个固定的 DOM 节点
- 确定相应 js、css 的资源加载和执行顺序
同时,常用的部署 HTML 的几种方式:
- 把整个经过 webpack 构建的产物放到静态资源服务器上,通过 nginx 进行代理,nginx 根据不同的接口分发请求路径到对应的后端服务上,然后自行处理域名等绑定逻辑,每次重新部署需要重新上传资源,前端可以自洽,但难免感觉这些事不那么“前端”
- HTML 移交后端部署,前端只负责把构建产物上传 CDN,此时不需要担心接口跨域问题,但需要后端负责处理动态版本号(解决每次前端发布都需要后端重新部署问题);Browser 路由模式下,需要后端支持 fallback(解决刷新 404 问题)。对前端很友好,但后端同学容易发飙:都前后端分离了,为什么这东西前端不能自己解决?
- 前端通过 node 起简单的静态资源服务器,同时构建产物上传 CDN,整个过程自洽,但需要增加域名维护、接口转发(nginx / node)、服务器维护等成本
发布顺序依赖
每次有非 UI 型需求变更,都需要前后端同时开发,开发过程各自独立,但发布/回滚过程却有明切的依赖关系。如果前后端步调不一致,轻则异常接口报错,重则应用崩溃(比如前端异常情况处理不到位的情况)。为了防止这种情况,需要双方协商一致,同时制定完善的代码提交、分支创建合并等规范,比如永久保障 master / release x.x 分支的代码是两边确认步调一致的最新代码等。
业务迭代
产品经理经过层层考虑,终于向研发提出了新的需求:参照 xx 页面的内容,新开一个页面,这个需求很好实现的吧?就是将 xx 页面的代码 copy 出来删减一下,然后联调下新接口就好了。然而,SPA 变成了又老又旧的项目,前端 copy 以及删减代码的成本远比自己重新写一份要高;多应用模式下,位于不同应用下的两个页面,技术体系可能存在差异(react 15.x -> react 16.x 、fusion 0.x -> fusion 1.x),跨应用级别 copy 也容易拔出萝卜带着泥。因此基本上每次的协商结果,技术统计耗时总是会比产品预估稍高一些。
解决方案
到这里,有读者可能会有疑问,微前端作为一种前端架构能解决以上问题吗?笔者认为:微前端本身确实只是一种前端架构,聚焦在前端资源整合和体验优化上;但同时,微前端改造也是一个契机,将各个细粒度的小型中后台有机整合成为一个一体化解决方案,这个过程中结合对研发模式的思考,带动一系列配套设施(需求评审、研发流程、发布校验、数据监控等等)的升级,最终产生的微前端效应,才是对以上痛点的有效解决方案。
笔者以 ice 在微前端的架构 icestark 结合上层抽象产出的小二工作台方案,来尝试说明我们对以上痛点的思考。
什么是 icestark
icestark 是基于前端路由改造的前端框架,兼容 Browser/Hash 两种路由模式,支持不同技术体系(ice-scripts、fusion、antd、umi、cra等)甚至不同技术栈(vue、angular、react等)前端应用的同屏渲染,共享运行时,保留 SPA 级用户体验,解决 iframe/混合应用架构下的性能、体验和通信问题。
特性和应用架构
icestark 包含以下特性:
- 基于前端路由,模块化管理多个独立应用
- 不同应用独立仓库、独立开发与部署
- 统一管理页面公共内容(Common Header、Common Sidebar 等)
- 支持子应用 0 改动嵌入
基于 icestark 的应用,整体架构如下:
icestark 架构图API 设计理念
icestark 原理上不受框架限制,考虑 react 体系是最受欢迎的技术栈之一以及集团内绝大部分使用的情况,我们在设计相关 API 的时候,基本复用了 react-router 的规范。虽然对子应用使用上不受限制,但框架应用层需要使用 react。后续考虑对非 react 体系推出相应的 icestark 框架(icestark-vue、icestark-angular等),如有相关建议,欢迎 在 issue 中参与讨论 。
组件和方法props代码示例:
<AppRouter>
<AppRoute
path={['/', '/message', '/about']}
basename="/"
exact
title="通用页面"
url={[
'//unpkg.com/icestark-child-common/build/js/index.js',
'//unpkg.com/icestark-child-common/build/css/index.css',
]}
/>
<AppRoute
path="/seller"
basename="/seller"
title="商家平台"
url={[
'//unpkg.com/icestark-child-seller/build/js/index.js',
'//unpkg.com/icestark-child-seller/build/css/index.css',
]}
/>
<AppRoute
basename="/waiter"
path="/waiter"
title="小二平台"
url={[
'//unpkg.com/icestark-child-waiter/dist/js/app.js',
'//unpkg.com/icestark-child-waiter/dist/css/app.css',
]}
/>
</AppRouter>
我们希望,icestark 在能力边界的设定上也跟 react-router 保持一致(react-router 通过前端路由的方式管理页面,icestark 通过前端路由的方式管理子应用)。保持 icestark 是一个轻量可靠的解决方案,同时又有强大的定制能力,通过生态共建的方式,满足各种复杂场景的需求。
通信方案
我们推出了基于原生浏览器的技术栈无关的通信方案 @ice/stark-data
,核心只有两个 API:store
和 event
。简单示例如下:
import { store, event } from '@ice/stark-data';
// 框架应用
store.set('language', 'CH'); // 设置语言
store.set('user', { name: 'Tom', age: 18 }); // 设置登录后当前用户信息
event.on('message', () => { // 监听刷新信息事件
// 调用刷新信息接口
});
// 子应用
// 监听语言变化
store.on('language', language => {
console.log(language);
// 切换成中文回调
// ...
}, true);
// 获取当前用户
const currentUser = store.get('user'); // { name: 'Tom', age: 18 }
// 触发刷新信息
event.emit('message');
子应用入口
我们希望大部分场景直接使用 js
和 css
的入口 url
,同时通过 AppRoute
的 rootId
配置通知 icestark 自动创建相应 DOM
节点,此时子应用甚至不需要关心 HTML 的部署问题,也不需要考虑资源访问的跨域问题,只需要发布对应 cdn 资源即可。示例代码如下:
<AppRouter>
<AppRoute
path={['/', '/message', '/about']}
rootId="root"
basename="/"
exact
title="通用页面"
url={[
'//unpkg.com/icestark-child-common/build/js/index.js',
'//unpkg.com/icestark-child-common/build/css/index.css',
]}
/>
</AppRouter>
同时,针对老旧的系统,比如后端 vm
模版的 jQuery
系统;以及希望进一步简化 url
配置的需求。推出了配置 htmlUrl
作为子应用入口。示例代码如下:
<AppRouter>
<AppRoute path="/" htmlUrl="//icestark.com" />
</AppRouter>
微前端衍生平台
笔者以小二工作台为例,简述对研发流程痛点梳理的一种解决方案。小二工作台是基于 icestark 的微前端架构改造的统一平台,同时包含了对以往研发模式、业务迭代的反思和沉淀。
研发管控
通过统一研发平台管理,从需求分析、需求拆解、到研发流程管控、发布校验等进行抽象,将离散的前后端研发流程重新整合起来,平定三国之乱,解决研发流程的问题。
研发管控流程图业务赋能
技术架构的升级,也给了业务迭代的发展提供了新的思路。这里以小二工作台的 SOP 流程为例,简单展示下微前端技术架构下的页面复用能力。
SOP 流程示意图
图中每一个节点,比如“活动选品”、“导入选品”、“节点运营”等都来自不同子应用的不同页面,这些页面可以通过流程引导的形式进行随机组合,同时可以方便的进行数据同步。基于此,极大提升了页面复用的能力,进而提升了业务迭代的效率。
生态共建
以上内容,就是 ice 在微前端整体解决方案上的探索和尝试。同时 icestark 会以不断降低使用成本为宗旨推出一系列官方推荐的解决方案,如:全局的权限管理、插件机制、配置懒加载机制、路由黑白名单等等。同时也欢迎社区参与共建,遇到任何问题,欢迎随时在 GitHub 提交问题。
相关链接
图片来自摄影世界