📦 前端构建工具 Webpack 速览
目录
- [1. 引言](#1. 引言)
- [2. 知识要点](#2. 知识要点)
- [3. 为什么需要 Webpack?](#3. 为什么需要 Webpack?)
- [4. Webpack 核心概念](#4. Webpack 核心概念)
- [5. 安装与初始化](#5. 安装与初始化)
- [6. 开发环境配置](#6. 开发环境配置)
- [7. 生产环境配置](#7. 生产环境配置)
- [8. Loaders 详解](#8. Loaders 详解)
- [9. Babel 配置](#9. Babel 配置)
- [10. 文件 Hash 与缓存](#10. 文件 Hash 与缓存)
- [11. clean-webpack-plugin](#11. clean-webpack-plugin)
- [12. 完整代码示例](#12. 完整代码示例)
- [13. 运行与验证](#13. 运行与验证)
- [14. AtomCode 实战](#14. AtomCode 实战)
- [15. 常见错误与排查](#15. 常见错误与排查)
- [16. 小结与下一篇预告](#16. 小结与下一篇预告)
1. 引言
为什么学这个?
在留言板项目中,我们直接通过 CDN 引入 Vue 和 axios,这种方式不需要任何构建工具,非常适合学习和快速原型开发。
但在真实项目中,你会遇到以下问题:
- 模块化:代码文件越来越多,需要拆分管理
- 兼容性:ES6+ 语法在旧浏览器上不兼容
- 性能:多个 JS 文件合并、压缩以减少请求数
- 资源处理:CSS、图片、字体等都需要打包
Webpack 就是解决这些问题的最流行工具。
解决了什么问题?
- ❌ 多个 JS 文件手动管理依赖 → ✅ 自动打包成一个文件
- ❌ 浏览器不支持 ES6 语法 → ✅ Babel 转译
- ❌ 开发和生产环境配置不同 → ✅ 分离配置
- ❌ 文件更新后浏览器缓存不刷新 → ✅ 文件 hash
2. 知识要点
| 编号 | 知识点 | 说明 |
|---|---|---|
| 1 | Webpack 核心概念 | Entry / Output / Loaders / Plugins |
| 2 | 安装与配置 | webpack, webpack-cli |
| 3 | 开发环境 | devServer, source-map |
| 4 | 生产环境 | mode: production, 压缩 |
| 5 | Loaders | babel-loader, css-loader, style-loader |
| 6 | Babel | @babel/core, @babel/preset-env |
| 7 | 文件 Hash | contenthash |
| 8 | 插件 | clean-webpack-plugin, HtmlWebpackPlugin |
3. 为什么需要 Webpack?
3.1 没有 Webpack 的世界
html
<!-- 需要手动管理多个 script 标签的顺序 -->
<script src="jquery.js"></script>
<script src="lodash.js"></script>
<script src="utils.js"></script>
<script src="app.js"></script>
问题: 如果 app.js 依赖 utils.js,你必须确保 utils.js 先加载。项目大了之后,依赖关系变得极其复杂。
3.2 有 Webpack 的世界
javascript
// app.js ------ 使用 ES Module 语法
import $ from 'jquery';
import { formatDate } from './utils';
import './style.css';
Webpack 会自动分析依赖关系,打包成一个文件。
3.3 Webpack 的定位
┌───────────────────────────────────────────────┐
│ Webpack │
│ │
│ .js →→→→→→→→→→→→→ babel-loader →→→→→→→→ │
│ .css →→→→→→→→→→→→→ css-loader →→→→→→→→ │ dist/
│ .vue →→→→→→→→→→→→→ vue-loader →→→→→→→→ │ bundle.js
│ .png →→→→→→→→→→→→→ file-loader →→→→→→→→ │
│ │
└───────────────────────────────────────────────┘
4. Webpack 核心概念
4.1 四大核心概念
| 概念 | 作用 | 类比 |
|---|---|---|
| Entry(入口) | 告诉 Webpack 从哪个文件开始打包 | 大门的入口 |
| Output(输出) | 告诉 Webpack 打包后的文件放哪里 | 出口 |
| Loaders(加载器) | 处理非 JS 文件(CSS、图片等) | 翻译官 |
| Plugins(插件) | 执行更复杂的任务(压缩、注入等) | 装修队 |
4.2 示意图
Entry Loaders Plugins Output
┌───────┐ ┌──────────┐ ┌──────────┐ ┌────────┐
│ app.js │──────→ │ babel │──────→ │ Html │──────→│ dist/ │
│ style.css│─→ │ css │ │ Webpack │ │ bundle │
│ image.png│─→ │ file │ │ Clean │ │ .js │
└───────┘ └──────────┘ └──────────┘ └────────┘
5. 安装与初始化
5.1 创建项目并初始化
bash
# 创建项目目录
mkdir webpack-demo
cd webpack-demo
# 初始化 package.json
npm init -y
这段代码做了什么: npm init -y 创建 package.json 文件,-y 表示使用默认配置。
5.2 安装 Webpack
bash
# 安装 webpack 核心和 CLI
npm install --save-dev webpack webpack-cli
参数说明:
--save-dev:安装到devDependencies(开发依赖),生产环境不需要webpack:核心库webpack-cli:命令行工具,让我们可以在终端运行webpack命令
5.3 创建项目文件
bash
# 创建源码目录
mkdir src
# 创建入口文件
cat > src/index.js << 'EOF'
function hello() {
console.log('Hello, Webpack!');
}
hello();
EOF
# 创建 HTML 文件
cat > src/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Webpack Demo</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
EOF
6. 开发环境配置
6.1 创建 Webpack 配置文件
javascript
// webpack.config.js
const path = require('path');
module.exports = {
// 模式:development 开发环境
mode: 'development',
// 入口文件
entry: './src/index.js',
// 输出配置
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
// 开发服务器配置
devServer: {
port: 3000,
hot: true,
open: true,
},
// Source Map:方便调试
devtool: 'inline-source-map',
};
这段代码做了什么:
| 配置项 | 作用 |
|---|---|
mode: 'development' |
开发模式,代码不压缩,打包速度快 |
entry: './src/index.js' |
从 src/index.js 开始打包 |
output.path |
打包结果输出到 dist/ 目录 |
output.filename |
输出文件名 bundle.js |
devServer |
开发服务器,端口 3000,热更新 |
devtool: 'inline-source-map' |
调试时能看到原始源码,而非打包后的代码 |
6.2 安装开发服务器
bash
npm install --save-dev webpack-dev-server
6.3 配置 package.json 脚本
json
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production"
}
}
这段代码做了什么: 添加两个脚本:
npm run dev:启动开发服务器npm run build:构建生产版本
7. 生产环境配置
7.1 生产环境的特点
| 特性 | 开发环境 | 生产环境 |
|---|---|---|
| 代码压缩 | ❌ | ✅ |
| Source Map | 详细 | 精简 |
| 打包速度 | 快 | 慢 |
| 文件大小 | 大 | 小 |
| 热更新 | ✅ | ❌ |
7.2 分离配置(推荐方式)
对于真实项目,通常将开发和生产配置分离:
bash
# 安装 webpack-merge 用于合并配置
npm install --save-dev webpack-merge
javascript
// webpack.common.js ------ 公共配置
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
clean: true, // 每次构建前清理 dist 目录
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
};
javascript
// webpack.dev.js ------ 开发环境
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
port: 3000,
hot: true,
},
});
javascript
// webpack.prod.js ------ 生产环境
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
output: {
filename: '[name].[contenthash].js',
},
});
8. Loaders 详解
8.1 什么是 Loaders?
Webpack 原生只理解 JavaScript 和 JSON 文件。Loaders 让 Webpack 可以处理其他类型的文件,并将它们转换为有效的模块。
8.2 常用 Loaders
| Loader | 处理文件 | 作用 |
|---|---|---|
| babel-loader | .js |
将 ES6+ 转译为 ES5 |
| css-loader | .css |
解析 CSS 中的 @import 和 url() |
| style-loader | .css |
将 CSS 注入到 DOM 的 <style> 标签 |
| file-loader | 图片、字体 | 处理文件路径 |
| sass-loader | .scss |
将 SCSS 编译为 CSS |
8.3 CSS Loaders 使用示例
安装:
bash
npm install --save-dev style-loader css-loader
配置:
javascript
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/, // 匹配所有 .css 文件
use: ['style-loader', // 2. 将 CSS 注入到 DOM
'css-loader'], // 1. 解析 CSS 文件
},
],
},
};
注意 Loaders 的执行顺序:从右到左,从下到上。
.css 文件 → css-loader(解析) → style-loader(注入) → bundle.js
8.4 图片 Loaders 使用示例
安装:
bash
npm install --save-dev file-loader
配置:
javascript
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif|svg)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'images/[name].[hash:8].[ext]',
outputPath: 'assets/',
},
},
],
},
],
},
};
9. Babel 配置
9.1 为什么需要 Babel?
很多现代 JavaScript 语法(箭头函数、const、模板字符串、解构赋值等)在**旧版浏览器(IE 11)**上不支持。Babel 将这些新语法转译为所有浏览器都支持的 ES5 语法。
转译前:
javascript
const greet = (name) => `Hello, ${name}!`;
转译后:
javascript
var greet = function greet(name) {
return "Hello, ".concat(name, "!");
};
9.2 安装 Babel
bash
npm install --save-dev @babel/core @babel/preset-env babel-loader
| 包 | 作用 |
|---|---|
@babel/core |
Babel 核心编译引擎 |
@babel/preset-env |
预设,根据目标浏览器自动决定转译规则 |
babel-loader |
Webpack 和 Babel 之间的桥梁 |
9.3 配置 Babel
方式一:在 webpack.config.js 中配置
javascript
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 不处理 node_modules 中的文件
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
};
方式二:创建独立的 babel 配置文件
json
// babel.config.json(推荐)
{
"presets": [
["@babel/preset-env", {
"targets": {
"browsers": ["> 1%", "last 2 versions", "not dead"]
}
}]
]
}
这段代码做了什么: targets 指定目标浏览器------市场份额 > 1%、最近两个版本、还在维护的浏览器。Babel 会根据这些目标自动决定需要转译哪些语法。
10. 文件 Hash 与缓存
10.1 为什么需要文件 Hash?
浏览器会缓存静态文件(JS、CSS)以提高加载速度。但当你更新代码后,浏览器可能仍然使用旧缓存,导致用户看不到新功能。
解决方案: 在文件名中加入内容哈希(Content Hash),内容变了,文件名就变了,浏览器自然会请求新文件。
10.2 Webpack 中的 Hash
javascript
module.exports = {
output: {
filename: '[name].[contenthash:8].js',
// 示例输出: main.a1b2c3d4.js
},
};
| 占位符 | 含义 | 示例 |
|---|---|---|
[name] |
入口名称 | main |
[contenthash] |
基于文件内容的哈希值 | a1b2c3d4e5f6 |
[contenthash:8] |
取哈希前 8 位 | a1b2c3d4 |
[hash] |
基于构建的哈希值(不推荐) | --- |
10.3 验证 Hash 效果
bash
# 第一次构建
npm run build
# 输出: dist/main.a1b2c3d4.js
# 修改 src/index.js 后再次构建
npm run build
# 输出: dist/main.e5f6a7b8.js ← 文件名变了!
11. clean-webpack-plugin
11.1 为什么需要清理?
每次构建都会生成新文件,但旧文件不会被自动删除。多次构建后,dist/ 目录会残留大量旧文件:
dist/
├── main.a1b2c3d4.js
├── main.e5f6a7b8.js ← 旧文件,应该删除
├── main.9a8b7c6d.js ← 旧文件,应该删除
├── index.html
└── ...
11.2 使用 clean-webpack-plugin
Webpack 5 内置方式:
javascript
module.exports = {
output: {
clean: true, // Webpack 5.20+ 内置支持
},
};
传统方式(安装插件):
bash
npm install --save-dev clean-webpack-plugin
javascript
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(),
],
};
12. 完整代码示例
下面是一个完整的 Webpack 配置示例,包含了本篇所有知识点:
12.1 package.json
json
{
"name": "webpack-demo",
"version": "1.0.0",
"scripts": {
"dev": "webpack serve --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
"devDependencies": {
"@babel/core": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0",
"css-loader": "^6.10.0",
"html-webpack-plugin": "^5.6.0",
"style-loader": "^3.3.4",
"webpack": "^5.90.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.2",
"webpack-merge": "^5.10.0"
}
}
12.2 webpack.common.js
javascript
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset/resource', // Webpack 5 内置
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
title: 'Webpack Demo',
}),
],
};
12.3 webpack.dev.js
javascript
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
port: 3000,
hot: true,
open: true,
historyApiFallback: true,
},
output: {
filename: 'bundle.js',
},
});
12.4 webpack.prod.js
javascript
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map',
output: {
filename: '[name].[contenthash:8].js',
},
});
12.5 src/index.js
javascript
import './style.css';
const app = document.querySelector('#app');
app.innerHTML = '<h1>Hello, Webpack!</h1>';
const greet = (name) => `Welcome to ${name}!`;
console.log(greet('Webpack 5'));
12.6 src/style.css
css
body {
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #667eea, #764ba2);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
margin: 0;
}
#app {
background: white;
padding: 40px 60px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
h1 {
color: #333;
font-size: 28px;
}
13. 运行与验证
13.1 安装所有依赖
bash
# 克隆或创建上述文件后,安装依赖
npm install
# 预期输出:所有包安装成功
13.2 启动开发服务器
bash
npm run dev
预期输出:
<i> webpack 5.90.0
<i> webpack-dev-server 4.15.2
<i> Project is running at:
<i> http://localhost:3000/
<i> webpack output is served from /
<i> Content not from webpack is served from /src
<i> 404s will fallback to /index.html
浏览器自动打开 http://localhost:3000,你应该看到带有渐变背景的 "Hello, Webpack!" 白色卡片。
13.3 构建生产版本
bash
npm run build
预期输出:
> webpack-demo@1.0.0 build
> webpack --config webpack.prod.js
asset main.5f3a2b1c.js 1.23 KiB [emitted] [minimized] ← 文件名带 hash
asset index.html 312 bytes [emitted]
asset main.5f3a2b1c.js.map 3.45 KiB [emitted]
Entrypoint main = main.5f3a2b1c.js main.5f3a2b1c.js.map
验证构建产物:
bash
# 查看 dist 目录
ls -la dist/
# 输出:
# -rw-r--r-- main.5f3a2b1c.js
# -rw-r--r-- main.5f3a2b1c.js.map
# -rw-r--r-- index.html
13.4 验证 Hash 更新
bash
# 修改 src/index.js 中的内容
echo 'console.log("Updated!");' >> src/index.js
# 重新构建
npm run build
# 查看新的 hash 文件名
ls dist/
# 输出: main.9a8b7c6d.js ← hash 变了!
14. AtomCode 实战
场景:用 AtomCode 生成 Webpack 配置
提示词:
"我是前端新手,请帮我生成一个完整的 Webpack 5 配置文件。
项目需求:
- 使用 ES6 模块语法
- 需要处理 CSS 文件
- 支持 Babel 转译
- 开发环境需要热更新,端口 3000
- 生产环境需要文件 hash
- 需要自动生成 HTML 文件
- 请分别输出 webpack.common.js, webpack.dev.js, webpack.prod.js"
AtomCode 回复: 生成上述三个配置文件,并附带详细的参数说明。
场景:AtomCode 解释 Loaders 执行顺序
提示词:
"Webpack 中 style-loader 和 css-loader 的顺序为什么是 ['style-loader', 'css-loader']?
如果顺序反过来会怎样?"
AtomCode 回复:
- Webpack Loaders 的执行顺序是从右到左(或从下到上)
- 先
css-loader解析 CSS 文件(处理@import、url()等) - 再
style-loader把解析后的 CSS 注入到 DOM 的<style>标签 - 如果反过来,
style-loader会先尝试注入,但 CSS 还没被解析,会报错
15. 常见错误与排查
错误 1:模块未安装
报错信息:
Module not found: Error: Can't resolve 'style-loader'
原因: 忘记安装对应的 Loader。
解决:
bash
npm install --save-dev style-loader css-loader
错误 2:Babel 配置错误
报错信息:
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Cannot find module '@babel/preset-env'
原因: 安装了 babel-loader 但没有安装 @babel/preset-env。
解决:
bash
npm install --save-dev @babel/core @babel/preset-env
错误 3:webpack-cli 未安装
报错信息:
CLI for webpack must be installed.
webpack-cli (https://github.com/webpack/webpack-cli)
We will use "webpack-cli" to run this project.
原因: 只安装了 webpack 核心,没有安装 webpack-cli。
解决:
bash
npm install --save-dev webpack-cli
错误 4:HtmlWebpackPlugin 输出路径不对
报错信息:
ERROR in Plugin: html-webpack-plugin
Error: Child compilation failed:
Entrypoint not found
原因: template 路径指向了不存在的文件。
解决: 确保 template 路径正确:
javascript
new HtmlWebpackPlugin({
template: './src/index.html', // 确保文件存在
})
错误 5:开发服务器端口被占用
报错信息:
Error: listen EADDRINUSE: address already in use :::3000
原因: 端口 3000 已被其他程序占用。
解决:
javascript
// 换一个端口
devServer: {
port: 3001,
}
16. 小结与下一篇预告
本篇小结
- ✅ 理解了 Webpack 的四大核心概念:Entry、Output、Loaders、Plugins
- ✅ 学会了安装和配置 Webpack 5
- ✅ 区分了开发环境(devServer、热更新)和生产环境(压缩、Hash)
- ✅ 掌握了 Loaders 的使用(babel-loader、css-loader、style-loader)
- ✅ 配置了 Babel 将 ES6+ 转译为 ES5
- ✅ 学会了使用
[contenthash]实现文件缓存控制 - ✅ 使用了
clean-webpack-plugin清理构建产物
下一篇预告
下一篇:05-留言板一:初始化与组件设计.md
从下一篇开始,我们终于要写真正的留言板项目了!在下一篇中,你将:
- 用 Flask 初始化后端项目
- 设计 UserAvatar 和 MessageItem 组件
- 编写主页面结构和样式
- 基于真实代码库的
app.py、static/index.html、static/app.js逐行讲解
前面 4 篇的理论基础已经打好了,现在开始实战!🚀