默认情况下,webpack 会把所有产物都输出到 dist 根目录下,文件名也比较随意。项目一旦变大,dist 里就会堆满 JS、CSS、图片、字体,难以区分和管理。本文介绍如何通过 output 相关配置,让构建产物按类型分目录,结构更清晰。
一、先认识默认行为
webpack 默认输出目录是 dist,入口产物名是 main.js:
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js',
},
};
如果配置了多入口、代码分割、样式抽离、图片资源等,产物会全部平铺在 dist 下:
dist/
├── main.js
├── app.js
├── 1.js # 异步 chunk
├── main.css
├── a1b2c3.png
└── d4e5f6.woff2
问题很明显:分不清哪些是 JS、哪些是 CSS、哪些是资源,缓存和部署也不方便。
二、核心:output.path 与 output.filename
一切从这两个字段开始:
const path = require('path');
module.exports = {
output: {
// 产物根目录,必须是绝对路径
path: path.resolve(__dirname, 'dist'),
// 入口产物的文件名,可用 [name] 区分多入口
filename: 'js/[name].[contenthash:8].js',
// 每次构建前清空 output.path
clean: true,
},
};
关键点:
- path:只负责「根目录在哪」,无法指定子目录。
- filename:可以带路径前缀,
js/[name].js就等于把 JS 放进dist/js/。这是让结构清晰的第一招。 - clean:配合结构调整使用,避免旧目录残留。
其中 [name] 是 entry 的 key,[contenthash] 随文件内容变化,适合做持久化缓存。
三、异步 chunk 与 runtime 的目录
代码分割产生的异步 chunk、以及运行时代码,各有独立字段:
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
// 异步加载的 chunk
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
},
optimization: {
// 把 webpack 运行时单独抽出
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
// 把第三方依赖拆到 vendors 目录
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: -10,
},
},
},
},
};
splitChunks 中通过 name 控制拆出的包名(如 vendors),再配合 filename / chunkFilename 的前缀,就能把第三方依赖、公共模块、运行时代码分别归到 js/ 下的不同文件,方便分析体积。
四、CSS 产物的目录
用 MiniCssExtractPlugin 抽离样式时,它有自己的 filename 和 chunkFilename:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
// 入口样式
filename: 'css/[name].[contenthash:8].css',
// 异步 chunk 对应样式
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
}),
],
};
这样 CSS 就统一进入 dist/css/,与 dist/js/ 分离。
五、图片、字体等资源目录
webpack 5 的 asset modules 通过 generator.filename 控制输出路径:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
type: 'asset',
parser: {
dataUrlCondition: { maxSize: 8 * 1024 },
},
generator: {
filename: 'images/[hash][ext][query]',
},
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: {
filename: 'fonts/[hash][ext][query]',
},
},
],
},
};
也可以设置一个全局兜底:
module.exports = {
output: {
assetModuleFilename: 'assets/[hash][ext][query]',
},
};
规则里单独写 generator.filename 的优先级更高,适合把图片、字体分得更细;assetModuleFilename 则作为默认值兜底。
六、publicPath:产物引用路径
调整目录后,要注意 HTML 里引用产物的路径是否正确。output.publicPath 决定运行时拼接资源 URL 的前缀:
module.exports = {
output: {
publicPath: '/', // 部署在域名根路径
// publicPath: '/assets/', // 部署在子目录
// publicPath: 'https://cdn.example.com/', // 走 CDN
},
};
如果目录结构变了却发现图片、异步 chunk 404,十有八九是 publicPath 没配对。
七、devServer 与多目录
开发服务器同样要指向输出目录。若使用 devServer.static,注意它的目录要和 output.path 对齐:
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js',
},
devServer: {
static: path.resolve(__dirname, 'dist'),
port: 8080,
},
};
更推荐用 HtmlWebpackPlugin 自动生成 HTML 并注入正确路径,省去手工维护 public/index.html 里 src 的麻烦。
八、完整目录规划示例
综合以上配置,最终产物结构可以是:
dist/
├── index.html
├── js/
│ ├── main.a1b2c3d4.js
│ ├── vendors.e5f6a7b8.js
│ ├── runtime.c9d0e1f2.js
│ └── 42.chunk.js
├── css/
│ └── main.11223344.css
├── images/
│ └── a1b2c3d4.png
└── fonts/
└── e5f6a7b8.woff2
对应的关键配置:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
assetModuleFilename: 'assets/[hash][ext][query]',
publicPath: '/',
clean: true,
},
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
generator: { filename: 'images/[hash][ext][query]' },
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
type: 'asset/resource',
generator: { filename: 'fonts/[hash][ext][query]' },
},
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].chunk.css',
}),
],
};
小结
调整产物目录的核心,就是给不同来源的产物分别指定「路径前缀」:
| 产物类型 | 控制字段 |
|---|---|
| 入口 JS | output.filename |
| 异步 chunk | output.chunkFilename |
| 抽离的 CSS | MiniCssExtractPlugin.filename / chunkFilename |
| 图片 / 字体 | generator.filename / output.assetModuleFilename |
| 全局引用前缀 | output.publicPath |
把这些字段按类型规划好,再配合 clean 和 HtmlWebpackPlugin,就能得到清晰、稳定、便于缓存和部署的产物结构。