标签

Garfish

Garfish 是一个轻量级的微前端框架,旨在帮助开发者实现多应用的集成与协作,提升大型复杂前端项目的开发效率和维护性。它通过沙箱技术隔离不同子应用的运行环境,保证各个微应用之间的独立性和安全性,同时支持多种加载方式,包括异步加载和预加载,优化整体性能。Garfish 兼容主流前端框架如 React、Vue 和 Angular,方便开发者在不同技术栈之间灵活切换和组合。它提供了丰富的生命周期钩子,方便开发者管理微应用的加载、挂载和卸载过程,支持路由同步和状态共享,增强子应用之间的协同能力。Garfish 还注重开发体验,具备良好的调试工具和友好的错误提示,帮助快速定位问题。通过使用 Garfish,企业能够实现前端架构的模块化和解耦,促进团队并行开发,降低项目复杂度,提高系统的可扩展性和可维护性,适用于大型互联网应用和多团队协作的场景。

Garfish
查看更多相关内容
前端5月27日 20:05
Garfish 支持哪些子应用加载方式,如何根据场景选择合适的加载策略?Garfish 子应用的加载方式主要分为**路由驱动自动加载**和**手动控制加载**两种模式,配合内置的预加载与缓存机制,可以覆盖从核心业务到低频功能的全场景需求。 ## 一、两种核心加载模式 ### 1. 路由驱动自动加载 通过 `Garfish.run()` 注册子应用并配置 `activeWhen` 路由匹配规则,Garfish 会自动劫持路由,当浏览器 URL 命中时加载并挂载对应子应用。这是最常用的方式,适合子应用与路由强关联的场景。 ```typescript import Garfish from 'garfish'; 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', // 也支持 JS 入口 }, ], }); ``` **关键配置项:** | 参数 | 说明 | |------|------| | `activeWhen` | 路由匹配条件,支持字符串、正则或函数 | | `entry` | 子应用入口地址,支持 HTML 入口和 JS 入口两种格式 | | `domGetter` | 子应用挂载的 DOM 容器 | | `basename` | 基础路径,实际传给子应用的 basename 为 `basename + activeWhen` | ### 2. 手动控制加载 通过 `Garfish.loadApp()` 手动加载子应用,灵活控制挂载、显示、隐藏的时机。适合子应用不依赖路由、需要动态挂载到任意容器的场景,比如弹窗内嵌子应用、Tab 切换复用同一子应用等。 ```typescript import Garfish from 'garfish'; // 手动加载子应用 const app = await Garfish.loadApp('vue-app', { domGetter: '#container', entry: 'http://localhost:3000', cache: true, }); // 首次渲染调用 mount,后续切换调用 show app.mounted ? app.show() : await app.mount(); // 隐藏子应用(保留实例,不销毁) await app.hide(); // 完全卸载子应用 await app.unmount(); ``` **`mount()` 与 `show()` 的区别:** `mount()` 是首次渲染,会执行子应用的生命周期;`show()` 是将已挂载的子应用重新显示,跳过生命周期执行,切换更轻量。路由插件内部的核心判断逻辑是:当 `cache` 为 `true` 且 `app.mounted` 为 `true` 时调用 `show()`,否则调用 `mount()`。 ## 二、预加载机制 Garfish 内置了智能预加载能力,在主应用空闲时提前拉取子应用资源,用户真正访问时无需等待网络请求。 ### 自动预加载 默认开启(`disablePreloadApp: false`),Garfish 会在用户端统计子应用的打开频率,打开次数越多的子应用预加载权重越高。在弱网环境和移动端会自动关闭预加载以节省流量。 ### 手动预加载 使用 `Garfish.preloadApp()` 主动触发指定子应用的资源预加载,适合在主应用 HTML 阶段就提前拉取首屏需要的核心子应用: ```typescript import Garfish from 'garfish'; // 先注册子应用 Garfish.registerApp({ name: 'react', entry: 'http://localhost:3000', }); // 预加载 react 子应用的入口资源和子资源 Garfish.preloadApp('react'); ``` 预加载的资源存储在独立内存中,真正加载子应用时不会再发起资源请求,直接复用已缓存的静态资源。 ### 关闭预加载 如果不需要预加载(如子应用体积大且访问频率低),可以在 `Garfish.run()` 中配置: ```typescript Garfish.run({ disablePreloadApp: true, // 关闭预加载 // ... }); ``` ## 三、缓存机制 Garfish 默认开启子应用缓存(`cache: true`),已加载的子应用实例不会在切换时销毁,而是保留在内存中。再次激活时调用 `show()` 而非 `mount()`,显著减少重复渲染开销。 可以进一步配置缓存策略: ```typescript const app = await Garfish.loadApp('vue-app', { cache: true, cacheOptions: { maxAge: 15 * 60 * 1000, // 缓存有效期 15 分钟 }, }); ``` 如果子应用存在内存泄漏问题或需要每次重新初始化,可以关闭缓存: ```typescript Garfish.run({ apps: [ { name: 'problematic-app', activeWhen: '/problem', entry: 'http://localhost:4000', cache: false, // 每次切换都销毁并重建 }, ], }); ``` ## 四、加载生命周期钩子 Garfish 提供了 `beforeLoad` 和 `afterLoad` 钩子,可以在子应用加载前后执行自定义逻辑,比如埋点统计、权限校验、加载态展示等: ```typescript Garfish.run({ beforeLoad(appInfo) { console.log('子应用开始加载:', appInfo.name); showLoadingSpinner(); }, afterLoad(appInfo) { console.log('子应用加载完成:', appInfo.name); hideLoadingSpinner(); }, }); ``` ## 五、如何根据场景选择加载策略 ### 场景一:常规路由级子应用 **选择:路由驱动自动加载 + 默认预加载 + 默认缓存** 这是最典型的微前端接入方式。子应用与路由一一对应,Garfish 自动处理加载、挂载、卸载的全流程: ```typescript Garfish.run({ basename: '/', domGetter: '#subApp', apps: [ { name: 'crm', activeWhen: '/crm', entry: 'http://localhost:3001' }, { name: 'oa', activeWhen: '/oa', entry: 'http://localhost:3002' }, ], }); ``` ### 场景二:首屏核心子应用需要极速加载 **选择:路由驱动自动加载 + 手动 `preloadApp` 提前拉取** 在主应用 HTML 阶段就预加载首屏核心子应用,确保用户进入时资源已经就绪: ```typescript // 在主应用最早执行的脚本中预加载 Garfish.registerApp({ name: 'home', entry: 'http://localhost:3001' }); Garfish.preloadApp('home'); Garfish.run({ domGetter: '#subApp', apps: [{ name: 'home', activeWhen: '/home', entry: 'http://localhost:3001' }], }); ``` ### 场景三:子应用需要挂载到非路由驱动的容器 **选择:手动 `loadApp` 加载** 比如侧边栏中嵌入的子应用、弹窗中加载的子应用,路由不变但需要动态挂载: ```typescript const sidebarApp = await Garfish.loadApp('sidebar-widget', { domGetter: '#sidebar', entry: 'http://localhost:3003', cache: true, }); await sidebarApp.mount(); ``` ### 场景四:低频大型子应用 **选择:路由驱动自动加载 + 关闭预加载 + 关闭缓存** 低频使用的子应用不需要预加载占用带宽,也不需要缓存占用内存: ```typescript Garfish.run({ disablePreloadApp: true, // 如需全部关闭 apps: [ { name: 'admin-panel', activeWhen: '/admin', entry: 'http://localhost:3004', cache: false, }, ], }); ``` ### 场景五:多实例同类型子应用 **选择:手动 `loadApp` 加载 + 不同容器** 需要在同一页面同时展示多个同类型子应用实例时,路由驱动无法满足,必须手动控制: ```typescript const app1 = await Garfish.loadApp('chart', { domGetter: '#chart-container-1', entry: 'http://localhost:3005', }); const app2 = await Garfish.loadApp('chart', { domGetter: '#chart-container-2', entry: 'http://localhost:3005', }); await Promise.all([app1.mount(), app2.mount()]); ``` ## 六、常见问题 **Q: `loadApp` 提示 "Invalid domGetter" 怎么办?** 确保挂载节点已经存在于页面 DOM 中。在 Garfish 开始渲染时如果查询不到挂载节点,就会抛出此错误。可以在组件的 `mounted` 生命周期或 `useEffect` 回调中调用 `loadApp`。 **Q: 子应用切换后状态丢失怎么办?** 默认情况下 `cache: true`,子应用切换时调用 `hide()` 而非 `unmount()`,状态会保留。如果状态丢失,检查是否误将 `cache` 设为 `false`,或子应用内部在 `unmount` 生命周期中手动清理了状态。 **Q: 预加载在移动端不生效?** Garfish 在弱网环境和移动端会自动关闭预加载,这是预期行为。如需强制开启,需修改 Garfish 源码中的网络检测逻辑,但不建议这样做。
前端5月27日 20:04
Garfish 的生命周期钩子有哪些?provider 函数和 show/hide 怎么用?Garfish 子应用的生命周期围绕 provider 函数展开,核心钩子按执行顺序为:bootstrap → mount → update(可选) → unmount,另有 show/hide 用于缓存场景。与 qiankun 的最大区别在于:Garfish 子应用必须导出 provider 函数而非直接导出生命周期函数。 ## 核心钩子及执行顺序 | 钩子 | 触发时机 | 调用次数 | 作用 | |------|----------|----------|------| | bootstrap | 子应用首次加载 | 仅 1 次 | 初始化配置、注入依赖 | | mount | 子应用渲染到容器 | 每次激活 | 挂载 DOM、启动渲染 | | unmount | 子应用从页面移除 | 每次离开 | 清理 DOM、事件、定时器 | | update | 父应用传递 props 变更(可选) | 按需 | 响应属性更新 | | show | 缓存子应用重新显示(可选) | 按需 | 恢复运行状态 | | hide | 缓存子应用被隐藏(可选) | 按需 | 暂停但不销毁 | 执行顺序: - **首次加载**:provider() → bootstrap → mount - **路由切换离开**:unmount(非缓存)或 hide(缓存模式) - **路由切换回来**:mount(非缓存)或 show(缓存模式,跳过 bootstrap) - **属性变更**:update - **彻底销毁**:unmount ## provider 函数:Garfish 生命周期的入口 Garfish 子应用必须导出一个 provider 函数,它的返回值才是真正的生命周期对象: ```javascript // 子应用入口 export function provider({ basename, dom, ...props }) { return { bootstrap() { console.log('[sub-app] bootstrap, basename:', basename); return Promise.resolve(); }, mount({ basename, dom }) { const container = dom.querySelector('#app'); ReactDOM.render(<App basename={basename} />, container); return Promise.resolve(); }, unmount({ dom }) { const container = dom.querySelector('#app'); ReactDOM.unmountComponentAtNode(container); return Promise.resolve(); }, update({ ...newProps }) { // 响应主应用传入的属性变更 return Promise.resolve(); }, }; } ``` 关键点:provider 接收主应用传入的 props(如 basename、dom 容器),在 mount/unmount 中通过参数获取运行时上下文,而非闭包变量。 ## show/hide:缓存模式下的生命周期 当主应用配置 `sandbox.cache = true` 时,子应用不会被销毁,而是通过 show/hide 控制显隐: ```javascript export function provider() { let app = null; return { // ...bootstrap, mount, unmount 省略 show() { // 恢复定时器、重新订阅事件、恢复动画 console.log('[sub-app] show: 恢复运行状态'); return Promise.resolve(); }, hide() { // 暂停定时器、取消事件订阅、暂停动画(不销毁 DOM) console.log('[sub-app] hide: 暂停运行状态'); return Promise.resolve(); }, }; } ``` 缓存模式下 show/hide 与 mount/unmount 互斥:激活走 show(不走 mount),离开走 hide(不走 unmount)。 ## 完整生命周期流程图 ``` 首次加载: 下载子应用 JS → 执行沙箱隔离 → 调用 provider() → bootstrap() → mount() 路由切换(非缓存): 旧子应用 unmount() → 新子应用 mount() 路由切换(缓存模式): 旧子应用 hide() → 新子应用 mount() 或 show() 属性更新: 主应用 setProps() → 子应用 update() 彻底销毁: unmount() → 清理沙箱 → 释放内存 ``` ## 插件级生命周期钩子 除子应用生命周期外,Garfish 还提供主应用侧的插件钩子,用于拦截加载过程: ```javascript Garfish.run({ plugins: [ () => ({ beforeLoad(appInfo) { console.log('即将加载:', appInfo.name); return appInfo; }, afterLoad(appInfo) { console.log('加载完成:', appInfo.name); }, beforeMount(appInfo) { console.log('即将挂载:', appInfo.name); }, afterMount(appInfo) { console.log('挂载完成:', appInfo.name); }, beforeUnmount(appInfo) { console.log('即将卸载:', appInfo.name); }, afterUnmount(appInfo) { console.log('卸载完成:', appInfo.name); }, }), ], }); ``` 这些钩子在主应用侧执行,可用于日志采集、性能监控、权限校验等横切逻辑。 ## 与 qiankun 生命周期的对比 | 对比项 | Garfish | qiankun | |--------|---------|----------| | 导出方式 | provider 函数返回生命周期对象 | 直接导出 bootstrap/mount/unmount | | 缓存钩子 | show/hide | 无(需自行实现) | | 插件钩子 | beforeLoad/afterLoad 等 6 个 | 框架级 beforeLoad/afterMount 等 | | 参数传递 | provider(props) + mount(props) | mount(props) | | 沙箱集成 | 生命周期与沙箱强绑定 | 沙箱独立于生命周期 | ## 常见踩坑与解决方案 **1. mount 中拿不到容器 DOM** mount 回调中的 dom 参数是 Garfish 创建的容器,需要在 dom 内查找挂载点: ```javascript mount({ dom }) { // 错误:document.getElementById('app') // 正确:在 Garfish 提供的 dom 内查找 const container = dom.querySelector('#sub-app-root'); ReactDOM.render(<App />, container); } ``` **2. unmount 后仍然有内存泄漏** 定时器和全局事件监听不会随 DOM 移除而自动清理: ```javascript let timer = null; let resizeHandler = null; mount({ dom }) { timer = setInterval(sendHeartbeat, 5000); resizeHandler = () => recalculateLayout(); window.addEventListener('resize', resizeHandler); // ... }, unmount() { clearInterval(timer); window.removeEventListener('resize', resizeHandler); timer = null; resizeHandler = null; } ``` **3. 缓存模式下 show/hide 未实现导致状态异常** 如果开启缓存但只实现了 mount/unmount,子应用在 hide 后定时器仍在运行、事件仍在监听,切回时可能出现重复绑定。必须配套实现 show/hide。 ## 追问 **Q: Garfish 为什么选择 provider 函数模式,而不是像 qiankun 那样直接导出生命周期?** provider 模式有两个优势:一是每次加载都可以通过 provider 重新创建生命周期实例,避免单例模式下多次挂载的状态污染;二是 provider 在执行时可以拿到主应用传入的 props(如 basename、dom),在闭包中天然拥有运行时上下文,不需要在 mount 中额外合并参数。 **Q: 如果子应用不实现 unmount 会怎样?** 子应用的 DOM 不会从容器中移除,事件监听器和定时器继续运行,路由切换后旧应用的副作用仍在执行,会导致内存泄漏、事件重复触发、UI 叠加渲染等问题。Garfish 不会强制校验 unmount 的实现,这是开发者的责任。 **Q: bootstrap 和 mount 的区别是什么,能不能把初始化逻辑都放在 mount 里?** bootstrap 只执行一次,mount 每次激活都会执行。如果把初始化逻辑(如加载配置、注册全局插件)放在 mount 里,每次路由切回都会重复执行,既浪费性能又可能导致重复注册。正确的做法是:一次性初始化放 bootstrap,每次挂载都需要的渲染逻辑放 mount。
前端5月27日 20:03
Garfish 的错误处理和降级机制是如何工作的?Garfish 通过沙箱自动降级、生命周期错误捕获、资源加载容错三层机制保证微前端稳定性。 沙箱降级是核心。默认启用基于 Proxy 的 VM 沙箱,代理 window 对象隔离子应用全局变量,支持多实例并行。浏览器不支持 Proxy 时自动降级为快照沙箱——挂载前保存 window 全量快照,卸载后恢复原状,只支持单实例切换。降级过程对业务透明,框架内部自动判断。 生命周期层面,Garfish 在 beforeLoad、afterLoad、beforeMount、afterMount、beforeUnmount、afterUnmount 六个阶段提供钩子,任一阶段异常均触发全局 error 事件。主应用统一监听即可捕获所有子应用错误: ```javascript Garfish.router.on('error', (error) => { reportError(error); // 上报监控 showErrorPage(); // 降级 UI }); ``` 资源加载容错:Garfish 用 fetch 拉取子应用 JS/CSS,网络失败触发 afterLoad 错误回调。跨域动态脚本(JSONP 等)被转成 fetch 请求,后端未配 CORS 会报跨域错误。可用 `excludeAssetFilter` 放行特定脚本,但放行的资源会逃逸沙箱执行,副作用难以追踪。 ## 追问 ### Proxy 沙箱和快照沙箱的核心区别? Proxy 沙箱代理 window 读写,每个子应用有独立代理对象,支持多实例并行。快照沙箱在挂载时浅拷贝 window,卸载时还原,只能串行切换。Proxy 隔离更彻底但不兼容 IE;快照兼容性好但全局对象可能被意外修改。 ### 子应用崩溃会拖垮主应用吗? JS 错误被沙箱隔离,不会直接污染主应用状态。但 DOM 副作用可能逃逸——子应用在 document 上绑的事件监听器、插入的全局样式,卸载后不会自动清理。规范做法是在 unmount 钩子中手动移除副作用,或用 Garfish 的 DOM 沙箱自动收集和清理。 ### 沙箱里 sourcemap 行号为什么对不上? Garfish 通过 eval + sourceURL 执行子应用代码,sourceURL 改变了错误堆栈中的文件标识,导致行号偏移,sourcemap 还原指向错误位置。需要用 Garfish 提供的行号修正工具对齐偏移量。 ### excludeAssetFilter 放行的脚本出了问题怎么排查? 放行 = 脱离沙箱,脚本里的全局变量写入、事件绑定都不受管控。排查思路:在放行脚本的入口和出口打日志对比 window 差异;生产环境尽量不放行,让后端加 CORS 头保持资源在沙箱内加载。实际踩坑中,放行 JSONP 脚本导致全局变量污染另一个子应用的案例很常见。
前端5月27日 20:03
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', }, ], }); ``` 劫持的目的有两个: 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` 生命周期**:将 `dom`、`basename` 等信息通过 provider 的 `render` 函数传递给子应用。 4. **卸载非活跃子应用**:对不再匹配的子应用调用 `destroy` 生命周期,清理 DOM 和事件监听。 **关键细节**:不要使用根路径 `/` 作为 `activeWhen`,否则该子应用在任何路径下都会被激活,导致其他子应用永远无法加载。 ## basename 自动计算:路由隔离的核心机制 basename 是 Garfish 实现路由隔离的关键。子应用的 basename 计算公式为: ``` 子应用 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 提供 `beforeEach` 和 `afterEach` 钩子,用于在路由变化时执行拦截逻辑: ```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 的路由系统通过劫持、匹配、隔离、同步四层机制,解决了微前端架构中最棘手的路由协同问题。理解这套机制,才能在实际项目中避免路由冲突、白屏、状态丢失等常见坑。
前端5月27日 20:01
什么是 Garfish 微前端框架,它的核心特点和应用场景是什么?Garfish 是字节跳动开源的微前端框架,主要解决大型前端应用在跨团队协作、技术栈多样化和独立部署方面的痛点。与 qiankun 等方案相比,Garfish 在沙箱隔离和依赖共享上有独特设计,适合对隔离性和性能有更高要求的企业级场景。 ## Garfish 的核心架构 Garfish 的整体架构由以下核心模块组成: - **Loader(加载器)**:负责子应用资源的获取和解析,支持异步加载和预加载策略 - **Sandbox(沙箱)**:隔离子应用的 JavaScript 执行环境,防止全局变量污染 - **Router(路由)**:管理子应用的路由注册、匹配和切换 - **Store(状态管理)**:提供跨应用的通信机制 下面通过一个最小接入示例说明 Garfish 的基本用法: ```js import Garfish from 'garfish'; Garfish.run({ basename: '/', domGetter: '#sub-app', apps: [ { name: 'vue-app', entry: 'http://localhost:8080', activeWhen: '/vue', }, { name: 'react-app', entry: 'http://localhost:3000', activeWhen: '/react', }, ], }); ``` 主应用只需配置子应用的名称、入口地址和激活路由,Garfish 会自动处理加载、挂载和卸载。 ## 六大核心特点详解 ### 1. 沙箱隔离 Garfish 提供两种沙箱模式: - **快照沙箱(Snapshot Sandbox)**:在子应用挂载前快照 window 对象,卸载后恢复。适用于不支持 Proxy 的浏览器,但无法处理动态添加的全局变量。 - **VM 沙箱(Proxy Sandbox)**:基于 ES6 Proxy 实现,为每个子应用创建一个代理 window 对象,真正实现了全局变量的隔离。这是 Garfish 推荐的方式。 ```js // VM 沙箱原理示意 const proxyWindow = new Proxy(window, { get(target, key) { // 优先从子应用自己的状态中读取 return ownState[key] ?? target[key]; }, set(target, key, value) { ownState[key] = value; // 写入子应用独立状态 return true; }, }); ``` VM 沙箱的优势在于多个子应用可以同时运行而互不干扰,这也是 Garfish 支持多实例的基础。 ### 2. 依赖共享 Garfish 支持子应用之间共享公共依赖(如 React、Vue、Lodash 等),避免重复加载同一库的多个副本: ```js Garfish.run({ apps: [ { name: 'app1', entry: 'http://localhost:8081', props: { react: require('react'), // 共享 React 实例 }, }, ], }); ``` 依赖共享能显著降低整体包体积和加载时间,对于同时运行多个 React 子应用的场景尤为明显。 ### 3. 预加载策略 Garfish 内置智能预加载机制,会在浏览器空闲时提前获取子应用资源: - 自动记录用户访问习惯,为高频使用的子应用增加预加载权重 - 支持 `prefetch` 配置项,可自定义预加载行为 - 预加载的资源包括 HTML、JS、CSS 等子应用入口依赖 ```js Garfish.run({ prefetch: true, // 开启预加载 // 也可传入函数自定义预加载逻辑 // prefetch: (apps) => apps.filter(app => app.name === 'high-priority-app'), }); ``` ### 4. 框架无关 Garfish 对子应用的技术栈没有限制,React、Vue、Angular、Svelte 等均可接入。子应用只需导出固定的生命周期钩子: ```js // 子应用需要导出的生命周期 export function provider({ dom, basename, props }) { return { mount() { /* 挂载逻辑 */ }, unmount() { /* 卸载逻辑 */ }, update() { /* 可选,接收父应用传参更新 */ }, }; } ``` ### 5. 多实例支持 不同于部分微前端方案只允许单个子应用运行,Garfish 支持在同一页面中同时运行多个子应用实例: ```js // 同一页面同时挂载两个子应用 Garfish.run({ domGetter: '#container', apps: [ { name: 'sidebar', activeWhen: '/', entry: '...' }, { name: 'main-content', activeWhen: '/', entry: '...' }, ], }); ``` 这在后台管理系统等需要布局嵌套的场景中非常实用。 ### 6. 样式隔离 Garfish 提供多种样式隔离方案: - **CSS Scoped**:为子应用的样式自动添加作用域前缀 - **Shadow DOM**:利用浏览器原生的 Shadow DOM 实现完全隔离 - **CSS Modules**:配合构建工具使用,从源头避免样式冲突 ## Garfish vs Qiankun:如何选择? | 对比维度 | Garfish | Qiankun | |---------|---------|---------| | 出品方 | 字节跳动 | 蚂蚁集团 | | 沙箱方案 | 快照 + VM 双模式 | 快照 + Proxy 双模式 | | 依赖共享 | 原生支持 | 需额外配置 | | 多实例 | 原生支持 | 有限支持 | | 预加载 | 内置智能预加载 | 需手动配置 | | 社区规模 | ~2.9k Stars | ~16k Stars | | 文档完善度 | 中等 | 较完善 | | 适用场景 | 隔离性要求高、需多实例 | 通用场景、快速接入 | 选择建议: - **选 Garfish**:项目需要强隔离、多实例共存、依赖共享,或已在字节生态内 - **选 Qiankun**:追求社区支持、开箱即用,或团队微前端经验较少 ## 典型应用场景 ### 企业级后台管理系统 多个业务团队各自维护独立子应用(权限管理、数据分析、运营工具等),通过 Garfish 统一接入主框架,实现独立开发、独立部署。 ### 电商平台 活动页、商品详情、购物车等模块由不同团队负责,使用 Garfish 的预加载和依赖共享优化首屏性能。 ### 大型 SaaS 产品 不同功能模块(CRM、BI、工单系统)采用不同技术栈,Garfish 的框架无关特性允许各模块选择最合适的技术方案。 ## 接入注意事项 1. **子应用改造**:需要导出 `provider` 生命周期函数,并在打包配置中设置 `libraryTarget: 'umd'` 2. **跨域配置**:子应用需配置 CORS 头,允许主应用跨域获取资源 3. **环境变量**:子应用中访问 `window` 时需注意沙箱代理,避免直接操作导致泄漏 4. **公共路径**:子应用的静态资源路径需正确配置 `publicPath`,防止资源加载失败 ## 总结 Garfish 作为字节跳动出品的微前端框架,在沙箱隔离、依赖共享和多实例方面有独到优势。如果你的项目对应用隔离有较高要求,或者需要在一个页面中同时运行多个子应用,Garfish 是值得考虑的方案。但在社区生态和文档完善度上,它目前仍落后于 qiankun,团队在选型时需要权衡技术优势与社区支持之间的取舍。
前端2月21日 16:02
Garfish 如何实现主应用与子应用之间的通信和状态共享?Garfish 提供了多种通信机制,支持主应用与子应用之间、以及子应用之间的数据共享和交互。 ## 通信方式 ### 1. Props 传递 - **适用场景**:主应用向子应用传递配置、用户信息等静态数据 - **特点**:单向数据流,简单直接 - **示例**: ```javascript // 主应用配置 { name: 'sub-app', entry: '//localhost:3001', props: { userInfo: { name: 'John', role: 'admin' }, theme: 'dark', apiConfig: { baseUrl: '/api' } } } // 子应用接收 export function mount(props) { const { userInfo, theme, apiConfig } = props; // 使用传递的数据 } ``` ### 2. 事件总线 - **适用场景**:跨应用的事件通知和响应 - **特点**:解耦应用间依赖,支持一对多通信 - **示例**: ```javascript // 发布事件 Garfish.channel.emit('user-login', { userId: 123 }); // 订阅事件 Garfish.channel.on('user-login', (data) => { console.log('用户登录:', data.userId); }); // 取消订阅 Garfish.channel.off('user-login', handler); ``` ### 3. 共享状态 - **适用场景**:需要跨应用共享的业务状态 - **特点**:集中管理,响应式更新 - **示例**: ```javascript // 定义共享状态 Garfish.registerShared({ name: 'userStore', store: { state: { user: null }, mutations: { setUser(state, user) { state.user = user; } } } }); // 子应用使用 export function mount(props) { const userStore = props.shared.userStore; userStore.mutations.setUser({ name: 'John' }); } ``` ### 4. 自定义通信协议 - **适用场景**:复杂的业务交互逻辑 - **特点**:灵活定制,满足特定需求 - **示例**: ```javascript // 定义通信接口 Garfish.defineInterface('auth', { login(credentials) { return fetch('/api/login', { method: 'POST', body: JSON.stringify(credentials) }); }, logout() { return fetch('/api/logout'); } }); // 子应用调用 export function mount(props) { props.auth.login({ username, password }); } ``` ## 状态管理最佳实践 ### 1. 状态分层 - **全局状态**:用户信息、主题、权限等 - **应用级状态**:子应用内部状态 - **组件级状态**:组件内部状态 ### 2. 状态隔离 - 避免直接访问其他应用的状态 - 通过通信机制传递数据 - 保持应用的独立性 ### 3. 状态同步 - 使用事件机制同步状态变化 - 实现状态变更通知 - 避免状态不一致问题 ### 4. 状态持久化 - 使用 localStorage 或 sessionStorage - 实现跨会话的状态保持 - 考虑状态恢复机制 ## 通信安全考虑 ### 1. 数据验证 - 验证接收到的数据格式和内容 - 防止恶意数据注入 - 实现数据校验机制 ### 2. 权限控制 - 限制敏感数据的访问 - 实现基于角色的权限控制 - 审计通信日志 ### 3. 错误处理 - 完善的错误捕获和处理 - 提供友好的错误提示 - 实现降级方案 ## 性能优化 ### 1. 减少通信频率 - 合并多个通信请求 - 使用批量更新 - 实现防抖和节流 ### 2. 数据缓存 - 缓存常用数据 - 减少重复请求 - 实现缓存失效策略 ### 3. 异步通信 - 使用异步方式处理通信 - 避免阻塞主线程 - 优化用户体验 通过合理使用通信机制,可以实现微前端应用间的高效协作和数据共享。
前端2月21日 16:02
Garfish 的性能优化策略有哪些,如何提升微前端应用的加载速度和运行效率?Garfish 提供了多种性能优化策略,帮助开发者构建高性能的微前端应用。 ## 性能优化策略 ### 1. 代码分割与懒加载 - **策略**:将子应用代码分割成多个 chunk,按需加载 - **实现方式**: - 使用 Webpack 的动态 import - 配置路由级别的代码分割 - 实现组件级别的懒加载 - **优势**:减少初始加载时间,提升首屏性能 - **示例**: ```javascript // 动态导入子应用 const SubApp = React.lazy(() => import('./SubApp')); // 路由级别代码分割 const routes = [ { path: '/dashboard', component: React.lazy(() => import('./Dashboard')) } ]; ``` ### 2. 资源预加载 - **策略**:提前加载可能需要的资源 - **实现方式**: - 使用 `<link rel="preload">` 预加载关键资源 - 配置子应用预加载策略 - 利用空闲时间预加载 - **优势**:减少用户等待时间,提升体验 - **示例**: ```javascript // 预加载子应用 Garfish.preloadApp('app1'); // 预加载资源 <link rel="preload" href="/app1.js" as="script"> ``` ### 3. 缓存优化 - **策略**:利用浏览器缓存和 Service Worker 缓存资源 - **实现方式**: - 配置 HTTP 缓存头 - 使用 Service Worker 缓存静态资源 - 实现子应用缓存机制 - **优势**:减少网络请求,提升加载速度 - **示例**: ```javascript // Service Worker 缓存 self.addEventListener('install', (event) => { event.waitUntil( caches.open('garfish-cache').then((cache) => { return cache.addAll([ '/app1.js', '/app1.css' ]); }) ); }); ``` ### 4. 并行加载 - **策略**:同时加载多个子应用或资源 - **实现方式**: - 使用 Promise.all 并行加载 - 配置并行加载策略 - 优化资源加载顺序 - **优势**:缩短总加载时间 - **示例**: ```javascript // 并行加载多个子应用 await Promise.all([ Garfish.loadApp('app1'), Garfish.loadApp('app2'), Garfish.loadApp('app3') ]); ``` ### 5. 资源压缩 - **策略**:压缩 JavaScript、CSS、图片等资源 - **实现方式**: - 使用 Webpack 压缩插件 - 启用 Gzip 或 Brotli 压缩 - 优化图片格式和大小 - **优势**:减少传输数据量,加快加载速度 - **示例**: ```javascript // Webpack 压缩配置 module.exports = { optimization: { minimize: true, minimizer: [ new TerserPlugin(), new CssMinimizerPlugin() ] } }; ``` ## 性能监控 ### 1. 加载性能监控 - 监控子应用加载时间 - 统计资源加载成功率 - 分析加载瓶颈 ### 2. 运行时性能监控 - 监控内存使用情况 - 统计 CPU 占用 - 检测性能问题 ### 3. 用户体验监控 - 统计首屏渲染时间 - 监控交互响应时间 - 收集用户反馈 ## 最佳实践 ### 1. 性能预算 - 设定资源大小限制 - 控制加载时间预算 - 定期检查性能指标 ### 2. 性能测试 - 使用 Lighthouse 进行性能测试 - 进行压力测试 - 模拟不同网络环境 ### 3. 持续优化 - 定期分析性能数据 - 优化热点代码 - 更新依赖版本 ### 4. 性能优化工具 - 使用 Chrome DevTools 分析性能 - 利用 webpack-bundle-analyzer 分析包大小 - 使用性能监控工具 ## 常见性能问题及解决方案 ### 1. 首屏加载慢 - **原因**:资源过多、未优化 - **解决方案**:代码分割、懒加载、预加载 ### 2. 内存泄漏 - **原因**:未正确清理资源 - **解决方案**:完善生命周期管理、及时清理 ### 3. 重复加载 - **原因**:缓存策略不当 - **解决方案**:优化缓存配置、避免重复请求 ### 4. 渲染卡顿 - **原因**:计算量大、DOM 操作多 - **解决方案**:虚拟列表、防抖节流、优化渲染逻辑 通过综合运用这些性能优化策略,可以显著提升微前端应用的性能和用户体验。
前端2月21日 16:02
Garfish 与其他微前端框架(如 qiankun、single-spa)相比有哪些优势和劣势?Garfish 与其他微前端框架(如 qiankun、single-spa、Module Federation)各有特点,选择时需要根据项目需求进行评估。 ## 框架对比 ### 1. Garfish vs qiankun #### 相似点 - 都基于 single-spa 扩展 - 都提供沙箱隔离机制 - 都支持主流前端框架 - 都有完善的生命周期管理 #### 差异点 | 特性 | Garfish | qiankun | |------|---------|---------| | **沙箱机制** | 支持快照、代理、严格沙箱 | 主要使用快照沙箱 | | **样式隔离** | 支持 Shadow DOM、CSS 作用域 | 主要使用 CSS 作用域 | | **路由管理** | 内置路由系统 | 依赖 single-spa 路由 | | **性能** | 轻量级,性能开销小 | 相对较重 | | **学习曲线** | 较平缓 | 相对陡峭 | | **社区活跃度** | 较新,社区较小 | 成熟,社区活跃 | ### 2. Garfish vs single-spa #### 相似点 - 都提供微前端基础能力 - 都支持生命周期管理 - 都支持框架无关 #### 差异点 | 特性 | Garfish | single-spa | |------|---------|------------| | **易用性** | 开箱即用,配置简单 | 需要手动配置,复杂度高 | | **沙箱隔离** | 内置多种沙箱机制 | 需要自行实现 | | **样式隔离** | 内置样式隔离方案 | 需要自行实现 | | **路由管理** | 内置路由管理 | 需要自行实现 | | **文档完善度** | 文档相对简洁 | 文档详细但复杂 | ### 3. Garfish vs Module Federation #### 相似点 - 都支持模块共享 - 都支持独立部署 - 都支持技术栈无关 #### 差异点 | 特性 | Garfish | Module Federation | |------|---------|-------------------| | **实现方式** | 运行时加载 | 构建时模块共享 | | **依赖共享** | 需要手动管理 | 自动共享依赖 | | **版本管理** | 需要手动处理 | 自动处理版本冲突 | | **构建复杂度** | 相对简单 | 需要配置 Webpack | | **适用场景** | 完全独立的应用 | 模块级别的共享 | ## 选择建议 ### 选择 Garfish 的场景 - 需要轻量级的微前端解决方案 - 需要多种沙箱隔离机制 - 需要内置的路由和样式隔离 - 团队对微前端有一定了解 - 项目规模中等,不需要过度复杂的方案 ### 选择 qiankun 的场景 - 需要成熟的微前端解决方案 - 需要丰富的社区支持和文档 - 团队对 qiankun 有经验 - 项目规模较大,需要稳定可靠的方案 ### 选择 single-spa 的场景 - 需要高度定制的微前端方案 - 团队对微前端原理有深入了解 - 需要最大的灵活性 - 愿意投入时间进行配置和优化 ### 选择 Module Federation 的场景 - 需要模块级别的共享 - 使用 Webpack 5 - 需要自动依赖管理 - 团队熟悉 Webpack 配置 - 需要细粒度的代码复用 ## 迁移策略 ### 从其他框架迁移到 Garfish 1. **评估现有架构**:分析当前微前端实现 2. **逐步迁移**:先迁移部分子应用 3. **保持兼容**:确保新旧方案共存 4. **测试验证**:全面测试迁移效果 5. **优化调整**:根据实际情况优化配置 ## 最佳实践 ### 1. 技术选型原则 - 根据团队技术栈选择 - 考虑项目规模和复杂度 - 评估维护成本 - 考虑社区支持和生态 ### 2. 混合使用 - 可以结合多个框架的优势 - 根据不同场景选择不同方案 - 保持架构的一致性 ### 3. 持续评估 - 定期评估框架的适用性 - 关注框架的更新和发展 - 准备备选方案 通过合理选择微前端框架,可以更好地满足项目需求并提升开发效率。
前端2月19日 17:45
Garfish 的沙箱隔离机制是如何实现的,有哪些隔离策略?Garfish 的沙箱隔离机制主要通过以下方式实现: ## 1. 快照沙箱(Snapshot Sandbox) - 在子应用加载前,对当前全局环境(window 对象)进行快照 - 记录所有全局变量和属性 - 子应用运行期间,允许修改全局变量 - 子应用卸载时,恢复到加载前的快照状态 ## 2. 代理沙箱(Proxy Sandbox) - 使用 ES6 Proxy 对 window 对象进行代理 - 拦截对全局变量的读取和写入操作 - 维护一个独立的沙箱环境,每个子应用有自己的全局变量副本 - 通过代理层隔离不同子应用的全局状态 ## 3. 严格沙箱(Strict Sandbox) - 结合快照和代理的优势 - 提供更严格的隔离机制 - 防止子应用间的全局变量污染 - 确保子应用卸载后完全清理副作用 ## 隔离范围 - **全局变量隔离**:防止子应用间的变量冲突 - **事件监听器隔离**:自动清理子应用添加的事件监听 - **定时器隔离**:管理 setTimeout、setInterval 等定时器 - **样式隔离**:通过 CSS 作用域或 Shadow DOM 隔离样式 ## 实现优势 - 性能开销小,不影响应用运行效率 - 支持动态加载和卸载子应用 - 兼容主流浏览器,无需 polyfill - 提供降级方案,确保在不支持 Proxy 的环境中也能运行 沙箱机制确保了微前端架构下各个子应用的独立性和安全性,是实现微前端的关键技术之一。
前端2月19日 17:45
Garfish 的样式隔离机制是如何实现的,有哪些常见的样式隔离方案?Garfish 的样式隔离机制确保不同子应用的样式不会相互干扰,是微前端架构中的重要组成部分。 ## 样式隔离方案 ### 1. CSS 作用域 - **原理**:为每个子应用的样式添加唯一前缀或后缀 - **实现方式**: - 使用 PostCSS 插件自动添加作用域 - 通过 CSS Modules 实现局部作用域 - 使用 CSS-in-JS 方案(如 styled-components) - **优势**:简单易用,兼容性好 - **劣势**:需要额外的构建配置 - **示例**: ```javascript // PostCSS 配置 module.exports = { plugins: [ require('postcss-selector-prefix')({ prefix: '[data-garfish-app="app1"]' }) ] }; ``` ### 2. Shadow DOM - **原理**:使用浏览器原生的 Shadow DOM 技术隔离样式 - **实现方式**: - 将子应用挂载到 Shadow DOM 容器中 - 样式只在 Shadow DOM 内部生效 - 自动隔离全局样式 - **优势**:完全隔离,浏览器原生支持 - **劣势**:部分浏览器兼容性问题,事件冒泡处理复杂 - **示例**: ```javascript // 创建 Shadow DOM 容器 const shadowRoot = container.attachShadow({ mode: 'open' }); // 将子应用挂载到 Shadow DOM shadowRoot.appendChild(appElement); ``` ### 3. 动态样式表管理 - **原理**:在子应用挂载时加载样式,卸载时移除样式 - **实现方式**: - 动态创建和删除 `<style>` 标签 - 管理样式表的加载和卸载 - 避免样式残留 - **优势**:灵活控制,性能较好 - **劣势**:需要手动管理样式生命周期 - **示例**: ```javascript // 动态加载样式 function loadStylesheet(url) { const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = url; document.head.appendChild(link); return link; } // 卸载时移除样式 function unloadStylesheet(link) { document.head.removeChild(link); } ``` ### 4. CSS 命名约定 - **原理**:通过命名规范避免样式冲突 - **实现方式**: - 使用 BEM 命名规范 - 为每个子应用设置唯一的类名前缀 - 遵循统一的命名约定 - **优势**:无需额外工具,易于理解 - **劣势**:依赖开发者自觉,容易出错 - **示例**: ```css /* 子应用 app1 的样式 */ .app1__header { } .app1__button { } .app1__button--primary { } ``` ## 样式隔离配置 ### Garfish 配置示例 ```javascript Garfish.run({ apps: [ { name: 'app1', entry: '//localhost:3001', sandbox: { strictIsolation: true, styleIsolation: 'shadow-dom' // 或 'scoped-css' } } ] }); ``` ## 最佳实践 ### 1. 选择合适的隔离方案 - **简单项目**:CSS 作用域或命名约定 - **复杂项目**:Shadow DOM - **混合场景**:结合多种方案 ### 2. 全局样式处理 - 主应用提供全局基础样式 - 子应用避免使用全局选择器 - 使用 CSS 变量管理主题 ### 3. 第三方库样式 - 使用作用域化版本 - 手动修改第三方库样式 - 考虑使用样式隔离方案 ### 4. 性能优化 - 避免重复加载样式 - 使用样式压缩 - 合理使用 CSS 缓存 ### 5. 开发体验 - 提供样式隔离的调试工具 - 支持热更新 - 提供样式冲突检测 ## 常见问题解决 ### 1. 样式不生效 - 检查样式隔离配置 - 确认样式加载顺序 - 检查选择器优先级 ### 2. 样式冲突 - 使用更具体的选择器 - 调整样式隔离方案 - 检查全局样式影响 ### 3. 性能问题 - 减少样式文件大小 - 优化样式加载策略 - 使用样式缓存 通过合理配置样式隔离机制,可以确保微前端应用的样式独立性和可维护性。