文章目录
- 一、微前端是什么
- 二、iframe全新的微前端方案
- 三、iframe的使用优点
- 四、iframe的使用缺点
- 五、实现步骤
- 1、先创建一个基座项目,项目只有导航框架,没有页面,不需要路由。
- 2、直接在App.vue里写结构
- 3、渲染导航数据
- 4、添加iframe标签
- 六、需要解决的一些问题
- 1、项目之间的通讯
- 2、iframe的弹窗及遮罩层问题
- 解决方案:
- 3、iframe里的全屏问题
- 4、组件复用问题
- 5、浏览器的后退问题
- 当点击导航时,触发该事件,更新URL
- 6、刷新的问题
- 上面我们已经把路由信息记录并更新了URL地址。所以每当刷新或后退的时候,只要解析URL就可以了
- 7、实现子应用免登录
- 7.1、跨域共享cookie
- 7.2、代理跨域共享Cookie
- 7.3、Json Web Token
- 七、界面效果
一、微前端是什么
微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用还可以独立运行、独立开发、独立部署。
简单来说,就是利用一系列工具和技术,将各个团队的UI页面 组装成用户可以连贯的应用程序。
微前端是最近几年火起来的概念,iframe是早期实现微前端的理想方案,而现在有了其它的方案,比如qianduan框架,single-spa,以及webpack5带来的联邦模块方案。但是每一个方案都有其优缺点,感兴趣的可以去实践一下。
二、iframe全新的微前端方案
iframe是一个天然的微前端方案,但受限于跨域的严格限制而无法很好的应用,本文介绍一种基于iframe优雅实现全新的微前端方案,继承iframe的优点,补足 iframe 的缺点,让 iframe 焕发新生。
前端开发中我们对 iframe 已经非常熟悉了,那么 iframe 的作用是什么?可以归纳如下:
在之前使用iframe的时候,是在页面的使用中引入另外一个页面进行渲染,所以它的基本功能是:在一个web应用中独立的运行另一个web应用
三、iframe的使用优点
- 非常简单,使用没有任何心智负担
- 隔离完美,无论是 js、css、dom 都完全隔离开来
- 多应用激活,页面上可以摆放多个 iframe 来组合业务
四、iframe的使用缺点
- 路由状态丢失,刷新一下,iframe 的 url 状态就丢失了
- dom 割裂严重,弹窗只能在 iframe 内部展示,无法覆盖全局
- 通信非常困难,只能通过 postmessage 传递序列化的消息
能否打造一个完美的 iframe ,保留所有的优点的同时,解决掉所有的缺点呢?
本文以vue2为例,搭建一个左侧导航与顶部导航的二级导航的iframe框架项目。
五、实现步骤
1、先创建一个基座项目,项目只有导航框架,没有页面,不需要路由。
所有功能都在App.vue实现
2、直接在App.vue里写结构
<div class="el-container">
<div class="el-menu">左侧一级导航</div>
<div class="el-main">
<div class="el-header">右侧顶部二级导航</div>
<div class="el-aside" id="iframeBox">iframe的容器</div>
</div>
</div>
3、渲染导航数据
这里是data数据部分
data() {
return {
index1: 0, //一级导航当前下标
index2: 0, //二级导航当前下标
forWard: true, //是否记录路由
isOpen: false, //是否打开弹窗
menuTree: [
{
id: "1",
name: "menu1",
order: 10,
subMenu: [
{
id: "11",
name: "mneu1_sub1",
order: 11,
subMenu: null,
text: "二级菜单11",
url: "http://127.0.0.1:5500/js/alert.html",
},
],
text: "一级菜单1",
url: "",
},
],
};
4、添加iframe标签
当点击导航时,触发该事件,动态添加iframe标签
//显示iframe
replaceUrlFun() {
const bigNode = document.getElementById("iframeBox");
const contentIframe = document.getElementById("contentIframe");
if (contentIframe) {
contentIframe.remove();
}
const { index1, index2, menuTree } = this;
const url1 = menuTree[index1].url;
const url2 = menuTree[index1].subMenu?.[index2]?.url || "";
let url = url2 || url1;
const iframeCon = document.createElement("iframe");
bigNode.appendChild(iframeCon);
iframeCon.setAttribute("class", "iframe");
iframeCon.setAttribute("id", "contentIframe");
iframeCon.setAttribute("frameborder", 0);
iframeCon.setAttribute("allowfullscreen", true);
iframeCon.src = url;
},
六、需要解决的一些问题
1、项目之间的通讯
使用postMessage方法来完成基座项目和子项目之间的通讯。
2、iframe的弹窗及遮罩层问题
将弹出层代码写到父页面中,子页面使用postMessage方法发送消息告诉父页面打开,关闭弹层,父页面监听打开,关闭弹层。
<!-- 父页面弹窗蒙层 -->
<div class="openDiv" v-if="isOpen"></div>
//监听子应用消息
window.addEventListener(
"message",
function (event) {
if (event.data == "openDiv") {
that.openDiv();
}
if (event.data == "closeDiv") {
that.closeDiv();
}
},
false
);
遇到问题:父页面弹出层会把整个的iframe遮住。
解决方案:
1.父页面弹出层设置position: fixed;z-index: 100;
2.给ifame设置position: relative;z-index: 200;
3.子页面也要设置遮罩层,遮住ifame区域。position: fixed;z-index: 300;
4.设置弹窗容器,position: fixed;z-index: 400;
5.子页面事件,打开弹窗,向父页面发消息
6.父页面事件,监听消息,打开弹窗
<div class="box">
<button @click="openDiv">弹窗</button>
</div>
<!-- 子页面弹窗蒙层 -->
<div class="openDiv" v-if="isOpen">
<div class="openBox" @click="closeDiv">点击关闭</div>
</div>
// 子页面事件
openDiv() {
// 向父窗口发送消息
window.top.postMessage('openDiv', '*');
this.isOpen = true;
},
closeDiv() {
// 向父窗口发送消息
window.top.postMessage('closeDiv', '*');
this.isOpen = false;
},
3、iframe里的全屏问题
全屏方案,原生方法使用的是 Element.requestFullscreen(),iframe 标签设置 allowfullscreen=“true” 属性
//全屏事件
fullscreen() {
const bigNode = document.getElementById("app");
bigNode.requestFullscreen()
}
4、组件复用问题
公共组件可以单独提出来放到一个单独的项目里,在项目中把公共组件全部暴露出来供其它项目安装使用,也就是说主项目和子项目可以选择性安装需要的组件。
5、浏览器的后退问题
iframe 和主页面共用一个浏览历史,iframe 会影响页面的前进后退。并且 iframe 页面刷新会重置,因为浏览器的地址栏没有变化,iframe 的 src 也没有变化。
iframe页面外部的跳转尽管不会让浏览器地址栏发生变化,然而却会产生一个看不见的“history记录”,也就是点击后退或后退按钮(history.forward()或history.back())能够让iframe页面也后退后退,然而地址栏无任何变动。
所以精确来说后退无需咱们做任何解决,咱们要做的就是让浏览器地址栏同步更新即可。
当点击导航时,触发该事件,更新URL
//记录路由方法
routerKeyArrFun() {
if (!this.forWard) return;
const { index1, index2, menuTree } = this;
const name1 = menuTree[index1]?.name || "";
const name2 = menuTree[index1]?.subMenu?.[index2]?.name || "";
if (name1) {
const url = `#/?${name1}${name2 ? `#${name2}` : ""}`;
history.pushState(null, null, url);
}
},
6、刷新的问题
保障URL同步更新须要满足这3种状况:
- 页面刷新,iframe可以加载正确页面;
- 页面跳转,浏览器地址栏可能正确更新;
- 点击浏览器的后退,地址栏和iframe都可能同步变动;
上面我们已经把路由信息记录并更新了URL地址。所以每当刷新或后退的时候,只要解析URL就可以了
//解析浏览器信息
getUrlModuleInfo() {
this.forWard = false; //记录地址开关
const { menuTree } = this;
const ohref = window.location.href;
const len = ohref.indexOf("?");
if (len < 0) {
this.selectFirstMenu();
this.forWard = true;
return;
}
const params = ohref.substring(len + 1).split("#");
const [first, second] = params;
menuTree.forEach((item, index) => {
if (item.name !== first) return;
this.selectFirstMenu(index);
if (!second) return;
item.subMenu?.forEach((sub, i) => {
if (sub.name !== second) return;
this.selectSecondMenu(i);
});
});
this.forWard = true;
},
7、实现子应用免登录
7.1、跨域共享cookie
在XMLHttpRequest v2标准下,提出了CORS(Cross Origin Resourse-Sharing)的模型,试图提供安全方便的跨域读写资源。目前主流浏览器均支持CORS。(IE10+)
7.2、代理跨域共享Cookie
当我们的请求需要经过代理服务器时,可以将请求转发到同一个域名下的不同端口,这样就可以共享Cookie了。例如,假设服务器在8080端口,而我们需要共享Cookie,可以使用Nginx配置一个反向代理来实现。
server {
listen 80;
server_name xxx.com;
location / {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
}
}
7.3、Json Web Token
在前后端分离的项目中,可以使用JWT(Json Web Token)来进行身份验证,并用它来代替Cookie来实现跨域共享。