我给大家写了个使用乾坤的最佳模版,希望对大家有用。
Login微应用效果图

BI微应用效果图
仪表盘微应用效果图
如果你想看下我兼容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>
转成了以下代码
js
<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
的用法,能行了。
以任意一个微应用为例:
js
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
,我以为不重要😭:
js
qiankun(name),

以上就算是我给大家敲个警钟吧!!!!!
图片资源引入的问题
如上面微应用的配置,在本地开发环境一定要配置server.origin
js
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
来作为启动容器,以及子应用的。大家都知道在乾坤的配置上是这么样的
js
registerMicroApps([
{
name: 'app',
entry: 'http://localhost:8080',
container: '#container',
activeRule: '/app',
},
]);
entry
是固定的8080
端口,如果我在本地项目关闭后再启动,发现vue的端口虽然我设置是8080
但是检测到被占用过后,就自动递增了(8081
),那么这么我的子应用当然加载不出来了。
那么你就要杀端口,要么更改子应用的端口。
然后我就写了个sh脚本来处理这些子应用的端口问题,如下:
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
里面,如下:
json
{
"scripts": {
"start-all": "sh ./scripts/kill-port.sh && npm-run-all --parallel start:*",
}
}
子应用管理的问题
因为使用vite-plugin-qiankun
需要把name
值跟在基座中注册的name
一致,还有些是需要用到path
路径,然后我就在项目根目录下创建了一个project.json
文件,如下:
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
里面读取这个文件,如下:
ts
// 微应用中
import { name } from './package.json'
import packageJson from '../../project.json'
const microapp = packageJson.microapps.find((item: any) => item.name === name)
基座容器注册也变的简单明了,不用写一堆子应用的信息了如下:
ts
// 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
:
ts
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不能使用了,改造如下:
ts
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()
然后在子应用更改如下:
ts
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),
)
}
ts
//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')
就能展示对应的页面了。
打包部署
我会通过项目配置把应用以及基座打包在项目根目录下,以下目录结构
bash
dist
|- container
|- microapps
|- app1 // 也会根据配置文件的name一致
|- app2
|- app3
|- index.html
部署测NGINX
配置如下:
bash
# 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
中增加新增的子应用的系列指令
- 根目录下的
bash
"install:xxx": "cd ./microapps/子应用文件名 && yarn",
"start:xxx": "cd ./microapps/子应用文件名 && yarn dev",
"build:xxx": "cd ./microapps/子应用文件名 && yarn build",
总结
本篇中主要介绍了qiankun + vite + vue3
的应用搭建,以及子应用的路由跳转问题,以及打包部署。提供了一个可以直接使用的模版,可以直接拉取代码去使用。
大家好,我是前端三原,欢迎您的关注。我会持续更新...