Webpack 5 于 2020 年正式发布,官方把这次的更新主题称为 Thriving Universe,寓意整个构建生态进入了一个茁壮成长的新阶段。这个版本并不是简单的版本号递增,而是从构建性能、产物优化、模块化能力等多个维度进行了重构。对于还在使用 Webpack 4 的团队来说,理解这些新特性,有助于评估升级的成本与收益。

持久化缓存:二次构建速度的质变
在 Webpack 4 时代,每次启动开发服务器或执行构建,都需要完整地跑一遍编译流程。即使只是改了一行代码,模块解析、依赖收集、代码转换这些工作也要重新做一遍。Webpack 5 引入了文件系统级别的持久化缓存,把编译过程中模块的依赖关系和处理结果直接写到磁盘上。下次构建时,如果相关文件和配置没有变化,就直接复用缓存结果,跳过绝大部分编译工作。
开启方式非常简单,只需要在配置中增加一个 cache 字段即可。默认情况下,Webpack 5 在 development 模式下会自动启用这个能力,而在生产环境需要手动开启。
module.exports = {
cache: {
type: "filesystem", // 使用文件系统缓存
buildDependencies: {
config: [__filename], // 配置文件变化时缓存失效
},
},
};从实际项目反馈来看,大型项目的二次构建时间往往能缩短一半以上,有些场景甚至能达到百分之八十的提升。需要注意的是,缓存的失效策略由 Webpack 自动管理,它会追踪配置文件、依赖版本、Loader 版本的变化,开发者基本不需要手动清理缓存。这一特性也是官方推荐放弃 babel-loader 的 cacheDirectory 和 cache-loader 这类旧方案的原因,原生方案在粒度和准确性上都更胜一筹。
模块联邦:微前端时代的代码共享利器
模块联邦可以说是 Webpack 5 最受瞩目的特性。它解决的核心问题是:多个独立构建的应用之间,如何优雅地共享代码。过去的方案要么通过 npm 包发布共享组件,版本同步非常痛苦;要么用 externals 配合 CDN 引入公共库,但共享粒度只能停留在第三方库层面,业务组件无法直接共享。
模块联邦允许一个应用在运行时动态加载另一个应用暴露的模块,就像使用本地模块一样自然。每个应用既可以作为 Host 消费远程模块,也可以作为 Remote 提供模块,两者可以兼得。下面是一个典型的配置示例:
// 应用A:暴露自己的组件,同时消费应用B的模块
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "appA",
filename: "remoteEntry.js",
exposes: {
"./Button": "./src/components/Button.vue",
},
remotes: {
appB: "appB@http://localhost:8081/remoteEntry.js",
},
shared: {
vue: { singleton: true },
axios: { singleton: true },
},
}),
],
};shared 配置配合 singleton 选项,可以保证多个应用共享同一个版本的依赖实例,避免 Vue 这类有全局状态要求的库被重复加载。这套机制让微前端的实现成本大幅降低,不再依赖复杂的运行时框架,天然支持按需加载和依赖去重。当然,它也带来了运行时耦合的风险,远程模块加载失败时需要有兜底方案,这是使用时必须考虑的工程问题。
更好的 Tree Shaking 与产物优化
Webpack 5 对 Tree Shaking 的能力做了显著增强,提出了嵌套的 Tree Shaking 概念。在之前的版本中,如果一个模块导出的是一个对象,对象内部的方法即使没有被使用,也无法被摇掉。Webpack 5 可以分析导出值的属性访问关系,比如代码里只用了 utils 里的 format 函数,那么 utils 中其他属性对应的代码就可能被优化掉。
同时,CommonJS 的 Tree Shaking 支持也有所进步。通过静态分析 exports 的赋值情况,部分 CommonJS 模块也能参与无用代码删除。虽然不如 ESM 那样彻底,但对于大量还在使用 CommonJS 的 npm 包来说,已经是一个不小的改善。
// Webpack 5 可以摇掉 innerFn 这类嵌套导出
export const utils = {
format(date) {
return date.toISOString();
},
innerFn() {
// 如果没有任何地方使用 utils.innerFn,这段代码会被移除
console.log("unused");
},
};此外,Webpack 5 还内置了对生成代码的优化:Module Id 和 Chunk Id 默认采用确定性算法生成,不再因为新增文件而导致所有模块 ID 变化,这对长期缓存非常友好。旧的 ModuleConcatenationPlugin、NamedModulesPlugin 等插件也被整合进核心配置,配置写法更加简洁。
资源模块:告别 file-loader 和 url-loader
Webpack 4 处理图片、字体等静态资源时,需要组合使用 file-loader 和 url-loader,配置繁琐且概念割裂。Webpack 5 引入了资源模块机制,通过一个 asset 模块类型就能搞定所有静态资源处理,不再需要额外的 Loader。
module.exports = {
module: {
rules: [
{
test: /\.png$/,
type: "asset", // 自动在大文件与 base64 之间做选择
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 小于 8KB 转为 base64 内联
},
},
},
{
test: /\.woff2$/,
type: "asset/resource", // 直接输出文件
},
],
},
};asset 类型会根据文件大小自动决定是内联为 Data URL 还是输出为独立文件,开发者可以通过 dataUrlCondition 精确控制这个阈值。这一变化不仅简化了配置,还消除了旧方案中 URL 拼接和 publicPath 处理上的一些边界问题。迁移老项目时,把原有的 file-loader 和 url-loader 规则替换成资源模块,是最容易完成、收益也最直接的一步。
升级迁移的注意事项
Webpack 5 要求 Node.js 10.13 以上版本,同时移除了大量废弃的 API。如果项目中使用了 copy-webpack-plugin、html-webpack-plugin 等第三方插件,需要升级到与之兼容的新版本。曾经被广泛使用的 hard-source-webpack-plugin 可以直接删除,用原生的持久化缓存替代。Node.js 的 polyfill 不再自动注入,如果代码里用到了 Buffer、process 这类全局对象,需要通过 ProvidePlugin 手动配置或者改用兼容浏览器版本的依赖包。
总体来看,Webpack 5 的更新围绕更快、更小、更灵活三个目标展开。持久化缓存改善的是开发体验,Tree Shaking 和确定性 ID 优化的是产物体积与缓存命中率,模块联邦则开创了应用间代码共享的新范式。对于新项目,建议直接从 Webpack 5 起步;对于存量项目,可以分阶段迁移,先完成 Loader 和插件的版本升级,再逐步启用新特性,把风险控制在可接受的范围内。