前端面经-webpack篇--定义、配置、构建流程、 Loader、Tree Shaking、懒加载与预加载、代码分割、 Plugin 机制
看完本篇你将基本了解webpack!!!
目录
一、Webpack 的作用
1、基本配置结构
2、配置项详解
1. entry —— 构建入口
2. output —— 输出配置
3. mode:模式设置
4. module:模块规则
5. plugins:插件机制
6. resolve:模块解析配置(可选)
7. devServer:开发服务器(可选)
8. devtool:调试工具(可选)
9. target:目标平台(可选)
二、构建流程
1. 初始化(Initialization)
1. 读取配置文件
2. 初始化 Compiler 对象
3. 注册所有插件(Plugin 注册阶段)
2. 构建模块图(Build Dependency Graph)
3. 模块转换与解析
示例 1:JS 文件(可能含 ES6、TS)
示例 2:CSS 文件
示例 3:图片文件
4. 生成代码块(Chunk)与文件(Asset)
1、 Chunk(代码块)
常见的 Chunk 类型:
🔁 举个例子
为什么要把代码分成多个 chunk?
1. 性能优化:
2. 缓存优化:
2、Asset(最终产出资源文件)
3. Chunk 转换为 Asset( bundle)
4. 输出 Asset 到 output.path
5. 输出阶段(Emit)
三、插件机制
1、插件“注册”发生在 —— ✅ 初始化阶段(Initialization)
2、插件“执行”发生在 —— ✅ 构建过程的每一个阶段(Build Lifecycle)
3、compilation
1. 模块的管理
2. 构建 Chunk
3. 生成 Asset(输出资源)
4. Plugin 的生命周期钩子
四、配置项之间的相互关系
1. entry 与 module.rules
2. entry 与 output
3. module.rules 与 plugins
4. mode 与其他所有配置项
5. devServer 与 output
6. plugins 与 output
五、 Loader
1、什么是 Loader?
2、为什么需要 Loader?
3、常见 Loader 类型
1、 什么是 babel-loader?
2、你为什么需要 babel-loader?
3、核心概念说明
4、常见用途
4、Webpack 如何调用 Loader?
六、构建优化与高级功能
1. 代码分割(Code Splitting)
2. Tree Shaking
Tree Shaking 的基本原理
3. 懒加载(Lazy Loading)与预加载
1、什么是懒加载(Lazy Loading)?
Webpack 如何实现懒加载?
2、什么是预加载(Preload)和预取(Prefetch)?
1. webpackPrefetch: true → 浏览器空闲时加载
2. webpackPreload: true → 当前帧就加载
一、Webpack 的作用
Webpack 的主要作用是:
-
模块打包:将多个模块(JS、CSS、图片等)打包成一个或多个文件。
-
代码拆分(Code Splitting):根据需要拆分代码,提高首屏加载速度。
-
资源处理:处理 JS 之外的资源,如 CSS、LESS、SASS、图片、字体等。
-
开发工具支持:提供开发服务器、热更新(HMR)、调试等功能。
-
优化性能:压缩代码、Tree Shaking(去除无用代码)、懒加载等。
1、基本配置结构
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js', // 入口
output: { // 输出
filename: 'bundle.js', // 输出文件名
path: path.resolve(__dirname, 'dist') // 输出目录(必须是绝对路径)
},
module: { // 模块处理规则
rules: [
{
test: /.css$/, // 正则匹配 .css 文件
use: ['style-loader', 'css-loader'] // 使用的 loader,从右向左执行
}
]
},
plugins: [], // 插件列表
mode: 'development' // 构建模式:development | production
};
在了解 Webpack 原理前,我们需要先了解几个核心名词的概念:
-
入口(Entry):构建的起点。Webpack 从这里开始执行构建。通过 Entry 配置能够确定哪个文件作为构建过程的开始,进而识别出应用程序的依赖图谱。
-
模块(Module):构成应用的单元。在 Webpack 的视角中,一切文件皆可视为模块,包括 JavaScript、CSS、图片或者是其他类型的文件。Webpack 从 Entry 出发,递归地构建出一个包含所有依赖文件的模块网络。
-
代码块(Chunk):代码的集合体。Chunk 由模块合并而成,被用来优化输出文件的结构。Chunk 使得 Webpack 能够更灵活地组织和分割代码,支持代码的懒加载、拆分等高级功能。
-
加载器(Loader):模块的转换器。Loader 让 Webpack 有能力去处理那些非 JavaScript 文件(Webpack 本身只理解 JavaScript)。通过 Loader,各种资源文件可以被转换为 Webpack 能够处理的模块,如将 CSS 转换为 JS 模块,或者将高版本的 JavaScript 转换为兼容性更好的形式(降级)。
-
插件(Plugin):构建流程的参与者。Webpack 的构建流程中存在众多的事件钩子(hooks),Plugin 可以监听这些事件的触发,在触发时加入自定义的构建行为,如自动压缩打包后的文件、生成应用所需的 HTML 文件等。
2、配置项详解
1. entry
—— 构建入口
指定 Webpack 构建的起点,支持多入口配置。
entry: './src/index.js'
作用:指定 Webpack 构建的起点文件,从这个文件出发递归分析所有依赖。
-
默认值是
'./src/index.js'
。 -
支持单入口(字符串)和多入口(对象形式)。
多入口示例:
entry: {
app: './src/app.js',
admin: './src/admin.js'
}
🔁 Webpack 会为每个入口分别打包生成输出文件。
2. output
—— 输出配置
控制打包后的文件名称和路径。
作用:指定打包后文件的存储位置和命名规则。
-
filename
:输出文件名,可包含占位符(如[name]
,[contenthash]
) -
path
:输出目录,必须是绝对路径
output: {
filename: 'bundle.js', // 输出的文件名
path: path.resolve(__dirname, 'dist') // 绝对路径
}
动态命名(用于多入口):filename: '[name].[contenthash].js'
3. mode
:模式设置
mode: 'development' // 或 'production'
作用:指定打包模式,Webpack 会自动启用对应的优化。
-
development
:-
开启调试(source map)
-
不压缩代码
-
提高构建速度
-
-
production
:-
自动压缩 JavaScript/CSS
-
启用 Tree Shaking(移除未使用代码)
-
更小体积、适合上线
-
4. module
:模块规则
module: {
rules: [...]
}
作用:定义对不同模块(如 CSS、JS、图片等)的处理规则,核心由 rules
数组组成。
每条规则格式如下:
{
test: /.css$/, // 正则匹配需要处理的文件类型
use: ['style-loader', 'css-loader'] // 使用的 loader(从右到左执行)
}
📌 常见 Loader:
文件类型 | Loader 示例 |
---|---|
JS | babel-loader |
CSS | css-loader 、style-loader |
图片 | file-loader 、url-loader |
字体 | file-loader |
Vue | vue-loader |
5. plugins
:插件机制
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
作用:扩展 Webpack 的功能,用于执行更复杂的构建任务。
📌 常用插件及作用:
插件名 | 作用 |
---|---|
HtmlWebpackPlugin | 自动生成 HTML 文件并注入打包资源 |
CleanWebpackPlugin | 构建前自动清空输出目录 |
MiniCssExtractPlugin | 提取 CSS 到单独文件 |
DefinePlugin | 定义环境变量 |
6. resolve
:模块解析配置(可选)
resolve: {
extensions: ['.js', '.jsx', '.json']
}
作用:指定在导入模块时可省略的文件扩展名,提高模块查找效率。
📌 例如:import App from './App'
// 实际查找:./App.js -> ./App.jsx -> ./App.json
7. devServer
:开发服务器(可选)
devServer: {
static: './dist',
port: 3000,
hot: true
}
作用:启动本地开发服务器,支持热更新(HMR),提升开发效率。
HMR(Hot Module Replacement)是 Webpack 在开发环境下的一种“热更新”功能,允许你在不刷新页面的情况下替换、更新模块的内容。
配置说明:
-
static
: 提供静态文件目录 -
port
: 设置访问端口 -
hot
: 启用热更新功能(无需刷新页面即可应用更改)
8. devtool
:调试工具(可选)
devtool: 'source-map'
作用:生成 source map,帮助调试代码时映射到源码位置。
常用选项:
-
source-map
:完整 source map,最详细(用于生产) -
eval-source-map
:快且可调试(用于开发) -
none
:关闭 source map
9. target
:目标平台(可选)
作用:指定构建目标环境(浏览器 / Node.js)
target: 'web' // 或 'node'
二、构建流程
1. 初始化(Initialization)
-
读取配置文件(
webpack.config.js
) -
初始化 Compiler(核心对象)
-
注册所有 plugin,进入生命周期钩子(基于 Tapable)
在 Webpack 中,存在两个非常重要的核心对象:compiler
、compilation
,它们的作用如下:
- Compiler:Webpack 的核心,贯穿于整个构建周期。
Compiler
封装了 Webpack 环境的全局配置,包括但不限于配置信息、输出路径等。 - Compilation:表示单次的构建过程及其产出。与
Compiler
不同,Compilation
对象在每次构建中都是新创建的,描述了构建的具体过程,包括模块资源、编译后的产出资源、文件的变化,以及依赖关系的状态。在watch mode 下,每当文件变化触发重新构建时,都会生成一个新的Compilation
实例。
Compiler
是一个长期存在的环境描述,贯穿整个 Webpack 运行周期;而 Compilation
是对单次构建的具体描述,每一次构建过程都可能有所不同。
1. 读取配置文件
Webpack 启动时会查找配置文件(默认是 webpack.config.js
),并加载它。
支持的文件格式包括:
-
JavaScript (
webpack.config.js
) -
TypeScript (
webpack.config.ts
) -
JSON(部分字段)
Webpack 会执行配置文件的内容,读取其中的:
-
entry
,output
-
module.rules
-
plugins
-
mode
,devtool
,resolve
, 等等
📌 注意:Webpack 本质上会执行 require('./webpack.config.js')
,所以你甚至可以在里面写 JS 逻辑(例如根据环境动态返回不同配置)。
2. 初始化 Compiler 对象
Webpack 内部会构造出一个核心对象: Compiler:const compiler = new Compiler(options);
这个对象是整个构建系统的“大脑”,包含:
-
所有用户配置
-
所有构建状态
-
所有钩子(事件系统)
-
所有模块、chunk、asset 的数据结构
📌 Compiler
不是随便一个类,它继承了 Tapable
类,内置了很多“生命周期钩子”。
例如:
compiler.hooks.run.tap(...)
compiler.hooks.emit.tap(...)
compiler.hooks.done.tap(...)
3. 注册所有插件(Plugin 注册阶段)
Webpack 执行 plugins
数组中每一个插件的 .apply()
方法,把插件“挂载”到 compiler
上。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({ template: './index.html' })
]
}
HtmlWebpackPlugin 内部会执行:
apply(compiler) {
compiler.hooks.emit.tap('HtmlWebpackPlugin', compilation => {
// 插件逻辑
});
}
📌 这相当于在构建“还没开始前”,就准备好了插件的监听事件,让插件在适当时机介入构建流程。
初始化阶段的内部机制:Tapable 系统
Webpack 的插件机制基于一个库:Tapable。
你可以把它想象成“事件发布/订阅系统”:
-
Webpack 生命周期中各个阶段都会触发钩子
-
插件会提前通过
.tap()
注册函数 -
等构建进行到某一阶段时,就会执行对应钩子中的插件逻辑
常见的 Hook:
Hook 名称 | 触发时机 |
---|---|
run | 构建开始时 |
compile | 依赖图构建前 |
compilation | 创建 Compilation 对象时 |
emit | 输出资源前 |
done | 构建结束时 |
初始化阶段后的准备结果是什么?
-
所有配置信息都已解析
-
所有插件都已注册到合适的生命周期钩子
-
构建状态对象
compiler
构建完成
➡️ 现在,只等下一步开始正式构建了(构建依赖图、转换模块、生成 chunk)。
2. 构建模块图(Build Dependency Graph)
从 entry
开始递归分析:
-
读取入口文件
-
识别导入的模块(
import
/require
) -
通过
module.rules
选择合适的 loader 进行转换 -
继续递归读取依赖,直到没有新依赖为止
-
建立一个 模块依赖图(Module Graph)
模块图是一个 DAG(有向无环图),表示模块间的引用关系。
假设你有这样的文件结构:
src/
├── index.js
├── utils.js
├── style.css
└── logo.png
index.js
内容如下:
import './style.css';
import { add } from './utils.js';
import logo from './logo.png';
utils.js
内容:
export function add(a, b) {
return a + b;
}
Webpack 会生成的依赖图如下:
[index.js]
/ |
↓ ↓ ↓
[style.css][utils.js][logo.png]
-
index.js
是入口 -
它依赖了三个模块:
-
style.css
→ 由css-loader
和style-loader
处理 -
utils.js
→ 正常 JS 模块 -
logo.png
→ 被file-loader
或url-loader
转换成链接
-
这个图就是模块依赖图。Webpack 会根据它打包时决定文件的组织顺序、是否合并、是否拆分、是否优化等。
如果你希望看到你的项目真实的模块依赖图,可以使用以下工具:webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
plugins: [
new BundleAnalyzerPlugin()
]
它会生成一个交互式网页,展示模块结构图,包括文件大小、依赖关系等。
3. 模块转换与解析
-
JS 文件 → Babel 转译
-
CSS 文件 → CSS loader 转换为 JS 模块
-
图片 → file-loader / url-loader 生成可导入的 URL
-
每一个模块最终都会被转换为 JS 函数或对象
Webpack 会在“构建模块图”的同时,对每个模块立即进行“模块解析与转换”,这是一个“边走边处理”的过程。结合第二阶段
1. 入口 index.js
2. 识别其依赖 ['./a.js', './style.css']
3. 解析 index.js → 传给 babel-loader 转换
4. 进入 a.js,找依赖并转换
5. 进入 style.css → css-loader → style-loader
6. 构建出模块图 + 得到转换后的模块内容
所以当 Webpack 找到一个模块之后(如 style.css
、image.png
、index.jsx
),它还不能直接打包进 JS。Webpack 必须: 用 Loader 转换为 JS 模块格式
这一步是真正“处理文件内容”的阶段。
示例 1:JS 文件(可能含 ES6、TS)
// 原始代码
const fn = () => console.log('hi');
// 经过 babel-loader 转换为:
var fn = function() { return console.log("hi"); }
示例 2:CSS 文件
body { color: red; }
/* css-loader 转换为 */
module.exports = "body { color: red; }";
/* style-loader 再把这段 CSS 插入到