微前端工具选型别只比较参数
微前端选型经常被做成参数表:启动耗时、包体大小、沙箱类型和共享依赖能力各占一列。参数可以帮助发现差异,却不能替团队回答更基本的问题:为什么要拆成多个应用,哪些团队需要独立发布,跨应用状态有多少,以及出问题后能否单独回退。若这些条件没有写清,框架越灵活,后续约定反而越多。
1. 用真实页面检查三个边界
先选一条包含导航、表单、弹层和跨应用通信的真实流程做 POC,比复述 README 更能暴露成本。
1.1 沙箱隔离过严导致的 AI 全局 Context 穿透失效
Proxy、iframe 和约定式运行环境提供的隔离强度不同。跨应用助手若要读取选中行或当前表单,也不应直接穿透沙箱抓取 DOM。子应用应通过明确协议上报最少上下文,主应用验证来源、字段与权限。通信写得麻烦,往往是在提醒我们边界尚未定义,而不是隔离“过严”。
1.2 Shadow DOM 导致的 UI 组件库弹出层(Popover)样式失效
Shadow DOM 会改变样式作用范围和弹层挂载方式。Select、Tooltip、Modal 若默认挂到document.body,需要确认组件库能否指定容器,焦点与遮罩是否仍正确。CSS 前缀隔离则要检查选择器、全局重置和动态样式。没有一种方案对所有组件都自动成立。
1.3 共享运行时(Shared Runtime)死锁与版本漂移
共享运行时可以减少重复下载,同时建立了版本协商关系。主应用和子应用的 React 主版本、渲染器与组件库如果不兼容,共享可能在加载或运行时失败。选型时要用团队真实版本组合验证,并写明谁决定升级、旧应用可以保留多久、无法共享时怎样降级为独立依赖。
2. 跨应用上下文使用受控协议
子应用可以声明自己愿意提供的上下文类型,例如页面标识、已选对象的公开字段和用户主动选择的文本。主应用按应用身份和当前权限订阅,不默认收集所有表单。协议要带版本、来源和有效期,页面离开后及时清除。敏感数据过滤必须依据字段 Schema 和数据分类,而不是只删几个常见字段名。
3. 跨微应用 AI Context Bridge 核心代码实现
下面的 TypeScript 代码展示了事件订阅和上下文缓存的基本结构,适合讨论接口形状,不应直接作为安全隔离层。
export interface AIContextPayload { appId: string; actionType: string; selectedText?: string; formData?: Record<string, any>; timestamp: number; } export type AIContextListener = (payload: AIContextPayload) => void; export class MicroAppAIBridge { private static instance: MicroAppAIBridge; private listeners: Set<AIContextListener> = new Set(); private activeAppContext: Map<string, AIContextPayload> = new Map(); private constructor() { // 挂载至主应用沙箱白名单共享对象 if (typeof window !== 'undefined') { (window as any).__MICRO_APP_AI_BRIDGE__ = this; } } public static getInstance(): MicroAppAIBridge { if (!MicroAppAIBridge.instance) { MicroAppAIBridge.instance = new MicroAppAIBridge(); } return MicroAppAIBridge.instance; } // 子应用注册并上报当前视口上下文 public reportContext(payload: AIContextPayload): void { // 数据清洗与敏感词/隐私过滤 const sanitizedPayload = this.sanitizePayload(payload); this.activeAppContext.set(sanitizedPayload.appId, sanitizedPayload); // 广播给主应用 AI 模块 this.listeners.forEach((listener) => listener(sanitizedPayload)); } // AI 助手订阅上下文更新 public subscribe(listener: AIContextListener): () => void { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; } // 获取当前全站最活跃的子应用上下文 public getMergedContext(): Record<string, any> { const result: Record<string, any> = {}; this.activeAppContext.forEach((value, key) => { result[key] = value; }); return result; } private sanitizePayload(payload: AIContextPayload): AIContextPayload { // 确定性数据过滤规则:剔除密码、Token 等敏感字段 const cleanFormData = { ...payload.formData }; delete cleanFormData.password; delete cleanFormData.token; return { ...payload, formData: cleanFormData, timestamp: Date.now(), }; } }4. 核对示例代码的安全边界
示例把 Bridge 挂到window,任何能访问该对象的代码都可能调用reportContext,appId也由调用者自行填写,因此它没有验证应用身份。sanitizePayload只删除password和token两个键,别名、嵌套字段和其他敏感信息仍会保留;Record<string, any>也让协议无法在编译期约束。真正实现时应为每类 Context 定义类型与允许字段,由主应用在注册阶段发放受限通道,并在接收端再次校验。
getMergedContext返回所有应用的最近数据,但“最近”只由本地接收时间表示,也没有过期清理。AI 助手通常只需要当前激活应用和当前任务的少量信息,不应默认合并全站上下文。订阅者回调还需要隔离异常,避免一个监听器抛错影响其他监听器。
这些问题说明事件总线只是通信机制,不等于安全机制。选择 qiankun、Web Component 或 Module Federation,也不会自动解决身份、权限、数据最小化和生命周期。
5. 前端架构选型真实决策指南
框架 POC 可以按下面的顺序进行:
- 验证独立发布:子应用升级、加载失败和回退时,主应用与其他子应用仍可工作。
- 验证通信契约:导航、权限变化、页面卸载和协议版本不一致时,事件不会丢到错误应用,也不会保留过期数据。
- 验证样式与交互:弹层、焦点、主题切换、全局重置和不同组件库同时存在时表现一致。
- 验证依赖升级:用当前与计划中的框架版本组合测试共享和降级,记录包体只是其中一个结果。
最终决策应同时写下采用理由和退出方案。微前端解决的是团队与发布边界,不是单纯的页面加载参数。能够让子应用独立演进、跨应用协议保持克制,并且故障时容易隔离的方案,才与微前端的目标一致。