2025 乾坤(qiankun)和 Vue3 最佳实践(提供模版)

我给大家写了个使用乾坤的最佳模版,希望对大家有用。

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,
  },
})

我之前一直是这个样的,没有加上useDevModetrue,我以为不重要😭:

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.jsonscripts里面,如下:

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,大家在需要新增时可以直接复制。

步骤如下:

    1. 直接复制./microapps/kung-fu-template改成对应的子应用文件名
    1. package.json中的name更改
    1. 根目录下的project.json中定义新增的子应用
    1. 根目录下的package.json中增加新增的子应用的系列指令
bash 复制代码
"install:xxx": "cd ./microapps/子应用文件名 && yarn",
"start:xxx": "cd ./microapps/子应用文件名 && yarn dev",
"build:xxx": "cd ./microapps/子应用文件名 && yarn build",

总结

本篇中主要介绍了qiankun + vite + vue3的应用搭建,以及子应用的路由跳转问题,以及打包部署。提供了一个可以直接使用的模版,可以直接拉取代码去使用。

大家好,我是前端三原,欢迎您的关注。我会持续更新...

相关推荐
艾小逗26 分钟前
vue3中的effectScope有什么作用,如何使用?如何自动清理
前端·javascript·vue.js
Hamm4 小时前
用装饰器和ElementPlus,我们在NPM发布了这个好用的表格组件包
前端·vue.js·typescript
HhhDreamof_5 小时前
云贝餐饮 最新 V3 独立连锁版 全开源 多端源码 VUE 可二开
前端·vue.js·开源
Simaoya5 小时前
【vue】【element-plus】 el-date-picker使用cell-class-name进行标记,type=year不生效解决方法
前端·javascript·vue.js
Dnn015 小时前
vue3+element-push 实现input框粘贴图片或文本,图片上传。
前端·javascript·vue.js
sen_shan6 小时前
Vue3+Vite+TypeScript+Element Plus开发-23.客制Form组件
vue.js
Nick_zcy7 小时前
开发基于python的商品推荐系统,前端框架和后端框架的选择比较
开发语言·python·前端框架·flask·fastapi
vvilkim7 小时前
React 与 Vue 虚拟 DOM 实现原理深度对比:从理论到实践
前端·vue.js·react.js
brzhang8 小时前
代码即图表:dbdiagram.io让数据库建模变得简单高效
前端·后端·架构
MaCa .BaKa8 小时前
35-疫苗预约管理系统(微服务)
spring boot·redis·微服务·云原生·架构·springcloud