同一个 Framework 的多个 Major Version
并行运行同一个 Framework 的多个 Major Version,在技术上完全可行。但如果把它当成长期目标架构,通常既不经济,也很难称得上合理。
如果没有强隔离,不同 Framework Runtime 仍然共享同一个 Browser Context。若要获得彻底隔离,最终集成的就不再只是无缝组合的微前端,而是彼此嵌套的完整应用。
不过,这种强隔离确实可能合理:例如作为一条有时间边界的 Migration Bridge,用来包住一个因为风险、规模或现实约束暂时无法一次性迁移的旧应用。
此时它是过渡方案。
它明确不应该成为目标状态。
技术上能做,不代表适合长期运行
Section titled “技术上能做,不代表适合长期运行”微前端经常被宣传为:每个 Remote 可以独立选择自己的 Framework Version。
这句话并不错误。Integration Model 确实可以加载由不同 Major Version 构建的应用。但这只回答了“这些 Artifact 能不能想办法一起执行”,还没有回答“这样的系统是不是值得长期运营”。
它没有说明 Browser 最终要下载和初始化多少套 Framework Runtime,也没有说明它们仍然共享哪些 Global Resource、Compatible Version 如何解析、支持的 Test Matrix 会扩大到什么程度,更没有说明这种 Version Mix 打算存在多久。
最重要的是,它通常没有回答:谁负责最后把这些差异收回来?
Technical Feasibility 回答“能不能运行”。架构还必须回答“应该不应该长期运行”。
来看一个故意夸张的例子:
Host└── Angular 22
Remote A└── Angular 17
Remote B└── Angular 19具体 Version 只是代表彼此距离很远的 Framework Generation。关键并不是这组数字今天是否恰好能工作,而是:当这些应用无法可靠共享同一个 Framework Version 时,Integration Platform 到底要承担什么。
三个 Major Version 不只是三行版本号
Section titled “三个 Major Version 不只是三行版本号”如果 Framework Dependency 不共享,最坏情况下每个应用都带着自己的 Runtime:
Browser├── Host│ └── Angular-22-Runtime├── Remote A│ └── Angular-17-Runtime└── Remote B └── Angular-19-Runtime这不只是 Lockfile 中同一个包出现三个 Version。可能被重复带入的还有 Framework Code、Bootstrap Logic、Dependency Injection Structure、Rendering Infrastructure、Reactive / Change Detection Mechanism、Router 与 HTTP Infrastructure,以及 Framework-near Library。除此之外,每个应用还有自己的 Component Tree、Application Tree、内部数据结构、Subscription、Metadata 与 Lifecycle。
三个 Major Version,最坏情况下意味着三套 Framework Runtime。
当然,这并不代表每个 Byte 都会精确乘三。Tree Shaking、Lazy Loading、Browser Cache 与不同 Bundle Composition 都会改变真实成本。某些技术 Dependency 仍然可能共享,某个 Remote 也可能只有在用户真正进入该区域时才加载。
但这些细节并不会改变基本事实:不同 Runtime Version 仍然需要被下载、Parse、执行和初始化;会占用 Memory,也会建立自己的内部结构;Bootstrap 会消耗 Main Thread 时间。即使 Bundle 已经在 Browser Cache 中,执行和初始化后的 Memory Footprint 仍然存在。
Browser Cache 可以减少重复 Download。
它不会消除不同 Runtime 的执行成本和内存成本。
用户在为版本自由买单
Section titled “用户在为版本自由买单”从团队视角看,这很舒服:
Team A 停留在 Major 17Team B 使用 Major 19Team C 迁移到 Major 22每个 Team 都可以局部决策,不需要等待全产品一起迁移,组织依赖似乎消失了。
但 Browser 看到的是另一幅图:
用户└── 下载并运行多个 Framework Generation团队省掉了一部分共同协调,用户却承担了额外 Runtime 成本。
而代价还不止 Download Size 和 Startup Time。Version 越多,Platform Team 与 Product Team 必须理解和测试的 Runtime Combination 越多。不同 Toolchain、Library Compatibility、Browser Assumption 与 Maintenance Cycle 开始并存。有些 Bug 只会在特定 Host、Remote 和 Shared Infrastructure 组合下出现。Monitoring 与 Support 还必须能够判断,问题发生时究竟是哪一套 Runtime 在参与。
Security 与 Maintenance Responsibility 也会复制。旧 Version 并不会因为被包在 Remote Boundary 后面就自动变得“可维护”。仍然必须有人理解它的 Dependency、已知限制、Build Tool 与运行假设。
局部看起来方便的决策,可能在整个系统层面非常昂贵。
独立 Version Choice 并没有消灭协调,只是把一部分成本搬到了 Browser、Integration Platform 和长期维护中。
Shared Dependency 是版本契约
Section titled “Shared Dependency 是版本契约”最明显的另一种选择,是共同提供 Framework Runtime:
Host 与 Remotes │ └── 共享 Framework Runtime共享 Runtime 可以减少重复 Framework Bundle、降低 Bootstrap 成本,并让 Framework-near Contract 保持一致。Host 与 Remote 运行在同一环境中,因此可以更紧密组合。
代价是:它们必须与真正提供的 Version 兼容。
Shared Dependency 节省 Runtime 成本,却建立了一份共同 Version Contract。
这不是实现细节。一旦 Framework Dependency 被当成 Singleton 或类似的 Central Runtime,团队就不可能完全独立地选择 Major Version。Migration 需要协调、验证,并在约定的 Version Corridor 内完成。即使业务 Release 仍然独立,技术 Update 也可能同时影响多个应用。
另一种方案是分离 Runtime。它提供更大的技术 Version Freedom,也可以支持分阶段 Migration,但 Runtime 开销、测试范围与 Integration Complexity 都会上升。
这个 Trade-off 无法靠 Configuration 消失:要么多个应用共享 Runtime,并接受共同 Version Contract;要么各自运行 Runtime,并承担由此产生的技术和组织成本。
Runtime 分开了,Browser 并没有分开
Section titled “Runtime 分开了,Browser 并没有分开”不同 Bundle 很容易制造“这些应用已经完全隔离”的错觉。正常 Composition 下,它们仍然运行在同一个 Document 中。
它们共享同一个 window、同一个 document 和同一个 Main Thread;共享 Browser History 与 Global API。DOM、CSS Cascade、Global Style、CSS Custom Property、body 下的 Overlay Area、localStorage、Global Error Handler、Telemetry Instrumentation、Custom Element Registry 都可能成为共同冲突面。根据发布方式和 Scope,Service Worker、Global Polyfill 或 Patch 也可能如此。
Bundle Isolation 不是 Resource Isolation。
Browser 不认识 Team Boundary。
这并不意味着多套 Runtime 必然冲突。封装好的应用完全可以长期稳定并存。但 Version 距离越远、参与的 Global Mechanism 越多,必须验证并持续保护的假设也越多。
Router 可能想控制 URL 与 History。Global Reset Style 可能越过 Remote Root。Overlay 可能脱离 Component Tree,直接挂到 body。Global Registry 要求 Name 唯一。多个 Error Handler 和 Telemetry Instrumentation 观察的是同一个进程。一个计算密集型 Remote 仍然会阻塞 Host 所使用的 Main Thread。
因此,分离 Framework Runtime 仍然会争抢共同 Browser Resource。Runtime Boundary 并不能隔离 CPU、Memory 或 Document 级 Global Side Effect。

最硬的边界:iframe 中的完整隔离
Section titled “最硬的边界:iframe 中的完整隔离”如果不同 Runtime 之间的假设已经无法可靠控制,真正彻底的技术边界就是另一个 Browser Context:
Host 应用└── iframe └── 完整 Legacy 应用 ├── 自己的 window ├── 自己的 document ├── 自己的 Framework Runtime ├── 自己的 Router ├── 自己的 Styles └── 自己的 Lifecycleiframe 比同一 Document 内的另一个 Bundle 隔离得彻底得多。嵌入应用拥有自己的 JavaScript Global、DOM、CSS Structure 和 Router Infrastructure。Global JavaScript Patch 或 Registry 不会自动作用到 Host 的 window。差异巨大的 Framework Generation 因此能够更可靠地彼此隔离。
这种隔离是真的。
代价也是真的。
Navigation 与 Browser History 必须跨 Document Boundary 协调。Authentication 和业务 Context 需要受控传递。Focus Management、Keyboard Navigation 与 Accessibility 需要额外关注。Responsive Size 需要 Host 和嵌入文档协商。Modal 与 Overlay 仍被限制在 iframe 内,即使整体 UX 希望它们跨越整个产品。
Loading、Error 和 Telemetry 也需要跨边界暴露。Styling 与 User Flow 想保持统一,就需要更多工作。每一项必要通信都会成为一个显式 Message Contract。
iframe 用高昂 Integration Cost 换来强 Isolation。
完整隔离会改变架构本身
Section titled “完整隔离会改变架构本身”典型微前端希望在一个共同 UI 中组合业务区域:
共享 Host├── 共同组合的 UI├── 一致 Navigation├── 共享 Platform Service├── 集成 User Flow└── 可独立变化的业务区域彻底隔离时则更像:
Host 应用└── 嵌入的独立应用仍然有一些优点保留下来。嵌入应用可以独立 Deploy,拥有分离的技术 Lifecycle 和明确 Failure Boundary;Legacy Bestand 也可以继续运行,并逐步缩小。
但另一些优点会被削弱,或者只能用额外成本换回来。Seamless Composition、连续 Navigation、统一 Layout、共享 Platform Mechanism、Integrated Overlay、一致 Accessibility 和中央 Observability 都不再天然存在。Host 集成的已经不是单个业务片段,而是一个拥有自己 Browser Context 的完整应用。
Framework Version 越需要强隔离,架构就越从“共同组合的微前端”移动到“完整应用之间的集成”。
争论它在术语上是否还叫 Microfrontend 没有太大价值。更重要的问题是:我们最初希望通过微前端得到什么架构收益?在完成这些必要隔离之后,还剩多少?
完整 Isolation 可以做到。
但它往往通过远离原本希望得到的 Microfrontend Composition 实现。

被避免的 Migration 并没有消失
Section titled “被避免的 Migration 并没有消失”如果长期保持很大的 Version Mix,系统可能同时运行多套 Framework Runtime,Memory Footprint 增长,Compatibility Matrix 扩大,Toolchain 也开始分裂。Platform Test 必须覆盖更多组合,Debugging 需要掌握多个 Framework Generation。Security 与 Maintenance Cycle 彼此错开。极端情况下,整个 UI 实际上由多套完整应用拼成,而这些应用的 Integration 本身又需要长期基础设施。
每个 Team 都能避开自己的 Migration,并不代表整个架构就经济。
一个 Team 省掉的 Framework Migration,可能在未来多年以额外 Runtime、Integration 与运维工作支付。所谓 Independent Release 短期减少了协调,后续成本却由 Platform、Support 与用户承担。
这不是绝对禁令。把 Migration 拆开、错开时间完全可能合理。但此时成本应该被理解为对“过渡”的投资。没有 Target Architecture,同一笔投资就会变成永久运营费用。
团队的技术自由并不免费。
成本只是出现在系统的其他位置。
合理的例外:受控 Migration
Section titled “合理的例外:受控 Migration”应用停留在明显老旧的 Framework Version 上,通常并不是毫无原因。巨大的业务范围、多个团队、不足的自动化测试、不兼容的第三方 Library、自定义 Build Mechanism、强技术耦合与关键业务流程,都可能让一次性直接 Migration 变得不负责任。尤其是当多个技术基础必须同时变化,而可用 Capacity 又有限时。
此时一句“那就直接升级啊”不是架构决策,只是在拒绝讨论风险。
恰恰在这种情况下,强 Isolation 可能很有价值:
Phase 1├── 新 Host└── iframe 中的完整旧应用
Phase 2├── 第一项新 Capability└── iframe 中缩小后的旧应用
Phase 3├── 更多新 Capability└── iframe 中剩余 Legacy 区域
目标└── iframe 消失Migration Scenario 中,iframe 包住的是“现在还不能安全修改的存量”。长期运营 Scenario 中,它包住的是“缺少共同 Version Strategy”。
这是本质区别。
Migration 中的额外 Integration Work 是有意付出的成本,用来降低替换风险。每抽出一项 Capability,隔离范围就更小,架构也朝明确目标前进。
大型 Transformation Project 在尝试可靠隔离差异巨大的 Framework Generation 时,最后落到 iframe 上并不少见。这未必说明技术创造力不足,反而常常说明只有独立 Document 才能提供真正需要的 Isolation Level。
这只是实践观察,不是普遍证明,也不意味着 iframe 应该成为标准方案。它只是承认:有些历史很长的大系统,确实不存在更紧密、同时又足够安全的 Integration Option。
iframe 此时不是 Target Architecture。
它只是围住一个尚不能安全 Migration 的 Legacy Bestand 的受控边界。
Migration 需要 Target Architecture
Section titled “Migration 需要 Target Architecture”多个 Major Version 可以是合理的过渡阶段:
起点└── 所有区域都在旧 Version
过渡├── 第一批区域迁移到 Target Version└── 剩余区域仍在旧 Version
目标└── 所有活跃区域处于约定 Version Corridor过渡与永久状态的区别不在技术。两者都可能同时运行多个 Runtime。区别在于 Responsibility、Boundary 和 Direction。
受控例外至少需要:明确 Target Version、负责 Owner、Migration Order 和 Decommission Plan。支持哪些 Version Combination 必须记录。Performance Budget 与 Integration Cost 应该可观察。最重要的是,例外需要时间边界和定期复审。
微前端可以缩小 Migration 的批次,不必要求一个 Team 在一次 Release 中升级整个应用。
但微前端不会替你建立 Migration Strategy。
能够分阶段 Migration,并不等于应该永久保留 Version Mix。
当过渡变成永久状态
Section titled “当过渡变成永久状态”Version Drift 很少来自某一个大决定,而是由很多“都能解释”的例外累积出来:
Year 1├── Angular 17├── Angular 19└── Angular 22
Year 2├── Angular 17├── Angular 21└── Angular 23一个区域迁移,一个区域停留,新区域从当前 Target Version 开始。每个局部决策都可能合理,但总体上最老的应用仍然离 Platform 越来越远。
Migration 不是因此变小,而是在变大。Shared Library 要么同时兼容多个 Generation,要么也被拆开。Toolchain、Browser Support 与 Maintenance Cycle 开始分裂。关于旧 Version 的知识必须永久保留。Test Matrix 继续增长,而 Exception 慢慢变成 Normal State。
当没人再对整体 Technical Platform 负责时尤其危险。每个 Team 都把自己的 Framework Version 当成局部事项,但用户实际上在同一个产品里同时运行所有 Version。
Version Tolerance 是用于过渡的工具,不是永久技术碎片化的许可证。
否则,被推迟的 Migration 只会变成分布式 Technical Debt。
Framework Version 是平台 Contract
Section titled “Framework Version 是平台 Contract”在共同组合的 UI 中,默认状态应该是一个中央 Framework Major Version,或者一个很窄、明确支持的 Version Corridor。多个 Major Version 应该保持为记录在案的 Exception。
共同 Workspace 可以帮助实施这种 Governance。Nx 之类的工具可以让 Central Dependency Version、Project Relationship、Affected Application 和 Shared Migration Run 更可见。CI Rule 也可以阻止无人察觉的 Version Drift,并控制已批准的 Exception。
但 Nx 不会自动解决问题。不是 Monorepo 强迫大家共享 Version,而是组织必须把它当成 Technical Platform Contract。Tool 只是让这份 Contract 更可见、更自动化。
Polyrepo 一样可以实现这个原则:Central Policy、Automated Update Process、CI Check 和 Defined Compatibility Matrix 都能提供类似 Governance。关键不是 Repository Boundary,而是大家是否共同承担 Runtime Platform 的责任。
Single-Version Policy 也不意味着所有 Team 在同一天协调所有业务 Release。它只是意味着:只要这些应用在同一个产品和 Browser Context 中组合,Framework Runtime 就不能被当成完全私人的局部决策。
正常状态应该是一套共同 Framework Major Version,或者一条明确支持的窄 Version Corridor。
临时 Version Mix 可以作为受控 Migration。它需要记录原因、Owner、Target 和到期条件。新的 Remote 应该从 Target Version 开始,旧 Version 不应该再因为新增 Consumer 和 Dependency 被继续固化。额外 Runtime 与 iframe 从第一天起就应该有 Decommission Plan。
应避免的是没有 Target Architecture 的任意长期 Version Mix。它把技术灵活性误解成架构自由,把团队局部方便误解成系统长期经济性。
Framework Version 是 Platform 的一部分。并行运行多个 Version 的团队,不仅要证明“这些 Bundle 能加载”,还必须对 Runtime Cost、共享 Browser Resource、Integration Boundary 和长期 Maintenance 负责。
多个 Major Version 技术上可以运行。共享 Runtime 需要共同 Version Contract;分离 Runtime 会增加 Resource 与 Test 成本,却仍然共享同一个 Browser Context。
通过 iframe 可以获得真正完整的 Isolation。但那时被组合的不再只是业务片段,而是彼此嵌入的完整应用。作为长期目标架构,这个价格通常既不经济,也不值得。
同样的 Isolation 如果作为时间有限的 Migration Bridge,则完全可能合理。它保护一个尚不能安全修改的 Legacy Bestand,并支持逐步替换。为此,它必须有 Target Architecture、明确 Responsibility 和 Planned Decommission。
微前端可以让 Migration 分阶段进行。
它不应该成为永久避免 Migration 的理由。