Garfish 的路由管理系统如何工作,如何实现主子应用的路由协同?
Garfish 的路由管理系统是微前端架构中最关键的基础设施之一——主应用需要知道何时加载/卸载子应用,子应用需要知道自己的路由空间在哪,两者必须无缝协同才能实现"像单页应用一样"的用户体验。
本文将从路由劫持原理、basename 自动计算、路由分发机制、主子路由同步四个层面,拆解 Garfish 路由系统的完整工作流程。
路由劫持:一切从拦截浏览器路由开始
Garfish 在执行 Garfish.run() 时,会立即对浏览器的路由行为进行劫持。具体做法是重写 window.history.pushState 和 window.history.replaceState,同时监听 popstate 和 hashchange 事件。
javascript// Garfish.run() 执行后,路由劫持自动生效 Garfish.run({ basename: '/', domGetter: '#subApp', apps: [ { name: 'react-app', activeWhen: '/react', entry: 'http://localhost:3000', }, { name: 'vue-app', activeWhen: '/vue', entry: 'http://localhost:8080/index.js', }, ], });
劫持的目的有两个:
- 感知路由变化:每次 URL 变化时,Garfish 都能第一时间捕获到新的路径。
- 接管路由控制权:根据新的路径判断应该激活哪个子应用、销毁哪个子应用,而不是让浏览器默认行为接管。
这意味着在 Garfish 运行之后,所有路由跳转都经过 Garfish 的路由管理层,主应用不再直接操控浏览器路由,而是通过 Garfish 间接操控。
路由匹配与子应用分发
当路由劫持捕获到 URL 变化后,Garfish 进入路由匹配阶段。核心逻辑是遍历 apps 配置,用每个子应用的 activeWhen 规则与当前路径做匹配:
javascript// activeWhen 支持字符串、正则、函数三种形式 { name: 'react-app', activeWhen: '/react', // 字符串前缀匹配 } { name: 'admin-app', activeWhen: /^\/admin/, // 正则匹配 } { name: 'special-app', activeWhen: (path) => path.startsWith('/special'), // 函数匹配 }
匹配成功后,Garfish 执行以下流程:
- 检查子应用状态:如果子应用已加载且当前激活,则仅更新子应用路由;如果未加载,则触发子应用加载。
- 加载子应用资源:根据
entry配置(HTML 入口或 JS 入口)请求子应用资源,创建沙箱环境,执行子应用代码。 - 调用
render生命周期:将dom、basename等信息通过 provider 的render函数传递给子应用。 - 卸载非活跃子应用:对不再匹配的子应用调用
destroy生命周期,清理 DOM 和事件监听。
关键细节:不要使用根路径 / 作为 activeWhen,否则该子应用在任何路径下都会被激活,导致其他子应用永远无法加载。
basename 自动计算:路由隔离的核心机制
basename 是 Garfish 实现路由隔离的关键。子应用的 basename 计算公式为:
shell子应用 basename = 主应用 basename + activeWhen
例如,主应用 basename 为 /,子应用 activeWhen 为 /react,则子应用收到的 basename 为 /react。如果主应用 basename 改为 /portal,子应用的 basename 自动变为 /portal/react。
javascript// 子应用 provider 配置 export const provider = () => ({ render({ dom, basename }) { // basename = 主应用 basename + activeWhen // 必须将 basename 设置为子应用路由的 base path ReactDOM.render( <BrowserRouter basename={basename}> <App /> </BrowserRouter>, dom ? dom.querySelector('#root') : document.querySelector('#root') ); }, destroy({ dom }) { ReactDOM.unmountComponentAtNode( dom ? dom.querySelector('#root') : document.querySelector('#root') ); }, });
如果子应用不使用 basename,会出现两个严重问题:
- 路由冲突:子应用的路由
/home会和主应用的/home冲突。 - 路由丢失:子应用内部跳转时,路径不会带上
/react前缀,导致刷新页面后 Garfish 无法匹配到子应用。
主子应用路由同步
Garfish 的路由同步要解决的核心问题是:子应用既需要能独立运行(开发阶段直接启动),又需要能嵌入主应用运行(生产环境)。Garfish 通过以下机制实现两种模式的平滑切换:
1. 路由跳转方式
javascript// 方式一:使用 Garfish.router(推荐) Garfish.router.push('/react/dashboard'); // 自动带上全局 basename,跳转到正确的完整路径 // 方式二:使用子应用框架路由 // 需要手动添加 basename 前缀 history.push(`${basename}/dashboard`);
Garfish.router.push() 会自动拼接全局 basename 作为路径前缀,确保跳转目标正确。而使用框架自带路由跳转时,必须手动添加 basename,否则路径会缺少前缀。
2. autoRefreshApp 控制
javascriptGarfish.run({ autoRefreshApp: true, // 默认 true // ... });
autoRefreshApp: true(默认):路由变化时自动刷新子应用视图,子应用内部路由跳转完全正常。autoRefreshApp: false:路由变化时不自动刷新子应用,子应用子路由只能通过Garfish.router跳转,但子应用一级路由仍可使用框架路由。
3. 路由守卫
Garfish 提供 beforeEach 和 afterEach 钩子,用于在路由变化时执行拦截逻辑:
javascriptGarfish.router.beforeEach((to, from, next) => { // to: 目标路由信息 // from: 来源路由信息 // next: 继续路由跳转(必须调用) if (to.path.startsWith('/admin') && !isAuthenticated()) { // 重定向到登录页 next('/login'); } else { next(); } }); Garfish.router.afterEach((to, from) => { // 路由跳转完成后的逻辑 trackPageView(to.path); });
路由模式与限制
| 特性 | 支持情况 | 说明 |
|---|---|---|
| 主应用 History 模式 | 完全支持 | 推荐使用 |
| 主应用 Hash 模式 | 不支持 | Garfish 路由系统仅支持主应用 History 路由 |
| 子应用 History 模式 | 支持 | 需正确配置 basename |
| 子应用 Hash 模式 | 支持 | 子应用内部可使用 Hash 路由 |
常见问题与排错
子应用路由跳转后页面白屏
原因:子应用未使用 basename 配置路由基础路径。跳转后的路径缺少 activeWhen 前缀,Garfish 匹配不到子应用,触发卸载。
解决:在子应用 provider.render 中将 basename 传递给路由组件。
主应用 basename 变更后子应用路由异常
原因:部分子应用硬编码了路径前缀,而不是使用动态传入的 basename。
解决:确保所有子应用路由均基于 provider.render 接收的 basename 动态构建。
子应用内部路由跳转不生效
原因:autoRefreshApp 设为 false,但子应用使用了框架路由跳转而非 Garfish.router。
解决:将 autoRefreshApp 设为 true,或改用 Garfish.router.push() 进行跳转。
多个子应用同时激活
原因:某个子应用的 activeWhen 配置为 / 或过于宽泛的正则,导致路径匹配到多个子应用。
解决:确保每个子应用的 activeWhen 互斥,不要使用根路径作为激活条件。
最佳实践总结
- 主应用必须使用 History 路由模式,Hash 模式不被 Garfish 路由系统支持。
- 子应用必须使用
basename,且从provider.render参数中动态获取,不要硬编码。 - 跨应用跳转统一使用
Garfish.router.push(),避免手动拼接路径出错。 activeWhen规则保持互斥,禁止使用根路径/,避免多个子应用同时匹配。autoRefreshApp保持默认true,除非有明确的性能优化需求。- 路由守卫用于权限控制,复杂的业务逻辑放在应用内部,守卫层只做拦截和重定向。
Garfish 的路由系统通过劫持、匹配、隔离、同步四层机制,解决了微前端架构中最棘手的路由协同问题。理解这套机制,才能在实际项目中避免路由冲突、白屏、状态丢失等常见坑。