本文是小编之前在其他平台发表的,现迁移至csdn!!!
废话不多说,实战搞起
1.在主应用(基座)中安装qiankun
npm install qiankun
2.安装tmvc-base依赖
npm install tmvc-base
3.主应用注册子应用App
src/micro-app.js
const microApps = [
{
name: 'vue',
entry: '//10.201.1.18:7301/vue',
activeRule: '/vue',
},
{
name: 'vue3',
entry: '//10.201.1.18:7305/vue3',
activeRule: '/vue3',
},
{
name: 'vue3Vite',
entry: '//10.201.1.18:7316/vue3Vite',
activeRule: '/vue3Vite',
},
]
const apps = microApps.map((item) => {
return {
...item,
container: '#cnbi-viewport', // 子应用挂载的div
props: {
routerBase: item.activeRule, // 下发基础路由
getGlobalState: store.getGlobalState, // 下发getGlobalState方法
},
}
})
export default apps
4.主应用main.js中
import microApps from "./micro-app.js";
const apps = microApps.map(item => ({
...item
}));
registerMicroApps(apps, {
beforeLoad: app => {
console.log("before load app.name====>>>>>", app.name);
},
beforeMount: [
app => {
console.log("[LifeCycle] before mount %c%s", "color: green;", app.name);
}
],
afterMount: [
app => {
console.log("[LifeCycle] after mount %c%s", "color: green;", app.name);
}
],
afterUnmount: [
app => {
console.log("[LifeCycle] after unmount %c%s", "color: green;", app.name);
}
]
});
//默认加载的子应用
setDefaultMountApp("/vue");
//启动
start({
prefetch: false,
sandbox: {
// strictStyleIsolation: true, // 开启严格的样式隔离模式
experimentalStyleIsolation: true
}
});
5.子应用挂载点App.vue中
<template>
<div class="layout-wrapper">
<router-view />
<!-- 可以放到这里,也可以放到对应子路由的页面里面位置不重要 -->
<div id="cnbi-viewport"></div>
</div>
</template>
子应用改造,vue2项目
1.src/public-path.js,在main.js导入
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
2.子应用main.js
let router = null;
let instance = null;
function render(props = {}) {
const { container } = props;
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/',
mode: 'history',
routes
});
instance = new Vue({
router,
store,
render: (h) => h(App)
}).$mount(container ? container.querySelector('#app') : '#app');
}
//判断其是否作为qiankun子应用使用
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped');
}
//生命周期挂载
export async function mount(props) {
Vue.prototype.$qiankun = props;
Vue.prototype.$store = Vue.observable(props.pinia);
render(props);
}
//销毁
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = '';
instance = null;
router = null;
}
3.子应用webpack配置,在vue.config.js中
const path = require('path');
const { name } = require('./package');
function resolve(dir) {
return path.join(__dirname, dir);
}
//指定端口
const port = 7301; // dev port
module.exports = {
outputDir: 'dist',
assetsDir: 'static',
filenameHashing: true,
devServer: {
// host: '0.0.0.0',
hot: true,
disableHostCheck: true,
port,
overlay: {
warnings: false,
errors: true,
},
//子应用允许跨域
headers: {
'Access-Control-Allow-Origin': '*',
},
},
// 自定义webpack配置
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
},
},
output: {
// 把子应用打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
子应用改造,vue3+vite项目
1.main.js
import {
renderWithQiankun,
qiankunWindow
} from 'vite-plugin-qiankun/dist/helper';
function setDomain() {
window.ISNK = document.domain.indexOf('172') > -1; // 如果是172客户的域名,那就拿客户地址,自动判断,这里搞个全局判断标志
window.ORIGIN =
process.env.NODE_ENV === 'development'
? process.env.VITE_ORIGIN_DEV
: window.ISNK
? process.env.VITE_ORIGIN_PRO
: process.env.VITE_ORIGIN_PRO_TEST;
}
// 设置主域名,但不跟随基座端口变化而变化
setDomain();
let router = null;
let instance = null;
let history = null;
function render(props = {}) {
const { container } = props;
history = createWebHistory(
qiankunWindow.__POWERED_BY_QIANKUN__ ? '/vue3Vite' : '/'
);
// console.log(history,qiankunWindow.__POWERED_BY_QIANKUN__ ,"history");
router = createRouter({
history,
routes
});
instance = createApp(App);
instance.use(router);
// instance.use(store);
instance.mount(
container ? container.querySelector('#app') : document.getElementById('app')
);
if (qiankunWindow.__POWERED_BY_QIANKUN__) {
console.log('我正在作为子应用运行');
}
}
// some code
renderWithQiankun({
mount(props) {
console.log('viteapp mount');
render(props);
// console.log(instance.config.globalProperties.$route,"444444444");
},
bootstrap() {
console.log('bootstrap');
},
unmount() {
instance.unmount();
instance._container.innerHTML = '';
history.destroy(); // 不卸载 router 会导致其他应用路由失败
router = null;
instance = null;
}
});
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render();
}
2.vite.config.js中
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import qiankun from 'vite-plugin-qiankun';
import { resolve } from 'path';
import { loadEnv } from 'vite';
// useDevMode 开启时与热更新插件冲突
const useDevMode = true; // 如果是在主应用中加载子应用vite,必须打开这个,否则vite加载不成功, 单独运行没影响
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
let config = {
plugins: [vue(), qiankun('vue3Vite', { useDevMode })],
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src')
}
},
server: {
host: '0.0.0.0', // 暴露内网ip
port: 7316,
cors: true
},
define: {
'process.env': env
}
};
return config;
});
子主共享全局store
在主应用中src/storeQianKun.js
import { initGlobalState } from 'qiankun'
import { reactive } from 'vue'
// 父应用的初始state
// Vue.observable是为了让initialState变成可响应:https://cn.vuejs.org/v2/api/#Vue-observable。
const initialState = reactive({})
const actions = initGlobalState(initialState)
actions.onGlobalStateChange((newState, prev) => {
// state: 变更后的状态; prev 变更前的状态
// console.log("main change", JSON.stringify(newState), JSON.stringify(prev));
for (const key in newState) {
initialState[key] = newState[key]
}
})
// 定义一个获取state的方法下发到子应用
actions.getGlobalState = (key) => {
// 有key,表示取globalState下的某个子级对象
// 无key,表示取全部
return key ? initialState[key] : initialState
}
export default actions
在主应用注册APP的地方,下发store
const apps = microApps.map((item) => {
return {
...item,
container: '#cnbi-viewport', // 子应用挂载的div
props: {
routerBase: item.activeRule, // 下发基础路由
getGlobalState: store.getGlobalState, // 下发getGlobalState方法
},
}
})
我们在主应用中点击了某个marker将其信息存储进全局store
子应用监听到主应用被点击的marker,成功创建面板,并调用主应用中的方法,创建出面板。
以上粗略的讲解了qiankun的一些使用和项目改造的一些过程,接下来我们看看对一些特殊需求的探索与思考;
1.keep-alive需求
子项目 keep-alive 其实就是想在子项目切换时不卸载掉,仅仅是样式上的隐藏(display: none),这样下次打开就会更快。
keep-alive 需要谨慎使用,同时加载并运行多个子项目,这会增加 js/css 污染的风险。
解决方案:借助 loadMicroApp 函数
借助 loadMicroApp 函数来实现手动加载和卸载子项目,一般有 keep-alive 需求的就是 tab 页,新增一个 tab 页时就加载这个子项目,关闭 tab 页时卸载这个子项目。
import { loadMicroApp } from 'qiankun';
const apps = [
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer1',
props: { data : { store, router } }
},
{
name: 'app-vue-history',
entry: 'http://localhost:2222',
container: '#appContainer2',
props: { data : store }
}
]
export default {
mounted() {
// 优先加载当前的子项目
const path = this.$route.path;
const currentAppIndex = apps.findIndex(item => path.includes(item.name));
if(currentAppIndex !== -1){
const currApp = apps.splice(currentAppIndex, 1)[0];
apps.unshift(currApp);
}
// loadMicroApp 返回值是 app 的生命周期函数数组
const loadApps = apps.map(item => loadMicroApp(item))
// 当 tab 页关闭时,调用 loadApps 中 app 的 unmount 函数即可
},
}
切换子项目时,dom将不会被清空
2.复用公共依赖(子项目复用主项目)
解决方案1:加载子项目之前处理一下全局变量
registerMicroApps(apps,
{
beforeLoad(app){
if(app.name === 'app-vue-hash'){
// 如果直接在 app-vue-hash 子项目刷新页面,此时 window.Vue2 是 undefined
// 所以先判断下 window.Vue2 是否存在
if(window.Vue2){
window.Vue = window.Vue2;
window.Vue2 = undefined;
}
}else if(app.name === 'app-vue-history'){
window.Vue2 = window.Vue;
window.Vue = undefined
}
},
});
解决方案2:通过props传递依赖
import VueRouter from 'vue-router'
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:1111',
container: '#appContainer',
activeRule: '/app-vue-hash',
props: { data : { VueRouter } }
},
]);
子项目mount阶段接收
function render(parent = {}) {
if(!instance){
// 当它独立运行时,使用自己的外链依赖 window.VueRoute
const VueRouter = parent.VueRouter || window.VueRouter;
Vue.use(VueRouter);
router = new VueRouter({
routes,
});
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount('#appVueHash');
}
}
export async function mount(props) {
render(props.data);
}
3.父子项目间的组件共享
主项目入口文件:
import HelloWorld from '@/components/HelloWorld.vue'
window.commonComponent = { HelloWorld };
子项目直接使用:
components: {
HelloWorld: window.__POWERED_BY_QIANKUN__ ? window.commonComponent.HelloWorld :
import('@/components/HelloWorld.vue'))
}