第4章 Webpack 核心配置:entry、output、loader、plugin 的实战用法
如果你用过 Webpack,一定对这四个词不陌生:entry、output、loader、plugin。 说白了,它们就是 Webpack 打包的四根柱子。你只要把这四个东西搞明白,剩下的配置基本都是排列组合。
我个人习惯把 Webpack 想象成一条生产线——entry 是原材料入口,loader 是加工工序, plugin 是质检包装环节,output 是成品仓库。嗯,这个比喻不算严谨,但好记。
4.1 entry —— 打包的起点
entry 就是告诉 Webpack:"从哪个文件开始干活。" 所有依赖关系都从这里往外捋。有三种常见写法,我直接列出来:
- 单入口(字符串):适合简单 SPA 或单页应用。
- 多入口(对象):适合多页面应用,或需要分离 vendor 的场景。
- 动态入口(函数):适合按需加载或条件判断的场景,用得相对少一些。
// 单入口 —— 最常见
module.exports = {
entry: './src/index.js'
};
// 多入口 —— 我项目中常用,比如拆分管理后台和前台
module.exports = {
entry: {
main: './src/index.js',
admin: './src/admin.js',
vendor: ['react', 'react-dom']
}
};
// 动态入口 —— 返回一个 Promise 或字符串
module.exports = {
entry: () => './src/index.js'
};
我在项目中遇到过一个问题:多入口打包时,忘了给每个入口配独立的 output.filename,
结果所有文件都叫 bundle.js,后面的直接把前面的覆盖了。
嗯,从那以后我再也不偷懒了。
4.2 output —— 打包的归宿
output 控制打包结果往哪放、叫什么名字。核心就三个字段,我挨个说。
- filename:输出文件的命名规则。
- path:输出目录的路径。
- publicPath:静态资源在 HTML 中引用的基础路径。
const path = require('path');
module.exports = {
output: {
// 我建议加上 contenthash,方便缓存控制
filename: 'js/[name].[contenthash:8].js',
// 输出到 dist 目录
path: path.resolve(__dirname, 'dist'),
// 如果部署到 CDN,这里填 CDN 地址
publicPath: '/'
}
};
publicPath,
结果所有图片和字体都报了 404。因为打包后的资源路径是相对路径,而部署时静态资源放在了 CDN 子目录下。
记住:publicPath 不是写给本地看的,是写给服务器看的。
filename 里的 [contenthash] 是我个人最看重的配置。
为什么?因为只要文件内容不变,hash 就不变,浏览器就能放心用缓存。
你想想,如果每次部署文件名都一样,用户还得重新下载整个包,多亏。
4.3 loader —— 让 Webpack 理解所有文件
Webpack 原生只认识 JS 和 JSON。遇到 CSS、图片、字体、TypeScript 怎么办? 靠 loader。loader 就是一个"翻译官",把非 JS 文件转成 Webpack 能处理的模块。
常用 loader 我整理了一份:
| 文件类型 | 常用 loader | 说明 |
|---|---|---|
| JavaScript / JSX | babel-loader |
转译 ES6+ 和 React JSX |
| TypeScript | ts-loader 或 babel-loader |
我推荐用 babel-loader + @babel/preset-typescript |
| CSS | css-loader + style-loader |
css-loader 解析 import,style-loader 注入页面 |
| 图片 / 字体 | url-loader 或 asset module |
小图转 base64,大图走文件 |
| SCSS / LESS | sass-loader / less-loader |
配合 css-loader 一起用 |
module.exports = {
module: {
rules: [
{
test: /\.jsx?$/,
// 我建议一定加上 exclude,不然 node_modules 也会被转译,慢得离谱
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(png|jpg|gif)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8KB 以下转 base64
}
}
}
]
}
};
['style-loader', 'css-loader'],先由 css-loader 解析 CSS,
再由 style-loader 插入页面。顺序写反了会直接报错。
4.4 plugin —— 打包流程的扩展
loader 管的是文件级别的转换,plugin 管的是打包流程的"大活儿"。 比如生成 HTML、压缩代码、提取 CSS、注入环境变量……这些 loader 做不了,得靠 plugin。
我项目中几乎必用的几个 plugin:
- HtmlWebpackPlugin:自动生成 HTML,并注入打包后的 JS/CSS 链接。
- MiniCssExtractPlugin:把 CSS 从 JS 中提取成独立文件,生产环境必用。
- DefinePlugin:注入全局常量,比如
process.env.NODE_ENV。 - CopyWebpackPlugin:复制静态资源到输出目录。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
// 多入口时可以指定 chunks
chunks: ['main']
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
]
};
new 关键字。
我见过有人直接把 HtmlWebpackPlugin 扔进数组,没加 new,
结果 Webpack 直接报错。这种低级错误排查起来特别浪费时间。
📌 loader vs plugin —— 核心区别一张表
| 对比维度 | loader | plugin |
|---|---|---|
| 定位 | 模块转换器(翻译官) | 流程扩展器(指挥官) |
| 作用范围 | 单个文件级别 | 整个打包流程 |
| 配置方式 | module.rules 数组中配置 |
plugins 数组中实例化 |
| 典型场景 | 转译 JS、解析 CSS、加载图片 | 生成 HTML、提取 CSS、压缩代码 |
| 执行时机 | 在模块加载阶段执行 | 在打包生命周期各阶段执行 |
| 能否自定义 | 可以,但开发成本较高 | 可以,通过 Tapable 钩子机制 |
简单总结:loader 管"怎么读文件",plugin 管"怎么处理打包结果"。 两者不是替代关系,是协作关系。我见过不少新手把两者搞混,配置了半天就是不生效, 其实就是没搞明白这个区别。
下图梳理了 Webpack 核心配置的完整知识体系,你可以把它当作一张"配置地图":
✅ 第4章核心要点
- entry:定义打包起点,支持单入口、多入口、动态入口三种写法。
- output:控制输出文件的位置和命名,
contenthash是缓存利器。 - loader:模块转换器,在
module.rules中配置,注意执行顺序从右向左。 - plugin:流程扩展器,在
plugins中实例化,能干预打包全过程。 - 两者不是二选一,而是 各司其职、协同工作。