在微前端架构中,依赖共享 与版本控制始终是痛点和难点。Webpack Module Federation(简称MF)在底层实现了一套精巧的 runtime 版本协商逻辑,使 host 和 remotes 之间能更安全地共用 dependencies(比如 React、Axios、Lodash 等),也为灵活应对多样业务场景、兼容微前端异步加载提供了能力。
本文以源码(ConsumeSharedRuntimeModule.js)为基础,系统性梳理 MF 运行时如何判断、选择和加载合适的 shared 依赖,并如何回应各种"找不到/不满足版本/严格模式"等复杂情况。
1. 基本问题:共享依赖如何"判定谁有效"?
关键步骤简化为:
- 是否有 host/remote/本地提供了要加载的依赖(即是否共享空间
scope[key]
下存在该包)。 - 若有版本约束,需判断实际 version 是否满足 requiredVersion。
- 是否处于 singleton/singleton with requiredVersion/with strictVersion 模式,涉及只能有一个实例、必须完全一致,否则警告/报错。
- 如果都找不到,是否有 fallback?如果没 fallback,又找不到,如何提示?
2. 核心源码流程梳理
以下源码摘自 Webpack 5 主干 ConsumeSharedRuntimeModule,并简化命名便于理解:
2.1 普通共享依赖
js
var load = (scopeName, scope, key, eager, fallback) => {
if (!exists(scope, key))
return useFallback(scopeName, key, fallback); // 本地 fallback 或报错
return get(findLatestVersion(scope, key, eager)); // 找最新版本
};
流程:
- 没找到直接走 fallback;
- 否则用当前所有可见 version 里满足条件的。
2.2 普通共享(带 requiredVersion)
js
var loadVersion = (scopeName, scope, key, eager, requiredVersion, fallback) => {
if (!exists(scope, key))
return useFallback(scopeName, key, fallback);
var satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);
if (satisfyingVersion)
return get(satisfyingVersion);
warn(getInvalidVersionMessage(...));
return get(findLatestVersion(scope, key, eager));
};
流程:
- sharedScope里没找到直接走 fallback;
- 若有满足requiredVersion版本的,用它
- 没有则 warn 并退回所有可用版本中最新的。
2.3 严格 requiredVersion 模式(strictVersion: true
)
js
var loadStrictVersion = (scopeName, scope, key, eager, requiredVersion, fallback) => {
if (!exists(scope, key))
return useFallback(scopeName, key, fallback);
var satisfyingVersion = findSatisfyingVersion(scope, key, requiredVersion, eager);
if (satisfyingVersion)
return get(satisfyingVersion);
if (fallback)
return fallback(); // 主动 fallback
fail(getInvalidVersionMessage(...)); // 直接报错
};
优化与限制:
- sharedScope里没找到直接走 fallback;
- 若有满足requiredVersion版本的,用它
- 没有则报错
2.4 Singleton/Safe Singleton 加载
singleton、singleton with required Version、strictSingletonVersion------即保证整个运行时只能有一份依赖实例(如只准有一个 React),对版本的严格性不同:
js
// loadSingleton 是非 strict,找到任意(通常最新)版本就行
var loadSingleton = (scopeName, scope, key, eager, fallback) => {
if (!exists(scope, key))
return useFallback(scopeName, key, fallback)
var version = findSingletonVersionKey(scope, key, eager);
return get(scope[key][version])
)});
var loadSingletonVersion = (scopeName, scope, key, eager, requiredVersion, fallback) => {
if (!exists(scope, key))
return useFallback(scopeName, key, fallback);
var version = findSingletonVersionKey(scope, key, eager);
if (!satisfy(requiredVersion, version))
warn(...);
return get(scope[key][version]);
};
// strictSingletonVersion 必须完全匹配,否则 fail
var loadStrictSingletonVersion = (..., requiredVersion, ...) => {
...
if (!satisfy(requiredVersion, version))
fail(...);
return get(scope[key][version]);
};
区别在于 warn 还是 fail。
3. fallback 到底怎么做?
在所有加载函数开头或找不到合适实现时,都会调用:
js
useFallback(scopeName, key, fallback)
该函数:
- 有 fallback 配置时(如 new Webpack 允许的 fallback 字段),会用你自己指定的 require/工厂方法。
- 否则就尝试本地 node_modules 里的依赖(即本地实现)。
- 如果啥都没有,则直接抛异常报错(fail)。
4. 现实微前端开发中的用法和最佳实践
- 默认模式下,MF 优先选 remote/host 里所有提供者的最大可用版本(latest)。
- 允许版本范围误差(非 strict),找不到 exact version 时会 warn,不会硬 fail,但建议避免依赖过大漂移。
- strictVersion 或 strictSingletonVersion 场景,务必全链路锁定版本,不然一出错就 fail,终止 remote 加载。
- fallback 优先级低,如果有 remote/host/debug 本地 node_modules,都能作为最后一道保险。
常见配置示例:
js
shared: {
react: { singleton: true, requiredVersion: "^18.0.0", strictVersion: true }
}
5. 总结和建议
Webpack MF 的 shared 依赖协商机制本质是一套带 retry/fallback/错误提示的"版本半自动代理工厂":
- 非 strict 下结果可容忍 patch diff,但有 warn。
- strict 下对 requiredVersion 严重敏感,安全性更高但兼容风险大,需要团队所有微应用依赖锁死。
- 开发时推荐全链多端同一份 lock 文件和共享依赖,避免"看似没报错,实则大版本不兼容"的隐患。
建议所有微前端团队对 shared 依赖策略、singleton 使用和 fallback 行为做详细文档规划,并配合 CI 依赖检测、npm/pnpm workspace 锁定依赖,做到运行时心中有数。