公司项目之 qiankun 微前端项目总结
基于本项目的 qiankun 使用总结(pomp-front 基座,pomp_contract_web 与 pomp-background-web 子应用)
本文总结本仓库内微前端接入 qiankun 的方式与关键实现,覆盖主应用(基座)与两个子应用的改造要点、运行与部署注意事项、常见问题等。
项目角色与目录
- 基座主应用:
pomp-front - 子应用一(合同):
pomp_contract_web - 子应用二(后台管理):
pomp-background-web
主应用通过 qiankun 统一注册与管理子应用,两个子应用以 UMD 形式暴露生命周期并支持独立运行。
主应用(pomp-front)集成要点
- 关键文件:
pomp-front/src/main.js - 引入与启动:
import { registerMicroApps, start } from "qiankun";
// ...
registerMicroApps([
// 仅列出与当前仓库对应的两个子应用(还有其他配置项在代码中)
{
name: "pomp-back-manage",
entry: process.env.NODE_ENV === "production" ? "/pomp-back-manage/" : "//localhost:8082",
container: "#app-body",
activeRule: getActiveRule("#/back_manage"),
props: { data: { store, router } }
},
{
name: "pomp-contract",
entry: process.env.NODE_ENV === "production" ? "./pomp-contract/" : "//localhost:8084",
container: "#app-body",
activeRule: getActiveRule(["#/contract", "#/contract_view"]),
props: { data: { store, router } }
}
], {
beforeLoad: () => { /* 开启加载遮罩并等待路由数据就绪 */ },
beforeMount: () => { /* 切换主/子应用展示 */ },
beforeUnmount: () => { /* 还原主应用展示 */ }
});
start();
getActiveRule使用 Hash 模式正则匹配,支持单个或多个前缀:
const getActiveRule = (hash) => (location) => {
if (typeof hash === "string") {
return new RegExp(`^${hash}($|[/?]+)`).test(location.hash);
} else {
return hash.some(h => new RegExp(`^${h}($|[/?]+)`).test(location.hash));
}
};
- 容器节点:所有子应用挂载到主应用 DOM 容器
#app-body。 - 跨应用通信:通过
props传递{ data: { store, router } }给子应用,子应用在生命周期中读取父级store和router。 - 导航与样式细节:在主应用路由守卫中,结合
microFrontendPathList判断是否为子应用路由;当从子应用返回基座时调用setCurrentRunningApp(null),用于避免样式隔离导致的样式丢失问题(qiankun 沙箱相关)。
子应用通用改造点
两个子应用均包含以下特征:
- 注入运行时 publicPath(解决生产静态资源路径问题)
- 关键文件:
src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
/* eslint-disable */
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
- 在子应用入口第一行引入:
import "./public-path";
- 支持独立运行与 qiankun 运行
- 入口文件(示例:
pomp_contract_web/src/main.js与pomp-background-web/src/main.js)核心结构:
import "./public-path";
// ... 省略 Vue、Router、Store、UI 组件等初始化
let router = null;
let instance = null;
let _routes = null; // 动态路由
async function render(props = {}) {
const { container, data = {} } = props;
if (!data.store) {
// 独立运行或未传入父 store 的容错提示
// Message.error("请先启动主应用");
} else {
// 从父级 store 获取原始路由,生成自身路由并写回本地 store
// await initRoutes(data.store.state.originPompRoutes);
}
router = new VueRouter({ routes: _routes });
// 路由守卫中:
// - 根据 basePath 判断是否属于当前子应用
// - 未匹配到时在 qiankun 环境跳转父路由 /notauth
// - 设置页面标题、存储子路由等
instance = new Vue({ router, store, render: h => h(App) })
.$mount(container ? container.querySelector("#app") : "#app");
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
// qiankun 生命周期
export async function bootstrap(props) {
// 可在此对接父级 store,如关闭遮罩、同步主 store:
// props.data.store.dispatch("closeLoadingMask");
// store.dispatch("setMainStore", props.data.store);
}
export async function mount(props) { render(props); }
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = "";
instance = null;
router = null;
_routes = null;
}
- 路由基路径与鉴权
- 子应用通过自身 store 的 getter 读取基础路径,例如:
- 合同子应用:
store.getters.contractBasePath - 后台子应用:
store.getters.backManageBasePath
- 合同子应用:
- 在
router.beforeEach中:- 若当前访问路径不属于本子应用 basePath,直接
next()交由其他应用(或基座)处理; - 若在 qiankun 环境且未匹配到路由,跳转父级路由
/notauth; - 刷新时根据 meta 设置默认
routeType,并维护二级菜单等路由状态。
- 若当前访问路径不属于本子应用 basePath,直接
- 本地给与访问权限
- vue.config.js增加响应头允许跨域
module.exports = {
outputDir: "pomp-back-manage",
productionSourceMap: false,
devServer: {
open: false,
port: 8082,
headers: {
"Access-Control-Allow-Origin": "*" // 允许跨域访问
}
}
}
开发与本地联调
- 主应用本地启动后,按需启动子应用开发服务器。当前代码约定子应用端口:
- 后台管理:
//localhost:8082 - 合同:
//localhost:8084
- 后台管理:
- 确保主应用路由守卫中动态路由加载完成(主应用通过遮罩 + 轮询变量
routeInstall确保就绪),避免子应用尚未拿到父级路由而导致的白屏或 404。 - DOM 容器
#app-body需存在于主应用页面,子应用默认挂载点为其内的#app。
部署注意事项
- 生产环境子应用入口:
pomp-back-manage:/pomp-back-manage/pomp-contract:./pomp-contract/(注意相对路径,用于特定嵌入场景的兼容)
- 静态资源与 publicPath:务必保留
public-path.js注入逻辑,避免生产环境静态资源 404。 - 网关/Nginx 示例(仅示意):
location /pomp-back-manage/ {
root /your/deploy/root; # 指向构建产物根目录
try_files $uri $uri/ /pomp-back-manage/index.html;
}
location /pomp-contract/ {
root /your/deploy/root;
try_files $uri $uri/ /pomp-contract/index.html;
}
跨应用通信与数据
- 主应用通过
props向子应用传入{ data: { store, router } }; - 子应用在
bootstrap/mount中使用父级store:- 合同子应用:初始化自身路由,使用
data.store.state.originPompRoutes; - 后台子应用:关闭遮罩、注册全局组件(若存在)等;
- 合同子应用:初始化自身路由,使用
- 子应用在
render和路由守卫中必要时回调父级router(如跳转/notauth)。
常见问题与排查
- 样式丢失/污染:从子应用返回基座前调用
setCurrentRunningApp(null),缓解 qiankun 沙箱切换带来的样式问题(见主应用路由守卫与beforeEach中的调用)。 - 静态资源 404:确保子应用第一行引入
src/public-path.js,并未手动硬编码 webpackpublicPath。 - 路由不匹配:
activeRule为 Hash 前缀匹配(正则^hash($|[/?]+));检查是否与子应用basePath、实际访问路径保持一致。 - 首次进入白屏:主应用确保已完成用户信息与路由拉取(
routeInstall === true),再进入子应用;代码通过beforeLoad遮罩 + 轮询避免竞态。
关键代码位置速览
- 主应用注册与启动:
pomp-front/src/main.js(registerMicroApps与start) - 子应用合同:
pomp_contract_web/src/public-path.jspomp_contract_web/src/main.js
- 子应用后台管理:
pomp-background-web/src/public-path.jspomp-background-web/src/main.js
如需新增子应用,请对照本总结:在子应用内加入 public-path.js、导出生命周期、支持独立运行;在主应用中补充 registerMicroApps 配置(包含 name、entry、container、activeRule、props),并确保容器与路由前缀一致。
技术栈(基座与子应用)
- 基座主应用
pomp-front:- Vue 2.x + Vue Router + Vuex + Element UI
- qiankun(Hash 模式激活规则)、whatwg-fetch、vue-cookies
- 全局组件/指令/过滤器,事件总线,样式隔离问题通过
setCurrentRunningApp(null)处理
- 子应用
pomp_contract_web(合同):- Vue 2.x + Vue Router + Vuex + Element UI + vue-cookies
src/public-path.js注入运行时 publicPath;导出 qiankun 生命周期;支持独立运行
- 子应用
pomp-background-web(后台管理):- Vue 2.x + Vue Router + Vuex + Element UI
src/public-path.js注入;导出生命周期;事件总线与全局 Icon 组件初始化
新增 Vue 3 子应用指南
- 总体兼容:qiankun 对框架无强绑定,Vue 3 子应用可直接接入;需按 Vue 3 API 实现生命周期与挂载/卸载。
- 入口与生命周期(示例):
// main.ts / main.js
import "./public-path";
import { createApp } from "vue";
import App from "./App.vue";
import { createRouter, createWebHashHistory } from "vue-router";
import { createStore } from "vuex"; // 如需
let app = null;
let router = null;
function render(props = {}) {
const { container, data = {} } = props;
router = createRouter({ history: createWebHashHistory(), routes: [] });
app = createApp(App);
// app.use(store)
app.use(router);
app.mount(container ? container.querySelector("#app") : "#app");
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap(props) {
// 可读取 props.data.store / props.data.router
}
export async function mount(props) { render(props); }
export async function unmount() {
app?.unmount();
app = null;
router = null;
}
- publicPath 注入:沿用本仓库子应用的
src/public-path.js写法不变。 - 路由模式:建议使用
createWebHashHistory()与基座 Hash 规则一致,避免激活不匹配。 - 构建工具:
- Webpack:按 UMD 产物输出即可;
- Vite:建议使用
vite-plugin-qiankun,并在开发环境开启 CORS、配置正确的base与资源路径。
- 样式与隔离:保持根节点稳定为
#app;避免全局样式污染,必要时开启 CSS Modules 或 BEM 规范。
登录职责与约定(重要)
- 统一登录入口在基座
pomp-front:- 所有登录态获取(SSO、ticket、authCode、linkToken)、登录跳转、token 刷新、用户信息与动态路由拉取,均在主应用路由守卫内处理。
- 主应用在路由就绪后再激活子应用(通过
beforeLoad遮罩 +routeInstall控制)。
- 子应用不参与登录流程:
- 子应用禁止自行发起登录跳转或校验票据,只消费来自
props.data.store的已登录上下文(如originPompRoutes等)。 - 在
window.__POWERED_BY_QIANKUN__环境下,如未匹配到路由或缺少必要上下文,应回退父级路由(如/notauth)或给出只读提示,而非二次登录。
- 子应用禁止自行发起登录跳转或校验票据,只消费来自
- 凭证与接口调用:
- 推荐由主应用统一初始化请求拦截(或使用 Cookie 模式由后端校验);
- 子应用只发起业务请求,不管理登录状态;如需跨应用共享信息,请通过
props或主应用全局状态传递。
本文来自博客园,作者:Math点PI,个性签名:“不写bug怎么进步?”,转载请注明原文链接:https://www.cnblogs.com/MrZhous/p/19039955

浙公网安备 33010602011771号