共享依赖隔离:多共享池(Share Scope)
在 Module Federation 中,shared 默认都注册在 default 这个 Share Scope 下。当你的系统里存在以下情况时,单一 Scope 往往不够用:
- 希望把一部分共享依赖「隔离」出来,避免与默认共享池互相影响(例如两套 React 生态并存、灰度升级、微前端子域隔离)。
- 希望同一个包在不同业务域使用不同版本 / 策略,但仍保持在各自域内共享(每个域内仍可 singleton / 复用)。
多共享池(Share Scope)的核心价值是:把共享依赖的注册与解析切到不同的命名空间(Scope)里,从而实现共享池隔离与策略分层。
配置项速览
理解多共享池最简单的方法:只看「生产者怎么配」、「消费者怎么配」、「shared 条目落在哪个池里」,不需要了解运行时内部数据结构与变量名。
- 生产者:用 shareScope 声明该 Provider 会初始化哪些共享池(默认
default,支持string | string[])。 - 消费者:用 remotes[remote].shareScope 声明与某个 Consumer 需要对齐哪些共享池(默认
default)。 - shared 条目:用
shared[pkg].shareScope决定某个依赖注册 / 解析在哪个共享池(见 shared.shareScope)。
不同 shareScope 组合的效果
当消费者初始化某个生产者时,会先根据双方的 shareScope 配置把「共享池」对齐。让生产者知道哪些共享池要复用的,随后再按自己的 shareScope 初始化共享依赖。
为了更简单地说明对齐与初始化的关系,我们用:
- HostShareScope 表示消费者侧为某个生产者配置的
remotes[remote].shareScope - RemoteShareScope 表示生产者侧配置的
shareScope
不要将 shareScope / remotes[remote].shareScope 配置为 ['default'] 或 []:
- 单个 Scope:使用字符串,不要使用数组。两者的内部实现在「共享池对齐 / 初始化」的分支上是不同的:当消费者配的是数组而生产者配的是字符串时,生产者会按消费者的列表去对齐 Scope;而生产者配成数组时,则只会按生产者的列表处理。
- 空数组
[]:会导致没有任何 Scope 被初始化(既不会初始化default,也不会对齐其它 Scope),是错误配置。
共享池由消费者提供,由生产者初始化 —— 消费者没列出的 Scope 会被补成 {}(不会因找不到而崩溃),生产者没列出的 Scope 则不会被初始化。两边都列出,shared 才能真正在该 Scope 下复用。
Playground
构建插件如何配置
生产者
要点:
shareScope: ['default','scope1']决定生产者的 remoteEntry 在运行时会初始化哪些 Scope。shared[pkg].shareScope决定该包最终注册/解析时使用哪个 Scope;如果你把@company/design-system放在scope1,它将只在scope1的共享池中参与版本选择与复用。
消费者
要点:
remotes[remote].shareScope决定消费者在运行时初始化生产者时,会把哪些 Scope 作为shareScopeKeys传入生产者。- 如果消费者配了多 Scope,但生产者仍是单 Scope,生产者会把 scopeMap 对齐好,但只会初始化单 Scope 的 sharing(见上面的组合表)。因此多共享池要想「真正生效」,通常需要消费者和生产者两侧都配置一致。
纯运行时(Runtime API)如何配置
如果你不通过构建插件声明生产者和共享依赖(例如希望在运行时动态注册),可以改用 Runtime API 实现相同的多共享池效果。两个关键 API:
- 注册生产者:
registerRemotes或createInstance({ remotes }),在每条生产者配置里用shareScope: string | string[]声明要对齐的共享池。 - 注册共享依赖:
registerShared或createInstance({ shared }),在每条依赖配置里用scope: string | string[]决定它落在哪个共享池。
注册共享依赖时字段名是 scope,不是 shareScope(与构建插件的 shared[pkg].shareScope 不一致)。
用 Runtime Hook 做更精细的控制
多 Scope 的本质是「把共享池按名称分组」。当你希望更细粒度地控制 Scope 的选择、对齐与回退策略时,可以使用 Runtime Hooks 在 init 阶段或共享解析阶段介入。
1. 按生产者动态改写 shareScopeKeys(beforeInitContainer)
下面的例子会让 legacy_remote 永远使用 legacy Scope(即使它在构建期或运行时注册时写的是别的 shareScope):
2. Scope 缺失时做别名 / 回退(initContainerShareScopeMap / resolveShare)
initContainerShareScopeMap:在生产者初始化共享池过程中,对每个 Scope 的shareScope映射做改写。resolveShare:通过改写args.resolver来改写最终选择结果。按现在的运行时实现,只返回{ ...args, scope: 'default' }这种写法并不会真的切过去。
示例:如果 scope1 中找不到某个包,则回退用 default Scope:
你也可以在 initContainerShareScopeMap 中把某个 Scope 直接别名到另一个 Scope(让两个 Scope 共用同一个共享池):