2. Webpack 核心概念与打包流程深度剖析
Webpack 这东西,说白了就是一个模块打包器。你写了一大堆 JS、CSS、图片,它帮你整理成一个或几个文件。但它的核心概念,很多人用了两三年都没完全搞明白。今天咱们就把 Entry、Output、Loader、Plugin、Mode、Module 这六个概念彻底说透。
2.1 六大核心概念速览
先给个表格,让你一眼看清每个概念是干嘛的。我习惯把这六个概念分成两组:「流程控制」和「转换增强」。
| 概念 | 作用 | 类比 |
|---|---|---|
| Entry(入口) | 告诉 Webpack 从哪个文件开始打包 | 像一栋楼的入口大门 |
| Output(输出) | 指定打包后的文件放哪、叫什么 | 像快递的收货地址 |
| Loader(加载器) | 处理非 JS 文件(CSS、图片、TS 等) | 像翻译官,把不同语言转成 JS |
| Plugin(插件) | 在打包过程中做额外的事情 | 像装修队,能改造整个房子 |
| Mode(模式) | 设置 development / production 等环境 | 像工作模式:调试 or 上线 |
| Module(模块) | Webpack 中一切文件都是模块 | 像乐高积木,每块都能组合 |
2.2 Entry:打包的起点
Entry 就是 Webpack 开始工作的第一个文件。你想想看,如果不知道从哪开始,怎么构建依赖图?
// 单入口(最常见)
module.exports = {
entry: './src/index.js'
};
// 多入口(多页面应用)
module.exports = {
entry: {
home: './src/home.js',
about: './src/about.js',
contact: './src/contact.js'
}
};
2.3 Output:打包结果放哪
Output 决定了打包后的文件长什么样。我刚开始用 Webpack 时,经常忘记配 output,结果文件全跑 dist 目录里乱成一团。
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
// 多入口时用占位符
filename: '[name].[contenthash].js'
}
};
[contenthash] 这个占位符特别有用。文件内容变了,hash 才变。浏览器缓存就能精准失效。嗯,这里要注意:开发环境别用 contenthash,不然每次热更新都生成新文件,慢得要命。
2.4 Loader:文件转换器
Webpack 原生只认识 JS 和 JSON。遇到 CSS、图片、TypeScript 怎么办?Loader 就是干这个的。
Loader 的执行顺序是从右到左,从下到上。这个坑我踩过好几次。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
// 先执行 css-loader(解析 CSS),再执行 style-loader(注入到页面)
},
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
}
};
2.5 Plugin:打包过程的增强器
Loader 只能处理单个文件,Plugin 能干预整个打包过程。比如生成 HTML、压缩代码、提取 CSS 到单独文件。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
]
};
Plugin 的机制是「钩子」。Webpack 在打包的不同阶段会触发不同钩子,Plugin 就挂在这些钩子上干活。比如 HtmlWebpackPlugin 在 emit 阶段把生成的 JS/CSS 注入到 HTML 里。
2.6 Mode:一键切换环境
Mode 这个配置,说白了就是帮你省事。设成 development,Webpack 自动开启 source map、更快的增量编译。设成 production,自动开启 tree shaking、代码压缩。
module.exports = {
mode: 'development', // 开发模式
// 或
mode: 'production' // 生产模式
};
2.7 Module:一切皆模块
在 Webpack 的世界里,每个文件都是一个模块。JS 文件是模块,CSS 文件是模块,甚至图片也是模块。模块之间通过 import / require 建立依赖关系。
Webpack 会从 Entry 开始,递归地解析所有模块的依赖,最终生成一个模块依赖图(Module Graph)。这个图就是打包的核心依据。
2.8 Webpack 打包流程深度剖析
光知道概念还不够,得理解 Webpack 到底是怎么工作的。我把打包流程拆成四个阶段:
- 初始化阶段:读取配置文件,合并命令行参数,创建 Compiler 对象
- 编译阶段:从 Entry 开始,递归解析模块依赖,用 Loader 转换文件内容
- 生成阶段:把所有模块打包成 Chunk,生成最终代码
- 输出阶段:把打包结果写入磁盘
下面这张图,把整个流程串起来了。
2.9 核心流程的代码视角
咱们用代码来模拟一下 Webpack 的打包逻辑。虽然简化了,但核心思想一模一样。
// 伪代码:Webpack 打包核心逻辑
class Compiler {
run() {
// 1. 读取入口文件
const entryModule = this.buildModule(this.config.entry);
// 2. 递归构建所有模块
const modules = [entryModule];
while (modules.length) {
const module = modules.shift();
// 用 Loader 转换文件内容
const transformedCode = this.applyLoaders(module);
// 解析依赖(import / require)
const dependencies = this.parseDependencies(transformedCode);
dependencies.forEach(dep => {
modules.push(this.buildModule(dep));
});
}
// 3. 生成 Chunk
const chunk = this.createChunk(modules);
// 4. 输出文件
this.emitFiles(chunk);
}
}
2.10 避坑指南:我踩过的三个坑
- Loader 顺序搞反:我曾经把 sass-loader 和 css-loader 写反,结果报错说「不认识 $ 变量」。记住:从右到左执行,最后一个 Loader 最先处理。
- 多入口忘记配 output:单入口时
filename: 'bundle.js'没问题。多入口必须用[name]占位符,不然文件互相覆盖。 - Plugin 重复实例化:同一个 Plugin 在 plugins 数组里出现两次,会导致重复执行。比如 HtmlWebpackPlugin 实例化两次,会生成两个 HTML 文件。
2.11 本章小结
六个核心概念,其实就三件事:入口输出定边界,Loader 转换文件,Plugin 增强流程。Mode 帮你一键切换环境,Module 是 Webpack 看待文件的方式。
打包流程四个阶段:初始化 → 编译 → 生成 → 输出。其中编译阶段最复杂,涉及 Loader 转换、依赖解析、递归构建。理解了这个流程,你就能驾驭 Webpack 了。