本文记录一下使用qiankun整合vue2,vu3应用的过程。git仓库地址戳:qiankun-demo
线上访问demo戳 qiankun-demo
一.vue-cli 创建vue2子应用
1.使用 vue-cli 创建项目,勾选上Router。
vue create vue2
2.新建vue.config.js,配置如下:
module.exports = {
publicPath: '/vue2/', // 配合上线
productionSourceMap: false,
assetsDir: 'static',
devServer: {
port: 7001,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
configureWebpack: {
output: {
library: `${name}-[name]`,
// 把子应用打包成umd库格式
libraryTarget: 'umd',
// 注意 webpack 版本问题
// jsonpFunction: `webpackJsonp_${name}`, // webpack4
chunkLoading: "jsonp", // //webpack5
},
},
}
3.package.json配置全局参数
{
...,
"eslintConfig": {
"globals": {
"__webpack_public_path__": true
}
}
}
4.子应用路由不能component不能使用import的方式,需要改一下
5.引入element-ui,并且改造下main.js:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
let instance = null
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
render: h => h(App)
}).$mount(container ? container.querySelector('#app') : '#app')
}
// 本地调试
if (!window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
render()
}
// bootstrap,mount,unmount 必须使用 async 函数
export async function bootstrap() {
console.log('bootstrap')
}
// 挂载子应用
export async function mount(props) {
console.log(props)
render(props)
}
// 卸载子应用
export async function unmount() {
instance.$destroy()
}
二. 使用 vue-cli 创建 vue3 子应用
- 使用vue-cli 创建 vue3 应用,和vue2差不多,略
三.使用 vite + vue3 创建主应用
1.使用yarn create vite创建应用,我这边起名 main
2.配置vite.config.js
export default defineConfig({
plugins: [vue()],
base: '/qiankun',
server: {
port: 7000,
headers: {
'Access-Control-Allow-Origin': '*',
}
}
})
3.安装qiankun依赖
yarn add qiankun
4.安装vue-router,vue3 使用的是 vue-router4
yarn add vue-router@4
5.新增router.js ,内容大概如下,注:子应用如果vue-router3的版本,并且有在应用内切换的情况,需要拦截主应用路由跳转,不然报错,参考地址:blog.csdn.net/Lidppp/arti…
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory('/qiankun'), // 增加主应用路由前缀
routes: []
})
router.beforeEach((to, from, next) => {
if(!history.state || !history.state.current) {
Object.assign(history.state, {current: from.fullPath})
}
next()
})
export default router
6.main.js 修改
import { registerMicroApps, runAfterFirstMounted, addGlobalUncaughtErrorHandler } from 'qiankun'
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
registerMicroApps(
[
{
name: 'vue2',
entry: 'http://localhost:7001/vue2',
container: '#container',
activeRule: '/qiankun/vue2', // 记得加上主应用路由前缀
},
{
name: 'vue3',
entry: 'http://localhost:7002/vue3',
container: '#container',
activeRule: '/qiankun/vue3', // 记得加上主应用路由前缀
},
],
{
beforeLoad: [
(app) => {
return Promise.resolve()
},
],
afterMount: [
() => {
return Promise.resolve()
},
],
}
)
runAfterFirstMounted(() => {
console.log('子应用加载完毕')
})
/**
2. 捕获异常
*/
addGlobalUncaughtErrorHandler((event) => {
console.log(event)
const { message } = event
// 加载失败时提示
if (message && message.includes('died in status LOADING_SOURCE_CODE')) {
console.log('微应用加载失败_' + msg)
}
})
7.App.vue增加导航和子应用容器,再加两个导航,onMounted调用qiankun的start方法,开始预加载子应用
<template>
<ul>
<li @click="handleNav(item)" class="item" :class="{active: active === item.url}" v-for="item in navs" :key="item.url">{{item.name}}</li>
</ul>
<div id="container"></div>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import router from './router'
const navs = [
{
name: '测试vue3 首页',
url: '/qiankun/vue3#/',
},
{
name: '测试vue3 test',
url: '/qiankun/vue3#/test',
},
{
name: '测试vue2 Home',
url: '/qiankun/vue2#/',
},
{
name: '测试vue2 about',
url: '/qiankun/vue2#/about',
},
]
const active = ref('')
const route = useRoute()
watch(route, (val) => {
const item = navs.find(item => item.url === val.fullPath)
if(item) {
active.value = item.url
}
}, { deep: true })
onMounted(() => {
start({
prefetch: true, // 开启预加载
sandbox: {
experimentalStyleIsolation: true,
},
})
})
</script>
8.打开http://localhost:7000/qiankun/vue2#/ 查看页面
可切换路由 http://localhost:7000/qiankun/vue2#/about 查看子路由切换的效果
四. 父子应用通讯
4.1.1 使用qiankun提供的 initGlobalState 创建globalState实例,可以调用globalState.onGlobalStateChange 监听全局对象变化,通过globalState.setGlobalState设置全局对象。主应用由vue3开发,使用reactive实现数据监听,新建/src/store.ts
import { initGlobalState } from "qiankun"
import { reactive } from 'vue'
export const globalStateData = reactive({
userInfo: '',
})
const globalState = initGlobalState(globalStateData)
globalState.onGlobalStateChange((data) => {
Object.assign(globalStateData, data)
})
export const setGlobalState = (data: any) => {
Object.assign(globalStateData, data)
globalState.setGlobalState(data)
}
4.1.2 使用方法:
4.2.1 vue2子应用使用vuex监听全局对象变化和设置方法,新建/src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
let store = null
export function getStore(props = { }) {
if(props.setGlobalState) {
const { setGlobalState, onGlobalStateChange } = props
store = new Vuex.Store({
state: {
globalData: {},
},
mutations: {
setGlobalState(state, data) {
state.globalData = {
...state.globalData,
...data,
}
},
},
actions: {
setGlobalState({ }, data) {
setGlobalState(data)
},
},
})
if(onGlobalStateChange) {
onGlobalStateChange((data) => {
store.commit('setGlobalState', data)
}, true) // 第二个参数 true 代表需要初始化,加载后就会调用一次change,将全局对象赋值
}
}
return store
}
export default store
4.2.2 main.js 改造
import { getStore } from './store'
...
function render(props = {}) {
const { container } = props
instance = new Vue({
router,
store: getStore(props),
render: (h) => h(App),
}).$mount(container ? container.querySelector('#app') : '#app')
}
4.2.3 和正常使用store一样:
// 读取
<div>{{$store.state.globalData.userInfo}}</div>
// 设置
this.$store.dispatch('setGlobalState', {
userInfo: 'vue2 Login'
})
4.3.1 vue3 子应用使用 ref 保存全局对象,新建/src/store/useGlobalState.js
import { ref } from "vue"
const globalState = ref({})
let setGlobalState = null
function useGlobalState(props = {}) {
if(props.setGlobalState) {
setGlobalState = props.setGlobalState
props.onGlobalStateChange((val) => {
globalState.value = val
}, true)
}
return {
globalState,
setGlobalState,
}
}
export default useGlobalState
4.3.2 使用:
import useGlobalState from './store/useGlobalState'
const { globalState, setGlobalState } = useGlobalState()
五. 上线,nginx 配置如下
server {
listen 80;
server_name www.john-online.cn;
location / {
root /web/qiankun/demo/main;
index index.html index.htm;
try_files $uri $uri/ /index.html;
if ($request_filename ~* .*\.(?:htm|html)$) { # html不缓存
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
}
}
location ^~/qiankun {
alias /web/qiankun/demo/main;
index index.html index.htm;
try_files $uri $uri/ /index.html; # 使用history模式需要配置
}
location ^~/vue2 {
alias /web/qiankun/demo/vue2;
index index.html index.htm;
}
location ^~/vue3 {
alias /web/qiankun/demo/vue3;
index index.html index.htm;
}
}
六.遇到的问题
1.父子应用使用不同版本的vue-router导致路由切换报错,参考地址:blog.csdn.net/Lidppp/arti…
2.子应用使用element-ui导致字体文件报404,参考地址:qiankun.umijs.org/zh/faq#为什么微…
3.element-ui 弹窗没有遮罩:可配置 append-to-body: false, 解决