微前端框架(qiankun)使用记录
参考:
疑惑
- 主框架和子框架是否都要安装乾坤框架
答:不是,只需要安装在主框架即可,子框架不需要引入
使用方法
主应用
- 路由建议使用history模式
安装 qiankun
yarn add qiankun
或者npm i qiankun -S
配置微应用
// /src/micro/apps.js const apps = [ { name: 'planResource', entry: '//localhost:8083', container: '#iframe', activeRule: '/plan' }, { name: 'configration', entry: '//localhost:8081', container: '#iframe', activeRule: '/configure' } ]; export default apps;
在主应用中注册微应用
// src/micro/index.js import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from 'qiankun'; import apps from './apps'; // registerMicroApps 第二个参数可以不要,如果要做点啥,就写到对应的地方 registerMicroApps(apps, { beforeLoad: [ app => { console.log('[LifeCycle] before load %c%s', 'color: green;', app.name); }, ], beforeMount: [ app => { console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name); }, ], afterUnmount: [ app => { console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name); }, ], },); // 出错时显示的内容 addGlobalUncaughtErrorHandler((event) => { // console.log(event); const { message } = event; if (message && message.includes('died in status LOADING_SOURCE_CODE')) { console.log('微应用加载失败,请检查应用是否可运行'); } }); export default start;
使用 start
// /src/main.js import microApp from './micro'; new Vue({ router, store, render: (h) => h(App) }).$mount('#app'); microApp();
注意:
<font color=orange>不一定要放在入口文件内,看你的项目而定</font>
- 如果你的微应用容器在index.html里的话,是没有多大问题,
但是很多时候是嵌入在Layout框架里的,正常进入是没有问题,如果是在微应用路径下刷新页面,会有如下图片的报错
<font color=red>Uncaught Error: application 'cloud' died in status LOADING_SOURCE_CODE: [qiankun] Target container with #cloud not existed while cloud loading!</font>
这种情况下建议在改组件的mounted事件钩子下start, main.js下引入,不执行即可
eg:
//main.js ... import microApp from './micro' // 加载乾坤app microApp // 注册应用 // 包含子应用容器的VUE组件 <template> <div class="vab-app-main"> <section> <transition mode="out-in" name="fade-transform"> <vab-keep-alive v-if="routerView" /> </transition> <div id="cloud"></div> <div id="vue3"></div> </section> <vab-footer /> </div> </template> <script> ... import { start } from 'qiankun' export default { name: 'VabAppMain', data() { return { ... } }, ... mounted() { start({ prefetch: false, // 取消预加载 }) // 开启 }, ... } </script>
子应用
修改webpack配置
// webpack.config.js || vue.config.js const port = 8088; const packageName = require("./package.json").name; module.exports = { lintOnSave: false, // 关闭eslint检测 devServer: { port, // 这里的端口是必须和父应用配置的子应用端口一致 disableHostCheck: false, // 关闭主机检查,保证子应用可以被主应用fetch到 headers: { //因为qiankun内部请求都是fetch来请求资源,所以子应用必须允许跨域 "Access-Control-Allow-Origin": "*", }, }, configureWebpack: { output: { //资源打包路径 library: `${packageName}`, libraryTarget: "umd", jsonpFunction: `webpackJsonp_${packageName}`, // publicPath: `//localhost:${port}`, }, }, // outputDir: 'dist', // assetsDir: 'static', // filenameHashing: true, };
增加 public-path配置文件并在入口文件引入
// /src/micro/public-path.js if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } // /src/main.js import '@/micro/public-path'
路由实例化配置
// src/router/index.js import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) const routes = [ { path: '/', name: 'Home', component: () => import('../views/Home.vue') }, { path: '/about', name: 'About', component: () => import('../views/About.vue') } ] const router = new VueRouter({ mode: 'history', // @ts-ignore base: window.__POWERED_BY_QIANKUN__ ? '/cloud' : '/', // 独立运行是用"/"作为根路径 // base: '/cloud', routes }) export default router
Vue实例化包装和生命周期钩子导出
// /src/main.js import "@/micro/public-path"; import Vue from "vue"; import App from "./App.vue"; import actions from "./micro/actions.js"; import router from "./router"; import store from "./sotre"; let instance = null; function render(props = {}) { // 经过实践这个判断要有,不然在独立运行时报错 if (window.__POWERED_BY_QIANKUN__) { if (props) { actions.setActions(props); actions.onGlobalStateChange((state) => { console.log("app1", state); }, true); } } const { container } = props; // 这里是挂载到自己的html中 基座会拿到这个挂载后的html 将其插入进去 const renderContainer = container ? container.querySelector("#app") : "#app"; instance = new Vue({ router, store, render: (h) => h(App), }).$mount(renderContainer); } // @ts-ignore if (!window.__POWERED_BY_QIANKUN__) { // 默认独立运行 render(); } // 父应用加载子应用,子应用必须暴露三个接口:bootstrap、mount、unmount export async function bootstrap() { console.log("cloud app bootstraped"); } export async function mount(props) { console.log("mount"); render(props); } export async function unmount() { console.log("unmount"); instance.$destroy(); }
传值 <font color=red>Actions</font>
props 传值不够灵活,不推荐使用
<font color=red>Actions</font>推荐使用方法,可以在状态改变时触发
主应用
- 初始化全局跨应用通信方法
// /src/micro/actions.js
import { initGlobalState } from 'qiankun';
const initState = {};
const actions = initGlobalState(initState);
export default actions;
- 在需要设置跨应用全局状态的组件内:
// /src/app.vue
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
import actions from '@/micro/actions'
export default {
name: 'App',
computed: {
visitedRoutes() {
return this.$store.state.tabs.visitedRoutes //需要监听的数据
},
},
watch: {
visitedRoutes(newVal, oldVal) {
this.setGlobalState()
},
},
created() {
// this.setGlobalState()
},
methods: {
setGlobalState() {
let data = {
time: this.$store.state.user.time,
token: this.$store.state.user.token,
ability: this.$store.state.acl,
routes: this.$store.state.routes,
tabs: this.$store.state.tabs.visitedRoutes,
}
actions.setGlobalState(data)
},
},
}
</script>
注意:
<font color=orange>可以监听vuex数据,如果变化,执行setGlobalState</font>
子应用
- 全局actions定义
// /src/micro/actions.js
function emptyAction() {
// 警告:提示当前使用的是空 Action
console.warn('Current execute action is empty!');
}
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里
// /src/main.js
import "@/micro/public-path";
import Vue from "vue";
import App from "./App.vue";
import actions from "./micro/actions.js";
import router from "./router";
import store from "./sotre";
let instance = null;
function render(props = {}) {
// @ts-ignore
if (window.__POWERED_BY_QIANKUN__) {
if (props) {
actions.setActions(props);
actions.onGlobalStateChange((state) => {
console.log("app1", state);
const { token, ability, tabs } = state;
store.state.ability = ability
store.state.token = token
store.state.tabs = tabs
}, true);
}
}
const { container } = props;
// 这里是挂载到自己的html中 基座会拿到这个挂载后的html 将其插入进去
const renderContainer = container ? container.querySelector("#app") : "#app";
instance = new Vue({
router,
store,
render: (h) => h(App),
}).$mount(renderContainer);
}
// @ts-ignore
if (!window.__POWERED_BY_QIANKUN__) {
// 默认独立运行
render();
}
// 父应用加载子应用,子应用必须暴露三个接口:bootstrap、mount、unmount
export async function bootstrap() {
console.log("cloud app bootstraped");
}
export async function mount(props) {
console.log("mount");
render(props);
}
export async function unmount() {
console.log("unmount");
instance.$destroy();
}
开发DevTools
如果基座和子应用使用的都是VUE,要使用devtools是比较老火的事情:
- 如果使用的devtools 5.xx 的版本,那么只能查看主框架的数据
- 如果使用devtools 6.xx(目前只有bata版本),且基座和子应用使用不同的VUE版本,那么可以查看对应应用的数据,<font color=orange>可是开发开发工具会不停的报错,蛋痛</font>
建议:基座框架和子框架使用不同的框架语言开发(这样是否要好一些,或者有解决的办法)
<font color=red>主应用在什么时候传值</font>
很重要:不然首次是拿不到值的哦
参考传值Actions 主应用下的设置 监听vuex
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。