Webpack 5 带来的变化不只是版本号提升,它在缓存策略、模块标识、资源处理和代码分块上做了大量底层调整。Trot Universe 小跑宇宙这个说法,更多是社区里用来形容把这些新能力组合起来之后,应用构建和运行像一个小型宇宙那样既有独立边界又能互相牵引。接下来就围绕这个组合展开,先看 Webpack 5 在缓存和模块标识上的关键机制,再讨论模块联邦如何实现跨应用协同,最后落到资源模块与 Tree Shaking 的配合细节。

持久化缓存与确定性的模块标识
Webpack 4 时代长期缓存经常失效,根源之一在于模块标识不稳定。每当新增或删除一个模块,内部的 module id 就会发生整体偏移,导致 chunk 内容变化,浏览器端缓存大量失效。Webpack 5 默认启用了确定性的模块标识策略,通过 optimization.moduleIds 和 optimization.chunkIds 设置为 deterministic,让相同内容在不同构建之间保持稳定的数字标识。这个调整看起来小,但对生产环境的缓存命中率影响极大。
另一个关键变化是文件系统级缓存。过去使用 cache-loader 或者 hard-source-webpack-plugin 来做缓存,常常需要处理失效边界和序列化兼容问题。Webpack 5 原生支持 cache.type: 'filesystem',把模块编译结果和依赖关系直接存储到磁盘。二次构建时,Webpack 会先检查缓存条目是否仍然有效,如果源码和配置没有变化,就直接复用上次的编译产物,跳过解析、转换和代码生成阶段。对于中大型项目,冷启动后的二次构建时间可以缩短一半甚至更多。
要把持久化缓存用好,需要显式声明配置文件的依赖关系。buildDependencies 告诉 Webpack 当配置文件本身发生变化时,缓存必须全部失效。下面是一个基础配置,同时包含了确定性的模块标识和文件系统缓存。
module.exports = {
cache: {
type: 'filesystem',
buildDependencies: {
config: [__filename]
}
},
optimization: {
moduleIds: 'deterministic',
chunkIds: 'deterministic',
runtimeChunk: 'single'
}
};这里 runtimeChunk: 'single' 把运行时代码拆成单独文件,进一步避免业务代码变化影响 runtime 的缓存。需要注意,持久化缓存不适合在配置里使用动态生成的内容,例如直接读取环境变量并写入插件选项,否则缓存键会不稳定。团队实践中通常会把稳定配置与易变配置分开管理。
模块联邦构建小跑宇宙的协同基础
模块联邦是 Webpack 5 最受关注的新能力,它让多个独立构建的应用在运行时共享模块,而不需要把所有代码打包到一起。Trot Universe 小跑宇宙这个称呼正是对这种协同方式的形象描述:每个应用像宇宙中的天体,有自己的构建和部署节奏,同时又能通过远程入口互相加载对方暴露的组件或工具函数。这种模式与微前端架构天然契合,但比传统的 iframe 或 single-spa 方案更轻量,共享依赖的版本控制也更灵活。
实现模块联邦的核心是 ModuleFederationPlugin。宿主应用通过 remotes 声明需要加载的远程模块地址,远程应用通过 exposes 暴露自己的模块,两者通过 name 和 filename 建立连接。shared 字段用于声明共享依赖,比如把 React 和 ReactDOM 设置为单例,避免多个应用加载各自的 React 实例导致状态错乱。下面是一个宿主应用的配置示例。
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app_shell',
filename: 'remoteEntry.js',
remotes: {
app_products: 'app_products@http://localhost:3002/remoteEntry.js'
},
exposes: {
'./Header': './src/components/Header'
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true }
}
})
]
};远程应用也有对应的配置,name 必须与宿主中引用的 key 一致,filename 为远程入口文件名。共享依赖的版本协商由 Webpack 运行时自动处理,如果宿主与远程声明的版本范围不兼容,它会加载各自版本,此时单例要求就无法成立。因此在设计共享依赖时,需要提前约定主版本范围,避免运行时出现多个 React 副本。
模块联邦并不是银弹,它引入了网络请求和运行时加载的复杂度。对于首屏性能敏感的应用,需要合理拆分远程模块,避免在关键渲染路径上加载过多远程资源。另外,远程应用的部署地址变化时,宿主配置需要同步更新,这要求团队在 CI 和部署流程中维护一份清晰的模块映射。如果把持久化缓存和模块联邦结合使用,二次构建时远程入口文件的 hash 变化会正确反映在依赖图里,缓存失效也能自动处理。
资源模块与 Tree Shaking 的实践配合
Webpack 5 内置了资源模块,用来替代 file-loader、url-loader 和 raw-loader 的组合。过去处理图片、字体、文本等资源需要在 module.rules 里分别配置 loader 和 options,还要注意不同 loader 之间的顺序。现在只需要设置 type 字段,asset/resource 对应 file-loader,asset/inline 对应 url-loader 的 base64 内联,asset 则允许通过阈值自动在两者之间切换。这种统一减少了配置量,也避免了 loader 版本冲突的问题。
Tree Shaking 的效果在 Webpack 5 中进一步提升,尤其是对 ES 模块的静态分析更加准确。使用资源模块时,如果某些资源没有被引用,Webpack 仍然会将其包含在依赖图中,除非通过 sideEffects 标记让打包器知道哪些文件可以安全删除。对于使用了大量图片或样式文件的组件库,需要在 package.json 中正确声明 sideEffects,例如把 CSS 文件标记为有副作用,避免样式在摇树优化中被错误移除。
module.exports = {
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource'
},
{
test: /\.svg$/,
type: 'asset/inline'
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024
}
}
}
]
},
optimization: {
usedExports: true,
sideEffects: true
}
};配置中的 usedExports 和 sideEffects 默认在 production 模式下开启,但在开发环境调试时也可以手动打开观察效果。需要留意的是,资源模块和代码分割一起使用时,小体积的内联资源会增加 JavaScript 体积,大体积的资源文件则通过 file 形式输出,webpack 会自动处理文件名 hash 和输出路径。合理设置阈值可以在请求数量和包体积之间找到平衡。
回到 Trot Universe 小跑宇宙这个说法,它并不是 Webpack 官方术语,更像是一个工程文化的缩影。把模块联邦、持久化缓存、资源模块和确定性标识这些能力整合在一起,团队可以构建出独立部署、快速构建、长期缓存友好的前端架构。理解每一项新特性的适用边界,比单纯升级依赖更重要,这也是 Webpack 5 生态持续演进的方向。
Webpack 5Trot Universe小跑宇宙修改时间:2026-10-01 05:55:31