qiankun 微前端接入实战
微前端这个内容在之前就做过分享,但是对于完整的项目实战没有写过,在公司刚好有后台,解决一下之前遗留的登录态和权限的问题。
主应用
1、安装依赖
pnpm i qiankun
2、main.ts
采用 registerMicroApps 来注册子应用
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import 'src/styles/index.scss'
import router from 'src/router/index'
import { registerMicroApps } from 'qiankun'const app = createApp(App)app.use(router)
app.mount('#app')registerMicroApps([{name: 'h5App',entry: '//dev.yvyiai.com:8090/yvyiai-digital-human-h5', // 确保路径不以斜杠结尾container: '#sub-container',activeRule: '/yvyiai-digital-human-web/h5App'}
])
3、编写路由
这里需要添加子应用的路径匹配(不然子应用带路由的话,主应用会404)
const routes = [{path: '/h5App',component: () => import('src/views/Layout/index.vue'),children: [{path: '/:pathMatch(.*)*',name: 'h5App',component: () => import('src/views/subApp/index.vue'),meta: {title: 'h5App'}}]}
]
4、subApp/index.vue
需要添加一个子应用的渲染容器节点,需要和 registerMicroApps 注册的子应用的 container 节点一致
<template><div id="sub-container"></div>
</template><script setup lang="ts">
import { start } from 'qiankun'
import { onMounted } from 'vue'onMounted(() => {// 启动 qiankun,添加错误处理start({sandbox: {experimentalStyleIsolation: true},prefetch: false, // 禁用预加载避免冲突// 添加全局错误处理globalContext: window})
})
</script>
子应用
1、安装依赖
pnpm i vite-plugin-qiankun -D
2、配置vite.config.ts
-
如果子应用是webpack的话,可以看qiankun官网,vite需要使用插件。
-
子应用同时需要支持跨域
-
配置打包为umd
import qiankun from 'vite-plugin-qiankun'export default defineConfig({// 参数1:子应用名plugin: [qiankun('h5App', { useDevMode: true })],server: {// 开发环境的hosthost: 'dev.yvyiai.com',cors: true,// 添加跨域头部headers: {'Access-Control-Allow-Origin': '*','Access-Control-Allow-Methods':'GET, POST, PUT, DELETE, PATCH, OPTIONS','Access-Control-Allow-Headers':'X-Requested-With, content-type, Authorization'}},build: {lib: {entry: './src/main.ts', // 入口文件name: 'h5App', // 子应用名称fileName: 'h5App', // 打包后的文件名formats: ['umd'] // 打包为 UMD 格式}}
})
3、改造main.ts
webpack的应用改造方法有点不同,比vite简单
import { createApp, type App as AppInstance } from "vue";
import router from './router'
import App from './App.vue'
import {renderWithQiankun,qiankunWindow
} from 'vite-plugin-qiankun/dist/helper'let app: AppInstance | null = null
function render(props: any = {}) {const { container } = propsapp = createApp(App)app.use(router)app.mount(container ? container.querySelector("#app") : "#app");
}if (!qiankunWindow.__POWERED_BY_QIANKUN__) {render()
}renderWithQiankun({mount(props) {render(props)},bootstrap() {},unmount(_props) {if (app) {app.unmount()if (app._container) {app._container.innerHTML = ''}app = null}},update() {}
})
4、路由改造
这个项目的主子应用都配置了base route的,子应用在qiankun环境下,需要把base route换成主应用的base+route path
-
主应用base route:yvyiai-digital-human-web
-
主应用对应子应用的出口路由:/h5App
// 子应用router/index.ts
const ROUTER_BASE = 'yvyiai-digital-human-h5'const router = createRouter({history: createWebHistory(!qiankunWindow.__POWERED_BY_QIANKUN__ ? ROUTER_BASE : 'yvyiai-digital-human-web/h5App'),routes
})
对应如下:
-
子应用独立运行路径: http://dev.yvyiai.com:8090/yvyiai-digital-human-h5/xxx
-
在主应用下运行路径: http://dev.yvyiai.com:8080/yvyiai-digital-human-web/h5App/xxx
子应用在主应用运行的路径就要以主应用的base为准了,不然会找不到资源的。
开发模式下接口问题
在开发模式下,我们的子应用通常会做proxy代理,但是这就导致我主应用是没有子应用的代理配置的,例如:
-
子应用单独运行代理请求url: http://dev.yvyiai.com:8090/liveApi/api1
-
子应用在主应用下运行的请求url: http://http://dev.yvyiai.com:8080/liveApi/api1
这里的 /liveApi 就是子应用在vite配置中做的代理,但是在主应用下看到是没有这个代理的,所以请求会报错。
解决办法:
-
在后端对接口处理跨域,不采用代理的方式
-
在主应用的vite中也配置同样的代理
但是方法2这种方法会存在一个缺点就是:我的子应用很多,代理也很多,我当前的子应用下面就有快10个代理接口,如果子应用也多,就会导致主应用的配置不好维护。
上面的解决方法只是基础原理
最终方法:
可以采用 CMS 进行后端配置,在运行前去加载代理的应用数据(也就是我的子应用需要提前去CMS系统上注册)。
这种方法对主应用去注册子应用也同样有用,可以通过远程数据配置来动态注册应用,这样基座代码不用每次注册都进行修改。
新建 remote-proxy-loader.js ,用于去加载不同子应用的proxy,去主应用的 vite.config.ts 去使用即可。
const fs = require('fs');
const path = require('path');
const axios = require('axios');// 本地缓存文件路径
const PROXY_CACHE_PATH = path.resolve(__dirname, './proxy-cache.json');/*** 从远程获取代理配置*/
async function fetchRemoteProxyConfig() {try {const response = await axios.get('https://your-config-server.com/proxy-config');return response.data; // 假设返回格式: { subApp1: { ... }, subApp2: { ... } }} catch (error) {console.error('获取远程代理配置失败,使用本地缓存', error);// 尝试读取本地缓存if (fs.existsSync(PROXY_CACHE_PATH)) {return JSON.parse(fs.readFileSync(PROXY_CACHE_PATH, 'utf-8'));}return {}; // 无配置时返回空对象}
}/*** 保存配置到本地缓存*/
function saveProxyCache(config) {fs.writeFileSync(PROXY_CACHE_PATH, JSON.stringify(config, null, 2));
}/*** 获取当前应用的代理配置*/
async function getCurrentAppProxy(appName) {const allConfig = await fetchRemoteProxyConfig();// 保存到本地缓存saveProxyCache(allConfig);// 返回当前应用的配置return allConfig[appName] || {};
}module.exports = { getCurrentAppProxy };
登录态的问题
项目中少不了的就是接口或页面的权限问题,这对项目的安全性也是非常重要的。
1、接口权限
公司的项目采用的是cookie作为登录态校验的,核心思路是利用 cookie 的跨域特性和 qiankun 的通信机制来做控制。
1.1 原理
cookie 具有域(domain)属性,若主应用和子应用配置在同一主域名下(如主应用 app.example.com ,子应用 sub1.example.com ),可通过设置 domain=.example.com 实现 cookie 共享
1.2 实现方案
同域的情况下直接共享:
- 登录接口设置 cookie 时指定主域名
// 登录接口响应头设置(后端)
Set-Cookie: token=xxx; domain=.example.com; path=/; HttpOnly; Secure
- 主应用登录后,子应用自动获取同域名下的 cookie
不同域的情况下的登录态同步:
当主应用与子应用不在同一域时:
- 主应用登录后,通过 qiankun 的全局通信机制通知子应用
// 主应用登录成功后
import { initGlobalState } from 'qiankun';const globalState = initGlobalState({token: 'xxx', // 登录后获取的 tokenisLogin: true
});// 主应用监听子应用消息
globalState.onGlobalStateChange((state, prev) => {console.log('主应用监听到状态变化', state, prev);
});
- 子应用监听主应用的登录状态
// 子应用中
export function mount(props) {// 监听主应用传递的登录状态props.onGlobalStateChange((state, prev) => {if (state.isLogin) {// 子应用存储 token 到本地或内存localStorage.setItem('token', state.token);}}, true);
}
2、页面权限
页面权限可以在登录的时候,通过动态路由生成权限路由(还是靠CMS去拿)。但只限于主应用的路由,子应用的路由的话,只能通过主子应用通信+接口(接口返回403状态,可以直接返回login页面)解决权限
效果展示
由于没有做样式的特殊处理,导致子应用的样式污染到了主应用。
即使qiankun设置了样式隔离,vite还是会有影响,所以需要手动处理样式(webpack可以做到完美隔离)