项目概述
项目结构
base-pdp 是一个基于微前端架构的研发管理平台,采用 Vue 3 和 Pinia 构建主应用,通过 Wujie 微前端框架集成多个子系统。项目结构清晰,遵循功能模块化组织原则。
主应用位于 src 目录下,包含以下核心模块:
- api:API 接口定义
- components:通用与业务组件
- config:全局配置(路由、权限、API 模块等)
- layouts:布局组件,包括微前端容器
- router:路由配置
- stores:Pinia 状态管理
- utils:工具函数
- views:视图组件
子系统通过 layouts 中的 WujiePMP.vue 和 WujieMP.vue 组件集成,分别对应“产品管理平台”和“测试管理”子应用。
核心组件
主应用的核心组件包括:
- main.ts:应用入口,负责初始化 Vue 实例、注册插件、配置微前端。
- WujiePMP/WujieMP.vue:微前端容器组件,负责加载和渲染子应用。
- user.ts:用户状态管理,存储 token、用户信息、权限等。
- router.config.ts:路由配置,定义主应用与子应用的路由映射。
这些组件协同工作,实现主应用的全局状态管理、路由控制和子应用集成。
架构概览
base-pdp 采用微前端架构,主应用作为壳系统,负责用户认证、全局布局、菜单导航和状态共享。子应用(PMP、MP)作为独立系统运行在 iframe 中,通过 Wujie 框架实现沙箱隔离和通信。
主应用通过 setupApp 方法注册子应用,配置其 URL、生命周期钩子、通信 props 和插件。子应用通过 bus 事件与主应用通信,如路由变更、登出等。
详细组件分析
主应用启动流程分析
主应用的启动流程在 main.ts 中定义,是整个系统运行的起点。
启动流程图
依赖注入与插件注册
主应用通过 app.use() 方法注册多个插件和功能模块:
- Pinia:状态管理,用于存储用户信息、权限、菜单等全局状态。
- Vue Router:路由管理,控制页面导航。
- WujieVue:微前端核心框架,提供子应用加载、沙箱、通信能力。
- infiniteScroll:第三方无限滚动插件。
- useAll:自定义全局配置注册函数,可能包含组件、指令等。
代码示例:
app.use(createPinia());
app.use(router);
app.use(WujieVue);
app.use(infiniteScroll);
useAll(app);
这些插件的注册顺序至关重要,例如 Pinia 必须在权限控制模块之前初始化,因为后者依赖前者。
微前端集成机制分析
主应用通过 setupApp 函数配置和注册子应用,实现微前端集成。
PMP 与 MP 子系统集成
主应用在 main.ts 中为 pmp 和 mp 两个子系统调用 setupApp:
setupApp({
name: 'pmp',
url: hostMap('pmp'),
props,
attrs,
plugins,
...lifecycles,
});
setupApp({
name: 'mp',
url: hostMap('mp'),
props,
attrs,
plugins,
...lifecycles,
});
- name: 子应用唯一标识。
- url: 子应用地址,由 hostMap 函数根据环境动态生成。
- props: 传递给子应用的数据,如 jump 路由跳转函数、token 认证令牌。
- plugins: 注入的插件,用于解决样式、SVG 显示等问题。
- lifecycles: 生命周期钩子,用于监控子应用状态。
子应用通信机制
主应用通过 Wujie 的 bus 事件总线与子应用通信:
- sub-route-change: 子应用路由变化时,通知主应用同步高亮菜单。
- sub-route-back: 子应用触发浏览器后退。
- sub-logout: 子应用登出,主应用刷新页面。
代码示例:
bus.$on('sub-route-change', (name, $route) => {
const mainPath = `/${name}${$route.path}`;
router.push({ path: mainPath, query: $route.query });
});
全局布局与路由控制
微前端容器组件
WujiePMP.vue 和 WujieMP.vue 是主应用与子应用之间的桥梁。
- WujiePMP.vue:使用 <script setup> 语法,简洁地传递 props,如 userInfo、menuList、authInfo 等,实现状态共享。
- WujieMP.vue:使用选项式 API,包含更复杂的错误处理逻辑,能捕获子应用的 unhandledrejection 和 error 事件,并提供“重新加载”按钮。
两者都通过计算属性 subUrl 动态生成子应用的 URL,将主应用路由路径映射到子应用内部路由。
路由配置
router.config.ts 定义了主应用的路由表。
- constantRouterMap: 常量路由,如登录、404 页面。
- asyncRouterMap: 动态路由,包含主应用视图和子应用入口。
子应用路由通过 component: () => import('@/layouts/WujiePMP.vue') 指向微前端容器,并设置 name: 'pmp-sub',其子路由的 path 以 /pmp/ 开头,component 为 BlankRawLayout,表示内容由子应用自身渲染。
状态共享与通信机制
主应用通过两种方式与子应用共享状态:
- 通过 props 传递:在 setupApp 和微前端容器组件中,将用户信息、权限、菜单等作为 props 传递给子应用。子应用通过 window.$wujie.props 访问这些数据。
- 通过 bus 事件通信:主应用和子应用通过事件总线进行双向通信,如路由同步、登出通知等。、
此外,主应用使用 Pinia (user.ts) 集中管理用户状态。useUserStore 提供了 Login、GetUserInfo、Logout 等方法,并通过 bus.$emit('wujie-setToken') 通知子应用 token 变更。
依赖分析
主应用的核心依赖包括:
- Vue 3: 前端框架。
- Pinia: 状态管理。
- Wujie-vue3: 微前端框架。
- ant-design-vue: UI 组件库。
- vite: 构建工具。
子应用通过 hostMap.ts 动态获取其部署地址,支持开发、测试、生产等多环境配置。plugin.ts 中的插件解决了微前端常见的样式隔离和资源加载问题。
微前端架构
微前端架构原理
项目结构分析
项目采用基于Vue 3的微前端架构,主应用(base-pdp)通过Wujie框架集成多个远程子应用。项目结构清晰,按功能模块划分目录,核心微前端相关组件位于src/layouts目录下。
主应用负责全局状态管理、路由控制和用户认证,子应用包括PMP、MP、CMDB等独立系统,通过iframe沙箱方式嵌入主应用。这种设计实现了各子应用的技术栈独立和团队解耦。
核心架构概述
base-pdp项目采用Wujie微前端框架实现多应用集成,核心架构基于iframe沙箱技术,通过JS沙箱、样式隔离和资源预加载等机制,确保子应用间的独立性和安全性。
主应用作为壳系统,提供统一的登录认证、菜单导航和布局框架。子应用以独立部署的方式运行,通过配置化的URL映射动态加载。这种架构支持团队独立开发、测试和部署,有效降低了大型项目的协作成本。
主应用初始化流程
主应用的初始化逻辑在main.ts文件中实现,通过Vue 3的createApp API创建应用实例,并集成Wujie微前端框架。
import WujieVue from 'wujie-vue3';
const { bus, setupApp } = WujieVue;
import lifecycles from './lifecycles';
import hostMap from './hostMap';
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.use(WujieVue);
// 注册子应用
setupApp({
name: 'pmp',
url: hostMap('pmp'),
exec: true,
props: { jump, token },
degrade,
...lifecycles,
});
setupApp({
name: 'mp',
url: hostMap('mp'),
exec: true,
props: { jump, token },
degrade,
...lifecycles,
});
初始化过程包含以下关键步骤:
- 创建Vue应用实例并挂载核心插件(Pinia、Router)
- 注册WujieVue插件,获取事件总线(bus)和应用注册方法(setupApp)
- 通过hostMap工具函数获取子应用的运行时URL
- 调用setupApp注册PMP和MP子应用,配置通信参数和生命周期钩子
子应用隔离机制
Wujie框架通过多重机制实现子应用间的完全隔离,确保各应用互不干扰。
JS沙箱机制
Wujie通过Proxy代理技术创建JS沙箱环境,拦截子应用对全局对象(window、document等)的访问和修改。每个子应用拥有独立的执行上下文,变量和函数不会污染主应用或其他子应用。
样式隔离机制
采用CSS作用域控制实现样式隔离:
- 子应用样式被限制在iframe内部
- 通过Shadow DOM或样式前缀避免全局样式冲突
- 插件机制解决特殊样式问题(如SVG图标显示)
资源隔离
每个子应用的静态资源(JS、CSS、图片)独立加载,通过iframe的同源策略实现天然隔离。主应用不共享任何资源给子应用,确保技术栈完全独立。
生命周期管理
微前端架构通过精细化的生命周期管理控制子应用的加载、渲染和销毁过程。生命周期钩子在lifecycles.ts文件中定义,并在主应用初始化时注入。
const lifecycles = {
beforeLoad: (appWindow) => console.log(`[wujie-${appWindow.__WUJIE?.id}] 加载前`),
beforeMount: (appWindow) => console.log(`[wujie-${appWindow.__WUJIE?.id}] 挂载前`),
afterMount: (appWindow) => console.log(`[wujie-${appWindow.__WUJIE?.id}] 挂载后`),
beforeUnmount: (appWindow) => console.log(`[wujie-${appWindow.__WUJIE?.id}] 卸载前`),
afterUnmount: (appWindow) => console.log(`[wujie-${appWindow.__WUJIE?.id}] 卸载后`),
activated: (appWindow) => console.log(`[wujie-${appWindow.__WUJIE?.id}] 激活`),
deactivated: (appWindow) => console.log(`[wujie-${appWindow.__WUJIE?.id}] 停用`),
loadError: (url, e) => {
console.log(`[wujie] ${url} 加载失败`);
console.error(e);
},
};
生命周期流程如下:
- beforeLoad: 子应用资源开始加载前触发
- beforeMount: 子应用DOM挂载前触发
- afterMount: 子应用挂载完成后触发
- activated/deactivated: 子应用激活/停用时触发(用于保活模式)
- beforeUnmount/afterUnmount: 子应用卸载前后触发
此外,提供了refreshApp方法用于主动清除子应用沙箱缓存,实现应用刷新功能。
通信机制设计
主应用与子应用之间通过事件总线(Event Bus)和Props传递实现双向通信。
事件总线通信
基于Wujie的bus对象实现跨应用事件通信:
// 主应用监听子应用路由变化
bus.$on('sub-route-change', (name, $route) => {
const mainPath = `/${name}${$route.path}`;
router.push({ path: mainPath, query: $route.query });
});
bus.$on('sub-route-back', () => {
router.back();
});
bus.$on('sub-logout', () => {
location.reload();
});
Props数据传递
主应用通过props向子应用传递必要数据:
// 传递跳转方法和认证信息
const props = {
jump: (location) => {
router.push(location);
},
token: lStorage.get('token'),
};
子应用异常处理
MP子应用实现了完善的异常捕获机制,监听子应用内的错误事件并反馈给主应用:
autoCatchError() {
const subWindow = window.document.querySelector('iframe[name=mp]')?.contentWindow;
// 捕获未处理的Promise拒绝
subWindow.addEventListener('unhandledrejection', onUnhandledRejection);
// 捕获运行时错误
subWindow.addEventListener('error', onError);
}
资源预加载与性能优化
项目通过资源预加载策略提升子应用的加载性能,减少用户等待时间。
预加载配置
在main.ts中通过setupApp的exec参数控制预加载行为:
setupApp({
name: 'pmp',
url: hostMap('pmp'),
exec: true, // true表示预执行子应用代码
// ...
});
当exec设置为true时,子应用资源加载后立即执行代码,用户访问时可快速呈现。
动态URL映射
通过hostMap.ts实现环境自适应的URL映射:
export default function hostMap(app: string) {
if (import.meta.env.MODE === 'production') {
return getPrefixUrl(app); // 生产环境使用端口映射
}
return map[app].devUrl; // 开发环境使用配置的开发URL
}
按需加载
子应用布局组件根据路由动态计算加载URL,实现按需加载:
<template>
<WujieVue :url="subUrl" />
</template>
<script setup>
const subUrl = computed(() => {
const path = route.fullPath.replace('/pmp/', '');
return hostMap('pmp') + `${path}`;
});
</script>
插件扩展机制
Wujie框架通过插件机制提供灵活的扩展能力,plugin.ts文件定义了多个插件来解决特定问题。
const plugins = [
{
htmlLoader: (code) => code,
jsLoader: (code) => code,
cssLoader: (code) => code,
},
// 解决SVG图标不显示问题
{
appendOrInsertElementHook(element, iframeWindow) {
if (element.nodeName === 'svg' && /* 条件判断 */) {
iframeWindow.__WUJIE.styleSheetElements.push(element);
}
},
},
// 解决Vite5+Vue3样式元素被移除问题
{
patchElementHook(element, iframeWindow) {
if (element.nodeName === 'STYLE') {
element.insertAdjacentElement = function (_position, ele) {
iframeWindow.document.head.appendChild(ele);
};
}
},
},
];
插件类型包括:
- 加载器插件:拦截HTML、JS、CSS资源的加载过程
- DOM操作钩子:在元素添加或插入时执行自定义逻辑
- 元素补丁钩子:修改元素的默认行为
这些插件解决了微前端场景下的常见兼容性问题,增强了框架的适应能力。
架构优势总结
基于Wujie框架的微前端架构为base-pdp项目带来了显著的技术优势:
团队独立开发
各子应用团队可以独立选择技术栈(Vue、React等),独立开发、测试和部署,大幅提升开发效率和团队协作灵活性。
技术栈异构
主应用与子应用可以使用不同的前端框架和技术版本,避免了技术栈升级的全局影响,支持渐进式技术演进。
独立部署
子应用可以独立发布,无需与主应用同步部署,降低了发布风险和协调成本,支持持续交付和灰度发布。
故障隔离
子应用间的完全隔离确保了单个应用的故障不会影响整个系统,提高了整体系统的稳定性和可靠性。
渐进式迁移
支持将传统单体应用逐步拆分为微前端架构,降低架构升级的风险和成本。
该架构特别适合大型企业级应用,能够有效管理复杂度,支持多团队并行开发,是现代前端架构的优秀实践。
子应用集成机制
项目结构
项目采用基于功能的文件组织方式,主要模块包括api、components、layouts、router、stores等。src/layouts目录下存放了主应用的布局组件,其中WujiePMP.vue和WujieMP.vue是用于集成PMP和MP子应用的关键布局组件。src/config目录下的router.config.ts文件定义了主应用的路由规则,而hostMap.ts文件则配置了子应用的入口地址。
核心组件
核心组件包括WujiePMP.vue和WujieMP.vue两个布局组件,它们通过WujieVue组件实现对远程子应用的挂载和管理。WujieVue是无界微前端框架提供的Vue3组件,用于在主应用中安全地加载和运行远程子应用。
架构概述
主应用通过无界微前端框架实现对PMP和MP子应用的集成。当用户访问以/pmp或/mp开头的URL时,主应用的路由会匹配到对应的布局组件WujiePMP.vue或WujieMP.vue。这些布局组件内部使用WujieVue组件,根据当前路由动态构建子应用的入口URL,并将其挂载到指定的DOM节点中。主应用与子应用之间通过props传递数据,如用户信息、权限信息等,并通过事件总线进行通信。
详细组件分析
WujiePMP.vue 分析
WujiePMP.vue是PMP子应用的布局组件,其主要功能是通过WujieVue组件挂载和管理PMP子应用。
代码结构
<template>
<div class="pmp-sub">
<WujieVue
width="100%"
height="100%"
name="pmp"
:alive="false"
:sync="false"
:url="subUrl"
:props="{ jump, token, firstRedirectPath, userInfo, menuList, dicOptions, authInfo }"
/>
</div>
</template>
属性配置
- name: 子应用的唯一标识符,用于区分不同的子应用实例。
- alive: 设置为false,表示不启用保活模式,每次URL变化都会重新渲染子应用实例。
- sync: 设置为false,表示不将子应用的路由状态同步到主应用。
- url: 通过计算属性subUrl动态生成子应用的入口URL。
- props: 向子应用传递数据,包括跳转函数jump、用户令牌token、用户信息userInfo、菜单列表menuList、字典选项dicOptions和权限信息authInfo。
数据传递
WujiePMP.vue通过props向PMP子应用传递了丰富的上下文信息:
- jump: 一个函数,允许子应用通过主应用的路由进行页面跳转。
- token: 用户的认证令牌,用于子应用的身份验证。
- userInfo: 当前登录用户的基本信息。
- menuList: 用户的菜单权限列表。
- dicOptions: 字典数据,用于下拉框等组件的数据源。
- authInfo: 用户的详细权限信息。
WujieMP.vue 分析
WujieMP.vue是MP子应用的布局组件,其功能与WujiePMP.vue类似,但包含了更复杂的错误处理和生命周期管理。
代码结构
<template>
<div class="mp-sub">
<div v-if="!isError" style="width: 100%; height: 100%">
<WujieVue
width="100%"
height="100%"
name="mp"
v-bind="lifecycles"
:alive="false"
:sync="false"
:url="subUrl"
:props="{ jump, token }"
:loadError="handleMpLoadError"
:activated="handleActivated"
:deactivated="handleDeactivated"
:beforeMount="handleBeforeMount"
:beforeUnmount="handleBeforeUnMount"
></WujieVue>
</div>
<div v-else class="mp-sub__error">
<a-result status="500" title="客户端异常,请稍后再试!" sub-title="Error">
<template #extra>
<a-button type="primary" @click="handleRefresh">重新加载</a-button>
</template>
</a-result>
</div>
</div>
</template>
错误处理
WujieMP.vue实现了完善的错误处理机制:
isError: 一个布尔值,用于控制是否显示错误提示页面。
handleMpLoadError: 当子应用加载失败时触发,将isError设置为true,显示错误页面。
handleRefresh: 用户点击“重新加载”按钮时调用,刷新整个页面以尝试恢复。
生命周期钩子
WujieMP.vue通过v-bind="lifecycles"将lifecycles.ts中定义的生命周期钩子绑定到WujieVue组件上,并额外定义了handleActivated、handleDeactivated等方法,用于在子应用激活和失活时执行特定逻辑,如自动捕获子应用的异常。
异常捕获
WujieMP.vue通过autoCatchError方法实现了对子应用异常的主动捕获:
- 监听子应用iframe的unhandledrejection事件,捕获未处理的Promise拒绝。
- 监听子应用iframe的error事件,捕获JavaScript运行时错误。
- 通过useWujieBusOn监听子应用通过事件总线发送的TMP_ERROR异常事件。
路由配置分析
主应用的路由配置在router.config.ts文件中定义,通过asyncRouterMap数组配置了PMP和MP子应用的路由规则。
PMP子应用路由
{
path: '/pmp',
name: 'pmp-sub',
meta: { title: '产品管理平台', hide: true },
component: () => import('@/layouts/WujiePMP.vue'),
children: [
{
path: '/pmp/workBoard/versionBoard',
name: 'versionBoard',
component: BlankRawLayout,
meta: { title: '版本看板' },
},
// ... 其他子路由
],
}
- path: /pmp,所有PMP子应用的URL都以此为前缀。
- component: 指向WujiePMP.vue布局组件。
- children: 定义了PMP子应用内部的路由映射,如/pmp/workBoard/versionBoard映射到“版本看板”。
MP子应用路由
{
path: '/mp',
name: 'mp-sub',
meta: { title: '测试管理', hide: true },
component: () => import('@/layouts/WujieMP.vue'),
children: [
{
path: '/mp/product-risk/list',
name: 'productRiskList',
component: BlankRawLayout,
meta: { title: '产品风险' },
},
// ... 其他子路由
],
}
- path: /mp,所有MP子应用的URL都以此为前缀。
- component: 指向WujieMP.vue布局组件。
- children: 定义了MP子应用内部的路由映射。
URL映射机制
当用户访问/pmp/workBoard/versionBoard时,主应用的Vue Router会匹配到WujiePMP.vue组件。在WujiePMP.vue中,subUrl计算属性会执行以下操作:
- 获取当前完整路径:route.fullPath(例如/pmp/workBoard/versionBoard)。
- 移除/pmp/前缀,得到子应用内部路径:workBoard/versionBoard。
- 调用hostMap('pmp')获取PMP子应用的基地址(例如//localhost:8082/)。
- 拼接基地址和内部路径,得到最终的子应用入口URL://localhost:8082/workBoard/versionBoard。
依赖分析
主应用通过package.json依赖了wujie-vue3库,版本为^1.0.28。main.ts文件中通过app.use(WujieVue)注册了无界插件,使其在全局可用。WujiePMP.vue和WujieMP.vue组件直接依赖wujie-vue3库中的WujieVue组件来实现子应用的挂载。
性能考虑
- 按需加载: 子应用的布局组件(如WujiePMP.vue)是通过import()动态导入的,实现了路由级别的代码分割和懒加载。
- 资源隔离: 无界框架通过iframe或Web Worker技术实现了子应用的资源隔离,避免了样式和脚本的全局污染。
- 内存管理: WujiePMP.vue和WujieMP.vue都设置了alive="false",这意味着子应用在切换时会被销毁,有助于释放内存,但会牺牲一定的切换速度。
故障排除指南
- 子应用加载失败: 检查hostMap.ts中配置的子应用URL是否正确,确保子应用服务已启动。
- 样式错乱: 确认主应用和子应用的CSS作用域是否正确隔离,避免全局样式冲突。
- 通信失败: 检查事件总线的事件名称是否拼写正确,确保主应用和子应用都正确注册了事件监听器。
- 数据传递失败: 确认props传递的数据类型和结构是否符合子应用的预期。
应用间通信机制
核心通信机制
通信架构概述
系统采用事件总线(Event Bus)模式实现应用间通信,主应用与各子应用通过共享的全局事件总线进行消息的发布(emit)与订阅(on)。该模式实现了组件间的松耦合,使得主应用可以通知子应用执行操作,子应用也可以回调通知主应用更新状态。
事件总线封装分析
Wujie 事件总线封装
useWujie.ts 文件对 WujieVue 的事件总线进行了封装,提供了更安全、类型更明确的 API。
事件常量定义
文件中定义了 WujieBusEvents 常量对象,集中管理所有事件名称,避免硬编码,提高可维护性。
export const WujieBusEvents = {
/** 测试管理平台 抽屉事件 */
TMP_DRAWER: 'tmp-drawer',
/** 测试管理平台 抽屉事件 */
TMP_DRAWER_EVENT: 'tmp-drawer-emits',
/** 测试管理平台 关闭所有抽屉事件 */
TMP_DRAWER_CLOSE_ALL: 'tmp-drawer-close-all',
/** 产品管理平台抽屉 */
PMP_DRAWER: 'pmp-drawer',
// ... 其他事件
} as const;
类型安全
通过 as const 和类型推断,确保事件名称的类型安全。
type WujieBusEventsType = (typeof WujieBusEvents)[keyof typeof WujieBusEvents];
事件操作封装
提供了 useWujieBusOn、useWujieBusEmit 和 useWujieBusOff 三个函数,分别用于监听、触发和移除事件监听。
跨子应用通信实例
主应用向子应用传递参数
在 TmpDrawers.vue 中,主应用通过 ref 直接调用子应用的方法,并通过 props 传递配置参数。
<template>
<div>
<TestCaseInfoDrawer
ref="TestCaseInfoDrawer"
@submit="e => handleDrawerEventListener('TestCase', 'submit', e)"
/>
<!-- 其他抽屉组件 -->
</div>
</template>
<script>
export default {
computed: {
TestCaseDrawerRef() {
return this.$refs.TestCaseInfoDrawer;
},
},
methods: {
handleDrawerEventListener(type, eventName, payload) {
emitTmpDrawer({ type, data: payload, event: eventName });
},
},
};
</script>
子应用回调通知主应用
子应用通过 $emit 触发事件,主应用在 TmpDrawers.vue 中监听并处理。
跨子应用通信:转需求
ProductFaultInfoDrawer.vue 中的 handleCallPmpRequire 方法展示了如何通过事件总线调用另一个子应用(PMP)的功能。
handleCallPmpRequire() {
this.closeDrawer();
useWujieBusEmit(WujieBusEvents.PMP_DRAWER, {
component: 'CreatePro',
componentProps: {
sourceInfo: {
requireName: `【缺陷推送】${faultName}`,
// ... 其他预填数据
},
sourceByFault: id,
},
callback: () => {
// 回调逻辑
},
});
}
通信规范与最佳实践
事件命名规范
- 采用小写字母和连字符(kebab-case)命名,如 tmp-drawer。
- 按功能模块前缀划分,如 TMP_ 表示测试管理平台,PMP_ 表示产品管理平台。
- 语义清晰,避免歧义。
通信时序控制
- 事件监听时机:在组件 mounted 钩子中绑定监听,在 beforeUnmount 中移除。
- 事件触发时机:在操作完成且数据稳定后触发,避免频繁触发。
- 异步处理:对于耗时操作,使用 async/await 确保时序正确。
错误处理与内存泄漏防护
错误处理机制
- 事件监听检查:在触发事件前,检查目标组件是否存在。
- 异常捕获:在事件处理函数中使用 try-catch 捕获潜在错误。
- 日志记录:关键操作添加 console.log 便于调试。
防止内存泄漏 - 及时解绑:在组件销毁时,必须调用 useWujieBusOff 移除事件监听。
- 清理引用:在 beforeUnmount 钩子中清理 ref 和定时器。
路由同步策略
项目结构
项目采用基于功能的模块化结构,清晰地分离了主应用与多个子应用(PMP、MP)。主应用负责全局路由、布局和状态管理,而子应用作为独立的模块通过iframe嵌入。路由配置、布局组件和微前端集成点是理解整个系统的关键。
核心组件
系统的核心组件包括主应用的路由配置、Wujie布局组件、主应用入口文件和事件总线。这些组件协同工作,实现了微前端环境下的路由同步。
架构概览
该微前端系统采用主从架构。主应用作为容器,负责管理全局状态、用户认证和路由分发。子应用(PMP和MP)是独立开发、独立部署的前端应用,通过Wujie框架以iframe的形式嵌入主应用。主应用通过路由前缀(如/pmp/和/mp/)来识别和分发请求,并利用Wujie的事件总线实现主子应用间的双向通信。
详细组件分析
主应用路由配置分析
主应用的router.config.ts文件定义了静态路由和动态路由。asyncRouterMap中的/pmp和/mp路径是关键的微前端接入点。
Wujie布局组件分析
WujiePMP.vue和WujieMP.vue是子应用的容器组件。它们使用WujieVue组件来加载子应用。
MP子应用加载机制
MP子应用的布局组件WujieMP.vue除了加载子应用外,还集成了更复杂的错误处理和生命周期监听。
主应用入口与事件总线分析
main.ts是应用的入口,负责初始化Wujie框架和设置事件监听。
依赖分析
系统依赖关系清晰,主应用是核心,所有子应用和工具都依赖于它。
性能考虑
- 预加载: 代码中注释掉了preloadApp,说明团队考虑过预加载以提升性能,但可能因“防止子应用提前渲染路由”而禁用。
- 沙箱缓存: lifecycles.ts中的refreshApp函数允许主动清除子应用的沙箱缓存,可用于解决内存泄漏或状态污染问题。
- 网络请求: hostMap.ts根据环境(开发、生产)动态生成子应用URL,优化了网络请求路径。
故障排除指南
当出现路由同步问题或白屏时,可按以下步骤排查:
- 检查子应用URL: 确认hostMap.ts中配置的子应用URL是否正确,特别是端口号。
- 检查路由前缀: 确保主应用路由配置中/pmp和/mp的路径定义正确,且子应用内部路由与之匹配。
- 检查事件总线: 在浏览器控制台中,检查sub-route-change和routeChange事件是否被正确触发和监听。
- 检查iframe加载: 查看WujiePMP.vue和WujieMP.vue中的subUrl计算结果是否正确,并确认iframe是否成功加载子应用。
- 处理加载错误: WujieMP.vue中实现了对ChunkLoadError和SyntaxError的捕获,若子应用加载失败,会显示错误页面并提供“重新加载”按钮。
- 清除缓存: 使用refreshApp函数或强制刷新页面来清除可能的沙箱缓存问题。
结论
主应用通过router.config.ts分配专属路由前缀,并利用Wujie布局组件进行路由劫持。子应用的URL通过移除前缀并拼接主域名来构建。主应用通过bus.$on('sub-route-change')监听子应用的路由变化,确保浏览器地址栏与当前视图同步。同时,子应用通过useWujieBusEmit(WujieBusEvents.ROUTE_CHANGE)将内部路由变化通知给主应用,形成闭环。这种基于事件总线的双向通信模式,有效地解耦了主子应用,实现了无缝的用户体验。