qiankun 微前端实例化使用
一、qiankun使用场景
1. 简介:qiankun是在single-spa的基础上实现的,可以保证各个项目独立使用,也可以集成使用。各系统之间不受技术栈的限制,集成使用也能保证各样式和全局变量的隔离。
模块的插拔式使用,当公司项目集是一个大系统下包含多个子系统或者模块时,可以采用这种方式动态部署各个系统。
亦或者是老项目技术升级和重构,可以通过qiankun按模块进行改造,避免对整个系统产生较大的影响。
功能和iframe类似,但是由于iframe数据通信难度较大,且有安全和SEO的问题,所以iframe使用体验不佳。
2. 原理逻辑:
a. 需要在各个子应用的基础上新增一个主应用,通过主应用监听路由变化。
b. 当有路由切换时就会触发上述监听函数从而去匹配在主应用中注册的各个子应用路径(activeRule)是否匹配。
c. 匹配到子应用后就会加载子应用的资源到对应的容器当中去。
二、实现样例
本样例使用的是Node 16的版本,主应用采用Vue3框架,两个子应用分别使用Vue2和Vue3框架。qiankun版本是2.10.16。
1. 搭建主应用,利用脚手架创建一个qiankun-main的主应用,同时安装qiankun组件(qiankun只需要在主应用安装,子应用不需要),其中代码中标注重点的内容是配置qiankun的关键步骤
1.1 打开vue.config.js文件,添加跨域处理,避免跳转时出现跨域问题

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8085,
headers: { // 重点1: 允许跨域访问子应用页面
'Access-Control-Allow-Origin': '*',
}
}
})
vue.config.js
1.2 主应用中设置子应用接收容器

<template>
<div class="app">
<p><router-link to="/">点击跳转到父页面</router-link></p>
<button @click="login">登陆</button>
<p><router-link to="/vue3">跳转到Vue3子应用</router-link></p>
<p><router-link to="/vue2">跳转到Vue2子应用</router-link></p>
<router-view />
<div id="vue3"></div> <!-- 重点2:子应用容器 id -->
<div id="vue2"></div> <!-- 重点2:子应用容器 id -->
</div>
</template> <script>
import actions from '@/shared/actions'; export default {
name: 'App',
components: {
},
mounted() {
actions.onGlobalStateChange((state, prevState) => {
// state: 变更后的状态; prevState: 变更前的状态
console.log('主应用观察者:token值改为', prevState.token);
console.log("主应用观察者:登录状态发生改变,改变后的 token 的值为 ", state.token);
});
},
methods: {
login() {
console.log('进入登陆事件');
setTimeout(() => {
const token = 'token_' + Math.floor(Math.random() * 100000);
//登陆后随机生成token并设置
actions.setGlobalState({ token });
this.$router.push("/vue3");
}, 300);
}
}
}
</script> <style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
App.vue
1.3 在src根目录下新增public-path文件;同时改造路由,设置返回的base地址

if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
public-path.js

import {
createRouter,
createWebHashHistory
} from 'vue-router'
import '../public-path' // 重点3: 引入public-path文件
const router = createRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue3' : '/', // 重点4:qiankun进入子应用时,返回true
history: createWebHashHistory(), // 重点5
routes: [{
path: '/',
redirect: '/child'
},
{
path: '/child',
component: () => import('@/components/child')
}
]
})
export default router
router/index.js
1.4 注册和引入子应用

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { registerMicroApps, start, setDefaultMountApp } from 'qiankun'
createApp(App).use(router).mount('#app')
registerMicroApps([
{
name: "vue3 app",
entry: "//localhost:8086", // 重点8:对应重点6
container: '#vue3', // 重点9:对应重点2
activeRule: '/#/vue3', // 重点10:对应重点4
props: {
appContent: '我是主应用传给vue的值'
}
},
{
name: "vue2 app",
entry: "//localhost:8087", // 重点8:对应重点6
container: '#vue2', // 重点9:对应重点2
activeRule: '/#/vue2', // 重点10:对应重点4
props: {
appContent: '我是主应用传给Vue2的值'
}
}
])
setDefaultMountApp("/") // 重点11:启动默认的子模块
// 启动
start()
main.js
2. 搭建子应用1, 同样利用脚手架创建一个qiankun-vue3-child,项目使用Vue3作为基础框架
2.1 同样在src目录下创建public-path.js文件

if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
public-path.js
2.2 改造router/index.js文件, 确认在qiankun模式下的路由基础路径

import {
createRouter,
createWebHashHistory
} from 'vue-router'
import '../public-path' // 重点3: 引入public-path文件
const router = createRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue3' : '/', // 重点4:qiankun进入子应用时,返回true
history: createWebHashHistory(), // 重点5
routes: [
]
})
export default router
router/index.js
2.3 修改入口函数main.js,导入钩子函数

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import actions from './micros/actions'
let instance = null
function render(props = {}) {
// qiankun模式下实现父子应用之间通信
if (props) {
actions.setActions(props);
}
const { container } = props
// 为了避免根id#app与其他DOM冲突,需要限制查找范围
instance = createApp(App).use(router).mount(container ? container.querySelector('#child-app') : '#child-app')
}
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
//--------- 生命周期函数------------//
export async function bootstrap() {
console.log('[vue] vue app bootstraped')
}
export async function mount(props) {
console.log('[vue] props from main framework', props)
render(props)
}
export async function unmount() {
if (instance) {
console.log(instance, instance.unmount);
// instance.unmount();
instance = null
}
}
// createApp(App).use(router).mount('#child-app')
main.js
2.4 修改打包配置文件vue.config.js,设置服务端口以及打包模式

const { defineConfig } = require('@vue/cli-service')
const { name } = require('./package');
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8086, // 重点6
headers: { // 重点7:同重点1,允许子应用跨域
'Access-Control-Allow-Origin': '*',
},
},
// 自定义webpack配置
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把子应用打包成 umd 库格式
// jsonpFunction: `webpackJsonp_${name}`,
},
},
})
vue.config.js
3. 搭建子应用2,步骤与第2步类似,只是使用Vue2作为基础框架
三、功能演示

四、常见问题
1. 子应用部署在同一个服务器同一个端口的不同路径下如何配置?
基本和部署在不同服务器的类似,只是将注册子应用的entry的服务器端口号换成某个路径,同时将打包的publicPath改为该路径

// 主应用入口文件中注册子应用
registerMicroApps([
{
name: "vue3_app",
entry: "/entry_vue3", // 对应之前的 //localhost:8086
container: '#vue3',
activeRule: '/#/vue3',
props: {
appContent: '我是主应用传给vue的值'
}
}
]) // 子应用的 router/indexedDB.js
const router = createRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue3' : '/entry_vue3', // 设置路由路径
history: createWebHashHistory(),
routes: [
]
}) // 打包文件vue.config.js中添加默认路径
module.exports = defineConfig({
publicPath: devFlag ? '/' : '/entry_vue3',
transpileDependencies: true,
devServer: {
port: 8087, // 重点6
headers: { // 重点7:同重点1,允许子应用跨域
'Access-Control-Allow-Origin': '*',
},
},
// 自定义webpack配置 重点12
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd', // 把子应用打包成 umd 库格式
// jsonpFunction: `webpackJsonp_${name}`,
},
},
})
同服务器同端口部署配置
2. 主子应用之间通信?
2.1 使用qiankun框架提供的 initGlobalState 实现的,主要有下面三个函数:
onGlobalStateChange(callback, Immediately)在当前应用监听全局状态变化;
setGlobalState(state)按照一级属性进行状态设置,微应用只能修改一级属性;
offGlobalStateChange()移除当前的状态监听,微应用在unmount时默认调用;
2.2 使用方式,效果可见上面的案列图中对token的打印信息
a. 主应用的src目录下新增shared/actions.js文件。

import { initGlobalState } from "qiankun";
const initialState = {
token: 'no token'
};
const actions = initGlobalState(initialState);
export default actions;
actions.js
b. 比如在主应用的App.vue中使用并且实现登陆后生成token以及跳转到vue3子应用

import actions from '@/shared/actions';
export default {
name: 'App',
components: {
},
mounted() {
actions.onGlobalStateChange((state, prevState) => {
// state: 变更后的状态; prevState: 变更前的状态
console.log('主应用观察者:token值改为', prevState.token);
console.log("主应用观察者:登录状态发生改变,改变后的 token 的值为 ", state.token);
});
},
methods: {
login() {
console.log('进入登陆事件');
setTimeout(() => {
const token = 'token_' + Math.floor(Math.random() * 100000);
//登陆后随机生成token并设置
actions.setGlobalState({ token });
this.$router.push("/vue3");
}, 300);
}
}
}
App.vue
c. 子应用中使用时首先在根目录下创建一个micros/actions.js文件

function emptyAction() {
// 确保单独部署时不会报错
console.warn('当前无可执行的Action');
}
class Actions {
// 默认设置空Action
actions = {
onGlobalStateChange: emptyAction,
setGlobalState: emptyAction
}
// 设置Actions
setActions(actions) {
this.actions = actions;
}
// 映射
onGlobalStateChange(...args) {
return this.actions.onGlobalStateChange(...args);
}
// 映射
setGlobalState(...args) {
return this.actions.setGlobalState(...args);
}
}
const actions = new Actions();
export default actions;
actions.js
d. 子应用的APP.vue页面中监听主应用中数据的变化以及子应用主动修改数据观察主应用是否能接收到

import actions from '@/micros/actions.js';
export default {
name: 'App',
components: {
},
mounted() {
actions.onGlobalStateChange(state => {
console.log('子应用Vue的观察函数:', state);
}, true)
},
methods: {
changeToken() {
actions.setGlobalState({ token: 'Vue3_' + Math.floor(Math.random() * 100000) })
}
}
}
App.vue
3. 各个应用之间如何提取一些公共的资源或者模块?
可以将公共模块提取成一个公共组件发布到npm,然后由各个应用按需安装。
4. 各个系统如何做到只登陆一次?
可以参考单点登陆实现,简单逻辑就是比如:
a. 有一个地址sso.com做为控制中心,然后a.com、b.com为子模块。
b. 当访问a.com时无权限时路由会携带参数“a.com”自动跳转到登陆页面,输入用户名密码信息后,经过sso.com验证通过生成ticket并返回给页面同时跳转到a.com并下发ticket。
c. a.com请求获取到ticket后访问sso.com的服务器进行验证是否有效,有效则允许登陆,这样就完成了一次登陆。
d. 如果在已登录的状态下跳转到b.com,则省略第二步的登陆验证直接将ticket携带到b.com,然后再访问sso.com进行有消息验证。
注意:主应用注册的activeRule为/vue3时跳转到子应用不生效可能是因为浏览器路由跳转时自动加上/#/,所以在activeRule也需要修改为/#/vue3才可以跳转
qiankun 微前端实例化使用的更多相关文章
- 「微前端实践」使用Vue+qiankun微前端方案重构老项目的本地验证
10月份换了新的工作,参与完一个月的需求迭代后,接到了项目重构的任务.简单来说,需要在短时间内提出方案设想,同时进行本地验证,最终需要拿出一套技术替换方案来.于是,埋头苦干了一个月,总算干了点成绩出来 ...
- 基于微前端qiankun的多页签缓存方案实践
作者:vivo 互联网前端团队- Tang Xiao 本文梳理了基于阿里开源微前端框架qiankun,实现多页签及子应用缓存的方案,同时还类比了多个不同方案之间的区别及优劣势,为使用微前端进行多页签开 ...
- vue-qiankun公司微前端项稳定目落地后的总结(附github仓库demo,将会持续更新)
️本文为博客园社区首发文章,未获授权禁止转载 大家好,我是aehyok,一个住在深圳城市的佛系码农♀️,如果你喜欢我的文章,可以通过点赞帮我聚集灵力️. 个人github仓库地址: https:gi ...
- 微前端框架 之 qiankun 从入门到源码分析
封面 简介 从 single-spa 的缺陷讲起 -> qiankun 是如何从框架层面解决 single-spa 存在的问题 -> qiankun 源码解读,带你全方位刨析 qianku ...
- 【微前端】微前端最终章-qiankun指南以及微前端整体探索
序 这才2月中旬,广州就已经渐渐地进入了夏季,--夏天总是让人焦虑的.过年闲暇时间写下了微前端这系列的终章,欢迎拍砖.如果你习惯直接上手代码,不妨跳到实践一节,直接上代码教程玩一玩. qiankun原 ...
- 微前端框架 qiankun 技术分析
我们在single-spa 技术分析 基本实现了一个微前端框架需要具备的各种功能,但是又实现的不够彻底,遗留了很多问题需要解决.虽然官方提供了很多样例和最佳实践,但是总显得过于单薄,总给人一种&quo ...
- 初步认识微前端(single-spa 和 qiankun)
初步认识微前端 微前端是什么 现在的前端应用,功能.交互日益复杂,若只由一个团队负责,随着时间的推进,会越来越庞大,愈发难以维护. 微前端这个名词,第一次提出是在2016年底.它将微服务(将单一应用程 ...
- 微前端(qiankun)主应用共享React组件
前言 最近需要重构一个老项目,定的方案用微前端去改造.主应用是老的项目,微应用是新的项目,由于重构时间比较紧张,子应用还需要使用父应用的一些组件.过程中遇到一些问题,记录一下. 方案 我们知道qian ...
- 微前端大赏二-singlespa实践
微前端大赏二-singlespa实践 微前端大赏二-singlespa实践 序 介绍singleSpa singleSpa核心逻辑 搭建环境 vue main react child 生命周期 结论 ...
- 微前端 & 微前端实践 & 微前端教程
微前端 & 微前端实践 & 微前端教程 微前端 micro frontends https://micro-frontends.org/ https://github.com/neul ...
随机推荐
- [ARC145B] AB Game
The game is played by Alice and Bob. Initially, there are $n$ stones. The players alternate turns, m ...
- 文心一言 VS 讯飞星火 VS chatgpt (159)-- 算法导论12.3 6题
六.用go语言,当 TREE-DELETE 中的结点 z 有两个孩子时,应该选择结点 y 作为它的前驱,而不是作为它的后继.如果这样做,对 TREE-DELETE 应该做些什么必要的修改?一些人提出了 ...
- 【Python】【OpenCV】视频帧和摄像头帧操作 and 窗口显示
一.读取写入视频文件 1 import cv2 2 3 # 创建一个视屏捕获对象 4 videoCapture = cv2.VideoCapture('AVI.avi') 5 6 # 获取视频的属性值 ...
- 聊聊流式数据湖Paimon(四)
Partial Update 数据打宽 通过不同的流写不同的字段,打宽了数据的维度,填充了数据内容:如下所示: --FlinkSQL参数设置 set `table.dynamic-table-opti ...
- java漏洞学习平台搭建
java漏洞平台搭建 项目地址 : https://github.com/j3ers3/Hello-Java-Sec 配置mvn 先配置一下mvn,下载后解压 proxychains wget htt ...
- 文心一言 VS 讯飞星火 VS chatgpt (178)-- 算法导论13.4 1题
一.用go语言,在执行 RB-DELETE-FIXUP 之后,证明:树根一定是黑色的. 文心一言: 在证明 RB-DELETE-FIXUP 之后,树根一定是黑色的的过程中,我们首先需要理解红黑树的性质 ...
- MySQL进阶篇:详解索引概述
2.1 MySQL进阶篇:第二章_二.一_索引概述 2.1.1 介绍 索引(index)是帮助MySQL高效获取数据的数据结构(有序).在数据之外,数据库系统还维护着满足 特定查找算法的数据结构,这些 ...
- 跟我读论文丨ACL2021 NER BERT化隐马尔可夫模型用于多源弱监督命名实体识别
摘要:本文是对ACL2021 NER BERT化隐马尔可夫模型用于多源弱监督命名实体识别这一论文工作进行初步解读. 本文分享自华为云社区<ACL2021 NER | BERT化隐马尔可夫模型用于 ...
- 华为AppCube通过中国信通院“低代码开发平台通用能力要求”评估!
摘要:华为AppCube应用魔方顺利通过信通院评估,被认证为具备 "低代码开发平台通用能力"的企业服务平台. 本文分享自华为云社区<华为AppCube通过中国信通院" ...
- PNG文件解读(1):PNG/APNG格式的前世今生
PNG格式的前世今生 png是一种无损压缩的位图片形格式,其设计目的是试图替代GIF和TIFF文件格式,同时增加一些GIF文件格式所不具备的特性.PNG使用从LZ77派生的无损数据压缩算法--LZW专 ...