先说结论:Webpack 5 并没有一个叫 Stretch Universe 或者伸展宇宙的官方特性。翻遍 Webpack 5.0 的 release notes、官方文档和核心团队的公开演讲,都找不到这个名词。它大概率是中文社区对 Webpack 5 模块联邦(Module Federation)能力的一种浪漫化比喻,意思大概是构建产物不再局限于单一工程,而是可以像宇宙膨胀一样向外伸展、跨应用共享代码。这个比喻本身不算错,但如果你把它当成一个真实存在的技术特性去查资料,只会越查越糊涂。这篇文章就把这件事讲清楚,顺便把真正支撑这个想象的那套机制,模块联邦,掰开揉碎讲一遍。

先辨析:Stretch Universe 到底从哪来的
任何技术名词都应该能追溯到权威来源。判断一个特性是否真实存在,最直接的办法是去查 Webpack 的 GitHub 仓库。Webpack 5 在 2020 年 10 月正式发布,官方公告中列出的核心变化包括:持久化缓存(File System Caching)、更好的长期缓存算法(Deterministic Module IDs)、Module Federation、Tree Shaking 增强(嵌套的无用导出清理)、CommonJS 与 ESM 混用支持等。这份清单里没有任何与宇宙、伸展相关的内容。
那为什么会有伸展宇宙这种说法?合理的推测有两个。一是某些二手教程为了形容微前端场景下代码边界不断扩展的形态,自造了一个形象说法,后来被别的文章引用时丢掉了比喻的引号,变成了所谓的特性名;二是机器翻译和 SEO 农场的以讹传讹。无论是哪种情况,结论都一样:这不是官方概念,面试时如果聊到它,正确姿势是指出它的来源可疑,然后转向真正的话题,也就是 Webpack 5 如何让多个独立构建的应用在运行时共享模块。这个能力才是那个比喻想表达的东西。
真正的主角:Module Federation 模块联邦怎么工作
模块联邦解决的问题是:两个独立开发、独立部署、独立构建的前端应用,如何在运行时互相加载对方的模块,并且共享公共依赖避免重复加载。过去要实现这个效果,得靠 externals 加 CDN 脚本、或者用 Monorepo 把代码强行收到一起,工程约束都很重。模块联邦把它变成了构建层面的一等公民能力。
它的核心配置有三个角色概念:exposes 表示当前应用对外暴露哪些模块,remotes 表示当前应用要消费哪个远程应用,shared 声明双方共享的依赖及版本范围。下面是一个宿主应用的完整配置示例:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
mode: 'development',
devServer: { port: 3000 },
plugins: [
new ModuleFederationPlugin({
// 当前应用的远程入口名,供其他应用消费
name: 'host',
// 本应用要消费的远程应用
remotes: {
// 别名: 远程暴露的 "name@入口地址" 格式
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
// 声明共享依赖,双方会协商使用兼容版本
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
new HtmlWebpackPlugin({ template: './index.html' }),
],
};从原理上看,整套机制靠一个运行时容器实现。提供方构建时会额外产出一个 remoteEntry.js,它本质上是一个异步边界:宿主通过 import('remoteApp/Button') 发起请求时,运行时会先加载并执行这个入口文件,注册一个全局容器对象,再由容器按需拉取 Button 对应的 chunk。整个过程对业务代码是透明的,写起来和普通的动态 import 几乎没有区别。shared 的协商逻辑也发生在运行时:双方各自上报自己持有的 React 版本,如果满足版本范围要求,就复用已加载的那份;如果都不满足,才会各自加载。设置 singleton: true 是为了保证像 React 这种不允许双实例的库全局只有一份。
实际落地时的坑与最佳实践
模块联邦听起来美好,真正用起来有几个高频坑点需要提前知道。第一个是 TypeScript 类型问题,宿主工程里 import 远程模块时,编译器并不知道远程模块的类型定义,直接会报找不到模块。常见解法是在提供方工程通过 @module-federation/typescript 这类方案生成一份类型包,或者手动维护一份声明文件交给宿主引用。
第二个是共享依赖版本漂移。如果宿主和远程应用对某个 shared 依赖的版本要求差距过大,运行时协商失败就会导致同一个库被加载两次,页面体积膨胀甚至出现难以排查的渲染异常。实践中建议把 shared 依赖的版本策略固化在团队规范里,比如统一锁定到同一个大版本的最新补丁版本,并且对所有状态类库强制 singleton: true。
第三个是部署与缓存问题。远程入口地址通常是写死在宿主配置里的,远程应用一更新,宿主不一定感知得到。稳妥的做法是把 remoteEntry 的地址交给一个简单的配置服务或运行时接口下发,同时保证 chunk 文件名带内容哈希、采用不可变缓存策略,这样远程应用发版后旧 chunk 依然可用,新用户自然拉到新版本。下面是一段在运行时动态加载远程组件的示例代码:
import React, { Suspense, lazy } from 'react';
// 懒加载远程应用暴露的 Button 模块
// 对应远程应用配置里的 exposes: { './Button': './src/Button' }
const RemoteButton = lazy(() => import('remoteApp/Button'));
export default function App() {
return (
<div>
<h1>宿主应用</h1>
<Suspense fallback={<div>加载远程模块中...</div>}>
<RemoteButton onClick={() => alert('来自远程应用的按钮')} />
</Suspense>
</div>
);
}最后回到最初的问题。所谓 Stretch Universe 伸展宇宙并不存在,但如果你把这个词理解为对 Webpack 5 打破应用边界、让代码在运行时自由流动这个方向的概括,那它指向的正就是模块联邦。学习一个技术名词之前,先确认它有没有权威出处,再顺着真实特性去啃文档和源码,这比收藏一堆概念性的说法要有用得多。如果你的项目正在考虑微前端改造,模块联邦值得认真评估,但也要掂量一下它带来的部署复杂度,团队规模小、发布节奏一致的情况下,Monorepo 加统一构建往往反而是更省心的选择。
Webpack 5模块联邦Module Federation修改时间:2026-09-07 04:40:36