微前端之 Vite + qiankun + Vue3
2024年11月8日大约 4 分钟
微前端之 Vite + qiankun + Vue3
qiankun可能是你见过最完善的微前端解决方案 🧐
- 简单: 任意 js 框架均可使用。微应用接入像使用接入一个 iframe 系统一样简单,但实际不是 iframe。
- 完备: 几乎包含所有构建微前端系统时所需要的基本能力,如 样式隔离、js 沙箱、预加载等。
- 生产可用: 已在蚂蚁内外经受过足够大量的线上系统的考验及打磨,健壮性值得信赖。
1、主应用改造
技术栈:Vite
+ Vue3
1.1、安装 qiankun
npm i qiankun
1.2、子应用管理配置
项目src
目录下新建文件夹qiankun
进行集中管理,新建config.js
// /src/qiankun/config,js
const subApps = [
{
name: "OneSon", // 子应用名称
entry: "//localhost:3001", // 子应用入口
container: "#subapp-viewport", // 子应用挂载的dom
activeRule: "/one-son", // 子应用触发的路由规则
props: {}, // 子应用传递的参数
},
{
name: "TwoSon",
entry: "//localhost:3002",
container: "#subapp-viewport",
activeRule: "/two-son",
props: {}, // 子应用传递的参数
},
];
/**
* 创建子应用
* @param {Object} props 父应用传递给子应用的参数
* @returns
*/
export const createSubApps = (props) => {
return subApps.map((item) => {
return {
...item,
props: {
...item.props,
...props,
},
};
});
};
1.3、子应用注册
// src/qiankun/qiankunHooks.js
const useQianKunHooks = () => {
const registerApps = (props) => {
try {
registerMicroApps(createSubApps(props), {
beforeLoad: [
async (app) => {
console.log("before load", app.name);
},
],
beforeMount: [
async (app) => {
console.log("before mount", app.name);
},
],
afterMount: [
async (app) => {
console.log("after mount", app.name);
},
],
});
} catch (e) {
console.error(e);
}
};
const initQianKun = (props) => {
if (window.qiankunStarted) return;
registerApps(props);
start({
prefetch: "all",
sandbox: {
experimentalStyleIsolation: true, // 样式隔离(非严格模式)
},
// 过滤子应用资源加载
excludeAssetFilter: (assetUrl) => {
const witeList = ["map.qq.com"];
return !witeList.some((url) => assetUrl.includes(url));
},
});
};
return {
initQianKun,
};
};
export default useQianKunHooks;
1.4、Layout 改造
在src/components
目录下新建组件SubAppComponent.vue
,作为子应用compontent
组件,里面只需要<template></template>
标签
<!-- /src/components/SubAppComponent.vue -->
<template></template>
修改src/layout/main.vue
文件
template
结构
在router-view
下新增<div id="subapp-viewport"></div>
作为子应用的挂载容器;
<el-container class="h-screen">
<el-aside width="200px" class="bg-[#001529]">
<el-scrollbar>
<el-menu
active-text-color="#fff"
background-color="#001529"
text-color="#B2B6B9"
:default-active="route.path"
router
>
<el-menu-item index="/">
<el-icon><Setting /></el-icon>
<span>Dashboard</span>
</el-menu-item>
</el-menu>
</el-scrollbar>
</el-aside>
<el-main class="bg-gray-100">
<router-view />
<div id="subapp-viewport"></div>
</el-main>
</el-container>
script setup
逻辑
import useQianKunHooks from "@/qiankun/qiankunHooks";
import { onBeforeMount } from "vue";
const { initQianKun } = useQianKunHooks();
onBeforeMount(() => {
// 初始化乾坤,传入数据;如token
const props = {
token: "adminToken",
};
initQianKun(props);
});
1.5、添加子应用路由
// src/router/index.js
import {
createRouter,
createWebHistory,
createWebHashHistory,
} from "vue-router";
import Layout from "@/layout/main.vue";
const routes = [
{
path: "/",
component: Layout,
redirect: "",
children: [
{
path: "",
name: "dashboard",
component: () => import("@/views/dashboard/index.vue"),
},
// 子应用one-son
{
path: "/one-son",
name: "one-son",
children: [
{
path: "/one-son/user",
name: "one-son-user",
component: () => import("@/components/SubAppComponent.vue"),
},
],
},
// 子应用two-son
{
path: "/two-son",
name: "two-son",
children: [
{
path: "/two-son/dept",
name: "two-son-dept",
component: () => import("@/components/SubAppComponent.vue"),
},
],
},
],
},
];
const router = createRouter({
routes,
history: createWebHistory(),
});
/**
* 全局前置守卫
*/
router.beforeEach((to, from) => {});
export default router;
在layout
中添加对应菜单
<el-menu
active-text-color="#fff"
background-color="#001529"
text-color="#B2B6B9"
:default-active="route.path"
router
>
<el-menu-item index="/">
<el-icon><Setting /></el-icon>
<span>Dashboard</span>
</el-menu-item>
<el-menu-item index="/one-son/user">
<el-icon><Setting /></el-icon>
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="/two-son/dept">
<el-icon><Setting /></el-icon>
<span>部门管理</span>
</el-menu-item>
</el-menu>
注意
- 若子应用在开发环境中使用代理,则需要把子应用的代理也加入到主应用里面
2、子应用改造
2.1、修改 vite.config 配置
- 安装
vite-plugin-qiankun
插件 - 引用
qiankun
插件,第一个参数为子应用名称,需要和主应用中注册的子应用名称一致
// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import WindiCSS from "vite-plugin-windicss";
import qiankun from "vite-plugin-qiankun";
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
WindiCSS(),
qiankun("OneSon", {
useDevMode: true,
}),
],
resolve: {
alias: {
"@": "/src",
},
},
});
2.2、改造入口 main
- 添加
registerMicroApps
相关配置
// src/main.js
import { createApp } from "vue";
import "virtual:windi.css";
import App from "./App.vue";
import router from "./router";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import {
renderWithQiankun,
qiankunWindow,
} from "vite-plugin-qiankun/dist/helper";
let app;
const bootstrap = (container) => {
app = createApp(App);
app.use(ElementPlus);
app.use(router);
app.mount(container ? container.querySelector("#app") : "#app");
};
const initQiankun = () => {
renderWithQiankun({
mount(props) {
const { container, token } = props;
console.log("主应用传递的token:" + token);
bootstrap(container);
},
bootstrap() {},
unmount(props) {
app.unmount();
},
});
};
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQiankun() : bootstrap();
2.3、改造路由(History)
// src/router/index.js
import {
createRouter,
createWebHistory,
createWebHashHistory,
} from "vue-router";
import Layout from "@/layout/main.vue";
import { qiankunWindow } from "vite-plugin-qiankun/dist/helper";
const routes = [
{
path: "/",
component: Layout,
redirect: "/user",
children: [
{
path: "user",
name: "user",
component: () => import("@/views/user/index.vue"),
},
],
},
];
const router = createRouter({
routes,
// 判断是否是qiankun环境
history: createWebHistory(
qiankunWindow.__POWERED_BY_QIANKUN__ ? "/one-son" : ""
),
});
/**
* 全局前置守卫
*/
router.beforeEach((to, from) => {});
export default router;
扩展
路由使用
Hash
模式
- 将主应用路由模式改为
createWebHashHistory
- 子应用修改路由模式为
// src/router/index.js
const router = createRouter({
routes,
history: createWebHashHistory(
qiankunWindow.__POWERED_BY_QIANKUN__ ? "/#/two-son" : ""
),
});
强烈不推荐,控制台有警告 ⚠
3、演示
提示
4、FAQ
WindiCSS
主子应用相互影响样式,暂未解决