最近我们公司承接了一个新的项目,该系统集成的任务是将其无缝嵌入到之前开发好的主应用中。经过一周的技术探索以后,我成功填满qiankun以及vben admin下的坑。现在我将这些经验记录下来,希望为团队和社区成员提供参考和帮助。
我们的技术栈包括:
| 主应用 | 子应用 |
|---|---|
| umi + antd-V4 + react-V18 | vben admin + antdv-V4 + vite |
主应用配置
config下新建qiankun属性
qiankun: {
master: {
apps: [
{
name: 'ziemsb20',
entry: '//localhost:5174/',
},
{
name: 'zioms',
entry: '//localhost:8003/',
prefetch: true,
},
],
},
},
在路由中配置微前端与路由的对应关系
{
name: 'ziemsb20',
clientId: 'ziemsb20_console',
path: '/setting/ziemsb20',
microApp: 'ziemsb20',
microAppProps: {
autoSetLoading: true,
},
group: '工具',
},
另外如果同时启动主应用和子应用需要配置子应用的接口代理
'/api': {
target: 'http://10.32.28.117:1889/',
changeOrigin: true,
},
至此,主应用的框架搭建已经顺利完成。其实主应用的职责在于协调各个路由与地址的映射关系。后面的登录验证可以根据项目实际情况进行调整。本文重点介绍qiankun的引入,这部分内容就不细说了。
子应用配置
子应用采用vben admin框架,vite作为打包工具。
- 引入qiankun处理包
pnpm install --save-dev vite-plugin-qiankun
vben admin 这个框架对组件封装的比较多,我是在vite-config中直接添加qiankun包的配置。在vite.config.ts 中写也是可以的。
2. 修改main.ts文件。其实这个原理很简单,就是函数通过参数来加载app。参数为空时子应用是单独运行直接mount app,有值时为微前端模式,加载传递的值。我这边的处理是直接传值,传递什么值就加载什么值。因为我调用的时候就已经确定了当前处于那种模式。下面是全部的代码,vben下可以直接替换使用:
import 'uno.css'
import '@/design/index.less'
import '@/components/VxeTable/src/css/index.scss'
import 'ant-design-vue/dist/reset.css'
import 'vant/lib/index.css'
import './global.css'
import './css/variable.scss'
import './css/common.scss'
// Register icon sprite
import 'virtual:svg-icons-register'
import { createApp } from 'vue'
import { registerGlobComp } from '@/components/registerGlobComp'
import { setupGlobDirectives } from '@/directives'
import { setupI18n } from '@/locales/setupI18n'
import { setupErrorHandle } from '@/logics/error-handle'
import { initAppConfigStore } from '@/logics/initAppConfig'
import { router, setupRouter } from '@/router'
import { setupRouterGuard } from '@/router/guard'
import { setupStore } from '@/store'
import ant from 'ant-design-vue'
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
import App from './App.vue'
let app: any = null
async function bootstrapVue3(container) {
app = createApp(App)
app.use(ant)
// Configure store
// 配置 store
setupStore(app)
// Initialize internal system configuration
// 初始化内部系统配置
initAppConfigStore()
// Register global components
// 注册全局组件
registerGlobComp(app)
// Multilingual configuration
// 多语言配置
// Asynchronous case: language files may be obtained from the server side
// 异步案例:语言文件可能从服务器端获取
await setupI18n(app)
// Configure routing
// 配置路由
setupRouter(app)
// router-guard
// 路由守卫
setupRouterGuard(router)
// Register global directive
// 注册全局指令
setupGlobDirectives(app)
// Configure global error handling
// 配置全局错误处理
setupErrorHandle(app)
// https://next.router.vuejs.org/api/#isready
// await router.isReady();
app.mount(container)
}
// bootstrapVue3()
const initQianKun = () => {
console.log('处于子应用模式...')
renderWithQiankun({
// 当前应用在主应用中的生命周期
// 文档 https://qiankun.umijs.org/zh/guide/getting-started#
mount(props) {
console.log('b20 ziems 子应用 收到的 props:', props)
bootstrapVue3(props.container?.querySelector('#app'))
// 可以通过props读取主应用的参数:msg
// 监听主应用传值以及我本身的一些业务逻辑
props.setLoading(false)
props.onGlobalStateChange((res) => {
console.log('res:', res)
})
},
bootstrap() {
console.log('bootstrap')
// 做一些一次性的初始化的工作,因为只触发一次
},
update() {
console.log('update')
},
unmount(props) {
// 卸载子应用
app.unmount()
app = null
},
})
}
// 判断当前应用是否在主应用中
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : bootstrapVue3(document.getElementById('app'))
官网和网上有些教程说要写一个publicPath文件,引入到main.ts。实际用下来,不能说没有用,只能说那是完全没卵用,qiankunWindow这个变量是可以判断是否在微前端模式下。
做完上述步骤后,子应用确实可以加载。但是/login 跑到前面去了。等关闭main.ts 中的路由守卫时,就变成下面这种图了
出现这个问题一定要检查路由配置。
出现这个问题一定要检查路由配置。
出现这个问题一定要检查路由配置。
如果你项目中允许出现路由前缀,可以在createRouter直接传递常量, 这样就不需要判断
export const router = createRouter({
history: createWebHistory(import.meta.env.VITE_PUBLIC_PATH), // !!!这句话很重要,微前端下需要配置路由前缀
// 应该添加到路由的初始路由列表。
routes: basicRoutes as unknown as RouteRecordRaw[],
// 是否应该禁止尾部斜杠。默认为假
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
})
恭喜你,到这里你的子系统应该可以正常加载了。后面记录下加载以后遇到的问题。
- 图片加载失败
系统中需要引入本地图时,会出现由于路由原因加载失败的情况。这是因为把子应用放入到基座后,静态资源会默认走主应用地址去访问,但是主应用又没有这些静态资源文件,其结果显而易见,肯定是404了。不管是配置webpack的publicPath还是vite中的base, 都无法解决问题。qiankun是通过import-html-entry注入方式实现子系统的引入。然后通过eval方法执行生成的js脚本。如果系统中有通过import或者require方式引入的资源,插件本身是解决不了的。我在vite 官网上 找到了一个解决方法。其实就是通过new URL方法将本地资源生成一个url,进而加载该url来显示图片。
将原来的import dashbaord from 'assets/svg/menu/数据看板.svg'
变成
const dashboard = new URL('../assets/svg/menu/数据看板.svg', import.meta.url).href
- css 中 背景图片url() 失效,显示图片无法加载
这篇文章说的很清楚,大家感兴趣可以去瞧瞧。其实问题也是出在import-html-entry库。这个库会请求子应用的css文件,并将请求到的文件内容用嵌到子应用的标签中。这就导致了子应用的外联样式变成了内联样式,url()中的相对路径从原来的相对css文件地址变成了相对html文档地址了。有人提出了解决方案并提了pr给umi,结果umi觉得是库的问题直接给拒了。有人说是改源码,有人说是在打包工具上做个配置。
我的方法就比较简单粗暴。上面通过new URL生成了url地址不是可以正常访问嘛?我可以在js中定义一个变量,并想办法把这个变量手动赋值到dom的background-image属性上不就可以了么?
less语法虽然不可以直接处理new URL(),但是可以进行变量赋值啊。话不多说,直接上代码:
const createImageUrl = () => {
const imageUrl = new URL('@/assets/svg/table-icon.svg', import.meta.url).href;
document.documentElement.style.setProperty('--pseudo-bg-image', `url(${imageUrl})`);
};
onMounted(() => {
createImageUrl();
});
&::before {
content: '';
display: block;
width: 16px;
height: 16px;
margin-right: 8px;
background-image: var(--pseudo-bg-image);;
// background-image: url('@/assets/svg/table-icon.svg');
background-size: 100% 100%;
}
需要说明的是上面这句话 document.documentElement.style.setProperty('--pseudo-bg-image',
url(${imageUrl}));这行代码的作用是将名为
--pseudo-bg-image的CSS变量的值设置为由imageUrl变量提供的URL。这个变量可以在CSS或Less中通过var(--pseudo-bg-image)来引用。例如,如果你的
imageUrl变量包含值'http://example.com/image.png',那么执行这行代码后,CSS变量--pseudo-bg-image的值将被设置为'url("http://example.com/image.png")'。然后你可以在样式中这样使用这个变量
- 填补最后一坑,部署
首先说明下我的主应用与子应用路由定义:
主应用路由地址 http://127.0.0.1/html/zifoms其中 /html/zifoms是我的路由前缀。想要通过路由 /setting/ziems/ 定位到vben admin子应用中去。这个时候主应用的路由地址为 http://127.0.0.1/html/zifoms/setting/ziems
子应用路由前缀为/html/ziems。由于主,子两个应用部署在同一个端口下,子应用访问地址为http://127.0.0.1/html/ziems
vben admin 环境变量有个参数VITE_PUBLIC_PATH。他会自动将这个变量同时设置成路由前缀和目录地址。现实的问题是当我设置成/html/zimes时,子路由会劫持主应用路由。而设置成/html/zifoms/setting/ziems时,页面能正常现实,但当我刷新页面时,子应用会脱离qiankun框架,直接现实子应用内容。
最后的实现方式是目录依然保持/html/ziems 在路由定义时做个判断,在微前端下设置成/html/zifoms/setting/ziems, 正常情况依然保持/html/ziems配置。如下图
最后附上nginx 配置