2025 乾坤(qiankun)和 Vue3 最佳实践(提供模版)
我给大家写了个使用乾坤的最佳模版,希望对大家有用。
如果你想看下我兼容vue3 + qiankun + vite的解决方案,请往下看。也遇到不少问题,希望对你有所帮助。也会在下面介绍为什么框架会这么设计,运行、打包部署。
开发环境加载资源问题
背景是:我之前使用过qiankun + vue2,在运行、打包构建按照官方的示例中其实没有遇到太多问题。
最近我想搞一个新产品,然后前端来说还挺重的多个菜单,不想使用单页应用,然后就使用vue3+qiankun去运行构建。
由于vite的编译语法没有被编译,在html上是使用<script type="module" src="/src/main.ts"></script> 然后qiankun的机制又是直接使用eval去执行代码的,然后就报错。
看社区中都是引入import qiankun from 'vite-plugin-qiankun'去解决的,他把<script type="module" src="/src/main.ts"></script>转成了以下代码
<script>
import((window.proxy ? (window.proxy.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + '..') : '') + '/login/src/main.ts').finally(() => {
const qiankunLifeCycle = window.moudleQiankunAppLifeCycles && window.moudleQiankunAppLifeCycles['kung-fu-login'];
if (qiankunLifeCycle) {
window.proxy.vitemount((props) => qiankunLifeCycle.mount(props));
window.proxy.viteunmount((props) => qiankunLifeCycle.unmount(props));
window.proxy.vitebootstrap(() => qiankunLifeCycle.bootstrap());
window.proxy.viteupdate((props) => qiankunLifeCycle.update(props));
}
)</script>其实就是使用import 去引入 src/main.ts,然后使用finally去执行qiankun的生命周期。
然后就发生了以下一幕,请看浏览器的标签页(密密麻麻),折磨了我一下午,就一直在报错


其实看到文章大多是2023年的,我把问题怀疑到是vite API变更了,以上方案不受用了,然后我就疯狂找替代方案,问AI,找资料,其实有看到那么文章评论区有同样的困扰,最后不了了之
在那么文章评论区好多人说qiankun官网都不支持vue3,就不要折腾了,我其实都在看@micro-zoe/micro-app方案了,就准备放弃qiankun了,谁知我改了vite-plugin-qiankun qiankun的用法,能行了。
以任意一个微应用为例:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { name } from './package.json'
import qiankun from 'vite-plugin-qiankun'
import packageJson from '../../project.json'
const microapp = packageJson.microapps.find((item: any) => item.name === name)
const outDir = `../../dist/microapps/${name}`
// https://vite.dev/config/
export default defineConfig({
base: process.env.NODE_ENV === 'development' ? microapp?.path : `microapps/${microapp?.name}`,
server: {
origin: `http://localhost:${microapp?.port}`,
port: microapp?.port,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*', // 主应用获取子应用时跨域响应头
},
},
plugins: [
vue(),
qiankun(name, {
useDevMode: true, // 这里
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
build: {
outDir,
},
})我之前一直是这个样的,没有加上useDevMode为true,我以为不重要😭:
qiankun(name),
以上就算是我给大家敲个警钟吧!!!!!
图片资源引入的问题
如上面微应用的配置,在本地开发环境一定要配置server.origin
server: {
origin: `http://localhost:${microapp?.port}`, // 注意这里
port: microapp?.port,
cors: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
},比如我的图片background-image: url('@/assets/images/login/left.jpg');被编译成了background-image: url('容器的IP/login/assets/images/login/left.jpg');,我本地没有转发那指定获取不到这个图片资源了。配置上server.origin后就被编译成了background-image: url('当前应用IP/login/assets/images/login/left.jpg');就没问题了。
端口被占用问题
在本地开发环境我使用的是npm-run-all来作为启动容器,以及子应用的。大家都知道在乾坤的配置上是这么样的
registerMicroApps([
{
name: "app",
entry: "http://localhost:8080",
container: "#container",
activeRule: "/app",
},
]);entry 是固定的8080端口,如果我在本地项目关闭后再启动,发现vue的端口虽然我设置是8080但是检测到被占用过后,就自动递增了(8081),那么这么我的子应用当然加载不出来了。
那么你就要杀端口,要么更改子应用的端口。
然后我就写了个sh脚本来处理这些子应用的端口问题,如下:
#!/bin/bash
#scripts/kill-port.sh
# 定义要杀死的端口数组
PORTS=(5001 4001 3001)
echo "正在杀死指定端口上的进程:${PORTS[*]}"
# 遍历端口数组
for port in "${PORTS[@]}"; do
# 查找占用端口的进程
PID=$(lsof -i :$port -t)
if [ -n "$PID" ]; then
echo "正在杀死端口 $port 上的进程,进程 ID 为 $PID..."
kill -9 $PID
if [ $? -eq 0 ]; then
echo "端口 $port 上的进程已成功杀死。"
else
echo "未能成功杀死端口 $port 上的进程。"
fi
else
echo "端口 $port 上没有进程在运行。"
fi
done
echo "所有指定的端口已处理完毕。"在我启动项目的时候先去kill我子应用被占用的端口。然后放在package.json的scripts里面,如下:
{
"scripts": {
"start-all": "sh ./scripts/kill-port.sh && npm-run-all --parallel start:*"
}
}子应用管理的问题
因为使用vite-plugin-qiankun需要把name值跟在基座中注册的name一致,还有些是需要用到path路径,然后我就在项目根目录下创建了一个project.json文件,如下:
{
"container": {
"name": "kung-fu-container",
"description": "容器微应用"
},
"microapps": [
{
"name": "kung-fu-login",
"path": "/login",
"port": 5001,
"description": "登录页微应用"
},
{
"name": "kung-fu-dashboard",
"path": "/dashboard",
"port": 4001,
"description": "仪表盘微应用"
},
{
"name": "kung-fu-bi",
"path": "/bi",
"port": 3001,
"description": "BI微应用"
}
]
}用来统一管理微应用,然后我在vite.config.ts里面读取这个文件,如下:
// 微应用中
import { name } from "./package.json";
import packageJson from "../../project.json";
const microapp = packageJson.microapps.find((item: any) => item.name === name);基座容器注册也变的简单明了,不用写一堆子应用的信息了如下:
// import './assets/main.css'
import { createApp } from "vue";
import App from "./App.vue";
import { createRouter, createWebHistory } from "vue-router";
import { registerMicroApps, start } from "qiankun";
import "ant-design-vue/dist/reset.css";
import Antd from "ant-design-vue";
// 获取 project.json
import projectJson from "../../../project.json";
const router = createRouter({
history: createWebHistory("/"),
routes: [
{
path: "/",
redirect: "/login",
},
],
});
createApp(App).use(Antd).use(router).mount("#app");
const container = "#kung-fu-app";
registerMicroApps(
projectJson.microapps.map((item) => ({
name: item.name,
entry:
import.meta.env.MODE === "development"
? `http://localhost:${item.port}`
: `/microapps/${item.name}`,
container,
activeRule: item.path,
props: {
routerPerfix: item.path,
},
loader(loading) {
console.log("loading======", loading);
},
})),
);
start();子应用路由跳转的问题
我在构建本次应用的时候发现如果我把配置信息的path,作为子应用的路由前缀,比如login:
function render(props: IRenderProps) {
const { container, routerPerfix } = props;
history = createWebHistory("/login"); // 注意这里
router = createRouter({
history,
routes,
});
instance = createApp(AppCom);
instance.use(router).use(Antd);
instance.mount(
typeof container === "string"
? container
: (container.querySelector("#app") as Element),
);
}那么我当前子应用的路由router.push、replace切换到别的子应用路由的时候就会(比如去bi),就会到/login/bi,明显不是我想要的。我要的是/bi。
肯定不能让原本的API不能使用了,改造如下:
registerMicroApps(
projectJson.microapps.map((item) => ({
name: item.name,
entry:
import.meta.env.MODE === "development"
? `http://localhost:${item.port}`
: `/microapps/${item.name}`,
container,
activeRule: item.path,
props: {
routerPerfix: item.path, // 通过props传递给子应用 routerPerfix
},
loader(loading) {
console.log("loading======", loading);
},
})),
);
start();然后在子应用更改如下:
function render(props: IRenderProps) {
const { container, routerPerfix } = props;
history = createWebHistory("/");
router = createRouter({
history,
routes: getCurrentRoute(routerPerfix) as RouterOptions["routes"],
});
instance = createApp(AppCom);
instance.use(router).use(Antd);
instance.mount(
typeof container === "string"
? container
: (container.querySelector("#app") as Element),
);
}//getCurrentRoute
import type { RouterOptions } from "vue-router";
import Login from "@/views/login/index.vue";
/**1. 定义项目路由 */
const routes: RouterOptions["routes"] = [];
const getCurrentRoute = (routerPerfix?: string) => {
// Q: 为什么不使用路由前缀
// A: 因为有路由前缀后 router.push router.replace 都不能正常使用做微应用的跳转
return routerPerfix
? [
{
path: routerPerfix,
name: "Login",
component: Login,
children: routes,
},
]
: [
{
path: "/",
name: "Login",
component: Login,
},
];
};
export default getCurrentRoute;在子应用的路·createWebHistory('/')没有前缀,定义的路由都嵌套了一层children,然后在使用router.push('/bi')就能展示对应的页面了。
打包部署
我会通过项目配置把应用以及基座打包在项目根目录下,以下目录结构
dist
|- container
|- microapps
|- app1 // 也会根据配置文件的name一致
|- app2
|- app3
|- index.html部署测NGINX配置如下:
# nginx.conf root 指向服务放置的dist文件就行
root /data/qiankun;
location / {
try_files $uri $uri/ /index.html;
}新增微应用
我在项目中mircoapps目录下新增一个kung-fu-template,大家在需要新增时可以直接复制。
步骤如下:
- 直接复制
./microapps/kung-fu-template改成对应的子应用文件名
- 直接复制
package.json中的name更改
- 根目录下的
project.json中定义新增的子应用
- 根目录下的
- 根目录下的
package.json中增加新增的子应用的系列指令
- 根目录下的
"install:xxx": "cd ./microapps/子应用文件名 && yarn",
"start:xxx": "cd ./microapps/子应用文件名 && yarn dev",
"build:xxx": "cd ./microapps/子应用文件名 && yarn build",总结
本篇中主要介绍了qiankun + vite + vue3的应用搭建,以及子应用的路由跳转问题,以及打包部署。提供了一个可以直接使用的模版,可以直接拉取代码去使用。
大家好,我是前端CSC,欢迎您的关注。我会持续更新...