前端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 源码中的网络检测逻辑,但不建议这样做。标签
Garfish
Garfish 是一个轻量级的微前端框架,旨在帮助开发者实现多应用的集成与协作,提升大型复杂前端项目的开发效率和维护性。它通过沙箱技术隔离不同子应用的运行环境,保证各个微应用之间的独立性和安全性,同时支持多种加载方式,包括异步加载和预加载,优化整体性能。Garfish 兼容主流前端框架如 React、Vue 和 Angular,方便开发者在不同技术栈之间灵活切换和组合。它提供了丰富的生命周期钩子,方便开发者管理微应用的加载、挂载和卸载过程,支持路由同步和状态共享,增强子应用之间的协同能力。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. 性能问题
- 减少样式文件大小
- 优化样式加载策略
- 使用样式缓存
通过合理配置样式隔离机制,可以确保微前端应用的样式独立性和可维护性。