Incise Universe(切开宇宙)这个名字听上去很有冲击力,但它并不是 Webpack 官方发布的一个独立功能。更准确地说,它是社区对一些 Webpack 5 新能力的形象化称呼,尤其指模块联邦和更精细的代码分割带来的应用边界控制能力。Webpack 5 通过模块联邦、runtimeChunk 优化、持久化缓存等机制,让开发者能够把一个庞大的前端应用像切开宇宙一样,拆成多个独立又互相协作的模块星系。这篇文章会把这个比喻拆开,看看它背后具体对应哪些技术点,以及如何在项目里落地。

切开宇宙的比喻:模块联邦与运行时容器
Webpack 5 引入的模块联邦(Module Federation)是最接近这个比喻的能力。它允许不同构建产物在运行时共享模块,而不是在编译期把所有代码打成一个包。想象一下,主应用和微应用分别部署,主应用可以在运行时加载远程模块,就像切开宇宙后,不同星系之间依然有虫洞连接。这种能力让前端架构从单仓库大一统走向多团队独立发布。
模块联邦的核心是 ModuleFederationPlugin。配置里需要声明 name、filename、exposes 和 remotes。name 是当前容器的名字,filename 是远程入口文件,exposes 是当前容器暴露给外部的模块,remotes 则是要消费的远程模块。下面的配置展示了如何暴露一个按钮组件,并在另一个应用里动态加载它。
// webpack.config.js
const { ModuleFederationPlugin } = require('webpack');
module.exports = {
entry: './src/index.js',
mode: 'development',
devServer: {
port: 3001,
},
plugins: [
new ModuleFederationPlugin({
name: 'app_a',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button.js',
},
remotes: {
app_b: 'app_b@http://localhost:3002/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
这段配置里,exposes 把本地的 Button 组件暴露为名为 ./Button 的远程模块,remotes 声明了另一个远程容器的地址。shared 则指定 react 和 react-dom 为单例,避免多个容器加载重复依赖。模块联邦的好处很明显:应用拆分更彻底,但运行时又可以像调用本地模块一样调用远程模块。
不过,模块联邦也有代价。远程模块的加载需要处理网络延迟、版本不匹配和降级方案。因此在实际项目中,通常会把核心库通过 shared 配置为单例,并且为远程容器设置合理的加载策略。切开宇宙不是盲目切分,而是在明确边界后保持连通性。
代码分割的精细控制:splitChunks 与 runtimeChunk
除了模块联邦,Webpack 5 对代码分割的控制也更细。splitChunks 是内置优化项,允许开发者把公共依赖、业务代码、第三方库分别打包。Webpack 5 默认对异步模块自动分包,但通过 cacheGroups 可以更精细地定义切割规则。比如把 node_modules 下的所有依赖单独打成一个 vendors 包,把 React 相关依赖打成一个 react 包。
切开宇宙的另一个层面就是这里:把不同职责的代码放到不同包里,减少首屏加载体积。下面是一个比较常用的 splitChunks 配置,它能稳定地把第三方库和公共模块分开。
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 244000,
cacheGroups: {
react: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/,
name: 'react',
chunks: 'all',
priority: 20,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
},
common: {
minChunks: 2,
name: 'common',
chunks: 'all',
priority: 5,
reuseExistingChunk: true,
},
},
},
runtimeChunk: 'single',
},
};
这个配置里,priority 决定了匹配顺序,react 包的优先级最高,避免 react 被 vendors 规则覆盖。runtimeChunk 设置为 single 会把 Webpack 的运行时代码单独抽离,这样公共包的哈希不会因为运行时变化而改变,有利于长期缓存。很多项目在升级 Webpack 5 后,只关注到持久化缓存,却忽略了 runtimeChunk 对缓存命中率的巨大影响。
还有一个容易被忽视的细节是 maxSize。Webpack 5 会尽量把超过 maxSize 的包拆成更小的块,但并不会严格保证每个包都小于这个值。如果项目里某个包已经无法再拆分,Webpack 会保留原样。因此 maxSize 是一个软性目标,需要结合具体依赖关系来调整,而不是简单设置一个数字。
持久化缓存与资源模块:让切割后的宇宙更快重建
Webpack 5 另一个被低估的能力是持久化缓存。旧版本在每次构建时都需要重新解析和编译模块,而 Webpack 5 可以把编译结果缓存到磁盘,下次构建只处理发生变化的文件。对大型项目来说,这能极大缩短开发环境的启动和热更新时间。配置方式也比以前简单,只需在 cache 字段里指定 type 为 filesystem。
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
},
};
cache 使用文件系统缓存后,Webpack 会把模块编译信息存储在 node_modules/.cache 目录下。buildDependencies 用来指定哪些文件变化时缓存失效,这里把配置文件本身加入依赖,修改 webpack 配置后缓存会自动失效。如果不加这一项,改了配置但缓存没失效,可能出现非常难排查的构建问题。
资源模块也是 Webpack 5 的重要变化。过去处理图片、字体等资源需要配置 file-loader 或 url-loader,现在内置了 asset/resource、asset/inline、asset/source 和 asset 四种类型。这让资源处理不再依赖额外 loader,配置更简洁。切开宇宙后,每个星系内部的资源也能被统一管理,减少依赖链。
实践:在一个真实项目里组合这些能力
假设有一个电商后台系统,包含商品管理、订单管理、用户权限三个业务模块,外层还有一个公共的登录和菜单框架。利用 Webpack 5 的模块联邦,可以把外层和三个业务模块分别部署为四个容器,外层通过 remotes 加载三个远程模块。每个业务模块独立开发、独立发布,外层只在需要时才加载对应模块。
同时,在各自容器内部继续使用 splitChunks 分割第三方库和公共组件,配合 runtimeChunk 和 filesystem cache 保证构建速度。这样既实现了应用级的解耦,又不会牺牲构建性能和缓存效率。切开宇宙的本质不是把代码切得越碎越好,而是在合理的边界处切割,让每个部分可以独立演进。
落地时还需要注意几个问题。远程模块的版本管理需要约定好 shared 依赖的版本范围,避免运行时出现多个实例;远程入口地址在生产环境通常由环境变量注入,而不是写死在配置文件里;对不支持模块联邦的旧浏览器,需要提前准备好降级方案。只有把这些工程细节处理好,切开宇宙才不会变成一盘散沙。