vue create potal // 创建基座项目
vue create vue-app-one // 创建微应用1
vue create vue-app-two // 创建微应用2
import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App.vue";
import routes from "./router";
import "./public-path";
Vue.use(VueRouter);
Vue.config.productionTip = false;
let router = null;
let instance = null;
function render() {
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? "/vue-app-one" : "/",
mode: "history",
routes
});
instance = new Vue({
router,
render: h => h(App),
mounted() {
this.$bus.$emit("postMessage", {
from: "vue-app-one",
msg: "我是vue-app-one,我给你发个消息试试看你能收到不"
});
}
}).$mount("#app");
}
// 单独启动时候直接执行渲染
window.__POWERED_BY_QIANKUN__ || render();
export async function bootstrap(props = {}) {
if (props.fns && props.fns.length > 0) {
props.fns.map(i => {
Vue.prototype[i.name] = i[i.name];
});
}
// 子应用内部拿到监听器并绑定到vue实例,子应用若想要发送消息需要使用this.$subscriber.next({from:'', ...})
if (props.bus) {
Vue.prototype.$bus = props.bus;
}
console.log("vue app bootstraped");
console.log("接收到父应用传递来的数据:", props.data);
}
export async function mount(props) {
console.log("props from main app", props);
render();
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
新建src/public-path.js
, 并在main.js
中引入, public-path.js内容如下
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
vue-app-one和vue-app-two处理逻辑一致
const path = require("path");
const { name } = require("./package");
function resolve(dir) {
return path.join(__dirname, dir);
}
const port = 8081; // dev port 针对不同微应用配置不同端口
module.exports = {
/**
* You will need to set publicPath if you plan to deploy your site under a sub path,
* for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
* then publicPath should be set to "/bar/".
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
*/
outputDir: "dist",
assetsDir: "static",
filenameHashing: true,
// tweak internal webpack configuration.
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
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}`
}
}
};
这种情况下考虑不使用vue-router来进行路由管理, 完全将路由管理交给qiankun来进行控制,微应用使用qiankun registerMicroApps(apps, lifeCycles?)
进行注册,start
启动qiankun,, 比如想在某个页面中进行接入某个微应用, 如果路由没有渲染完成,就无法保证其中微应用的挂载节点的渲染时机,微应用会挂载失败。因此应该保证App.view这个根级组件中定义了微应用挂载的DOM节点。
main.js的修改
import Vue from "vue";
import Antd from "ant-design-vue";
import "ant-design-vue/dist/antd.css";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import { registerApps } from "./registerMicroApps";
Vue.use(Antd);
Vue.config.productionTip = false;
let app = null;
/**
* 渲染函数
* appContent 子应用html
* loading 如果主应用设置loading效果,可不要
*/
function render({ appContent, loading } = {}) {
if (!app) {
app = new Vue({
el: "#container",
router,
store,
data() {
return {
content: appContent,
loading
};
},
render(h) {
// 创建App实例时候将props传入, 以在渲染盒子挂载
return h(App, {
props: {
content: this.content,
loading: this.loading
}
});
}
});
} else {
app.content = appContent; // 切换微应用时候不重复创建Vuew实例, 只更新content和loading值
app.loading = loading;
}
}
// 调用渲染主应用
render();
// 注册并启动微应用
registerApps({ store, render });
registerMicroApps.js封装
import {
registerMicroApps, // 注册子应用
runAfterFirstMounted, // 第一个子应用装载完毕
setDefaultMountApp, // 设置默认装载子应用
addGlobalUncaughtErrorHandler, // 添加全局的未捕获异常处理器
start // 启动
} from "qiankun";
// 总线对象,用于主应用和子应用之间进行通信
/**
import Vue from "vue";
const bus = new Vue();
export default bus;
**/
import bus from "./utils/Bus";
/**
* 路由监听
* @param {*} routerPrefix 前缀
*/
const genActiveRule = routerPrefix => {
return location => location.pathname.startsWith(routerPrefix);
};
// 在主应用注册监听器,这里可以监听到其他应用的广播
const onMessage = v => {
console.log(`监听到子应用${v.from}发来消息:`, v);
// TODO 处理监听到子应用消息后的处理
};
/**
* qiankun生命周期钩子函数
*/
const lifeCycle = {
beforeLoad: [
app => {
console.log("before load", app);
}
], // 挂载前回调
beforeMount: [
app => {
console.log("before mount", app);
}
],
// 挂载后回调
afterMount: [
app => {
console.log("after mount", app);
}
],
// 卸载前回调
beforeUnmount: [
app => {
console.log("before mount", app);
}
],
// 卸载后回调
afterUnmount: [
app => {
console.log("after unload", app);
}
]
};
export const registerApps = ({ store, render }) => {
// 监听子应用消息
bus.$on("postMessage", onMessage);
// 传递到子应用的数据
const props = {
data: store.getters,
fns: [],
bus // 使用bus来进行主应用和微应用通信
};
// 注册子应用
registerMicroApps(
[
{
name: "vue-app-one",
entry: "http://localhost:8081",
activeRule: genActiveRule("/vue-app-one"),
render,
props
},
{
name: "vue-app-two",
entry: "http://localhost:8082",
activeRule: genActiveRule("/vue-app-two"),
render,
props
}
],
lifeCycle
);
// 设置默认子应用,参数与注册子应用时genActiveRule("/aaa")函数内的参数一致
setDefaultMountApp("/vue-app-one");
// 第一个子应用加载完毕回调
runAfterFirstMounted(() => {
console.log("首个子应用完成加载");
});
// 启动微服务
start();
// 添加全局的未捕获异常处理器。
addGlobalUncaughtErrorHandler(event => console.log(event));
};
App.view
主应用系统主页面,包含菜单栏和微应用的渲染DOM
<template>
<div id="root" class="main-container">
<div class="main-container-menu">
<a-menu
:defaultSelectedKeys="defaultSelectedKeys"
mode="horizontal"
theme="dark"
>
<a-menu-item key="vue-app-one">
<router-link to="/vue-app-one">
<a-icon type="appstore" />vue app 1
</router-link>
</a-menu-item>
<a-menu-item key="vue-app-two">
<router-link to="/vue-app-two">
<a-icon type="appstore" />vue app 2
</router-link>
</a-menu-item>
</a-menu>
</div>
<!-- 子应用盒子, 微应用会渲染在这里 -->
<div id="root-view" class="app-view-box" v-html="content"></div>
</div>
</template>
<script>
export default {
name: "root-view",
props: {
loading: Boolean,
content: String
},
data() {
return {
defaultSelectedKeys: ["vue-app-one"]
};
}
};
</script>
这种情况下也就是系统仅有部分页面接入其他微前端项目, 其他页面还是正常的系统内页面。这种情况下我们考虑使用qiankun2.0 loadMicroApp
api进行手动微前端应用加载, 这样就能够在vue生命周期mounted
中实现微应用挂载, destroyed
中实现微应用销毁, 在updated
中实现微应用更新
<template>
<div ref="microBox"></div>
</template>
<script>
import { loadMicroApp } from "qiankun";
export default {
name: "micro-app",
props: ["name", "entry"],
data() {
return {
microApp: null
};
},
mounted() {
const name = this.name;
const entry = this.entry;
this.microApp = loadMicroApp({
name,
entry,
container: this.$refs.microBox,
});
},
destroyed() {
this.microApp.unmount();
},
updated() {
this.microApp.update({
name: this.name,
entry: this.entry
});
}
};
</script>
AppOne.vue
<template>
<MicroApp name="vue-app-one" entry="http://localhost:8081" />
</template>
<script>
import MicroApp from "../../components/MicroApp.vue";
export default {
name: "app-one",
components: {
MicroApp
}
};
</script>
AppTwo.vue
<template>
<MicroApp name="vue-app-two" entry="http://localhost:8082" />
</template>
<script>
import MicroApp from "../../components/MicroApp.vue";
export default {
name: "app-two",
components: {
MicroApp
}
};
</script>
我们以主微应用都是用history的路由模式
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/",
component: () => import("../Login.vue")
},
{
path: "/home",
component: () => import("../Home.vue"),
redirect: "/vue-app-one",
children: [
{
path: "/vue-app-one*", // 这个*一定要加, 否则微应用路由跳转时候,加载会不正常
component: () => import("../views/app-one/AppOne.vue")
},
{
path: "/vue-app-two*", // 这个*一定要加, 否则微应用路由跳转时候,加载会不正常
component: () => import("../views/app-two/AppTwo.vue")
}
]
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;