Webpack 配置速查表 - 前端构建工具常用配置大全
Webpack 的能力是真实的,而这份能力要么在配置里成全你、要么在配置里藏起来。本表覆盖入口/输出、各类资源的 Loader、插件、dev-server/HMR、代码分割与打包优化、mode/environment。新资源类型加载不上、HMR 失效、或 bundle 莫名巨大时最有用。不会只丢给你一段样板配置,而是指到能解决问题的那个 flag 或插件。读完能在不啃完整文档的前提下配置并调优一次构建。
典型使用场景
面向需要把前端工程化从零搭起来或接手已有构建配置的开发者。webpack 的难点不在命令本身,而在配置之间的耦合:entry 决定产物粒度,output.publicPath 决定运行时去哪儿加载 chunk,splitChunks 与 runtimeChunk 共同决定缓存命中率和首屏体积,devServer.proxy 又决定本地开发时请求能不能打到后端。本表按「入口与输出 → Loader → Plugin → 开发服务器 → 优化 → 模式与环境 → 构建分析」把这七个环节的常用配置排好序,每条都配了可直接粘贴的示例,并在易错点里集中了 contenthash 反复变化、Loader 顺序反了、Tree Shaking 不生效、代理写错路径这几类高频问题。
入口与输出 6
entry: "./src/index.js"entry: { app: "./src/app.js", vendor: "./src/vendor.js" }output.filename: "[name].[contenthash].js"output.path: path.resolve(__dirname, "dist")output.publicPath: "/assets/"output.clean: trueLoader 配置 8
{ test: /\.css$/, use: ["style-loader", "css-loader"] }{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"] }{ test: /\.js$/, exclude: /node_modules/, use: "babel-loader" }{ test: /\.tsx?$/, use: "ts-loader" }{ test: /\.(png|jpg|gif)$/, type: "asset/resource" }{ test: /\.svg$/, type: "asset/source" }{ test: /\.(woff2?|ttf|eot)$/, type: "asset/resource" }{ test: /\.(js|ts)$/, enforce: "pre", use: "source-map-loader" }Plugin 插件 7
new HtmlWebpackPlugin({ template: "./src/index.html" })new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })new DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") })new CopyWebpackPlugin({ patterns: [{ from: "public", to: "." }] })new ProvidePlugin({ _: "lodash" })new HotModuleReplacementPlugin()new BannerPlugin({ banner: "Built at " + new Date().toISOString() })开发服务器 6
devServer.hot: truedevServer.historyApiFallback: truedevServer.port: 5000, open: truedevServer.static: path.join(__dirname, "public")devServer.proxy: { "/api": "http://localhost:3000" }devServer.proxy: { "/api": { target: "http://localhost:3000", pathRewrite: { "^/api": "" } } }优化配置 7
optimization.splitChunks: { chunks: "all" }optimization.minimize: trueoptimization.minimizer: [new TerserPlugin({ extractComments: false })]optimization.runtimeChunk: "single"import("./module").then(m => m.default)/* webpackChunkName: "vendor" *//* webpackPrefetch: true */模式与环境 6
mode: "production" | "development" | "none"webpack --config webpack.prod.js --mode productionmodule.exports = (env, argv) => ({ mode: argv.mode })devtool: argv.mode === "development" ? "eval-cheap-source-map" : "source-map"const { merge } = require("webpack-merge"); merge(common, prodConfig)new webpack.EnvironmentPlugin(["NODE_ENV", "API_URL"])构建分析 5
new BundleAnalyzerPlugin({ analyzerMode: "static" })stats: "minimal" | "normal" | "detailed"performance: { hints: "warning", maxAssetSize: 250000 }webpack --json > stats.jsondevtool: "source-map" | "eval-cheap-module-source-map"参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
entry | 指定打包入口,可写字符串或映射为多入口 | entry: { app: "./src/app.js", vendor: "./src/vendor.js" } |
output.filename: "[name].[contenthash].js" | 输出文件名带内容 hash,利于长期缓存 | output.filename: "[name].[contenthash].js" |
output.path | 输出目录,需绝对路径 | output.path: path.resolve(__dirname, "dist") |
output.publicPath | 运行时加载 chunk/资源的公共路径前缀 | output.publicPath: "/assets/" |
output.clean: true | 构建前自动清理 dist 目录(Webpack 5 内置) | output.clean: true |
mode | 内置模式:production / development / none | mode: "production" |
devtool | 控制 source map 类型,影响构建速度与调试质量 | devtool: "source-map" |
{ test: /\.css$/, use: [...] } | 按扩展名匹配并指定 Loader 链 | { test: /\.css$/, use: ["style-loader", "css-loader"] } |
MiniCssExtractPlugin.loader | 把 CSS 抽离为独立文件而非内联注入 | { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"] } |
exclude: /node_modules/ | 排除不处理的目录,跳过第三方代码 | { test: /\.js$/, exclude: /node_modules/, use: "babel-loader" } |
type: "asset/resource" | Webpack 5 资源模块:输出为独立文件并返回 URL | { test: /\.(png|jpg|gif)$/, type: "asset/resource" } |
type: "asset/source" | 资源模块:把文件内容作为字符串内联 | { test: /\.svg$/, type: "asset/source" } |
enforce: "pre" | 把 Loader 提前到普通 Loader 之前执行 | { test: /\.(js|ts)$/, enforce: "pre", use: "source-map-loader" } |
HtmlWebpackPlugin({ template }) | 按模板自动生成 HTML 并注入产物引用 | new HtmlWebpackPlugin({ template: "./src/index.html" }) |
MiniCssExtractPlugin({ filename }) | 配合抽离 Loader 输出带 hash 的 CSS 文件 | new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }) |
DefinePlugin | 把常量在编译期替换进产物,用于注入环境判断 | new DefinePlugin({ ... }) |
EnvironmentPlugin([...]) | 批量把环境变量注入为全局常量 | new EnvironmentPlugin(["NODE_ENV", "API_URL"]) |
CopyWebpackPlugin({ patterns }) | 把不需要编译的静态文件原样复制到产物目录 | new CopyWebpackPlugin({ patterns: [{ from: "public", to: "." }] }) |
ProvidePlugin | 自动注入模块引用,无需逐处 import | new ProvidePlugin({ _: "lodash" }) |
BannerPlugin({ banner }) | 给输出文件加头部注释 | new BannerPlugin({ banner: "Built at " + new Date().toISOString() }) |
devServer.hot: true | 开启热模块替换,改代码不刷新页面 | devServer.hot: true |
devServer.historyApiFallback: true | SPA history 路由 404 时回退到 index.html | devServer.historyApiFallback: true |
devServer.port / open | 指定开发服务器端口并自动打开浏览器 | devServer.port: 5000, open: true |
devServer.static | 指定静态资源服务目录 | devServer.static: path.join(__dirname, "public") |
devServer.proxy: string | 把前缀请求原样代理到后端 | devServer.proxy: { "/api": "http://localhost:3000" } |
devServer.proxy: { target, pathRewrite } | 代理并重写请求路径 | devServer.proxy: { "/api": { target: "http://localhost:3000", pathRewrite: { "^/api": "" } } } |
optimization.splitChunks: { chunks: "all" } | 自动把公共代码切成共享 chunk | optimization.splitChunks: { chunks: "all" } |
optimization.runtimeChunk: "single" | 抽离运行时代码到单独文件,减少主包变更 | optimization.runtimeChunk: "single" |
optimization.minimizer | 自定义压缩器配置 | optimization.minimizer: [new TerserPlugin({ extractComments: false })] |
import("./module").then(...) | 动态导入,按需加载实现代码分割 | import("./module").then(m => m.default) |
/* webpackChunkName: "vendor" */ | 魔法注释给动态导入的 chunk 命名 | /* webpackChunkName: "vendor" */ |
/* webpackPrefetch: true */ | 魔法注释在空闲时预取懒加载资源 | /* webpackPrefetch: true */ |
webpack-merge 的 merge() | 合并通用配置与环境配置的深拷贝结果 | const { merge } = require("webpack-merge"); merge(common, prodConfig) |
function (env, argv) => ({...}) | 函数式配置,可读取 argv.mode 做差异化 | module.exports = (env, argv) => ({ mode: argv.mode }) |
stats | 控制构建日志详略 | stats: "minimal" |
performance: { hints, maxAssetSize } | 资源体积超限时给出性能提示 | performance: { hints: "warning", maxAssetSize: 250000 } |
BundleAnalyzerPlugin({ analyzerMode }) | 生成静态体积分析报告 | new BundleAnalyzerPlugin({ analyzerMode: "static" }) |
易错点与避坑指南
现象文件名里的 hash 每次构建都变,CDN 长期缓存完全失效。
原因contenthash 算的是模块内容,但产物里被注入了随构建变化的内容(如构建时间 Banner),或 contenthash 粒度配置让运行时模块变更牵动全部文件。
处置去掉随构建变化的注入内容,BannerPlugin 改用固定字符串或只在需要时启用;保持 output.runtimeChunk: "single" 隔离运行时。
现象样式写完页面完全不生效,且控制台无报错。
原因Loader 链执行顺序是从右到左,顺序写反了;或该扩展名没有任何 Loader 命中,CSS 被当成普通资源。
处置按「从右到左」检查 use 数组:css-loader 负责解析,style-loader 或 MiniCssExtractPlugin.loader 负责输出,两者位置不能颠倒。
现象生产构建里没用的函数仍然被打进包里,Tree Shaking 没生效。
原因代码用的是 CommonJS 的 module.exports / require,webpack 只能做静态分析,无法判定哪些导出未被使用。
处置把被摇树的模块改写成 ESM 的 import/export,production 模式才会真正裁掉未引用代码。
现象本地刷新页面正常,部署后路由页面白屏或资源 404。
原因output.publicPath 与实际部署路径不一致,运行时按错误前缀去加载 chunk 和静态资源。
处置把 publicPath 显式设为站点真实前缀(如 "/assets/"),并给动态导入的 chunk 用 /* webpackChunkName */ 固定命名便于定位。
现象本地开发时 /api/xxx 请求 404,而直接 curl 后端是通的。
原因devServer.proxy 没配或没覆盖该前缀,请求被 dev server 当作静态资源处理。
处置配置 devServer.proxy 并在需要时用 pathRewrite 去掉前缀:{ "/api": { target: "http://localhost:3000", pathRewrite: { "^/api": "" } } }。
现象主包体积只增不减,改一行业务代码后整个 JS 全部失效重下。
原因所有依赖都打进了同一个入口文件,缺少 splitChunks 与 runtimeChunk 拆分,任何改动都会改变主文件内容。
处置加 optimization.splitChunks: { chunks: "all" } 与 optimization.runtimeChunk: "single",把第三方库与运行时抽成长期稳定的独立文件。
现象dist 目录里堆着历次构建的旧文件,线上访问到了过期资源。
原因没有开启产物目录清理,旧 hash 文件一直残留。
处置开启 output.clean: true(Webpack 5 内置),构建前自动清空输出目录。
现象打包产物体积排查全靠猜,始终不知道哪个依赖最重。
原因构建日志用了 minimal 之类的精简档位,没有产物粒度的体积数据。
处置接入 new BundleAnalyzerPlugin({ analyzerMode: "static" }) 生成静态报告,或用 webpack --json > stats.json 导出统计后逐层下钻。
排障路径
1导出构建统计用于体积分析
npx webpack --json > stats.json生成 stats.json 后可逐 chunk 查看模块构成,定位体积大头。
2生成静态体积分析报告
new BundleAnalyzerPlugin({ analyzerMode: "static" })在 plugins 中加入后重新构建,会产出一份可离线打开的 HTML 报告。
3按模式分别指定配置与 source map 策略
npx webpack --config webpack.prod.js --mode production控制台应输出 production 模式的构建摘要,devtool 走函数式配置里 argv.mode 的分支。
4打开精简构建日志快速看整体结果
stats: "minimal"仅打印错误与警告及产物列表,构建结果一眼可见。
5开启资源体积告警定位超大产物
performance: { hints: "warning", maxAssetSize: 250000 }超过 250000 字节的产物会在构建日志里给出 warning。
6核对运行时资源公共路径
output.publicPath: "/assets/"部署后若资源 404,先确认这个前缀与站点真实路径一致,再检查 HTML 里的引用。
命令示例
生产配置骨架:contenthash + clean + HTML 插件
module.exports = {
mode: "production",
entry: { app: "./src/app.js" },
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[contenthash].js",
publicPath: "/assets/",
clean: true,
},
plugins: [new HtmlWebpackPlugin({ template: "./src/index.html" })],
}[contenthash] 只在文件内容变化时改变,配合 output.clean 清掉过期产物,才是长缓存策略的前提;publicPath 必须与部署路径一致,否则运行时所有资源 404。
CSS 抽离成独立文件 + 公共代码分割
module: {
rules: [
{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"] },
],
},
plugins: [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })],
optimization: {
splitChunks: { chunks: "all" },
runtimeChunk: "single",
}内联 style-loader 的 CSS 会被塞进 JS 并阻塞渲染,抽离后浏览器能并行下载 CSS 并提前解析;runtimeChunk 单抽一份,避免每个 chunk 重复带运行时。
devServer:SPA 回退 + 接口代理并去掉 /api 前缀
devServer: {
port: 5000,
hot: true,
historyApiFallback: true,
static: path.join(__dirname, "public"),
proxy: {
"/api": {
target: "http://localhost:3000",
pathRewrite: { "^/api": "" },
},
},
}前端用相对路径 /api 调后端,同源问题一并消失;pathRewrite 决定后端看到的路径,前后端口不一致时少了这一步就是 404。historyApiFallback 负责刷新深层路由。
路由懒加载:动态导入 + 魔法注释命名与预取
const Settings = () =>
import(/* webpackChunkName: "settings" */ /* webpackPrefetch: true */ "./pages/Settings")每个动态 import 会被打成独立 chunk,路由切换时才下载;webpackChunkName 让产物名可读,webpackPrefetch 在空闲时预取,首屏只付主包代价。
按模式切换 source map,导出 stats 供体积分析
module.exports = (env, argv) => ({
mode: argv.mode,
devtool: argv.mode === "development" ? "eval-cheap-source-map" : "source-map",
})
// CLI: webpack --config webpack.config.js --json > stats.json开发用 eval-cheap-source-map 换取重建速度,生产用 source-map 换可定位的堆栈;两者取舍不同所以要按模式切换。stats JSON 可直接喂给体积分析工具找大模块。
常见坑与注意事项
- output.filename 用 [hash] 而不是 [contenthash]:任一模块变化都会改掉整个 hash,用户缓存全部失效,回访等于重新下载整站。
- devtool 用 "eval-*" 变体产出的代码必须靠 eval 执行;页面配了 CSP 且禁用 unsafe-eval 时整站脚本全挂,症状是白屏而不是构建报错,排查成本极高。
- 生产把 source-map 直接部署到线上等于公开可还原的业务源码;正确做法是上传到错误监控平台并在配置里隐藏 sourceMap URL,或仅在内网保留。
- 不写 mode 时 Webpack 默认为 production:本地调试会走压缩与 tree-shaking,报错堆栈是一行压缩后的代码,开发体验与线上表现同时失真。
- output.publicPath 与实际部署路径不一致(挂在子目录、CDN 域名变更)时,构建成功但页面所有静态资源 404,首屏白屏且控制台只有资源加载错误。
- DefinePlugin 注入整个 process.env 对象会连同 process 这个标识符一起被替换,依赖 process.env 下的 NODE_ENV 做环境分支的库(以及 polyfill 检测代码)会静默走错分支。
提示
- Webpack 5 内置了 asset modules,替代了 file-loader 和 url-loader。
- dev-server 的 hot: true 开启 HMR 热更新,开发体验更好。
- splitChunks 可以按 vendor、common 等策略分割代码,减少首屏加载体积。
- webpack-bundle-analyzer 可以可视化分析打包体积。
- production 模式自动开启 Tree Shaking,需使用 ESM(import/export)才能生效。
- [name].[contenthash].js 实现长期缓存,文件内容不变时 hash 不变,命中浏览器缓存。
常见问题
文件名里加 [contenthash] 到底解决了什么问题?
长期缓存。output.filename: "[name].[contenthash].js" 让产物名随内容变化:内容不变的 chunk hash 也不变,浏览器可以安心用强缓存;内容一变 hash 变、URL 变,用户自动拿到新文件,无需清缓存也不会拿到旧资源。相对地,固定名如 bundle.js 会让浏览器一直复用旧文件。配套设置有三项:output.path 必须给绝对路径(path.resolve(__dirname, "dist")),output.publicPath: "/assets/" 决定引用前缀,output.clean: true 让 Webpack 5 在构建前清空 dist,避免旧 hash 文件堆积成垃圾。CSS 侧同理用 MiniCssExtractPlugin 的 [name].[contenthash].css。
style-loader 和 MiniCssExtractPlugin.loader 什么时候换?
按「CSS 是运行时注入还是产出独立文件」选。开发期用 { test: /\.css$/, use: ["style-loader", "css-loader"] },style-loader 把 CSS 内联到 DOM,配合 devServer.hot: true 能做到样式热替换,改完立刻生效且不需要额外网络请求。生产期换成 { test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"] },抽离成独立文件并接上 PostCSS,再用 new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" }) 决定命名。判断依据很简单:内联方式无法利用浏览器缓存、首屏还会闪一下白,所以构建产物一律要抽离。
Webpack 5 里图片和字体怎么处理,还需要 file-loader 吗?
不需要,asset modules 已经内置并取代了 file-loader 和 url-loader。图片用 { test: /\.(png|jpg|gif)$/, type: "asset/resource" },产出独立文件并返回 URL;SVG 若要当字符串用用 { test: /\.svg$/, type: "asset/source" };字体同理 { test: /\.(woff2?|ttf|eot)$/, type: "asset/resource" }。另有一条 { test: /\.(js|ts)$/, enforce: "pre", use: "source-map-loader" },用 pre 阶段加载依赖包已有的 source map,这样压缩后的代码栈也能还原到原始 TS。静态目录整体搬运交给 new CopyWebpackPlugin({ patterns: [{ from: "public", to: "." }] })。
前端路由一刷新就 404,接口跨域又报错,怎么配 devServer?
两个问题分别对应两个选项。historyApiFallback: true 把 SPA 的 history 路由在找不到文件时回退到 index.html,于是直接访问 /users/123 也能由前端路由接管,刷新不再 404。跨域则用 proxy 转发:{ "/api": "http://localhost:3000" } 已能解决大部分;需要改路径时用对象形式 { "/api": { target: "http://localhost:3000", pathRewrite: { "^/api": "" } } },把前缀剥掉再发给后端,这样后端不用为开发环境加 /api 前缀。其它常用项:devServer.hot: true 开 HMR,devServer.port: 5000, open: true 指定端口并自动开浏览器,devServer.static 指向 public 目录。
首屏包太大,怎么拆包和懒加载?
四条配置一起上。optimization.splitChunks: { chunks: "all" } 自动把公共代码从各入口切出来;optimization.runtimeChunk: "single" 把 webpack runtime 抽成单独文件,避免每个 chunk 重复携带;optimization.minimize: true 开启压缩,需要自定义时用 optimization.minimizer: [new TerserPlugin({ extractComments: false })]。路由级懒加载靠动态导入 import("./module").then(m => m.default),配合魔法注释 /* webpackChunkName: "vendor" */ 命名 chunk、/* webpackPrefetch: true */ 在空闲时预取。生产模式自动开启 Tree Shaking,但只有用 ESM 的 import/export 才能真正生效。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。