效率前端微应用推进之微前端研发提效|得物技术
目录
一、背景
1. 业务背景
2. 名词解释
二、研发费力度痛点
1. 费力点 1
2. 费力点 2
3. 费力点 3
三、技术调研
1. Shared 通信
2. Mock 父应用环境
3. Chrome 代理插件
四、Chrome 代理插件
1. 产品设计
2. 技术设计
五、推进情况
1. 项目覆盖率
2. 研发效率
3. 用户反馈
六、总结
一
背景
业务背景
得物效率前端所在的效率工程为提升企业协作效率而生,面临大量的 PC 侧的中后台应用场景。
在之前的微信公众号《得物效率前端微应用推进过程与思考》中详细介绍了效率前端推进微应用落地的思路和部分效果。
这篇文章将着重介绍得物效率前端微应用推进中,微前端的研发效率遇到的挑战和解决方案。
名词解释
微应用
「微应用」是得物效率前端内部称谓,是一个基于“monorepo & 微前端 & 基座与业务分离”的、包括“文档 & 工具”的一套体系化降低研发成本和提升用户体验的技术产品。
微前端
「微前端」是得物效率前端微应用推进的重要一环,尤其是父子应用技术栈不同时,利用 iframe / qiankun / wujie / micro-app
等工具进行微前端化改造,能显著增强业务扩展性。
基座应用/父应用
微前端中,基座应用(父应用)、子应用是常见概念,本文描述中,“基座应用”又名“父应用”,为简化文案,“基座应用与子应用”也被称为“父子应用”。
浏览器插件/扩展
chrome://extensions/
页面中对第三方工具称为“扩展”,在本文语境下,又称为“插件”。
二
研发费力度痛点
就研发效率而言,微前端在团队多个业务落地后,面临研发过程费力度高的问题。
费力点1
本地开发时,从启动 1 个本地服务,变为需启动 2 个本地服务:
大大多数情况下,项目需要启动 2 个本地服务(基座应用和子应用)才能进行日常开发,因为子应用通常依赖基座应用透传一些依赖数据。
而非微前端场景下,启动 1 个应用就可以了,这反而引入了降低了研发效率。虽然微前端方案更注重业务效率,但研发效率也是必须要考虑的。
如果电脑性能一般的话,卡顿问题就随之而来了。
费力点2
基座应用本地代码需要做适配性改造:
如父应用需要区分本地环境和生产环境类似这样的代码,虽然代码量不大,但还是需要关注的:
// 基座代码
if (isLocal) {
return <microapppage src="localhost://8000/a/b/c" />
} else if (isProd) {
return <microapppage src="https://www.abc.com/a/b/c">
}
费力点3
频繁无规律刷新:
在 Qiankun 微前端框架且本地开发环境下,基座应用与子应用页面均需要 WebSocket 与其自身本地服务进行通信。
在同时启动基座与子应用的本地服务后,修改子应用代码以及偶发的,页面会触发 Reload,而不是局部更新,开发体验很差。针对这个问题我们做了一些分析。
HMR 的热更新逻辑
本地开发过程会启动 Webpack-Dev-Server 服务,其会监听业务文件变化,浏览器通过 WebSocket 与 Webpack-Dev-Server 进行通信。
当发现文件内容改变时 Webpack-Dev-Server 会根据更新的文件内容生成 Hash 信息传递给浏览器,如图:
当浏览器收到信息时,会根据收到的信息和配置进行判断是刷新操作还是热更新操作。热更新时,Webpack-Dev-Serer 通过 Jsonp 拉取最新的 JS 模块代码,并进行模块替换,如图:
若此过程异常,则会降级为页面刷新。
无规律刷新原因分析
基座应用(端口 8010)热更新时返回的文件 Json 和 JS 文件(分别是 **:8010/update.json 和 **:8010/update.js)内容如下:
但在嵌套在基座应用中的本地子应用(端口 8020)热更新时,两个同类文件并没有返回内容(**:8020/update.json
和 **:8020/update.js
)
若浏览器单独打开两个文件的地址(**:8020/update.json
和 **:8020/update.js
),有内容返回。
但是在基座与子应用嵌套的情况下,子应用的任何请求(包括 update.json
/ update.js
)都会被基座应用代理,开发环境下很容易出现子应用的更新探针请求被基座代理后,出现内容丢失的情况。
子应用 HMR 逻辑检测到更新探针请求内容异常,局部更新失效,降级为页面刷新。
即使该问题解决,微前端应用开发者依然面临同时启动 2 个应用才能启动开发的问题,所以我们不过度投入精力关注这个问题。
三
技术调研
解决「默认情况下,父子应用需要分别独立启动,并指定关联关系」的问题,最好的方式是回归到非微前端场景下的常规开发方式,即只启动 1 个应用进行本地开发。
通常情况下,我们开发的是子应用(也就是业务页面),那先实现子应用单独启动即可开启项目开发吧,以下是面向该需求的技术调研。
Shared 通信
Shared 通信方案的原理是,主应用维护一个状态池,通过 Shared 实例暴露一些方法给子应用使用。
同时,子应用需要单独维护一份 Shared 实例,在独立运行时使用自身的 Shared 实例,在嵌入主应用时使用主应用的 Shared 实例,这样就可以保证在使用和表现上的一致性。
Shared 通信方案要求父子应用都各自维护一份属于自己的 Shared 实例,同样会增加项目的复杂度。同时,在子应用独立运行时,Shared 只能获取本地缓存数据,无法真正做到完全独立于子应用运行。
Mock 父应用环境
也就是在子应用中模拟父应用嵌套环境,提供一个独立的模拟父应用的组件,封装了 Layout 布局、权限、用户信息等,并且具备必要的父子通信能力,子应用调用该 Mock 组件,独立启动以后进行日常开发。
这个方案和 Shared 通信有类似之处。
用户体验
// 这是子应用代码
import { MicroLayout } from '@abc/components';
// 组件内使用
<MicroLayout title="Layout 内容">
{children}
</MicroLayout>
流程设计
其他
Chrome 代理插件
也就是通过 Chrome 插件,将线上子应用 URL 代理到本地代码。
用户体验
其他
四
Chrome 代理插件
产品设计
“Chrome 插件代理子应用到本地代码”以提升微前端研发效率,该浏览器插件需要具备以下核心能力:
规则灵活配置。插件实现了 from 和 to 的地址映射规则配置表单。
缓存能力。用户关闭浏览器/电脑后再次打开,仍然能够使用之前保存的代理规则。 快捷操作。为常用产品配置内置规则,一键即可启动,非常方便。 实时显示。需要实时显示代理规则的生效情况,方便用户确认哪些规则正在生效。
技术设计
Proxy 和 Popup
功能流转
无感更新(Seamless Update)
这么做需要在 chrome 插件的 csp 安全策略配置中加入 cdn 域名白名单,允许插件访问外部 cdn 资源。
{
...
"permissions": [
"webRequest", // 允许浏览器开放http请求劫持的功能
"storage", // 允许使用浏览器缓存
"activeTab",
"background",
"webRequestBlocking",
"<all_urls>"
],
// 允许特定域名可以访问的安全策略
"content_security_policy":"script-src 'self' https://cdn.xxxxx.com 'sha256-G7YAg/PQDo8GYc/fSYvWtXP98kXS7iqT7K4QZgyhUIE='; object-src 'self'",
"content_scripts": [
{
"matches": ["*://*.xxxxxxx.net/*"],
"js": ["contentScript.bundle.js"]
}
]
}
// ==========attention===========
//v2配置,v2版本中可以配置script等通过外部引入,这个content_security_policy配置参数不加或者加上之后相应的值填none
"content_security_policy": "script-src 'none'; object-src 'none'",
//v3配置,v3版本中安全政策配置script引入等信息,都必须填写self,即只允许script标签引用当前插件内部文件,不允许引用外部链接,如果不填写self的话,插件添加到扩展程序时会报错
"content_security_policy": {
//原文:此政策涵盖您的扩展程序中的页面,包括 html 文件和服务人员;
"extension_pages": "script-src 'self'; object-src 'self'",
//原文:此政策涵盖您的扩展程序使用的任何[沙盒扩展程序页面](https://developer.chrome.com/docs/extensions/mv3/manifest/sandbox/)。;
"sandbox": "sandbox allow-scripts; script-src 'self'; object-src 'self'"
},
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Popup</title>
<script>
var scriptEl = document.createElement('script');
scriptEl.defer = "defer";
scriptEl.src = "https://cdn.xxxxxxxxxxx/popup.js?timestamp=" + Date.now();
document.getElementsByTagName('head')[0].appendChild(scriptEl);
</script>
</head>
<body>
<div id="app-container"></div>
</body>
</html>
Proxy
拦截页面请求。 提供缓存配置的功能,用户在关闭浏览器后,配置的映射关系不会自动消失,以便用户下次打开的时候正常提供服务。 通信功能,需要将用户在 Popup 页面交互时提交的数据地址数据提供给拦截方法,从而实现对应的拦截效果。 提供配置信息状态的缓存数据,方便观测拦截效果。
chrome.webRequest.onBeforeRequest.addListener(
worker.getRequest.bind(worker),
{urls: worker.getFilterUrls(worker.replaceRules)},
['blocking']
);
// Class background
class Background {
constructor() {
this.replaceRules = []
}
getRequest(details) {
if (!details) {
// chrome未返回任何request
return false
}
const {url} = details
const returnObj = {}
if (this.replaceRules.length) {
// 存在替换规则
this.replaceRules.map((item) => {
if (url.includes(item.from)) {
// 更新 icon 状态
this.setBadgeInfo(true)
// 代理替换
returnObj.redirectUrl = url.replace(item.from, item.to)
// 缓存更新当前代理域名
chrome.storage.sync.get(null, function (data) {
if (data.messageProxyingData) {
// 存在被代理的数据,合并数据
const isExistMessageProxyingData = data.messageProxyingData
chrome.storage.sync.set({messageProxyingData: Object.assign(isExistMessageProxyingData, {[item.key]: true})})
} else {
// 未存在缓存,使用新数据
chrome.storage.sync.set({messageProxyingData: {[item.key]: true}})
}
})
}
})
}
return returnObj
}
}
chrome.webRequest.onBeforeRequest
/ getRequest
配合拦截获取请求地址,然后拦截替换目标路径,当然这是比较简单的逻辑,复杂的可以参考 glob
写法代理链接。Popup
const sendMessage = (data: DataType[]) => {
// 保留数据中已开启的数据,进行数据传输
const finalData = data.filter.((item) => item.is_open);
chrome.runtime.sendMessage({ data: finalData, type: 'rule' });
};
const handleSaveData = (
data: DataType[],
isNeedSendMsg: boolean,
key?: number[]
) => {
setDataSource([...data]);
// 缓存配置
chrome.storage.sync.set({ popupData: data });
if (isNeedSendMsg) {
sendMessage(data);
if (key?.length) {
chrome.storage.sync.get(null, function (data) {
const messageProxyingData = data.messageProxyingData || {};
if (messageProxyingData) {
key.forEach.((item) => {
delete messageProxyingData[item];
});
}
chrome.storage.sync.set({ messageProxyingData });
});
}
}
};
ContentScript
const scrpit = document.createElement('script')
scrpit.textContent = `
window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ =
window.__REACT_ERROR_OVERLAY_GLOBAL_HOOK__ || {
iframeReady: function () {
var overlay = document.querySelector(
'iframe[style*="z-index: 2147483647;"]',
);
if (overlay) {
overlay.style.display = 'none';
}
},
};
`
document.body.appendChild(scrpit)
五
推进情况
项目覆盖率
目前该插件在效率前端的微应用项目覆盖率达到了 100%,在推进过程中,只需要用户「安装 1 次插件」,即可使用,后续更新无需关心(减少「插件需要更新」的心理负担)。
研发效率
避免了父子应用均启动时,子应用代码更新后,父应用被动触发 Reload 的问题。 子应用 HMR 热更新延时与非微前端项目没有差距。
用户反馈
从研发侧的反馈来看,确实有效地提高了微应用场景下的研发效率,告别启动多个本地服务的烦恼。
六
思考
这个产品是效率前端业务小组发起,从 0 到 1 进行产品设计、技术方案调研、开发、完成落地的,既让参与的同学经历了完整的技术产品研发过程,也解决了实际业务中遇到的问题,并得到了正反馈。
往期回顾
1. 得物商品状态体系介绍
2. 线程剖析 - 助力定位代码层面高耗时问题|得物技术
3. 知识抽取简述|得物技术
4. 一文搞懂得物前端监控
5. 虚拟线程原理及性能分析|得物技术
6. 彩虹桥架构演进之路-性能篇|得物技术
关注得物技术,每周一、三、五更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
“
扫码添加小助手微信
如有任何疑问,或想要了解更多技术资讯,请添加小助手微信:
线下活动推荐-1
主题:得物技术沙龙- SRE稳定性工程探索与实践
时间:2023年11月25日(周六)14:00-18:00
地点:杭州 · 西湖区学院路77号得物杭州研发中心12F
活动亮点:本次得物技术沙龙主题为SRE稳定性工程探索与实践,将在杭州(线上同步直播)为你带来五个令人期待的演讲话题:
《得物C端核心链路稳定性保障实践》
《阿里集团系统稳定性运营管理》
《腾讯游戏SRE在复杂异构业务中的云原生服务实践》
《蚂蚁集团混沌工程技术理论与实践》
《资损防控技术体系简介及实践》
相信这些话题将对你的工作和学习有所帮助,期待与你共同探讨最前沿的技术趋势和实践经验!欢迎线下参与,如果没办法到现场,也可以锁定我们的“得物Tech”视频号哦~
快快点击下面图片报名吧~
线下活动推荐-2
主题:得物技术沙龙- 「项目管理」专场
时间:2023年12月3日 14:00 — 18:00(13:30开始签到)
地点:上海市杨浦区黄兴路221号互联宝地C2栋5楼 培训教室
活动亮点:随着业务的快速发展以及资源规模的增长,项目管理也需要根据团队规模及不同阶段的特点及时做好调整及应对。PMO/项目经理承担着资源使用、项目进度、团队协作等相关管理工作,过程的成功与否也决定着团队交付目标是否达成,结果是否符合预期。本次沙龙希望以得物PMO在公司发展不同阶段的实践来分享对资源使用、管理,价值交付的一些共性及差异化思考;活动也邀请了不同行业标杆来一起探讨项目管理各领域的现状及优秀案例。
本次项目管理专场沙龙由得物技术出品,将在上海(线上同步直播)为你带来四个令人期待的演讲话题:
《得物资源管理解码》
《OPM-组织的项目管理》
《自驱的威力 - 通过构建团队自驱力让项目管理更加高效》
《互联网企业项目管理与项目治理的思考与实践》
希望通过以上话题的分享,以及得物技术沙龙-项目管理专场这个交流平台,来自不同行业、拥有不同经验的项目管理/PMO从业者可以互相学习及借鉴业界最佳实践,进而实现项目管理能力的进一步提升。
欢迎线下参与!现场参与有机会赢互动礼品,还有和大咖面对面交流的机会~ (线下活动地点见下文「活动介绍」)如果没办法到现场,也可以锁定「得物Tech」视频号观看直播。
快快点击下面图片报名吧~