5月27日 20:03

Garfish 的路由管理系统如何工作,如何实现主子应用的路由协同?

Garfish 的路由管理系统是微前端架构中最关键的基础设施之一——主应用需要知道何时加载/卸载子应用,子应用需要知道自己的路由空间在哪,两者必须无缝协同才能实现"像单页应用一样"的用户体验。

本文将从路由劫持原理、basename 自动计算、路由分发机制、主子路由同步四个层面,拆解 Garfish 路由系统的完整工作流程。

路由劫持:一切从拦截浏览器路由开始

Garfish 在执行 Garfish.run() 时,会立即对浏览器的路由行为进行劫持。具体做法是重写 window.history.pushStatewindow.history.replaceState,同时监听 popstatehashchange 事件。

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', }, ], });

劫持的目的有两个:

  1. 感知路由变化:每次 URL 变化时,Garfish 都能第一时间捕获到新的路径。
  2. 接管路由控制权:根据新的路径判断应该激活哪个子应用、销毁哪个子应用,而不是让浏览器默认行为接管。

这意味着在 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 执行以下流程:

  1. 检查子应用状态:如果子应用已加载且当前激活,则仅更新子应用路由;如果未加载,则触发子应用加载。
  2. 加载子应用资源:根据 entry 配置(HTML 入口或 JS 入口)请求子应用资源,创建沙箱环境,执行子应用代码。
  3. 调用 render 生命周期:将 dombasename 等信息通过 provider 的 render 函数传递给子应用。
  4. 卸载非活跃子应用:对不再匹配的子应用调用 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 控制

javascript
Garfish.run({ autoRefreshApp: true, // 默认 true // ... });
  • autoRefreshApp: true(默认):路由变化时自动刷新子应用视图,子应用内部路由跳转完全正常。
  • autoRefreshApp: false:路由变化时不自动刷新子应用,子应用子路由只能通过 Garfish.router 跳转,但子应用一级路由仍可使用框架路由。

3. 路由守卫

Garfish 提供 beforeEachafterEach 钩子,用于在路由变化时执行拦截逻辑:

javascript
Garfish.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 互斥,不要使用根路径作为激活条件。

最佳实践总结

  1. 主应用必须使用 History 路由模式,Hash 模式不被 Garfish 路由系统支持。
  2. 子应用必须使用 basename,且从 provider.render 参数中动态获取,不要硬编码。
  3. 跨应用跳转统一使用 Garfish.router.push(),避免手动拼接路径出错。
  4. activeWhen 规则保持互斥,禁止使用根路径 /,避免多个子应用同时匹配。
  5. autoRefreshApp 保持默认 true,除非有明确的性能优化需求。
  6. 路由守卫用于权限控制,复杂的业务逻辑放在应用内部,守卫层只做拦截和重定向。

Garfish 的路由系统通过劫持、匹配、隔离、同步四层机制,解决了微前端架构中最棘手的路由协同问题。理解这套机制,才能在实际项目中避免路由冲突、白屏、状态丢失等常见坑。

标签:Garfish