主应用:umi+react
@umi/max 内置了 Qiankun 微前端插件,它可以一键启用 Qiankun 微前端开发模式
配置子应用
// .umirc.ts
export default {
qiankun: {
master: {
apps: [
{
name: 'app1',//微应用的名称 通常跟子应用的 base 保持一致
entry: '//localhost:7001',//微应用的 HTML 地址
},
],
},
},
};
引入子应用
在父应用中引入子应用,插件提供了三种不同实现的方式:
- 路由绑定引入子应用。
<MicroApp />组件引入子应用。<MicroAppWithMemoHistory />组件引入子应用。
路由绑定引入子应用
手动配置 .umirc.ts 文件中的 routes 项,通过路由的方式绑定子应用。何时使用:
- 子应用包含完整的路由切换逻辑时。
- 父子应用路由相互关联时。
现在,我们想要在 /app1/project 和 /app2 路由分别加载子应用 app1 和 app2,可以配置父应用的路由如下:
// .umirc.ts
export default {
routes: [
{
path: '/',
component: '@/layouts/index.tsx',
routes: [
{
path: '/app1',
component: '@/layouts/app-layout.tsx',
routes: [
// 配置微应用 app1 关联的路由
{
// 带上 * 通配符意味着将 /app1/project 下所有子路由都关联给微应用 app1
path: '/project/*',
microApp: 'app1',
},
],
},
// 配置 app2 关联的路由
{
path: '/app2/*',
microApp: 'app2',
},
],
},
],
};
配置好后,子应用的路由 base 会在运行时被设置为主应用中配置的 path。
例如,在上面的配置中,我们指定了 app1 关联的 path 为 /app1/project,假如 app1 里有一个路由配置为 /user,当我们想在父应用中访问 /user 对应的页面时,浏览器的 url 需要是 base + /user,即 /app1/project/user 路径,否则子应用会因为无法匹配到正确的路由而渲染空白或 404 页面。
qiankun 插件拓展了 Umi 原有的路由对象,新增了 microApp 字段,它的值为注册子应用的 name。切换到对应路由后,Umi 将会使用 <MicroApp /> 组件渲染此子应用,并替换原来路由的 component。
拓展后的 Umi 路由对象 API 可见此。
<MicroApp /> 组件引入子应用
通过 <MicroApp /> 组件加载(或卸载)子应用。何时使用:
- 子应用包含完整的路由切换逻辑时。
- 父子应用路由相互关联时。
现在,我们想在父应用的某个页面中引入子应用 app1,可以编写代码如下:
import { MicroApp } from 'umi';
export default function Page() {
return ;
}
使用该方式引入子应用时,父子应用的路由将一一对应。例如,当父应用路由为 /some/page 时,子应用路由同样为 /some/page。切换子应用路由时,父应用将同步切换。
如果父应用的路由包含前缀,可以通过配置 base 属性保证父子应用的路由正确对应。例如,父应用路由为 /prefix/router-path/some/page 时,我们希望子应用的路由为 /some/page,可以修改代码如下:
import { MicroApp } from 'umi';
export default function Page() {
return ;
}
<MicroAppWithMemoHistory /> 组件引入子应用
通过 <MicroAppWithMemoHistory /> 组件加载(或卸载)子应用。何时使用:
- 仅使用子应用的指定路由时。
- 父子应用路由相互独立时。
<MicroAppWithMemoHistory /> 组件是 <MicroApp /> 组件的变体,您需要显式提供 url 属性作为子应用的路由。当父应用的路由发生变化时,子应用的路由不会改变。
现在,我们想在父应用的某个组件内部引入 app2 子应用,子应用的路由为 /some/page,可以编写代码如下:
import { MicroAppWithMemoHistory } from 'umi';
export default function Page() {
return ;
}
子应用 :Vue 3 + Vite
安装qiankun
npm install vite-plugin-qiankun --save-dev
# 或者 yarn add vite-plugin-qiankun -D
配置main
import * as antIcons from '@ant-design/icons-vue';
import Antd, { message } from 'ant-design-vue';
import lodash from 'lodash';
import { createApp } from 'vue';
import JsonViewer from 'vue3-json-viewer';
import { smartSentry } from '/@/lib/smart-sentry';
import { loginApi } from '/@/api/system/login/login-api';
import constantsInfo from '/@/constants/index';
import { privilegeDirective } from '/@/directives/privilege';
import i18n from '/@/i18n/index';
import privilegePlugin from '/@/plugins/privilege-plugin';
import smartEnumPlugin from '/@/plugins/smart-enums-plugin';
import { buildRoutes, changeHome, router } from '/@/router/index';
import { store } from '/@/store/index';
import { useUserStore } from '/@/store/modules/system/user';
import { getTokenFromCookie } from '/@/utils/cookie-util';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import App from './App.vue';
import './pablic-path';
import 'vue3-json-viewer/dist/index.css';
import '/@/theme/index.less';
// 应用实例和路由实例
let appInstance = null;
let appRouter = null;
/**
* 创建并配置 Vue 应用
*/
function createVueApp(props = {}) {
const container = props.container ? props.container.querySelector('#app') : '#app';
// 创建 Vue 应用
const app = createApp(App);
// 使用插件
app.use(router).use(store).use(i18n).use(Antd).use(smartEnumPlugin, constantsInfo).use(privilegePlugin).use(JsonViewer);
// 注册权限指令
app.directive('privilege', {
mounted(el, binding) {
privilegeDirective(el, binding);
},
});
// 注册所有图标组件
Object.keys(antIcons).forEach((key) => {
app.component(key, antIcons[key]);
});
// 配置全局属性
app.config.globalProperties.$antIcons = antIcons;
app.config.globalProperties.$lodash = lodash;
// 挂载应用
appInstance = app.mount(container);
return appInstance;
}
/**
* 获取用户登录信息并构建动态路由
*/
async function setupUserInfoAndRoutes() {
const token = getTokenFromCookie();
if (!token) {
changeHome();
return;
}
try {
const res = await loginApi.getLoginInfo();
const menuRouterList = res.data.menuList.filter((e) => e.path || e.frameUrl);
// 构建动态路由
buildRoutes(menuRouterList);
// 更新用户信息到 store
useUserStore().setUserLoginInfo(res.data);
} catch (error) {
console.error(' 获取用户信息失败:', error);
message.error(' 获取用户信息失败');
smartSentry.captureError(error);
}
}
/**
* 初始化应用程序
*/
async function initializeApplication(props = {}) {
try {
// 先设置用户信息和路由
await setupUserInfoAndRoutes();
// 然后创建 Vue 应用
return createVueApp(props);
} catch (error) {
console.error(' 应用初始化失败:', error);
smartSentry.captureError(error);
// 即使失败也创建基础应用
return createVueApp(props);
}
}
renderWithQiankun({
/**
* 微前端生命周期函数 - 启动
*/
bootstrap() {
console.log('Micro app bootstrap');
},
/**
* 微前端生命周期函数 - 挂载
*/
mount(props) {
console.log('Micro app mount');
initializeApplication(props);
},
/**
* 微前端生命周期函数 - 卸载
*/
unmount() {
console.log('Micro app unmount');
if (appInstance) {
// 正确的卸载方式
appInstance.unmount?.();
appInstance = null;
}
if (appRouter) {
appRouter = null;
}
},
});
/**
* 独立启动应用程序
*/
async function startApplication() {
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
await initializeApplication();
}
}
// 启动应用
startApplication();
配置 vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import qiankun from 'vite-plugin-qiankun';
const { name } = require('./package.json');///主应用name:app1
export default defineConfig({
plugins: [
vue(),
qiankun(name, { //主应用name
useDevMode: true // 开发模式也要能上台排练
})
});
配置router
import { createRouter, createWebHistory } from 'vue-router';
import { routerArray } from './routers';//本地路由
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
const { name } = require('./package.json');///主应用name:app1
export const router = createRouter({
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? `/${name}` : ''),
routes: routerArray,
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
});
完整路由配置
import nProgress from 'nprogress';
import { nextTick } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import { PAGE_PATH_404, PAGE_PATH_LOGIN, PAGE_PATH_SEARCH } from '/@/constants/common-const';
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
import SmartLayout from '/@/layout/smart-layout.vue';
import { useUserStore } from '/@/store/modules/system/user';
import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper';
import lodash from 'lodash';
import { routerArray } from './routers';
import { name } from ;
import 'nprogress/nprogress.css';
export const router = createRouter({
history: createWebHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? `/${name}` : ''),
routes: routerArray,
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
});
// ----------------------- 路由加载前 -----------------------
router.beforeEach(async (to, from, next) => {
// 进度条开启
nProgress.start();
// 公共页面,任何时候都可以跳转
if (to.path === PAGE_PATH_404 || to.path === PAGE_PATH_SEARCH) {
next();
return;
}
// 首页( 需要登录 ,但不需要验证权限)
if (to.path == HOME_PAGE_NAME) {
next();
return;
}
// 下载路由对应的 页面组件,并修改组件的Name,如果修改过,则不需要修改
let toRouterInfo = routerMap.get(to.name);
if (toRouterInfo && lodash.isFunction(toRouterInfo.component) && toRouterInfo.meta.renameComponentFlag === false) {
// 因为组件component 为 lazy load是个方法,所以可以直接执行 component()方法
toRouterInfo.component().then((val) => {
// 修改组件的name
val.default.name = to.meta.componentName;
// 记录已经修改过 组件的name
toRouterInfo.meta.renameComponentFlag = true;
});
}
// 是否刷新缓存
// 当前路由是否在tag中 存在tag中且没有传递keepAlive则刷新缓存
let findTag = (useUserStore().tagNav || []).find((e) => e.menuName == to.name);
let reloadKeepAlive = findTag && !to.params.keepAlive;
// 设置tagNav
useUserStore().setTagNav(to, from);
// 设置keepAlive 或 删除KeepAlive
if (to.meta.keepAlive) {
if (reloadKeepAlive) {
useUserStore().deleteKeepAliveIncludes(to.meta.componentName);
}
nextTick(() => {
useUserStore().pushKeepAliveIncludes(to.meta.componentName);
});
}
next();
});
// ----------------------- 路由加载后 -----------------------
router.afterEach(() => {
nProgress.done();
});
// ----------------------- 构建router对象 -----------------------
const routerMap = new Map();
export async function buildRoutes(menuRouterList) {
const routerList = [];
routerList.push({
path: '/',
name: 'searchHome',
redirect: '/languge',
});
//2、添加到路由里
await router.addRoute({
path: '/',
meta: {},
component: SmartLayout,
children: routerList,
});
}
export function changeHome() {
//2、添加到路由里
router.addRoute({
path: '/',
name: 'searchHome',
redirect: '/home',
});
}
浙公网安备 33010602011771号