这是第 61 篇不掺水的原创,想获取更多原创好文,请扫 👆 上方二维码关注咱们吧~ 本文首发于政采云前端团队博客:如何开发跨框架的组件javascript
笔者所在的业务中台团队,须要提供业务组件给不一样的上层业务方使用,但由于一些历史遗留问题,不一样业务线使用的框架不统一,包括 jQuery、React 、Vue 。为了知足不一样业务方的需求,每每须要根据业务方使用的框架,开发对应框架的组件。css
这样作就会产生一些痛点:html
每种选型都须要开发一次,费时劳力前端
组件升级,须要业务方同步发版升级,沟通成本高、迭代效率低java
如何设计一个符合上面提到的跨框架、少升级指望的通用方案呢?npm
很容易想到用原生 JS 来实现,避免跨框架的问题。api
用原生 JS 实现,包含页面里用到的 UI 组件,不依赖任何框架。跨域
优势:app
缺点:框架
适用场景:
不须要复杂交互的场景,如前台吊顶、后台菜单侧边栏可采用这种方式。
但在实际的业务场景中,业务组件中有比较多复杂的交互场景, 上面的方案不太能知足要求,因此咱们在上面的方案之上进行迭代:
咱们将业务组件拆分为两部分:
1、容器组件:
用原生 JS 实现中间层容器组件,解决跨框架的加载问题,容器组件主要负责:
2、业务逻辑组件
根据 iframe 自然的沙箱特性,业务逻辑用 iframe 页面加载,就保证了业务组件的实现不受框架的限制,能够完美解决问题。业务逻辑组件主要负责:
缺点:
此方案容器组件做为中间层,封装不变的逻辑,将多变的业务逻辑隔离出来,从而保证协做方尽可能少升级或不升级。业务定制性可根据接口配置,返回不一样的 iframe 地址,加载不一样的业务逻辑组件,一次开发任意使用。
下面是整个组件的逻辑图:
使用方经过容器组件初始化参数、并注册相应的回调:
容器组件
// 获取主域名
function getTopLevelDomain(host) {
let data = host || window.location.host;
return data.split('.').slice(-2).join('.');
}
// 设置主域名
function setDomainToTopLevelDomain() {
try {
window.document.domain = getTopLevelDomain();
} catch (error) {
console.error("设置domain失败")
}
}
复制代码
class Vanilla {
// 获取配置信息
constructor(config) {
const options = { ...defaultConfig, ...config };
this.options = options;
this.elCls = options.elCls;
}
// 生成容器 div
render() {
const div = document.createElement('div');
this.el = div;
const { width, height } = this.options;
div.className = `${prefixCls}-wrap ${prefixCls}-wrap-loading ${this.elCls || ''}`;
const maskNode = getMaskNode(prefixCls);
const iframeNode = getIframeNode(prefixCls, width, height);
div.innerHTML = maskNode + iframeNode;
document.body.appendChild(div);
this.fn();
}
init() {
// 设置主域名
setDomainToTopLevelDomain();
// 初始化 div
this.render();
// 初始化全局回调函数
this.initCallbacks();
}
...
}
复制代码
class Vanilla {
...
initCallbacks() {
const self = this;
const options = this.options;
// 初始化全局变量
window[paramsName] = options;
window.onSuccess = function onSuccess(data, res) {
options.onSuccess && options.onSuccess(data, res);
// 延迟1500ms删除用来显示成功提示
setTimeout(() => {
self.removeNode();
}, 1500);
self.resetCallbacks && self.resetCallbacks();
};
window.onCancel = function onCancel() {
options.onCancel && options.onCancel();
self.removeNode();
self.resetCallbacks && self.resetCallbacks();
};
window.onError = function onError(data) {
options.onError && options.onError(data);
};
}
}
复制代码
let timer = function timer() {};
class Vanilla {
...
$mount() {
...
this.fn();
}
fn() {
const {
width,
height,
isAutoSize,
} = this.options;
const el = this.el;
const url = getContentUrl('你的iframe地址');
const iframeWidth = width;
const iframeHeight = height;
const iframeEle = el.querySelector('.J_CreditIframe');
const modalNode = el.querySelector(`.${prefixCls}`);
if (!isAutoSize && (iframeWidth !== width || iframeHeight !== height)) {
this.setNodeSizeAndPostion(modalNode, iframeEle, iframeWidth, iframeHeight);
}
iframeEle.setAttribute('src', url);
// 监听load后,隐藏loading
addEvent(iframeEle, 'load', () => {
el.className = `${prefixCls}-wrap ${this.elCls || ''}`;
const maxTime = 3000;
const duration = 1000;
let timerCounter = 0;
let w = defaultConfig.width;
let h = defaultConfig.height;
// 自适应宽高
if (isAutoSize) {
timer = setInterval(() => {
...
//
this.setNodeSizeAndPostion(modalNode, iframeEle, scrollWidth, scrollHeight);
}
timerCounter += duration;
if (timerCounter >= maxTime) {
clearInterval(timer);
}
}, duration);
}
});
}
// 设置iframe宽高
setNodeSizeAndPostion(container, iframe, width, height) {
container.style.cssText = `width: ${width}px; height: ${height}px;margin-left: -${width / 2}px;margin-top: -${height / 2}px;`;
iframe.style.cssText = `width: ${width}px; height: ${height}px;`;
}
// 删除DOM
removeNode() {
timer && clearInterval(timer);
if (this.el) {
document.body.removeChild(this.el);
}
}
...
}
复制代码
上面咱们完成了整个业务组件的加载过程,下面咱们须要处理的就是业务逻辑组件如何与容器组件之间进行通讯:
一般咱们能够这样处理:
// 获取父页面属性
const params = window.parent.paramsName;
// 调用父页面方法
window.parent.onSuccess && window.parent.onSuccess(data);
复制代码
但在实际的业务场景中,咱们可能会面临的问题是业务方的域名与 iframe 加载的组件地址域名不一致,这个时候咱们就必需要解决组件的跨域通讯问题了.
跨域的通讯问题
咱们能够经过如下三种方式去解决:
postMessage
主域名修改
Nginx 代理
// 静态页面地址
location ~ ^/your-project/ {
root /opt/front/your-project/;
try_files $uri $uri/ /index.html = 404;
access_log off;
}
// 反向代理
location ~ ^/api/service/(.*)$ {
proxy_pass http://your-ip;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header requestId $request_id;
proxy_http_version 1.1;
proxy_set_header Connection "";
expires 30d;
access_log off;
}
复制代码
注意处理非白色背景的圆角部分,容易出现毛边。处理方法是 iframe 容器不设置背景色,由 iframe 里面设置圆角
版本控制:小版本保证向前兼容,大版本可经过动态获取 iframe 地址来实现版本控制
多框架背景下的组件重复开发问题,根源仍是多框架的历史债务问题。更好的方式则是推进技术栈的统一,从根源上避免出现此种状况。此场景下更为完善的解决方案则是微前端,咱们也在向这个方向探索,本文提供的是一种基础的解决多技术栈业务场景的思路,若是有更好的方案欢迎你们一块儿讨论~
政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队,隶属于政采云产品研发部,Base 在风景如画的杭州。团队现有 50 余个前端小伙伴,平均年龄 27 岁,近 3 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的“老”兵,也有浙大、中科大、杭电等校的应届新人。团队在平常的业务对接以外,还在物料体系、工程平台、搭建平台、性能体验、云端应用、数据分析及可视化等方向进行技术探索和实战,推进并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变既定的节奏,将会是“ 5 年工做时间 3 年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手推进一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给 ZooTeam@cai-inc.com