我正在参与掘金创作者训练营第5期,点击了解活动详情
前言
都2022年了,还在使用iframe实现微前端是不是已经out了。确实,目前的微前端已经不同以往的解决方案匮乏,社区已经有许多成熟的微前端框架,诸如大家耳熟能详的single-spa
,站在巨人肩膀上的qiankun
,webpack5
带来的模块联邦,脱离了single-spa
拥抱WebComponent
的micro-app
,有很多的选择,后续会出关于这些方案的调研与选择的文章,今天我们先聊聊iframe实现微前端。
回归到问题,明明知道iframe实现微前端有许多缺点,也有更好的选择,为什么在2022年还是依然选择它做为微前端的解决方案。对于框架的选型还是比较巧妙的,并不是框架技术先进,做的好,所以我们必须要选择它,而是对于当前,什么样的框架能够解决我的问题,满足我的需求,所以我选择它。在团队还未有成熟的微前端方案以及时间很赶的情况下,又需要集成不同团队的项目到同一个系统时,优先使用iframe快速实现一套方案,后续在经过一段时间的调研,实践,迭代,这是我的解决思路。
iframe实现微前端
不管是iframe,还是其他的框架,都有其优点和缺点,在使用过程中总会遇到一些难点,接下来就聊聊iframe的优缺点,以及怎么去解决iframe实现微前端存在的问题。
为什么iframe可以实现微前端
微前端实现的是一个系统集成了多个子应用,这时候就需要考虑到JS
和css
的互相影响问题,所以微前端框架很重要的一步就是实现样式隔离、js 隔离。而对于iframe来讲,它具有先天优势,其最大的特性就是提供了浏览器原生的硬隔离方案,不论是样式隔离、js 隔离这类问题统统都能被完美解决,这是iframe可以作为微前端方案的原因。
iframe实现微前端的缺点和解决方案
iframe的优点我们已经了解了,原生支持硬隔离,但有句话叫成也萧何,败也萧何。由于隔离性无法被突破,导致应用间上下文无法被共享,随之带来的开发体验、产品体验的问题,这也成为了它的缺点。接下来聊聊它实现微前端的缺点和对应的解决方案,对于其缺点,这篇文章Why Not Iframe总结的挺好的。
一、url 不同步。浏览器刷新 iframe url 状态丢失、后退前进按钮无法使用。
遇到这种问题,可能有些人的第一反应是监听事件addEventListener('hashchange')
或者addEventListener('popstate')
,这样子是不科学的,会导致问题变得很复杂。现在我们的项目基本上都是spa
,都会跟路由相关联,我们只需要做一个路由的url
和iframesrc
之间的映射就可以了,路由数据结构如下:
const routes = {
path: "/system/order",
name: "order",
meta: {
src: "http://xxx.com",
name: "xxx",
type: "iframe",
keepAlive: true,
},
}
二、UI 不同步,DOM 结构不共享。想象一下屏幕右下角 1/4 的 iframe 里来一个带遮罩层的弹框,同时我们要求这个弹框要浏览器居中显示。
对于这个问题,解决起来确实比较麻烦,需要实现子应用与基座之间的通讯,将其实现交由基座来实现,这样子基座的耦合性较为严重。对于iframe的通信等会在讲,现在分析一下这个弹窗问题,我们现在实现的大多数都是类后台管理系统,类似下面这样的页面结构,弹窗显示在子应用区域完全符合我们的需求,只有一些特殊情况,才需要做些处理,总体开发成本来讲,还是可以接受的。
三、全局上下文完全隔离,内存变量不共享。iframe 内外系统的通信、数据同步等需求,主应用的 cookie 要透传到根域名都不同的子应用中实现免登效果。
cookie 共享
子应用需要和基座共享cookie的话,条件还是比较苛刻的,需要主域名一致,我们是采用nginx代理的。
iframe 通信
iframe实现通信是很重要的一环,子应用和主应用和子应用都需要,每个项目都需要使用到的,建议可以独立封装成一个npm包,当然,如果使用webpack5,也可以直接使用模块联邦,来实现共享,对于通信,还是需要规定好规则,这样子比较好维护,接下来是通讯部分代码的具体实现。
目录结构
/src/main-app.js
import packageJSON from "../package.json";
import MessageType from "./message-type";
const { version, name } = packageJSON;
/**
* 主应用,
*/
class MainApp {
constructor() {
this.registerEvents();
}
// 注册事件
registerEvents() {
window.addEventListener("message", (e) => {
try {
const { type, data } = e.data;
const arg = { data, originEvent: e };
if (type === MessageType.CHECK_COOKIE) {
app.checkCookie(arg);
}
} catch (err) {
console.error("主应用接收到消息失败", { info: { version, name } }, err);
}
});
}
}
let app = null;
const start = ({ onCheckCookie }) => {
app = new MainApp();
app.checkCookie = onCheckCookie;
};
export default {
start,
};
/src/message-type.js
const MESSAGE_TYPE = {
CHECK_COOKIE: "CHECK_COOKIE", // 验证 cookie
};
export default MESSAGE_TYPE;
/src/micro-app.js
import packageJSON from "../package.json";
import MessageType from "./message-type";
const { version, name } = packageJSON;
let _targetOrigin = "*";
const setup = ({ targetOrigin }) => {
_targetOrigin = targetOrigin;
};
// 通知事件
const notify = (type, data) => {
top.postMessage(
{
type,
data,
info: {
version,
name,
},
},
_targetOrigin
);
};
// 验证 cookie 是否过期
const checkCookie = (data) => {
notify(MessageType.CHECK_COOKIE, data);
};
// 是否 iframe
const isIframe = () => {
return window.top !== window;
};
export default {
setup,
notify,
checkCookie,
isIframe,
};
/index.js
export { default as MainApp } from './src/main-app';
export { default as MicroApp } from './src/micro-app';
对于发布npm包,可以查看我之前发布的这边文章发布团队脚手架,对于发布的具体细节,以及可能遇到的问题,都讲的蛮清楚的,这里假设我已经发布了一个名为@LBINGXINj/iframe
的npm包。
在主引用使用MainApp做事件监听
// main.js
import { MainApp } from "@LBINGXINj/iframe";
MainApp.start({
// 监听事件
onCheckCookie(res) {
// 打印输出子应用传递过来的数据
console.log(res)
// to do something
},
});
在子应用MicroApp注册microApp
// main.js
import { MicroApp } from '@LBINGXINj/iframe'
// iframe源,为了安全
const targetOrigin = '*';
MicroApp.setup({ targetOrigin: targetOrigin })
Vue.prototype.microApp = MicroApp
这样子就可以直接在任何页面做事件发布,实现数据通信了。
// any.vue
this.microApp.checkCookie({
name: 'xxx'
})
四、慢。每次子应用进入都是一次浏览器上下文重建、资源重新加载的过程。
每一次进入都是页面重新加载,用户体验比较差,有能力的话,需要对于子应用做一定的优化,比如实现静态资源压缩,异步加载,分包,gz压缩,提供更好的服务器带宽,让子应用的加载速度更快,用户体验会好一些,同时也可以对于iframe做页面缓存,你可能会想用直接使用路由的keep-alive
,想的很美好,现实很骨感哈,这样子是不行的,但是我们可以使用v-show
或者display:none
和display: block
来实现页面的缓存。
小结
经过一步步的分析,从为什么选择iframe实现微前端,iframe实现微前端的优劣势,以及怎么解决其所带来的问题,相信你对于是否选择它作为微前端方案,有了很清晰的认识,有问题欢迎评论区讨论,共同成长。