文章目录
- 前言
- 一、核心概念
-
- [1.1 Entry(入口)](#1.1 Entry(入口))
- [1.2 Output(输出)](#1.2 Output(输出))
- [1.3 完整的构建流程](#1.3 完整的构建流程)
- 二、Loader
-
- [2.1 定义](#2.1 定义)
- [2.2 常用 Loader](#2.2 常用 Loader)
- [2.3 Loader 执行顺序](#2.3 Loader 执行顺序)
- 三、Plugin
-
- [3.1 定义](#3.1 定义)
- [3.2 常用 Plugin](#3.2 常用 Plugin)
- [3.3 Loader vs Plugin](#3.3 Loader vs Plugin)
- [四、devServer 与 HMR](#四、devServer 与 HMR)
-
- [4.1 devServer](#4.1 devServer)
- [4.2 HMR(Hot Module Replacement)](#4.2 HMR(Hot Module Replacement))
- [五、contenthash 缓存策略](#五、contenthash 缓存策略)
-
- [5.1 三种 hash 类型](#5.1 三种 hash 类型)
- [5.2 为什么用 contenthash](#5.2 为什么用 contenthash)
- [5.3 最佳配置](#5.3 最佳配置)
- 六、从入口到产出:完整链路
- 七、易混淆点
- 八、思考与练习
前言
Webpack 是前端工程化的核心工具,理解其工作原理对构建优化、性能调优至关重要。本篇会讲清楚:
- Entry / Output 配置
- Loader 与 Plugin 的区别与执行顺序
- devServer 与 HMR(热模块替换)
contenthash缓存策略
一、核心概念
1.1 Entry(入口)
javascript
// webpack.config.js
module.exports = {
// 单入口
entry: './src/index.js',
// 多入口(多页应用)
entry: {
app: './src/app.js',
admin: './src/admin.js'
}
}
1.2 Output(输出)
javascript
module.exports = {
output: {
filename: '[name].[contenthash].js', // 输出文件名
path: path.resolve(__dirname, 'dist'), // 输出目录
clean: true // 构建前清空输出目录
}
}
1.3 完整的构建流程
Entry → 依赖分析 → Loader 转换 → 模块图 → 生成 Chunk → Output
二、Loader
2.1 定义
Loader 是模块转换器,将非 JS 文件(CSS、图片、TypeScript 等)转换为 Webpack 可处理的模块。
2.2 常用 Loader
javascript
module.exports = {
module: {
rules: [
// CSS 处理
{
test: /\.css$/,
use: ['style-loader', 'css-loader'] // 从右到左执行
},
// TypeScript
{
test: /\.tsx?$/,
use: 'ts-loader'
},
// 图片
{
test: /\.(png|svg|jpg|gif)$/,
type: 'asset/resource'
},
// Babel 转译
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
}
2.3 Loader 执行顺序
从右到左,从下到上。
javascript
use: ['style-loader', 'css-loader']
// 执行顺序:css-loader → style-loader
// 1. css-loader:解析 CSS 文件,处理 @import 和 url()
// 2. style-loader:将 CSS 注入到 DOM 的 <style> 标签
三、Plugin
3.1 定义
Plugin 是构建流程的扩展,可以在构建的各个阶段执行自定义逻辑。
3.2 常用 Plugin
javascript
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
module.exports = {
plugins: [
// 生成 HTML 文件
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 提取 CSS 到单独文件
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
// 打包分析
new BundleAnalyzerPlugin()
]
}
3.3 Loader vs Plugin
| 对比项 | Loader | Plugin |
|---|---|---|
| 作用 | 模块转换 | 构建流程扩展 |
| 时机 | 模块加载时 | 构建生命周期各阶段 |
| 配置 | module.rules |
plugins |
| 本质 | 函数 | 类(带 apply 方法) |
javascript
// Loader 本质:一个函数
module.exports = function(source) {
return transformedSource
}
// Plugin 本质:一个带 apply 方法的 class
class MyPlugin {
apply(compiler) {
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('构建完成!')
})
}
}
四、devServer 与 HMR
4.1 devServer
javascript
module.exports = {
devServer: {
port: 3000,
hot: true, // 启用 HMR
open: true, // 自动打开浏览器
proxy: { // 代理配置
'/api': 'http://localhost:8080'
},
historyApiFallback: true // SPA 路由支持
}
}
4.2 HMR(Hot Module Replacement)
HMR 在不刷新整个页面的情况下,替换、添加或删除模块。
javascript
// 开启 HMR 后,Webpack 会:
// 1. 建立 WebSocket 连接
// 2. 文件变化时,通过 WebSocket 通知客户端
// 3. 客户端请求更新的模块
// 4. 替换旧模块,保持应用状态
// 手动处理 HMR(如状态管理)
if (module.hot) {
module.hot.accept('./module', () => {
// 模块更新后的处理逻辑
})
}
五、contenthash 缓存策略
5.1 三种 hash 类型
javascript
// 1. hash:每次构建都变化,所有文件共用
output: { filename: '[name].[hash].js' }
// 2. chunkhash:同一 chunk 的文件相同
output: { filename: '[name].[chunkhash].js' }
// 3. contenthash:根据文件内容变化
output: { filename: '[name].[contenthash].js' }
5.2 为什么用 contenthash
javascript
// 假设有两个文件:app.js 和 vendor.js
// 使用 chunkhash:vendor.js 内容没变,但 app.js 变了
// 结果:vendor.js 的 hash 也变了(因为它们在同一 chunk)
// 使用 contenthash:只有内容变化的文件 hash 才变
// 结果:只有 app.js 的 hash 变化,vendor.js 保持不变
// 浏览器可以利用缓存,避免重新下载未变化的文件
5.3 最佳配置
javascript
module.exports = {
output: {
filename: '[name].[contenthash:8].js', // 8 位 hash
chunkFilename: '[name].[contenthash:8].chunk.js'
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
六、从入口到产出:完整链路
1. 读取 Entry
2. 从 Entry 开始,递归分析依赖
3. 对每个模块应用对应的 Loader 转换
4. 生成模块图(Module Graph)
5. 将模块合并为 Chunk
6. 应用 Plugin(如 HtmlWebpackPlugin)
7. 输出到 dist 目录
七、易混淆点
- Loader vs Plugin :Loader 是模块转换器 (处理单个文件),Plugin 是构建流程扩展(干预整个构建过程)。
- 执行顺序:Loader 从右到左执行;Plugin 按数组顺序执行,但会根据 hooks 类型在不同阶段触发。
- HMR 原理:通过 WebSocket 实现,文件变化时通知客户端请求更新的模块,替换旧模块保持应用状态。
- contenthash:根据文件内容计算 hash,只有内容变化时 hash 才变化,利于浏览器缓存。
八、思考与练习
1. Loader 和 Plugin 的区别是什么?
解析:
- Loader :模块转换器,处理单个文件(如 CSS → JS),配置在
module.rules - Plugin :构建流程扩展,干预整个构建过程(如生成 HTML),配置在
plugins
2. Loader 的执行顺序是怎样的?
解析:从右到左 ,从下到上。例如 use: ['style-loader', 'css-loader'],先执行 css-loader,再执行 style-loader。
3. HMR 是如何工作的?
解析:
- 建立 WebSocket 连接
- 文件变化时,Webpack 通过 WebSocket 通知客户端
- 客户端请求更新的模块
- 替换旧模块,保持应用状态
4. 为什么推荐使用 contenthash?
解析:contenthash 根据文件内容计算 hash,只有内容变化时 hash 才变化。这使得未变化的文件可以利用浏览器缓存,避免重新下载。
5. 说一下 Webpack 从入口到产出的完整流程。
解析:
- 读取 Entry
- 递归分析依赖
- 对每个模块应用 Loader 转换
- 生成模块图
- 合并为 Chunk
- 应用 Plugin
- 输出到 dist 目录