公司项目之 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 } } 给子应用,子应用在生命周期中读取父级 storerouter
  • 导航与样式细节:在主应用路由守卫中,结合 microFrontendPathList 判断是否为子应用路由;当从子应用返回基座时调用 setCurrentRunningApp(null),用于避免样式隔离导致的样式丢失问题(qiankun 沙箱相关)。

子应用通用改造点

两个子应用均包含以下特征:

  1. 注入运行时 publicPath(解决生产静态资源路径问题)
  • 关键文件:src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  /* eslint-disable */
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
  • 在子应用入口第一行引入:import "./public-path";
  1. 支持独立运行与 qiankun 运行
  • 入口文件(示例:pomp_contract_web/src/main.jspomp-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;
}
  1. 路由基路径与鉴权
  • 子应用通过自身 store 的 getter 读取基础路径,例如:
    • 合同子应用:store.getters.contractBasePath
    • 后台子应用:store.getters.backManageBasePath
  • router.beforeEach 中:
    • 若当前访问路径不属于本子应用 basePath,直接 next() 交由其他应用(或基座)处理;
    • 若在 qiankun 环境且未匹配到路由,跳转父级路由 /notauth
    • 刷新时根据 meta 设置默认 routeType,并维护二级菜单等路由状态。
  1. 本地给与访问权限
  • 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,并未手动硬编码 webpack publicPath
  • 路由不匹配activeRule 为 Hash 前缀匹配(正则 ^hash($|[/?]+));检查是否与子应用 basePath、实际访问路径保持一致。
  • 首次进入白屏:主应用确保已完成用户信息与路由拉取(routeInstall === true),再进入子应用;代码通过 beforeLoad 遮罩 + 轮询避免竞态。

关键代码位置速览

  • 主应用注册与启动:pomp-front/src/main.jsregisterMicroAppsstart
  • 子应用合同:
    • pomp_contract_web/src/public-path.js
    • pomp_contract_web/src/main.js
  • 子应用后台管理:
    • pomp-background-web/src/public-path.js
    • pomp-background-web/src/main.js

如需新增子应用,请对照本总结:在子应用内加入 public-path.js、导出生命周期、支持独立运行;在主应用中补充 registerMicroApps 配置(包含 nameentrycontaineractiveRuleprops),并确保容器与路由前缀一致。


技术栈(基座与子应用)

  • 基座主应用 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 或主应用全局状态传递。
posted @ 2025-08-15 16:42  Math点PI  阅读(128)  评论(0)    收藏  举报