Extending Universe 延伸宇宙并不是 Webpack 5 中某个单独的 API,而是一组能力协同后呈现的架构效果。它将原本封闭在单一构建产物内部的模块作用域,延伸到了多个独立构建之间,使不同团队部署的应用可以在运行时共享代码和依赖。这个特性的核心是模块联邦,配合持久化缓存和资源模块等基础能力,让前端架构从静态打包转向动态组合。理解这一变化,需要先看清传统构建方式的边界在哪里。

延伸宇宙的设计动机:从单体构建到运行时组合
传统的 Webpack 构建通常围绕一个入口文件生成完整的模块图,所有 import 和 require 的关系在编译期就已经确定。代码分割虽然能把模块拆成多个 chunk,但这些 chunk 仍然属于同一个构建上下文。一旦应用需要集成另一个团队维护的功能,常见做法要么是发布 npm 包让对方安装,要么是使用 iframe 或中心化路由的微前端方案。前者会带来版本耦合,后者会牺牲模块之间的直接依赖共享,甚至丢失类型提示和热更新体验。
延伸宇宙的目的就是打破这种静态边界。Webpack 5 在运行时为每个构建生成一个远程容器,容器中记录了对外暴露的模块列表。宿主应用加载这个远程容器后,远程模块会被当作本地模块图的一部分进行解析、依赖处理和缓存。这样,两个独立构建的应用可以在运行时组合成一个更大的应用,同时共享相同的依赖实例。这种机制类似于把多个星系的恒星连接起来,形成一个更大的宇宙。
与早期的微前端方案相比,延伸宇宙最明显的优势是依赖去重。通过 shared 配置,多个远程应用和宿主应用可以复用同一个 React 或 Vue 实例,避免同一个库被重复加载导致状态不一致。此外,模块联邦保留了 Webpack 的代码分割和懒加载能力,远程模块只有在真正被访问时才会加载,启动性能不会因为接入更多远程应用而明显下降。
模块联邦:延伸宇宙的核心实现
模块联邦由 ModuleFederationPlugin 提供,是 Webpack 5 内置的官方插件。远程应用通过 exposes 选项暴露模块,宿主应用通过 remotes 选项声明要消费的远程入口。先看远程应用的配置示例:
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
port: 3001,
},
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
上面的配置中,name 是远程应用的唯一标识,filename 是生成的远程入口文件名,exposes 把本地相对路径映射为对外暴露的模块路径。宿主应用的配置则通过 remotes 指定远程入口地址:
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
port: 3000,
},
plugins: [
new ModuleFederationPlugin({
name: 'hostApp',
filename: 'remoteEntry.js',
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
],
};
宿主应用在运行时通过动态 import 加载远程组件时,Webpack 会先解析 remotes 中的地址,加载远程入口文件,再从中读取模块定义。一个简单的加载示例如下:
import React, { Suspense } from 'react';
const RemoteButton = React.lazy(() => import('remoteApp/Button'));
export default function App() {
return (
<div>
<h1>宿主应用</h1>
<Suspense fallback="加载中...">
<RemoteButton />
</Suspense>
</div>
);
}
代码中的 import('remoteApp/Button') 并非普通的静态导入,而是由 Webpack 在编译期识别出一个远程模块引用。加载完成后,远程组件会像本地异步组件一样被渲染。需要注意的是,远程入口文件通常只包含模块声明和共享作用域的引导逻辑,实际组件代码只有在组件被渲染时才会按需下载。
shared 配置是整个机制的关键之一。singleton 参数表示该依赖在全局范围内只允许一个实例,requiredVersion 声明兼容版本范围。如果宿主应用和远程应用声明的 React 版本不一致,Webpack 会尝试加载一个满足所有声明的版本,并在控制台给出警告。这样有效避免了多个 React 实例导致的上下文失效问题。
持久化缓存与资源模块:支撑延伸宇宙的基建
延伸宇宙强调运行时灵活组合,但开发阶段的构建速度同样重要。Webpack 5 引入了持久化缓存,可以将模块解析结果、代码生成产物等缓存到磁盘。首次构建后,后续构建只处理发生变化的部分,大幅缩短冷启动和增量编译时间。在多个团队分别维护远程应用时,每个仓库都能从持久化缓存中受益,改动某个组件不会触发全量重新编译。
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
},
],
},
};
资源模块则是 Webpack 5 对静态资源处理的统一方案。原来需要借助 file-loader、url-loader、raw-loader 的场景,现在只需在 rules 中声明 type: 'asset/resource' 或 type: 'asset/inline'。这一变化减少了配置和依赖数量,同时让远程模块中引用的图片、字体等资源能够稳定地随模块一起打包和加载。资源模块与模块联邦没有直接耦合,但两者共同降低了多应用集成的复杂度。
从架构角度看,持久化缓存和资源模块属于基础能力层,它们让延伸宇宙在大型项目中具备可维护性。没有持久化缓存,频繁构建多个远程应用会消耗大量时间;没有原生资源模块,跨应用共享图片或样式文件则需要额外配置。Webpack 5 将这些能力内建,说明延伸宇宙不是孤立的优化点,而是整体构建体验的升级。
实战:用延伸宇宙构建微前端应用
假设一个电商平台包含主应用和商品详情子应用,两个团队独立开发和部署。商品详情子应用通过模块联邦暴露一个商品详情组件,主应用在运行时按需加载该组件。远程应用的 exposes 配置可以这样写:
// 商品详情子应用 webpack.config.js 片段
new ModuleFederationPlugin({
name: 'productDetail',
filename: 'remoteEntry.js',
exposes: {
'./ProductDetail': './src/components/ProductDetail',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
});
远程组件本身保持普通 React 组件的写法,不需要做任何特殊处理:
// src/components/ProductDetail.js
export default function ProductDetail() {
return '这是商品详情模块';
}
主应用只需要在 remotes 中声明远程入口,并在页面中使用动态导入即可。即使商品详情子应用后续发布了新版本,主应用无需重新构建或重新部署,刷新页面后就会加载最新的远程入口。这种独立发布的体验正是延伸宇宙带来的核心价值。
实际落地时还需要考虑远程模块加载失败的情况。可以使用 React 的错误边界(ErrorBoundary)包裹远程组件,提供降级 UI。同时,shared 中使用 singleton 可以避免多个 React 实例导致的状态异常,但需要确保所有远程应用和宿主应用的 React 版本满足兼容范围。如果版本冲突,Webpack 会在控制台输出详细警告,帮助排查。
延伸宇宙的实践不限于 React,Vue、Angular 或其他框架只要通过 JavaScript 模块导出组件,都可以接入模块联邦。Webpack 5 的远程容器协议是框架无关的,这使得跨技术栈的微前端组合成为可能。不过在实际项目中,建议先统一主要框架和构建工具版本,再逐步尝试跨框架集成,以减少调试成本。