模板微应用配置说明文档
微应用不需要额外安装任何其他依赖即可接入 qiankun
。
一. 配置
1. 创建并引入 public-path.js
在src/qiankun
目录中创建 public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
在main.js
顶部引入
import "./qiankun/public-path";
2. 在 main.js 封装 render 方法创建 vue 实例
// new Vue({
// store,
// router,
// render: (h) => h(App)
// }).$mount('#app');
let instance = null;
function render(props = {}) {
const { container } = props;
container &&
container.setAttribute(
"style",
"height:100%;overflow-y: auto;background-color: #ffffff;"
);
instance = new Vue({
router,
store,
render: (h) => h(App),
}).$mount(container ? container.querySelector("#app") : "#app");
}
// 独立运行时
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
3. 在 main.js 导出相应的生命周期钩子
微应用需要在自己的入口 js (vue 项目在 main.js) 导出 bootstrap
、mount
、unmount
三个生命周期钩子,以供 qiankun 在适当的时机调用。
在 mount 生命周期中通过 props 参数获取到基座应用传递过来的参数
- qiankunEventBus 用于 eventBus 通信
- qiankunStore 用于 vuex 通信
- setGlobalState 用于 改变 gloabalState 的值并触发全局监听
- onGlobalStateChange 用于 注册监听 gloabalState 的监听器
- qiankunCommonStore 用于 接收基座应用的 common 模块并注册到微应用的 vuex 中
// bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
export async function bootstrap() {
console.log("[vue] vue app bootstraped");
}
// 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
export async function mount(props) {
console.log("[vue] props from main framework", props);
Vue.prototype.$qiankunEventBus = props.qiankunEventBus;
Vue.prototype.$qiankunStore = props.qiankunStore;
Vue.prototype.$setGlobalState = props.setGlobalState;
Vue.prototype.$onGlobalStateChange = props.onGlobalStateChange;
// 将基座的common注册的微应用自己的vuex实例上,这样微应用就可以使用自己的vuex实例访问该模块
if (store && store.hasModule) {
if (!store.hasModule("qiankunCommonStore")) {
store.registerModule("qiankunCommonStore", props.qiankunCommonStore);
}
}
store.dispatch("common/setIsPoweredByQiankun", true);
render(props);
}
// 应用每次 切出/卸载 会调用的unmount方法,通常在这里我们会卸载微应用的应用实例
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = "";
instance = null;
}
4. 配置 vue.config.js
除了代码中暴露出相应的生命周期钩子之外,为了让主应用能正确识别微应用暴露出来的一些信息,微应用的打包工具需要增加如下配置:
- 允许跨域让基座加载微应用
- 配置打包格式为 umd 打包
const { defineConfig } = require("@vue/cli-service");
// 每个微应用的name必须唯一
const { name } = require("./package.json");
module.exports = defineConfig({
// 部署应用包时的基本 URL
publicPath: `/child/${name}`,
devServer: {
// 推荐固定端口,方便调试(可选)
port: 9081,
// 允许跨域让基座加载微应用
headers: {
"Access-Control-Allow-Origin": "*",
},
},
configureWebpack: {
// 配置打包格式
output: {
library: `${name}-[name]`,
libraryTarget: "umd",
// webpack5以下使用 jsonpFunction 配置
// jsonpFunction: `webpackJsonp_${name}`
// webpack5及以上使用 chunkLoadingGlobal 配置
chunkLoadingGlobal: `webpackJsonp_${name}`,
},
},
});
5. 配置 Vuex 的 common 模块
function initState() {
return {
// 是否处于乾坤环境
isPoweredByQiankun: false,
};
}
const state = initState(),
mutations = {
/**
* @description 设置当前是否处于乾坤环境
* @return {void}
* @example
* this.$store.commit('common/setIsPoweredByQiankun')
*/
setIsPoweredByQiankun(state, payload) {
state.isPoweredByQiankun = payload;
},
},
actions = {
/**
* @description 设置当前是否处于乾坤环境
* @return {void}
* @example
* this.$store.dispatch('common/setIsPoweredByQiankun')
*/
setIsPoweredByQiankun({ commit }, payload) {
commit("setIsPoweredByQiankun", payload);
},
},
getters = {
/**
* @description 获取当前是否处于乾坤环境
* @return {boolean}
* @example
* this.$store.getters['common/isPoweredByQiankun']
*/
isPoweredByQiankun(state) {
return state.isPoweredByQiankun;
},
};
export default {
namespaced: true,
state,
mutations,
getters,
actions,
};
6. 配置路由
修改router/routes.js
每个微应用的路由地址都需要一个不重复的路由前缀,用于让 qiankun 根据当前路由匹配并启动对应的微应用。
const routes = [
{
path: "/",
redirect: "/heaven-sub-app1",
},
{
path: "/index",
redirect: "/heaven-sub-app1",
},
{
path: "/heaven-sub-app1",
name: "Index",
component: () => import("@/views/index.vue"),
children: [
{
path: "example-a",
name: "App1ExampleA",
},
{
path: "example-a/1",
name: "App1ExampleA1",
component: () => import("@/views/example-a/example-a-1.vue"),
},
{
path: "404",
name: "NotFound",
component: () => import("@/views/404.vue"),
},
],
},
];
export default routes;
修改router/index.js
router.beforeEach((to, from, next) => {
// 如果处于乾坤环境,那么权限交由基座处理
if (store.state.common.isPoweredByQiankun) {
next();
} else {
if (process.env.NODE_ENV === "development") {
// 如果在开发环境下独立运行项目时,为了便于开发调试直接放行。
next();
} else {
// eslint-disable-next-line no-alert
window.alert("请使用基座访问本项目");
}
}
});
7. 微应用页面如何与主应用通信
- 通过 qiankunEventBus 进行通信,示例:
this.$qiankunEventBus.$emit("logout");
- 通过 setGlobalState 进行通信,示例:
this.$setGlobalState({
// 事件触发来源
eventFrom: "microApp",
// 事件的标识
eventCode: "logout",
// 事件传递的参数
eventData: {
time: new Date().getTime(),
},
});
- 使用 globalState 进行全局状态改变监听,示例:
this.$onGlobalStateChange((state, prev) => {
console.log("微应用监听到全局状态改变", state, prev);
});
- 通过 Vuex 进行通信,示例:
this.$store.dispatch("qiankunCommonStore/logout");
- 使用 store 获取基座应用的数据,示例:
...mapGetters('qiankunCommonStore', {
// token
token: 'getToken',
// 用户信息
userInfo: 'getUserInfo'
})
8. 微应用跳转页面
- 跳转当前微应用的其他页面,推荐使用 name,示例:
this.$router.push({
name: "App1ExampleA2",
});
- 跳转其他微应用的页面,需要写完整路径,示例:
this.$router.push({
path: "/heaven-sub-app2/example-a/1",
query: {
from: "App1ExampleA1",
},
});
二. Q&A
- 路由模式应如何选择?
为了方便集成和部署,基座应用以及微应用的路由都要求使用hash
模式。 - 微应用的路由必须要加前缀吗?
是,每个微应用的路由地址都需要一个不重复的路由前缀,用于让 qiankun 根据当前路由匹配并启动对应的微应用。 - 每个页面都要定义 name 吗
是,为了保证当前已经打开的同一个微应用下的页面可以正确的被keep-alive
组件缓存。 - 使用
$setGlobalState
的修改全局数据失败?
微应用中只能修改已存在的一级属性,基座应用不受该限制。 - qiankunActions.onGlobalStateChange 事件监听被覆盖
onGlobalStateChange
只能同时创建一个监听,新创建的事件监听会覆盖上一个事件监听。推荐在 index.vue
、main.js
等仅创建一次监听,根据eventCode
做不同的动作 - class、id 选择器命名有什么注意事项吗?
为了避免影响基座的样式,请勿使用 main-app 开头的 class、id 选择器修改样式。
修改 Element-UI 等组件库的样式时,推荐限制样式作用范围如 .my-table .el-table{} - 集成后报错 Uncaught Error: application ‘heaven-demo-digital’ died in status LOADING_SOURCE_CODE: only one instance of babel-polyfill is allowed
这是因为多个应用的 babel 被重复引入了,解决方法如下:
在 main.js 直接引入 import “babel-polyfill”; 改为判断是否存在再引入if (!global._babelPolyfill) {
require('babel-polyfill');
}
如果还不行查看 webpack 配置中是否也引入了 babel