背景

项目使用qiankun 改造的背景:

项目A、项目B、项目C;

项目A和项目B具有清晰的服务边界,从服务类型的角度能够分为两个项目。

在公司项目一体化的背景下,所有的项目又应该是一个项目。

项目B研发启动的时候
1. 由于开发时间紧张;
2. 项目B需要共用A项目中的“项目模块”和“人员管理”模块;
3. 项目B中的功能模块根据项目A的路由进行激活加载;

基于以上的情况,采取了在项目A中增加模块进行项目B的开发,

由于B项目包含在A项目中,当A项目和B项目同时开始需求迭代的时候,两个开发人员开始代码合并的时候简直就是灾难,需要花费大量的时间小心谨慎的进行这项工作
为了不使项目A变为巨石应用,需要将A项目进行解构。
微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用(Frontend Monolith)后,随之而来的应用不可维护的问题。

我们的项目为什么不使用iframe?

iframe 是在主应用中嵌入子应用系统,iframe 为子应用提供了一个完美的隔离环境,完美的样式隔离和js 隔离。

iframe 所带来的问题:
1. iframe拥有独立的window 。独立的浏览器缓存(无法便捷的实现单点登陆。例如:主应用登陆后将token 存储在sessionStorage中,子应用无法直接拿到token。
2. 刷新后iframe url 状态会丢失
3. iframe是会阻塞页面的加载的,会影响到网页的加载速度。比如window的onload事件会在页面或者图像加载完成后立即执行,但是如果当前页面当中用了iframe了,那还需要把所有的iframe当中的元素加载完毕才会执行,这样就会让用户感觉网页加载速度特别慢,影响体验感。

webpack5模块联邦

不同于qiankun的基座应用,模块联邦是去中性化的,两个项目间可以互相引用。
在webpack.config.js中进行配置ModuleFederationPlugin插件
模块联邦其实可以当作是webpack5将需要导出来的组件打包成一个运行时的文件,然后在其他项目可以进行运行时的动态加载,加载的过程是异步的,执行的时候是同步的。根据这个特性我们可以实现一个中心化组件模块中心,然后对外进行模块的分发。

为什么选用qiankun

  1. 技术栈无关。

  2. 主应用与微应用能够独立运行,独立开发。

  3. 微应用与主应用之间做到了该隔离的隔离,不该隔离的共用。(浏览器缓存可共用)

当遇到像我们这种项目情况的时候:

    主应用与微应用基地相同,意味着,两个项目的缓存操作方法相同,vuex store方法相同时,应该首选采取qiankun 接入微应用的方式。由于qinkun 没有隔离浏览器缓存,因此,可以不用考虑子应用的登录问题,菜单栏tab 的显示问题。

简直比德芙还丝滑!!

什么是qiankun?

首先,qiankun 并不是单一个框架,它在 single-spa 基础上添加更多的功能。以下是 qiankun 提供的特性:

实现了子应用的加载,在原有 single-spa 的 JS Entry 基础上再提供了 HTML Entry
样式和 JS 隔离
更多的生命周期:beforeMount, afterMount, beforeUnmount, afterUnmount
子应用预加载
全局状态管理
全局错误处理

qiankun的使用

首先你需要一个基座来承载各类跨技术栈的子应用,这里以vue为例

主应用配置

首先在src下写一个registerApps.js

import { registerMicroApps, start } from "qiankun"; // 底层是基于single-spaconst loader = (loading) => {console.log(loading);
};
registerMicroApps([{name: "m-vue",//package.json的nameentry: "//localhost:20000",//项目起的端口号container: "#container",activeRule: "/vue",loader,},{name: "reactApp",entry: "//localhost:30000",container: "#container",activeRule: "/react",loader,},],{beforeLoad: () => {console.log("加载前");},beforeMount: () => {console.log("挂在前");},afterMount: () => {console.log("挂载后");},beforeUnmount: () => {console.log("销毁前");},afterUnmount: () => {console.log("销毁后");},}
);
start({sandbox: {// experimentalStyleIsolation:truestrictStyleIsolation: true,},
});

在main.js中引入
import ‘./registerApps’

子应用配置

vue微应用
vue.config.js

module.exports = {publicPath: '//localhost:20000', //保证子应用静态资源都是像20000端口上发送的devServer: {port: 20000, // fetchheaders:{'Access-Control-Allow-Origin': '*'}},configureWebpack: { // 需要获取我打包的内容  systemjs=》 umd格式output: {libraryTarget: 'umd',library: 'm-vue'// window['m-vue']}}
}// 3000 -> 20000 基座回去找20000端口中的资源,  publicPath  /

router的index.js
导出的是路由表,不是router

const routes = [{path: '/',name: 'Home',component: () => import( '../views/Home.vue')},{path: '/about',name: 'About',component: () => import( '../views/About.vue')}
]export default routes

main.js

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue'
import routes from './router'// 不能直接挂载 需要切换的时候 调用mount方法时候再去挂载
let history;
let router;
let app;
function render(props = {}){history =  createWebHistory('/vue');//加上路由前缀router = createRouter({history,routes});app = createApp(App);let {container} = props; // 默认他会拿20000端口的html插入到容器中,//没传container,就是自己跑起来的,传了代表是在基座中跑的app.use(router).mount(container ? container.querySelector('#app'):'#app')
}// 乾坤在渲染前 给我提供了一个变量 window.__POWERED_BY_QIANKUN__if(!window.__POWERED_BY_QIANKUN__){ // 独立运行自己
render();
console.log(window.__POWERED_BY_QIANKUN__)
}// 需要暴露接入协议,返回需要是promise,所以加上async
export async function bootstrap(){console.log('vue3 app bootstraped');
}export async function mount(props){console.log('vue3 app mount',);render(props)
}
export async function unmount(){console.log('vue3 app unmount');history = null;app = null;router = null;
}

react微应用
.rescriptsrc.js
在package.json中的scripts也要修改,因为.rescriptsrc的使用

module.exports = {webpack:(config)=>{config.output.library = 'm-react';  config.output.libraryTarget = 'umd';config.output.publicPath = '//localhost:30000/';return config},devServer:(config)=>{config.headers = {'Access-Control-Allow-Origin':'*'};return config}
}

.env
PORT=30000
WDS_SOCKET_PORT=30000
index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';reportWebVitals();function render(props = {}) {let { container } = props;ReactDOM.render(<App />,container ? container.querySelector('#root') : document.getElementById('root'));
}
if (!window.__POWERED_BY_QIANKUN__) {render();
}export async function bootstrap() {}export async function mount(props) {render(props)
}export async function unmount(props) {const { container } = props;ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.getElementById('root'))
}

qinakun的样式隔离如何实现?

qiankun 中处理样式 如何处理的
子应用与子应用他会采用动态样式表 加载的时候添加样式 删除的时候卸载样式 (子应用之间的样式隔离)
主应用和子应用 如何隔离 (我们可以通过BEM规范) -> (css-modules) 动态生成一个前缀 (并不是完全隔离)
其实是因为start()函数中有一个sandbox沙箱。其中使用shadow dom来解决样式冲突,shadow dom就是一个隔离的环境,他会把子应用的所有内容放在shadowdom里面,shadow dom中的样式不会影响外部的样式。

什么是影子dom?

通过影子 DOM 就可以将一个 完整的 DOM 树 作为节点添加到 父 DOM 树。
即可以实现 DOM 封装,意味着 CSS 样式和 CSS 选择符可以限制在影子 DOM 子树中,而不是作用于整个顶级 DOM 树。

创建影子 DOM

影子 DOM 是通过 attachShadow() 方法创建并添加给有效 HTML 元素的:

影子宿主**(shadow host)**,即容纳影子 DOM 的元素
影子根(shadow root),即影子 DOM 的根节点
attachShadow() 方法需要一个 shadowRootInit 对象,即这个对象必须包含一个 mode 属性,值为 “open” 或 “closed”
mode 属性值为 “open” 的影子 DOM 的引用可通过 shadowRoot 属性在 HTML 元素上获得,属性值 “closed” 影子 DOM 的引用则无法获取

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><style>div {color: blue !important;}</style></head><body><div>hello world</div><script>const appContent = `<div id="qiankun"><div>hello world</div><style>div{color:red}</style></div>`; // 好比qiankun中获取的html,我们拿到后包裹了一层const containerElement = document.createElement("div");console.log(containerElement);containerElement.innerHTML = appContent;const appElement = containerElement.firstChild; // 拿出第一个儿子,中的内 容const { innerHTML } = appElement;appElement.innerHTML = "";let shadow = appElement.attachShadow({ mode: "open" }); // 将父容器变为 shadowDOMshadow.innerHTML = innerHTML; // 将内容插入到shadowDOM中document.body.appendChild(appElement);</script></body>
</html>

[ 影子 DOM 具有最高优先级 ]

正常情况下,影子 DOM 一添加到元素中,浏览器就会赋予它 最高优先级,优先渲染它的内容而不是原来的 dom 内容,比如下面的例子:

document.body.innerHTML = `<div id="foo"><h1>I'm foo's child</h1></div>`;
const foo = document.querySelector('#foo');
const openShadowDOM = foo.attachShadow({mode: 'open'
});
// 为影子 DOM 添加内容
openShadowDOM.innerHTML = `<p>this is openShadowDOM content</p>`

qiankun的js隔离方案?

js沙箱隔离主要分为三种,snapshot sandbox(快照沙箱)、Proxy sandbox(代理沙箱)、lagacySandBox(遗留沙箱)。

sanpshotsandbox快照沙箱

原理就是在子应用激活 / 卸载时分别去经过快照的形式记录/还原状态来实现沙箱的。总结起来,对当前的 window 和浅拷贝的快照作 diff 来实现沙箱。
但是快照沙箱明显的缺点就是每次切换时需要去遍历window,这种做法会有较大的时间消耗。

legacysandbox遗留沙箱

在微应用修改 window.xxx 时直接记录 diff,将其用于环境恢复

Proxy sandbox代理沙箱

了避免真实的 window 被污染,qiankun 实现了 proxysandbox。它的想法是:
把当前 window 的一些原生属性(如document, location等)拷贝出来,单独放在一个对象上,这个对象也称为 fakewindow
之后对每个微应用分配一个 fakewindow
当微应用修改全局变量时:
如果是原生属性,则修改全局的 window
如果不是原生属性,则修改 fakewindow 里的内容
微应用获取全局变量时:
如果是原生属性,则从 window 里拿
如果不是原生属性,则优先从 fakewindow 里获取
这样一来连恢复环境都不需要了,因为每个微应用都有自己一个环境,当在 active 时就给这个微应用分配一个 fakewindow,当 inactive 时就把这个 fakewindow 存起来,以便之后再利用。

实现沙箱的几种思路?

方法:
with和eval
proxy+with
iframe

qiankun-子使用的加载

Html Entry办法的次要轨范如下:首先通过url获与到整个Html文件,从html中解析出html,js和css文原,正在主使用中创立容器,把html更新到容器中,而后动态创立style和script标签,把子使用的css和js赋值正在此中,最后把容器放置正在主使用中。

JS Entry

而 JS Entry 的理念就在加载微应用的时候用到了,在使用 single-spa 加载微应用时,我们加载的不是微应用本身,而是微应用导出的 JS 文件,而在入口文件中会导出一个对象,这个对象上有 bootstrap、mount、unmount 这三个接入 single-spa 框架必须提供的生命周期方法,其中 mount 方法规定了微应用应该怎么挂载到主应用提供的容器节点上,当然你要接入一个微应用,就需要对微应用进行一系列的改造,然而 JS Entry 的问题就出在这儿,改造时对微应用的侵入行太强,而且和主应用的耦合性太强。

Html Entry原理

HTML Entry 是由 import-html-entry 库实现的,通过 http 请求加载指定地址的首屏内容即 html 页面,然后解析这个 html 模版得到 template, scripts , entry, styles

qiankun​​ 常见报错

1、子项目未 ​​export​​ 需要的生命周期函数

2.子项目加载时,容器未渲染好

 检查容器 ​​div​​ 是否是写在了某个路由里面,路由没匹配到所以未加载。如果只在某个路由页面加载子项目,可以在页面的 ​​mounted​​ 周期里面注册子项目并启动。
 3.子应用中的请求是200,但是在基座中却是400
因为你在基座去发子应用的请求时,子应用的代理会失效,解决方法就是给基座一个代理即可

devServer: {proxy: {'/proxyApi': {changeOrigin: true,target: 'http://redcloud.devops.sit.xiaohongshu.com',pathRewrite: {'^/proxyApi': '',},},},},

4.基座和微应用之前怎么通信?
1.基座可以在注册子应用时传入props,子应用在mount中的prop参数中可以接收到
基座

子应用

qiankun的核心就在于子应用的加载(Html Entry)和样式与js的隔离

微前端qiankun使用+踩坑相关推荐

  1. qiankun 传统项目配置_微前端 qiankun 项目实践

    原标题:微前端 qiankun 项目实践 作者:zxh1307 https://juejin.im/post/5ea55417e51d4546e347fda9 导语 最近在做微前端的项目 , 过程中真 ...

  2. 基于微前端qiankun的多页签缓存方案实践

    作者:vivo 互联网前端团队- Tang Xiao 本文梳理了基于阿里开源微前端框架qiankun,实现多页签及子应用缓存的方案,同时还类比了多个不同方案之间的区别及优劣势,为使用微前端进行多页签开 ...

  3. 微前端qiankun 问题

    微前端qiankun 遇到问题,连接子应用之后报错 We're sorry but micro-front-web3 doesn't work properly without JavaScript ...

  4. 瑞芯微RV1109平台交叉编译踩坑

    背景 最近的一个工作任务是将之前在联咏平台上做的一个人脸识别的项目移植过去.之前这个项目主要是做的,移植的事情自然落到我身上了.组里也没人搞过瑞芯微的,开始踩坑淌水. 交叉编译工具 问了公司一圈人也没 ...

  5. 微前端搭建子项目踩得坑(子应用挂载后容器不存在问题)

    基于阿里的qiankun 框架搭建的微前端 1.先搭建主项目 先安装qiankun框架 官网https://qiankun.umijs.org/zh/guide cnpm i qiankun -S 1 ...

  6. py获取前端的参数_微前端 qiankun 项目实践

    (给前端大全加星标,提升前端技能) 作者:zxh1307 https://juejin.im/post/5ea55417e51d4546e347fda9 导语 最近在做微前端的项目 , 过程中真是踩了 ...

  7. 前端静态服务踩坑实践

    前言 随着前端项目的增大,越来越多时候会把动静态资源进行分离部署,对于分离部署时常常涉及到代理转发的问题,专网项目主要使用 nginx + docker + k8s的部署方式,本文主要分享一些相关项目 ...

  8. 微前端 - qiankun

    前言 qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单.无痛的构建一个生产可用微前端架构系统. 本文主要记录下如何接入 qiankun 微前端.主应用使用 vue ...

  9. 微前端qiankun从搭建到部署的实践

    最近负责的新项目用到了qiankun,写篇文章分享下实战中遇到的一些问题和思考. 示例代码: github.com/fengxianqi/-. 在线demo:qiankun.fengxianqi.co ...

最新文章

  1. NanoPi NEO Air使用十六:使用python做开发
  2. 20145204 张亚军《信息安全系统设计基础》第10周学习总结
  3. 黑科技抢先尝 | Windows全新终端初体验(附代码Build全过程)
  4. gradle的二进制版本_Gradle入门:创建二进制分发
  5. laravel安装laravel-ide-helper扩展进行代码提示(二)
  6. [转载] java左移右移和无符号右移
  7. 计算机专业英语第三章在线测试,《计算机专业英语》第03章在线测试
  8. xxs漏洞危害_PHP开发中经常遇到的Web安全漏洞防御详解
  9. Kafka-consumer(消费者)
  10. 如何增加虚拟机ubuntu的硬盘
  11. S3VM和TSVM的不同
  12. c++5.8.2免费 dev_devc 中文版下载
  13. Word 二级标题不跟随一级标题变化
  14. 每日新知——MySQL索引类型及创建
  15. 什么是GPU服务器?如何正确选择?
  16. 识别PDF关键词,在文件页数和坐标
  17. GEF活性检测试剂盒的主要用途和应用
  18. 无人驾驶小车调试笔记(五)-- 命令行通信
  19. Python 跨类传参与跨模块传参
  20. gcc: buildin函数: __builtin_unreachable __builtin_constant_p;__atomic_load_n

热门文章

  1. 大学“电路分析基础”试题合集第一章
  2. 虹膜识别门禁,筑起智能安防首道屏障
  3. 使用nlite将SCSI RAID 阵列驱动整合到系统安装光盘内
  4. win10下安装虚拟系统
  5. 安卓-实现阴影的几种方式
  6. 巨人史玉柱经典创业语录
  7. 【Android 】零基础到飞升 | Git使用教程之本地仓库的基本操作
  8. 操作系统(二)从图灵机到现代计算机
  9. 考研英语 之 长难句
  10. 【一周头条盘点】中国软件网(2018.5.2~2018.5.4)