最新 Webpack 的常规配置怎么写?
很多人一说 Webpack 配置,就开始堆 loader、堆 plugin、堆“黑魔法”。但面试官真正想听的,通常不是你会不会背几十个配置项,而是你能不能讲清楚:一个当前主流的 Webpack 5 项目,开发和生产分别应该怎么配,为什么这么配,哪些旧写法已经不值得再用了。
本文按 截至 2026-04 官方文档仍以 Webpack 5 为主线 来讲,示例使用最常见的 SPA 常规工程基线。如果你的项目是 SSR、组件库、Module Federation、Monorepo 或多入口场景,这套配置可以作为骨架继续扩展,但不要生搬硬套。
面试速答(30 秒版 TL;DR)
- 现在讲“最新 Webpack 常规配置”,默认讲 Webpack 5,而不是 Webpack 4 那套
file-loader/url-loader/raw-loader思路。 - 常规项目最好拆成
webpack.common+webpack.dev+webpack.prod,避免开发和生产目标混在一起。 - 必配核心项通常是:
mode、entry、output、resolve、module.rules、plugins、devtool、devServer、optimization、cache。 - 开发环境重点是:启动快、热更新快、定位问题快;生产环境重点是:包体可控、缓存稳定、Source Map 策略清晰。
- Webpack 5 里,静态资源优先用 Asset Modules;构建提速优先看 filesystem cache;拆包先从
splitChunks.chunks = 'all'起步,不要一上来就过度手写cacheGroups。
一、先把“常规配置”理解成三层
常规 Webpack 配置,不是一个大而全的 webpack.config.js,而是按职责拆:
这个拆法的价值是:
- 公共层只放跨环境都成立的事实,比如入口、输出目录、路径别名、JS 规则、图片字体规则。
- 开发层只解决本地开发效率问题,比如
devServer、快速 Source Map、样式热更新。 - 生产层只解决线上产物问题,比如 CSS 抽离、稳定 hash、拆包、运行时代码分离。
如果你把所有配置都揉在一个文件里,短期能跑,长期很难维护。
二、先给一套当前还能落地的基线配置
下面这套不是“最炫配置”,而是更接近企业项目里常见的 Webpack 5 基线。
2.1 webpack.common.cjs
如果你的项目使用 "type": "module",或者配置文件后缀是 .mjs,可以改成 import/export。这里先用兼容面更广的 CommonJS。
const path = require('node:path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: path.resolve(__dirname, 'src/main.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[name].[contenthash:8][ext][query]',
publicPath: '/',
clean: true,
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
module: {
rules: [
{
test: /\.[jt]sx?$/i,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.(png|jpe?g|gif|svg|webp|avif)$/i,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024,
},
},
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/index.html'),
}),
],
cache: {
type: 'filesystem',
},
stats: 'errors-warnings',
};
2.2 webpack.dev.cjs
const {merge} = require('webpack-merge');
const path = require('node:path');
const common = require('./webpack.common.cjs');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
],
},
devServer: {
static: {
directory: path.resolve(__dirname, 'public'),
},
port: 3000,
open: true,
hot: true,
compress: true,
historyApiFallback: true,
client: {
overlay: true,
},
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
});
2.3 webpack.prod.cjs
const {merge} = require('webpack-merge');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const common = require('./webpack.common.cjs');
module.exports = merge(common, {
mode: 'production',
devtool: 'hidden-source-map',
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
}),
],
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
},
minimizer: ['...', new CssMinimizerPlugin()],
},
});
三、这套配置为什么是“常规基线”?
3.1 mode:不是装饰项,而是默认策略开关
很多人把 mode 当成“顺手一写”,这是不对的。
它背后会影响很多默认行为,比如:
- 是否压缩
- 是否启用更激进的优化
- 是否更偏向调试体验
- 某些缓存和性能相关默认值
面试里一句话要讲清楚:
development追求构建反馈速度和可调试性production追求产物体积、运行性能和缓存稳定性
3.2 entry + output:定义输入和产物边界
entry 很好理解,就是从哪开始构建。
output 里常规项目最该讲的不是 path,而是这几个点:
filename/chunkFilename用[contenthash],是为了让浏览器缓存更稳定assetModuleFilename是 Webpack 5 里统一管理静态资源输出路径的关键位clean: true可以在每次构建前清理旧产物publicPath: '/'对前端路由和静态资源基路径很重要
很多线上 404、资源路径错乱、发布后缓存不生效,本质都和这一层有关。
3.3 resolve:减少导入噪声,统一工程语义
常规项目里,resolve 主要解决两类问题:
- 路径别名
例如
@/components/Button - 自动补全扩展名
避免每次都写
.js、.ts、.tsx
它不是性能优化主角,但对代码可维护性很重要。
3.4 module.rules:Webpack 5 时代先记住“资源优先走 Asset Modules”
Webpack 5 的一个明显变化是:静态资源不再优先依赖 file-loader / url-loader / raw-loader。
现在更常规的思路是:
- 图片:
type: 'asset' - 字体:
type: 'asset/resource' - 文本原始内容:按需用
asset/source
为什么这算“最新常规配置”?
因为 Webpack 5 已经把这套能力内建进模块系统了。你继续沿用老 loader 不是不能跑,但从认知上已经不属于当前最推荐的基础写法。
3.5 CSS 为什么开发用 style-loader,生产用 MiniCssExtractPlugin?
这是非常经典的面试点。
开发环境:
- 更关注热更新快
- 样式直接注入页面更方便
- 调试链路更短
生产环境:
- 更关注 CSS 独立缓存
- 更关注首屏资源加载策略
- 更适合把 CSS 抽成单独文件
所以:
- 开发用
style-loader - 生产用
MiniCssExtractPlugin
这不是“两个都能用,随便选”,而是目标不同。
3.6 devServer:只解决开发期体验,不参与正式打包
常规配置里,devServer 高概率会写这些:
static:本地静态目录port:端口open:启动后自动打开hot:热更新historyApiFallback:SPA 刷新路由不 404proxy:把/api代理到后端服务client.overlay:把编译错误直接打到页面上
这里的高频误区是:
- 误区:
devServer会影响生产环境。 不会。它是开发服务器行为配置,不是线上服务配置。
3.7 devtool:不要用一句“开 source map”糊弄过去
devtool 的本质是 调试精度、构建速度、源码暴露程度 的平衡。
常规项目里可以这样回答:
- 开发环境常用
eval-source-map质量高,适合本地调试 - 生产环境如果只给错误监控平台用,常用
hidden-source-map生成 map,但不把引用暴露给浏览器 - 如果你不打算保留线上 Source Map,可以直接
false
真正稳的表达不是背某一个字符串,而是能解释:为什么开发和生产不能共用一套 Source Map 策略。
3.8 cache:Webpack 5 里最值得主动开的常规提速项之一
很多人只盯 Loader 数量,却忘了 Webpack 5 现在有比较成熟的持久化缓存能力。
常规项目里,把:
cache: {
type: 'filesystem',
}
作为基线是合理的,因为它能显著改善二次构建速度。
如果你继续往前答,可以补一句:
- 当配置文件本身变化时,最好补
buildDependencies.config - 多配置场景下,可以给不同构建定义不同 cache name
3.9 optimization:先做稳定拆包,再谈精细策略
常规项目里,最常见也最稳的一步是:
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
},
}
它解决的是:
- 运行时代码和业务代码解耦
- 公共依赖不要重复打进多个 chunk
- 浏览器缓存命中率更稳定
面试里有个很容易加分的点:
- 先用通用拆包基线
- 再根据打包分析结果决定要不要细拆
cacheGroups
一上来就手写一堆 vendors、commons、react、chart、editor 分组,不一定更专业,很多时候只是把缓存策略复杂化了。
四、常规项目里,哪些配置现在已经不算“首选”?
4.1 不要把 Webpack 5 还讲成 file-loader / url-loader 时代
如果面试官问“图片怎么处理”,你第一反应还是:
file-loaderurl-loaderraw-loader
那你的知识点大概率停在 Webpack 4。
Webpack 5 常规回答应该优先讲:
asset/resourceasset/inlineasset/sourceasset
4.2 不要把所有环境都放进一个配置文件里
单文件大配置的常见问题是:
- 条件分支越来越多
- 可读性越来越差
- 不同环境互相污染
- 改动风险变大
所以常规工程实践才会强调 common/dev/prod 拆分。
4.3 不要把 splitChunks 当成“随便拆越多越好”
拆得太碎会带来:
- 请求数增加
- chunk 关系复杂
- 缓存命中策略更难推理
- 首屏链路反而变长
Webpack 优化不是“越多配置越强”,而是“刚好够用”。
4.4 不要把前端密钥硬塞进 DefinePlugin
前端里能被打包进去的变量,本质都能被用户拿到。
所以你可以注入:
- 接口基地址
- 构建标识
- feature flag
但不要注入:
- 私有 token
- 数据库密码
- 服务端密钥
这是工程边界问题,不是 Webpack 语法问题。
五、典型题 & 标准答法
Q1:最新 Webpack 常规配置里,最核心的几项是什么?
可以按“输入、转换、输出、开发体验、生产优化”来答:
- 输入:
entry - 输出:
output - 转换:
module.rules - 扩展:
plugins - 开发:
devServer、devtool - 生产:
optimization、MiniCssExtractPlugin - 提速:
cache.filesystem
这样答比单纯背配置项更有结构。
Q2:为什么要把开发和生产配置拆开?
因为两者目标冲突。开发追求反馈快、热更新快、调试友好;生产追求体积小、缓存稳定、资源可控。如果强行共用一套,最后通常是谁都照顾不好。
Q3:Webpack 5 里图片和字体常规怎么配?
优先用 Asset Modules。图片通常用 type: 'asset',小图转 data URI、大图输出文件;字体通常用 type: 'asset/resource',直接发射到输出目录。
Q4:生产环境为什么常配 runtimeChunk: 'single'?
因为这样能把运行时代码单独拆出去,减少业务 chunk 因 runtime 变化而一起失效,有利于长期缓存。
Q5:线上 Source Map 应该怎么配?
看你的目标:
- 只想最快构建:
devtool: false - 要给错误监控平台做堆栈还原:
hidden-source-map - 要保留完整线上调试能力:
source-map
但后两者都要考虑源码暴露和部署策略。
六、常见追问
6.1 contenthash、chunkhash、hash 有什么区别?
面试里常规项目优先回答:
- 现在最常用的是
contenthash - 它更贴近文件内容变化,有利于长期缓存
不要把重点放在死记历史细节,而是讲清楚为什么现代项目更偏向 contenthash。
6.2 babel-loader 和 Babel 是什么关系?
- Babel 是实际做语法转换的编译器
babel-loader是 Webpack 接入 Babel 的桥梁
Loader 负责接进 Webpack,Babel 负责转换代码。
6.3 devServer.proxy 和 Nginx 反向代理是什么关系?
devServer.proxy解决的是本地开发联调- Nginx 反向代理解决的是线上流量入口和转发
前者是开发期工具能力,后者是正式部署能力,不能混为一谈。
七、易错点 / 坑
-
坑 1:开发也抽离 CSS。 通常会让 HMR 体验变差,调试收益也不大。
-
坑 2:生产还用很重的内联 Source Map。 容易让产物膨胀,还可能暴露源码。
-
坑 3:长期缓存只开
[contenthash],却不拆 runtime。 这样缓存稳定性往往还不够理想。 -
坑 4:继续沿用老资源 loader,又没处理好重复发射。 在 Webpack 5 里容易把资源链路弄得更混乱。
-
坑 5:把构建慢全部归因于 Webpack。 很多时候真正慢的是 Babel、PostCSS、大量无效依赖、过度拆包、Source Map 策略太重。
八、速记要点(可背诵)
- 最新常规 Webpack 配置,默认讲 Webpack 5
- 配置拆分:common / dev / prod
- 静态资源:优先 Asset Modules
- 开发样式:
style-loader - 生产样式:
MiniCssExtractPlugin - 开发调试:高质量、快反馈的
devtool - 生产调试:按是否需要线上还原堆栈决定
hidden-source-map/source-map/false - 常规提速:
cache.type = 'filesystem' - 常规拆包:
splitChunks.chunks = 'all'+runtimeChunk = 'single' - 核心原则:开发快、生产稳、缓存准、资源路径清晰