Webpack 深度学习指南
本文档帮助你系统学习 Webpack 的工作原理、常用配置和最佳实践。
第一章:Webpack 工作流程详解
Webpack 做的事情是什么?
简单说:把你的代码打成浏览器能理解的文件。
详细说:
源代码(多个文件)
↓
[初始化] - 读取 webpack.config.js,初始化编译器
↓
[编译] - 从 entry 开始,递归分析所有模块的依赖
↓
[转换] - 用 loader 转换每个模块(TS→JS、JSX→JS 等)
↓
[构建] - 生成模块之间的依赖图,处理 require/import
↓
[优化] - Tree Shaking、代码分割、压缩等优化
↓
[生成] - 输出最终的 bundle 文件
↓
打包完成!可以在浏览器中运行
Webpack 的四个核心概念
// webpack.config.js
module.exports = {
// 1. entry - 入口,从哪里开始打包
entry: './src/index.js',
// 2. output - 出口,打包后的文件放在哪里
output: {
path: './dist',
filename: 'bundle.js'
},
// 3. module - 模块,怎么处理各种类型的文件
module: {
rules: [
// loader 就在这里配置
{ test: /\.js$/, use: 'babel-loader' }
]
},
// 4. plugin - 插件,控制整个打包流程
plugins: [
new HtmlWebpackPlugin()
]
};
这四个概念贯穿整个 Webpack 学习过程。
第二章:详解 Entry(入口)
什么是 Entry?
告诉 Webpack 从哪个文件开始分析依赖。Webpack 会从 entry 文件开始,找到它 import/require 的文件,再找这些文件的依赖,形成一个依赖图。
Entry 的配置方式
方式 1:单入口(字符串)
module.exports = {
entry: './src/index.js'
}
// Webpack 会在 dist 目录生成:
// dist/main.js
方式 2:多入口(对象)
module.exports = {
entry: {
main: './src/index.js',
admin: './src/admin.js',
user: './src/user.js'
}
}
// Webpack 会生成多个 bundle:
// dist/main.js
// dist/admin.js
// dist/user.js
// 这样做的好处:不同页面加载不同的代码
方式 3:数组(合并入口)
module.exports = {
entry: {
bundle: ['./src/index.js', './src/app.js']
}
}
// 会先加载 app.js,再加载 index.js,最后打成一个 bundle.js
实战:单页应用 vs 多页应用
单页应用(SPA)
module.exports = {
entry: './src/index.js',
// 只有一个入口,所以只生成一个 bundle
// React、Vue 项目都是这样
}
多页应用(MPA)
module.exports = {
entry: {
home: './src/pages/home/index.js',
about: './src/pages/about/index.js',
contact: './src/pages/contact/index.js'
}
}
// 这样你可以为每个页面生成单独的 bundle
// 用户访问 home 页面只加载 home.js
// 用户访问 about 页面只加载 about.js
第三章:详解 Output(出口)
出口配置
module.exports = {
output: {
// 输出目录的绝对路径
path: path.resolve(__dirname, 'dist'),
// 输出文件名
filename: 'bundle.js',
// 输出的文件前缀路径(通常是 CDN 地址或相对路径)
publicPath: '/',
// 库的输出名称(如果你在打包库)
library: 'MyLib',
// 库的输出格式
libraryTarget: 'umd'
}
}
Filename 的高级用法
静态文件名
filename: 'bundle.js'
// 输出:bundle.js
动态文件名(包含 hash)
filename: 'bundle.[hash].js'
// 输出:bundle.abc123def456.js
// 好处:不同版本的 bundle 有不同的名字,利用浏览器缓存
filename: 'bundle.[contenthash].js'
// contenthash:只有文件内容改变,hash 才改变(比 hash 更聪明)
// 这是最常用的方式
filename: 'bundle.[contenthash:8].js'
// 只用 hash 的前 8 位,缩短文件名
按照入口名称输出
entry: {
main: './src/index.js',
admin: './src/admin.js'
},
output: {
filename: '[name].js' // [name] 对应 entry 的 key
}
// 输出:
// main.js
// admin.js
按照chunk id输出
output: {
filename: 'js/[name].[contenthash:8].js'
}
// 输出到 js 目录:
// js/main.abc123de.js
// js/admin.def456ab.js
PublicPath 的重要性
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/' // 资源的基础路径
}
// 如果你的应用部署在 https://example.com/app/
// 应该设置:publicPath: '/app/'
// 这会影响打包结果中引用资源的路径
// <img src="/assets/logo.png"> // publicPath: '/'
// <img src="/app/assets/logo.png"> // publicPath: '/app/'
第四章:详解 Module 和 Loader
Loader 是什么?
Loader 是转换文件的工具。Webpack 默认只理解 JavaScript,但你的项目可能有:
- TypeScript 文件
- JSX 文件
- CSS 文件
- 图片文件
- 字体文件
Loader 的作用就是把这些文件转成 Webpack 能理解的形式。
Loader 的执行原理
源文件内容
↓
[第一个 loader] - 处理,输出中间结果
↓
[第二个 loader] - 继续处理,输出中间结果
↓
[第三个 loader] - 继续处理,输出最终结果
↓
Webpack 继续处理
重要:Loader 执行顺序是从右到左(或从下到上)!
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
// 执行顺序:css-loader 先执行,style-loader 后执行
}
]
}
}
常用 Loader 详解
1. babel-loader - 转译 JavaScript
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 不处理 node_modules 中的文件(加快速度)
use: {
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env', // 转译最新的 JavaScript 语法
'@babel/preset-react' // 处理 JSX
]
}
}
}
]
}
}
作用:把 ES6+ 语法转成浏览器能理解的 ES5 语法。
// 输入(ES6)
const name = 'Tom'
const fn = () => console.log(name)
// 输出(ES5)
var name = 'Tom'
var fn = function() { console.log(name) }
2. ts-loader - 转译 TypeScript
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
}
作用:把 TypeScript 代码转成 JavaScript。
// 输入
const greet = (name: string): string => {
return `Hello, ${name}`
}
// 输出
const greet = (name) => {
return `Hello, ${name}`
}
3. css-loader 和 style-loader - 处理 CSS
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
}
执行顺序:css-loader → style-loader
- css-loader:处理 CSS 中的
@import和url(),变成可以 import 的 JavaScript 模块 - style-loader:把 CSS 注入到页面中(通过
<style>标签)
// 你的代码
import './style.css'
// css-loader 处理后变成
const css = '.box { color: red }'
// style-loader 处理后变成
const style = document.createElement('style')
style.innerHTML = '.box { color: red }'
document.head.appendChild(style)
4. MiniCssExtractPlugin.loader - 提取 CSS 成单独文件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, // 代替 style-loader
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css'
})
]
}
// 打包结果:
// dist/
// main.js
// css/main.abc123de.css ← CSS 被提取成单独文件
// index.html
为什么要提取 CSS?
- 减少 JavaScript 文件体积
- 浏览器可以并行下载 CSS 和 JS
- 可以缓存 CSS 文件,提升性能
5. less-loader 和 sass-loader - 处理预处理器
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
// 执行顺序:less-loader → css-loader → style-loader
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
]
}
}
// less-loader 作用:Less 语法 → CSS
// .box {
// width: 100px;
// &:hover { width: 110px; }
// }
// 转换为:
// .box { width: 100px; }
// .box:hover { width: 110px; }
6. file-loader / asset module - 处理图片和字体
// 旧写法(file-loader)
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif|jpeg)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
outputPath: 'images/'
}
}
}
]
}
}
// 新写法(webpack 5+ 推荐,用 asset module)
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
type: 'asset/resource',
generator: {
filename: 'images/[name].[hash:8][ext]'
}
}
]
}
}
作用:把图片复制到输出目录,返回路径。
import logo from './logo.png'
// 打包后变成
const logo = '/images/logo.abc123de.png'
// HTML 中可以使用
<img src={logo} />
7. url-loader - 小文件内联
// 旧写法
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: {
loader: 'url-loader',
options: {
limit: 8192, // 小于 8KB 的文件转成 data URL
fallback: 'file-loader' // 大于 8KB 用 file-loader
}
}
}
]
}
}
// 新写法(webpack 5+ 推荐)
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8 KB
}
}
}
]
}
}
作用:小文件转成 Base64 嵌入到代码中。
// 小于 8KB 的图片
import logo from './logo.png'
// logo = 'data:image/png;base64,iVBORw0KGgo...'
// 直接嵌入 HTML,减少网络请求
<img src={logo} />
Loader 的配置模式
模式 1:字符串(简单情况)
use: 'babel-loader'
模式 2:对象(需要传参)
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
模式 3:数组(多个 loader)
use: [
'style-loader',
'css-loader',
'less-loader'
]
模式 4:数组 + 对象(复杂情况)
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true // 启用 CSS Modules
}
},
'less-loader'
]
实战配置:完整的 loader 配置
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash:8].js'
},
module: {
rules: [
// JavaScript
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
// TypeScript
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
// CSS
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
// Less
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'less-loader'
]
},
// 图片
{
test: /\.(png|jpg|gif)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024
}
},
generator: {
filename: 'images/[name].[hash:8][ext]'
}
},
// 字体
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource',
generator: {
filename: 'fonts/[name].[hash:8][ext]'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
]
}
第五章:详解 Plugins(插件)
Plugin 是什么?
Plugin 可以在 Webpack 打包的整个过程中插入自定义逻辑。
Loader vs Plugin:
- Loader:处理单个文件,转换文件内容
- Plugin:作用在整个打包过程,可以监听各个环节
Plugin 的工作原理
Webpack 打包过程中有很多”钩子”(生命周期事件),Plugin 可以监听这些钩子。
初始化
↓
emit (即将输出文件)
↓
输出文件
↓
done (打包完成)
Plugin 就是在这些时刻执行自定义代码。
常用 Plugin 详解
1. HtmlWebpackPlugin - 生成 HTML
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
// HTML 模板文件
template: './src/index.html',
// 输出文件名
filename: 'index.html',
// 是否压缩 HTML
minify: {
removeComments: true, // 删除注释
collapseWhitespace: true, // 删除空白
removeRedundantAttributes: true // 删除冗余属性
},
// 在 HTML 中注入变量
inject: 'body' // 把 script 标签插入到 body 末尾
})
]
}
原理:
<!-- 源文件:src/index.html -->
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
<!-- 打包后:dist/index.html -->
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script src="main.abc123de.js"></script> <!-- 自动插入 -->
</body>
</html>
多页应用场景:
const pages = ['home', 'about', 'contact']
module.exports = {
entry: {
home: './src/pages/home/index.js',
about: './src/pages/about/index.js',
contact: './src/pages/contact/index.js'
},
plugins: [
// 为每个页面生成 HTML
...pages.map(page => new HtmlWebpackPlugin({
template: `./src/pages/${page}/index.html`,
filename: `${page}.html`,
chunks: [page] // 只注入对应的 JS
}))
]
}
2. MiniCssExtractPlugin - 提取 CSS
前面已经讲过了,这里再强调一下用法:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css'
})
]
}
// 打包结果:
// dist/
// main.abc123de.js
// css/main.def456ab.css
// index.html
3. TerserPlugin - 压缩 JavaScript
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'production', // 生产模式自动启用压缩
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
// 保留注释中的许可证信息
format: {
comments: false
}
},
extractComments: false // 不提取注释
})
]
}
}
// 原始代码
const message = 'Hello World'
console.log(message)
// 压缩后
const a="Hello World";console.log(a)
4. CssMinimizerPlugin - 压缩 CSS
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
}
// 原始 CSS
.box {
color: red;
margin: 10px;
}
// 压缩后
.box{color:red;margin:10px}
5. CleanWebpackPlugin - 清空输出目录
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
plugins: [
new CleanWebpackPlugin() // 每次打包前删除 dist 目录
]
}
// 这样可以避免旧文件留在 dist 中
6. DefinePlugin - 定义全局变量
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
API_URL: JSON.stringify('https://api.example.com'),
DEBUG: false
})
]
}
// 在你的代码中可以直接使用
console.log(process.env.NODE_ENV) // 'production'
console.log(API_URL) // 'https://api.example.com'
console.log(DEBUG) // false
重要:注意要用 JSON.stringify() 包装字符串值!
7. ProvidePlugin - 自动注入模块
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
_: 'lodash'
})
]
}
// 在你的代码中可以直接使用,不需要 import
$(document).ready(function() {
// 可以直接用 $,不需要 import 'jquery'
})
_.debounce(fn, 300) // 可以直接用 _
8. WebpackBundleAnalyzer - 分析包体积
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static', // 生成静态 HTML 报告
reportFilename: 'report.html'
})
]
}
// 打包后会生成一个可视化报告,显示各个模块的大小
// 帮助找出哪些模块最大
实战配置:完整的 Plugin 配置
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
mode: isDev ? 'development' : 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
},
{
test: /\.css$/,
use: [
isDev ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
// 清空输出目录
new CleanWebpackPlugin(),
// 生成 HTML
new HtmlWebpackPlugin({
template: './src/index.html',
minify: !isDev
}),
// 提取 CSS(仅生产环境)
...(isDev ? [] : [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
]),
// 定义全局变量
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
APP_VERSION: JSON.stringify('1.0.0')
}),
// 包体积分析(仅在需要时启用)
...(process.env.ANALYZE ? [
new BundleAnalyzerPlugin()
] : [])
]
}
第六章:常用配置详解
1. Mode - 开发模式
module.exports = {
// development:开发模式
// 不压缩代码,便于调试,打包快速
mode: 'development',
// production:生产模式
// 压缩代码,性能优化,打包较慢
mode: 'production',
// none:不做任何优化
mode: 'none'
}
2. DevtoolSourceMap(源码映射)
module.exports = {
// cheap-module-source-map:推荐用于生产环境
// 行映射,文件较小,满足大部分需求
devtool: 'cheap-module-source-map',
// source-map:最完整的映射,文件最大
devtool: 'source-map',
// eval-cheap-module-source-map:推荐用于开发环境
// 速度快,增量编译快
devtool: 'eval-cheap-module-source-map',
// false:不生成 source map
devtool: false
}
实际应用:
const isDev = process.env.NODE_ENV === 'development'
module.exports = {
devtool: isDev ? 'eval-cheap-module-source-map' : 'cheap-module-source-map'
}
3. DevServer(开发服务器)
module.exports = {
devServer: {
// 监听端口
port: 3000,
// 自动打开浏览器
open: true,
// 热更新(HMR)
hot: true,
// 当路由不存在时,重定向到 index.html(SPA 必需)
historyApiFallback: true,
// 代理后端 API
proxy: {
'/api': {
target: 'http://localhost:8080',
pathRewrite: { '^/api': '' }, // 移除 /api 前缀
changeOrigin: true
}
},
// 压缩
compress: true,
// 允许跨域
headers: {
'Access-Control-Allow-Origin': '*'
}
}
}
常见场景:
// SPA 应用配置
devServer: {
port: 3000,
open: true,
historyApiFallback: true, // 路由都重定向到 index.html
hot: true
}
// API 代理配置
devServer: {
proxy: {
'/api': {
target: 'http://api.example.com',
changeOri

