在 Webpack 5 的讨论中,Divine Universe 并不是官方文档里的正式条目,而是社区对一系列新能力组合后的戏称——它把模块联邦、文件系统缓存、资源模块等特性比作一个互相协作的“构建宇宙”。这个比喻背后,是 Webpack 5 从架构层面对打包流程的重构:不再只是把文件拼起来,而是让不同构建产物之间能够安全地共享代码,同时大幅降低二次构建的时间成本。下面从四个核心方向展开这些变化。

模块联邦:跨应用共享代码的基石
模块联邦(Module Federation)是 Webpack 5 最受关注的特性之一,它允许一个应用在运行时动态加载另一个独立构建的模块,而不需要把代码打包进自己的产物。这与传统的 npm 包共享不同:npm 包在构建时就被固化,升级需要重新发布和安装;模块联邦则让共享代码保持独立部署,应用可以在运行时获取最新版本。
实现模块联邦的核心是 ModuleFederationPlugin。在宿主应用和远程应用两端分别配置 exposes 和 remotes,远程应用暴露模块,宿主应用声明远程地址。例如,一个组件库应用可以这样暴露按钮组件:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button.jsx',
},
shared: ['react', 'react-dom'],
}),
],
};
宿主应用则通过 remotes 引用远程入口,同时把 react、react-dom 等依赖声明为 shared,避免多份实例冲突。这种机制非常适合微前端场景:多个团队可以独立开发、独立部署,主应用负责组装。不过它也带来新的复杂度,比如共享依赖版本不一致时的回退策略、远程模块加载失败时的错误边界处理,都需要在实际项目中提前规划。
从性能角度看,模块联邦并不会自动减少总体积,如果远程模块和宿主应用都打包了相同的第三方库,而 shared 配置不当,反而可能出现重复加载。正确做法是把体积较大的运行时依赖(如 React、Vue、lodash 等)统一放在 shared 中,并设置 singleton 和 requiredVersion,让 Webpack 在加载时进行版本协商。这样既能保证兼容性,又能减少重复下载。
持久化缓存:把二次构建时间压到秒级
Webpack 5 内置了文件系统缓存,通过 cache 配置可以持久化存储模块和 chunk 的编译结果。在 Webpack 4 时代,二次构建通常需要依赖 cache-loader 或 hard-source-webpack-plugin 等第三方方案,配置繁琐且经常与后续插件冲突。Webpack 5 的原生缓存则简单得多:
module.exports = {
cache: {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '.webpack_cache'),
},
};
开启 filesystem 缓存后,Webpack 会把模块依赖图、编译后的代码、loader 处理结果等序列化到磁盘。下次构建时直接反序列化,跳过大部分 loader 和解析阶段。在大型项目中,热更新和增量构建的提速效果非常明显,二次构建往往能从几十秒降到几秒。需要注意的是,缓存目录应该加入 .gitignore,并且当 Webpack 版本升级或配置发生结构性变化时,最好手动清理缓存,避免过期数据导致构建异常。
缓存虽然好用,但并非所有场景都适合。对于频繁改动且依赖关系不稳定的项目,缓存失效频繁,收益会打折扣。此外,某些 loader 自身带有不确定输出(例如基于当前时间戳生成内容),会导致缓存失效或结果不一致。此时可以在 webpack 配置中通过 cache.buildDependencies 和 cache.managedPaths 来细化控制,把不参与缓存的目录排除掉,保证缓存正确性。
资源模块:告别 raw-loader 与 url-loader
Webpack 5 引入了 asset modules,把资源处理从 loader 层面提升到内置能力。以前处理图片、字体、文本等内容,需要配置 raw-loader、url-loader、file-loader 等一系列 loader,还要记住 limit 参数和 fallback 关系。现在只需要在 rules 中指定 type 为 asset/resource、asset/inline、asset/source 或 asset,Webpack 会自动完成对应处理。
例如,想把小于 8KB 的图片转成 base64 内联,大于该值的文件复制到输出目录,可以这样写:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024,
},
},
},
],
},
};
asset/resource 对应 file-loader 的行为,asset/inline 对应 url-loader 的 base64 内联,asset/source 则对应 raw-loader。这种统一不仅简化了配置,还减少了 loader 之间的依赖冲突。迁移时要注意,asset modules 的默认输出文件名规则与旧 loader 略有不同,可以通过 output.assetModuleFilename 统一控制,例如设置为 'assets/[name].[hash:8][ext]',保持目录结构清晰。
对于需要自定义字体处理的场景,asset/resource 同样适用。只要把字体文件纳入规则,Webpack 会自动处理引用路径,CSS 中的 url() 也会被正确重写。唯一需要注意的是,旧项目里如果使用了 url-loader 的 limit 和 fallback 组合,迁移到 asset 类型后需要确认 dataUrlCondition 的设置是否与原来一致,否则可能出现内联数量变化导致首屏体积波动。
运行时与代码分割的默认优化
Webpack 5 对 splitChunks 和 runtimeChunk 的默认行为做了调整,使代码分割更符合现代浏览器的加载策略。默认 splitChunks 的 minSize 和 maxSize 计算方式发生了变化,同时新增了 automaticNameDelimiter、maxAsyncRequests 等细节控制。更重要的是,Webpack 5 对异步 chunk 的加载逻辑进行了优化,减少了运行时对 chunk 加载失败时的重试机制,配合原生 import() 可以让错误处理更直观。
一个常见的优化配置是把 runtime 单独抽离:
module.exports = {
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
},
},
},
},
};
把 runtime 抽成单独文件后,由于 runtime 变化频率通常较低,配合长效缓存可以让浏览器更长时间复用该文件。splitChunks 的缓存组策略也需要根据项目实际依赖结构调整,例如把体积较大的库(如 echarts、moment)单独拆出,避免 vendors 包过大影响首屏解析。
另外,Webpack 5 在开发模式下的模块热替换(HMR)也做了底层优化,更新速度比 Webpack 4 更快,配合持久化缓存,改一行代码后热更新几乎瞬时完成。这些改进共同构成了社区所说的 Divine Universe 的实践基础:构建工具不再是黑盒,而是可以由开发者精细调控的协作系统。