Webpack 5 在 2020 年发布后,并没有停留在对旧有 loader 和 plugin 的小修小补上,而是从构建内核、模块解析、缓存策略等多个维度重构了打包体验。社区中流传的 Skilled Universe 熟练宇宙这一说法,并不是官方文档里的某一个独立配置项,它更接近一种开发状态的描述:当你充分掌握 Webpack 5 的模块联邦、持久化缓存、资源模块等能力后,就可以像编排宇宙中的星体一样,把多个独立构建的前端应用组合成一个运行流畅的微前端体系。换句话说,熟练宇宙强调的不是某个新 API,而是 Webpack 5 赋予开发者在大型前端项目中自由组合模块、复用运行时依赖、缩短构建反馈周期的综合能力。

一、模块联邦:让多个构建产物像星系一样互相通信
模块联邦是 Webpack 5 最具影响力的特性之一。它允许一个 JavaScript 应用在运行时动态加载另一个应用暴露出来的模块,不需要将它们编译进同一个 bundle,也不需要依赖全局变量。这与传统的微前端方案相比,最大的区别在于它实现了运行时共享依赖和模块级联邦。例如,宿主应用可以通过 remoteEntry.js 发现远程应用暴露的按钮组件,并且共享 React、ReactDOM 等库,避免重复打包。
从 Skilled Universe 的视角看,每个独立构建的远程应用就像一个星系,模块联邦提供的是它们之间的引力通道。配置时,宿主应用通过 remotes 声明远程地址,远程应用通过 exposes 声明自己愿意共享的模块。以下是一个典型的宿主配置示例。
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
entry: './src/index.js',
mode: 'development',
devServer: { port: 3000 },
plugins: [
new ModuleFederationPlugin({
name: 'host_app',
remotes: {
remote_app: 'remote_app@http://localhost:3001/remoteEntry.js'
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true }
}
})
]
};
shared 字段中的 singleton 表示全局只允许存在一个共享实例,eager 则控制是否立即初始化。这种设计让多个应用可以共享同一份 React 运行时,大幅减少包体积,也避免了多个 React 实例带来的状态和上下文问题。
二、持久化缓存与资源模块:为熟练开发铺设基础设施
模块联邦解决的是应用间协作问题,而持久化缓存解决的是单个应用构建效率问题。Webpack 5 内置了文件系统缓存,只需一行 cache: { type: 'filesystem' },就能把构建结果缓存到 node_modules/.cache 目录。二次构建时,Webpack 会跳过未变化的模块,只重新编译变更部分,构建时间从分钟级压缩到秒级。对于需要频繁调试的大型项目,这种反馈速度的提升非常明显。
资源模块同样值得关注。Webpack 5 之前处理图片、字体、SVG 通常需要 file-loader、url-loader、raw-loader 等额外依赖,配置繁琐且容易冲突。Webpack 5 用 asset/resource、asset/inline、asset/source、asset 四类模块类型替代这些 loader,原生支持文件资源模块。例如一个图片文件可以直接通过 type: 'asset/resource' 打包。
module.exports = {
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource'
},
{
test: /\.svg$/,
type: 'asset/inline'
}
]
},
cache: {
type: 'filesystem'
}
};
这些基础设施共同构成了熟练开发体验的底层保障。当热更新速度足够快、静态资源无需额外插件就能处理时,开发者就可以把精力集中在业务逻辑和应用编排上,而不是消耗在构建配置调试中。
三、用 Webpack 5 构建一个最小化的熟练宇宙
为了更直观地感受 Skilled Universe,可以用两个独立应用做一个最小化微前端示例。假设 remote_app 远程应用暴露一个按钮组件,host_app 宿主应用在运行时加载它。远程应用需要配置 filename: 'remoteEntry.js' 和 exposes 字段。
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
entry: './src/index.js',
mode: 'development',
devServer: { port: 3001 },
plugins: [
new ModuleFederationPlugin({
name: 'remote_app',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button'
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true }
}
})
]
};
宿主应用启动后访问远程入口,通过动态 import 的方式加载远程模块。具体的加载方式可以结合 React.lazy 实现。
import React from 'react';
const RemoteButton = React.lazy(() => import('remote_app/Button'));
export default function App() {
return (
<div>
<h1>Host Application</h1>
<React.Suspense fallback={<div>Loading Button...</div>}>
<RemoteButton />
</React.Suspense>
</div>
);
}
实际运行前需要先启动远程应用,确保 remoteEntry.js 可访问,再启动宿主应用。由于模块联邦在运行时通过 HTTP 请求远程入口,需要处理跨域等开发环境配置。Webpack 的 devServer 默认允许跨域,因此可以较容易地运行。最后总结:熟练宇宙并非一蹴而就,需要理解模块联邦的 shared 策略、缓存失效规则、资源模块边界等细节。只有把这些底层机制掌握清楚,才能真正发挥 Webpack 5 的编排能力,构建出稳定、高效的前端微服务式架构。