看完本篇你将基本了解webpack!!!
目录
[一、Webpack 的作用](#一、Webpack 的作用)
[1. entry ------ 构建入口](#1. entry —— 构建入口)
[2. output ------ 输出配置](#2. output —— 输出配置)
[3. mode:模式设置](#3. mode:模式设置)
[4. module:模块规则](#4. module:模块规则)
[5. plugins:插件机制](#5. plugins:插件机制)
[6. resolve:模块解析配置(可选)](#6. resolve:模块解析配置(可选))
[7. devServer:开发服务器(可选)](#7. devServer:开发服务器(可选))
[8. devtool:调试工具(可选)](#8. devtool:调试工具(可选))
[9. target:目标平台(可选)](#9. target:目标平台(可选))
[1. 初始化(Initialization)](#1. 初始化(Initialization))
[1. 读取配置文件](#1. 读取配置文件)
[2. 初始化 Compiler 对象](#2. 初始化 Compiler 对象)
[3. 注册所有插件(Plugin 注册阶段)](#3. 注册所有插件(Plugin 注册阶段))
[2. 构建模块图(Build Dependency Graph)](#2. 构建模块图(Build Dependency Graph))
[3. 模块转换与解析](#3. 模块转换与解析)
[示例 1:JS 文件(可能含 ES6、TS)](#示例 1:JS 文件(可能含 ES6、TS))
[示例 2:CSS 文件](#示例 2:CSS 文件)
[示例 3:图片文件](#示例 3:图片文件)
[4. 生成代码块(Chunk)与文件(Asset)](#4. 生成代码块(Chunk)与文件(Asset))
[1、 Chunk(代码块)](#1、 Chunk(代码块))
[常见的 Chunk 类型:](#常见的 Chunk 类型:)
[🔁 举个例子](#🔁 举个例子)
[为什么要把代码分成多个 chunk?](#为什么要把代码分成多个 chunk?)
[1. 性能优化:](#1. 性能优化:)
[2. 缓存优化:](#2. 缓存优化:)
[3. Chunk 转换为 Asset( bundle)](#3. Chunk 转换为 Asset( bundle))
[4. 输出 Asset 到 output.path](#4. 输出 Asset 到 output.path)
[5. 输出阶段(Emit)](#5. 输出阶段(Emit))
[1、插件"注册"发生在 ------ ✅ 初始化阶段(Initialization)](#1、插件“注册”发生在 —— ✅ 初始化阶段(Initialization))
[2、插件"执行"发生在 ------ ✅ 构建过程的每一个阶段(Build Lifecycle)](#2、插件“执行”发生在 —— ✅ 构建过程的每一个阶段(Build Lifecycle))
[1. 模块的管理](#1. 模块的管理)
[2. 构建 Chunk](#2. 构建 Chunk)
[3. 生成 Asset(输出资源)](#3. 生成 Asset(输出资源))
[4. Plugin 的生命周期钩子](#4. Plugin 的生命周期钩子)
[1. entry 与 module.rules](#1. entry 与 module.rules)
[2. entry 与 output](#2. entry 与 output)
[3. module.rules 与 plugins](#3. module.rules 与 plugins)
[4. mode 与其他所有配置项](#4. mode 与其他所有配置项)
[5. devServer 与 output](#5. devServer 与 output)
[6. plugins 与 output](#6. plugins 与 output)
[五、 Loader](#五、 Loader)
[1、什么是 Loader?](#1、什么是 Loader?)
[2、为什么需要 Loader?](#2、为什么需要 Loader?)
[3、常见 Loader 类型](#3、常见 Loader 类型)
[1、 什么是 babel-loader?](#1、 什么是 babel-loader?)
[2、你为什么需要 babel-loader?](#2、你为什么需要 babel-loader?)
[4、Webpack 如何调用 Loader?](#4、Webpack 如何调用 Loader?)
[1. 代码分割(Code Splitting)](#1. 代码分割(Code Splitting))
[2. Tree Shaking](#2. Tree Shaking)
[Tree Shaking 的基本原理](#Tree Shaking 的基本原理)
[3. 懒加载(Lazy Loading)与预加载](#3. 懒加载(Lazy Loading)与预加载)
[1、什么是懒加载(Lazy Loading)?](#1、什么是懒加载(Lazy Loading)?)
[Webpack 如何实现懒加载?](#Webpack 如何实现懒加载?)
2、什么是预加载(Preload)和预取(Prefetch)?
[1. webpackPrefetch: true → 浏览器空闲时加载](#1. webpackPrefetch: true → 浏览器空闲时加载)
[2. webpackPreload: 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 会在"构建模块图"的同时,对每个模块立即进行"模块解析与转换" ,这是一个"边走边处理"的过程。结合第二阶段
入口 index.js
识别其依赖 ['./a.js', './style.css']
解析 index.js → 传给 babel-loader 转换
进入 a.js,找依赖并转换
进入 style.css → css-loader → style-loader
构建出模块图 + 得到转换后的模块内容
所以当 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 插入到 <style> 标签中 */
示例 3:图片文件
import img from './logo.png';
/* file-loader 转换为 */
module.exports = "https://cdn.xxx.com/assets/logo.png";
📌 Loader 负责把"非 JS 文件"变成 JS 模块,或者把 JS 新语法变旧语法。
4. 生成代码块(Chunk)与文件(Asset)
-
Chunk:是 Webpack 内部构建产物,对应一组模块集合(可用于代码分割)。
-
Asset :是最终生成的文件,例如
bundle.js
、main.css
、图片等。
1、 Chunk(代码块)
-
Chunk 是 Webpack 构建产物的逻辑单元,代表一个或一组模块的集合。
-
每个入口文件、异步模块、被共享的模块都会生成一个 chunk。
-
Chunk 本质上是"模块打包中间结果",最终会转化为一个或多个 Asset 文件。
常见的 Chunk 类型:
类型 | 示例 |
---|---|
Entry Chunk | 入口文件构建的 chunk(如 main.js ) |
Lazy Chunk | 动态导入的模块(如 import('./modal') ) |
Vendor Chunk | 被多个入口共享的第三方库(如 react , lodash ) |
Runtime Chunk | Webpack 运行时代码(如 webpackRuntime.js ) |
Chunk 是模块的集合,这些模块有"某种关系",所以被打成一块儿,生成一个 JS 文件(或 CSS 文件)。
想象你在打包搬家,有很多小物品(模块),你不可能一个一个带,而是:
-
把相关的东西打进一个箱子(chunk)
-
每个箱子贴一个标签(chunk 名字)
-
最后封箱、打包(生成 asset 文件)
Webpack 就是在打包项目的模块,它根据**"模块之间的引用关系"**来决定哪些模块一起放进同一个箱子(chunk),再把这些箱子输出成 .js
、.css
文件等。
🔁 举个例子
src/
├── index.js // 入口
├── a.js
├── b.js
└── c.js
index.js
import './a.js';
import('./b.js'); // 动态导入
a.js
console.log('a');
b.js
import './c.js';
console.log('b');
Webpack 打包时会怎么处理?
index.js
是入口 → 它会生成一个 入口 Chunk,我们叫它 main
。
它包含:(因为 a.js 是同步导入的)
main chunk:
index.js
a.js
b.js
是动态导入(import()
) → 它会生成一个 懒加载 Chunk,单独打包。
chunk-b:
b.js
c.js
(因为 b.js 和 c.js 是一起的、在被点击时一起加载)
🎯 打包后会生成哪些文件(Asset)?
最终输出:
/dist
├── main.[hash].js ← 入口 chunk,对应 index + a
├── chunk-b.[hash].js ← 动态 chunk,对应 b + c
为什么要把代码分成多个 chunk?
1. 性能优化:
-
首页加载只加载必要模块(index + a)
-
用户点击功能时,再懒加载(b + c)
2. 缓存优化:
-
不同 chunk 对应不同 hash 名字
-
修改 b.js 不会影响 main.js 的缓存
2、Asset(最终产出资源文件)
-
Asset 是 Webpack 输出到磁盘的文件:如
bundle.js
、main.css
、logo.png
-
它是 Chunk 的最终形态(经过 loader/plugin 处理后的文件结果)
模块依赖图(谁依赖谁)
↓
每个模块用 loader 转换
↓
模块被分组为 chunk(按策略)
↓
chunk 转换为 asset(最终输出文件)
Webpack 如何生成 Chunk 与 Asset?
根据 Module Graph 拆分 Chunk
-
Webpack 会遍历整个模块依赖图,确定哪些模块属于哪个 chunk。
-
应用分包策略(SplitChunksPlugin、动态导入)来进行 chunk 拆分。
-
一般每个入口或异步模块会生成一个 chunk。
📌 你动态导入模块时,Webpack 会为其生成一个单独的 chunk 文件(实现懒加载)。
3. Chunk 转换为 Asset( bundle**)**
-
Chunk 只是"逻辑结构",Webpack 需要将它们转换为 JS/CSS/资源文件(Asset)。
-
通过插件(如
MiniCssExtractPlugin
)或自身模板系统,Webpack 将:-
各 chunk 合并成字符串内容
-
输出 JS 文件(如
main.[hash].js
) -
输出 CSS 文件(如
style.[contenthash].css
)
-
4. 输出 Asset 到 output.path
最终所有 Asset 文件写入硬盘或开发服务器的内存:
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
}
5. 输出阶段(Emit)
-
所有的 Asset 文件写入到配置的
output.path
目录中 -
插件(如
HtmlWebpackPlugin
)可在此阶段修改/生成资源
我们了解了webpack的构建过程以后,我们还要了解一下webpack的插件机制
三、插件机制
Webpack 的插件机制贯穿整个构建生命周期,但它的"注册"和"激活"分别发生在不同的阶段。
1、插件"注册"发生在 ------ ✅ 初始化阶段(Initialization)
Webpack 会读取 webpack.config.js
里的 plugins
数组,并对每一个插件执行:
plugin.apply(compiler);
也就是说,每个插件通过 .apply(compiler)
方法注册到 生命周期钩子上(Hooks)。
🔧 这些钩子都挂在 Webpack 的 Compiler
或 Compilation
对象上,由 Tapable
提供事件发布订阅机制。
📌 所以,"插件机制"的准备阶段就在初始化阶段完成了。
2、插件"执行"发生在 ------ ✅ 构建过程的每一个阶段(Build Lifecycle)
注册只是开始,真正让插件起作用,是当 Webpack 构建流程进入具体某个阶段时,会调用对应的钩子,插件就"被唤醒"并执行它的逻辑。
插件发挥作用的典型阶段有:
构建阶段 | 生命周期钩子(hook) | 插件可能做的事情 |
---|---|---|
构建启动 | compiler.hooks.run |
打印开始标志 |
编译模块图前 | compiler.hooks.beforeCompile |
注入额外依赖 |
创建 Compilation | compiler.hooks.compilation |
操作模块处理逻辑 |
模块生成完毕 | compilation.hooks.buildModule |
修改模块源码 |
资源输出前 | compilation.hooks.emit |
添加/修改生成文件 |
构建结束 | compiler.hooks.done |
输出统计信息或清理 |
所以插件机制在构建期间是事件驱动的,根据构建"走到哪一步"触发哪个插件
示例:HtmlWebpackPlugin 插件如何接入机制?
plugins: [
new HtmlWebpackPlugin({ template: './index.html' })
]
这个插件会:
-
在
compiler.hooks.thisCompilation
注册事件 -
在
compilation.hooks.processAssets
阶段,插入一个生成的 HTML 文件作为 asset
所以它注册在初始化阶段,被调用在资源生成阶段(emit 前)。
compilation
是 Webpack 每次构建时用来 处理模块、生成 chunk、输出文件(asset) 的对象,它代表了一次"构建资源的具体过程"。
与 compiler
的关系
对象 | 描述 |
---|---|
compiler |
全局构建对象,贯穿整个构建生命周期(只创建一次) |
compilation |
表示一次"编译过程",可能有多次(如监听模式中每次变化都会重新创建) |
📌 换句话说:
-
compiler
是构建管理者(总控) -
compilation
是实际干活的工人(负责模块、chunk、asset 的生成)
3、compilation
compilation
主要包含什么功能?
Webpack 会使用 compilation
做以下事情:
1. 模块的管理
-
所有模块都被封装为
Module
对象 -
保存在
compilation.modules
中
2. 构建 Chunk
-
将模块分组为 chunk(如入口 chunk、异步 chunk)
-
保存在
compilation.chunks
中
3. 生成 Asset(输出资源)
-
将 chunk 转换为最终要输出的 JS/CSS/Image 等文件
-
保存在
compilation.assets
中(是一个对象,键是文件名,值是文件内容)
4. Plugin 的生命周期钩子
compilation
自己也有一套 hooks:
Hook 名称 | 描述 |
---|---|
buildModule |
构建每个模块时触发 |
optimizeChunks |
生成 chunk 后优化时触发 |
processAssets (Webpack 5) |
生成最终资源前触发 |
seal |
准备打包所有模块和资源时触发 |
四、配置项之间的相互关系
了解了构建流程以后 我们看看配置项之间的相互关系
1. entry
与 module.rules
-
关系 :Webpack 从
entry
开始构建依赖图,遇到各种类型的文件(.js
,.css
,.png
)时,会查找module.rules
中的匹配规则。 -
实际效果 :比如你在
index.js
中引入了style.css
,Webpack 会用css-loader
和style-loader
来处理它。
2. entry
与 output
-
关系 :
entry
决定了打包的起点;output
决定了打包的终点(文件名和路径)。 -
实际效果 :如果有多个入口,
output.filename
应使用占位符[name].js
来分别生成文件。
3. module.rules
与 plugins
-
关系 :
rules
是资源的预处理;plugins
则是在整个构建过程中插入逻辑或修改结果。 -
实际效果 :比如通过
css-loader
把 CSS 模块化,然后使用MiniCssExtractPlugin
把它提取为独立文件。module: {
rules: [
{ test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] }
]
},
plugins: [
new MiniCssExtractPlugin({ filename: '[name].css' })
]
4. mode
与其他所有配置项
-
关系 :
mode
是全局行为开关,会影响:-
是否压缩代码(output)
-
是否启用 Tree Shaking(module)
-
是否显示 SourceMap(devtool)
-
插件行为(如优化)
-
-
mode
为'production'
时,默认启用TerserPlugin
压缩 JS 和CssMinimizerPlugin
压缩 CSS。
5. devServer
与 output
-
关系 :
devServer
不真正写入output.path
,而是在内存中生成并托管输出文件。 -
实际效果:更快的热更新(HMR),页面自动刷新。
6. plugins
与 output
-
比如
HtmlWebpackPlugin
插件会在output
中自动注入打包后的文件路径和名字(如<script src="bundle.js">
)。 -
文件名如果包含 hash,插件会自动处理。
五、 Loader
1、什么是 Loader?
Loader 是 Webpack 的模块转换器,用于将各种非 JavaScript 文件转换为 Webpack 可以理解的模块(JavaScript 模块)。
2、为什么需要 Loader?
Webpack 默认只懂 .js
和 .json
,但项目中常常使用:
-
.css
、.scss
-
.ts
、.vue
-
图片
.png
、字体.woff
等
Webpack 无法直接理解这些文件,所以我们用 Loader 来预处理它们 → 转换成 JS 模块。
Loader 的本质:是一个函数
module.exports = function(source) {
// source 是文件原始内容(字符串)
return transformedCode;
}
Webpack 读取文件 → 调用 Loader → 得到转换后的 JS 模块
Loader 的执行过程(串行、链式)
多个 Loader 组合时,从 右到左(从下到上) 顺序执行:
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
执行顺序是:css-loader → style-loader=
分工:
-
css-loader
:把 CSS 转成 JS 字符串模块 -
style-loader
:把字符串插入到页面<style>
标签中
3、常见 Loader 类型
类型 | Loader 名称 | 功能 |
---|---|---|
JS 转译 | babel-loader |
转换 ES6/TS 等 |
样式处理 | css-loader , style-loader , sass-loader |
支持 CSS/SASS |
文件资源 | file-loader , url-loader , asset modules (Webpack 5) |
处理图片/字体 |
Vue/React | vue-loader , @svgr/webpack |
支持框架组件文件 |
Markdown | markdown-loader |
支持 md 文件转 HTML |
1、 什么是 babel-loader
?
babel-loader
是 Webpack 和 Babel 的桥梁,让 Webpack 可以在打包时调用 Babel 进行代码转译 。它本身并不执行语法转换,而是将文件传递给 Babel,Babel 再通过配置(如 .babelrc
)完成真正的转换。
2、你为什么需要 babel-loader
?
现代前端项目通常使用:
-
ES6+ 新语法(如箭头函数、类、模块)
-
JSX(React)
-
TypeScript(结合
@babel/preset-typescript
)
这些语法不是所有浏览器都支持(尤其是 IE / 老旧 Android 浏览器)。所以我们需要 Babel + babel-loader 来 向下编译,以确保兼容性。
如何配置 babel-loader
?
npm install babel-loader @babel/core @babel/preset-env --save-dev
如果你是 React 项目,还需要:
npm install @babel/preset-react --save-dev
如果是 TypeScript 项目,还需要:
npm install @babel/preset-typescript --save-dev
Webpack 配置示例:
module.exports = {
module: {
rules: [
{
test: /\.js$/, // 匹配 JS 文件
exclude: /node_modules/, // 不处理依赖库
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'] // 使用的转换预设
}
}
}
]
}
};
3、核心概念说明
概念 | 含义 |
---|---|
@babel/core |
Babel 的核心库,执行转换 |
babel-loader |
Webpack 的桥接器,调用 Babel |
preset-env |
根据目标浏览器自动选择转换插件 |
preset-react |
转换 React 的 JSX |
exclude: /node_modules/ |
防止 Babel 转换第三方库(提高性能) |
4、常见用途
使用场景 | Babel preset |
---|---|
ES6+ 转 ES5 | @babel/preset-env |
JSX 转义 | @babel/preset-react |
TypeScript | @babel/preset-typescript |
Polyfill(如 Promise ) |
配合 @babel/polyfill 或 core-js |
4、Webpack 如何调用 Loader?
Webpack 内部会做这样的事情:
-
读取资源内容(如
style.css
) -
依次调用匹配的 loader
-
把返回的内容作为模块打包入 bundle
六、构建优化与高级功能
1. 代码分割(Code Splitting)
-
方式一:
entry
多入口分割 -
方式二:动态导入(如
import()
) -
方式三:使用
SplitChunksPlugin
自动提取公共代码
代码分割是指将项目代码打包成多个文件(chunk),按需加载,而不是打成一个大文件。
📌 这样做的好处:
-
减少首次加载时间(只加载入口必要代码)
-
实现懒加载(如路由切换时才加载页面)
-
提升缓存命中率(比如第三方库提取为独立文件)
多入口(entry)分割
当项目有多个完全独立的入口页面(如多页面网站 MPA)。
module.exports = {
entry: {
home: './src/home.js',
admin: './src/admin.js'
},
output: {
filename: '[name].bundle.js', // 输出 home.bundle.js / admin.bundle.js
path: path.resolve(__dirname, 'dist')
}
};
优点 | 缺点 |
---|---|
简单直观 | 不适合组件共享和 SPA |
适合多页面站点 | 无法自动抽取公共依赖 |
使用 SplitChunksPlugin
自动提取公共代码
Webpack 4+ 默认启用的优化插件 ,用于自动将多个入口之间共享的代码抽离成单独的 chunk。
原因:
多个入口或异步模块之间,可能使用相同依赖(如 React、Lodash),如果不处理,会重复打包。
如何启用?
Webpack 4 起就默认开启 了 SplitChunksPlugin
,你也可以手动配置它:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all', // 默认只分 async,改为 all 表示同步和异步都提取
minSize: 20000, // 最小分割体积(单位:字节)
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
打包结果:
-
main.bundle.js
→ 主代码 -
vendors.js
→ 第三方库 -
common.js
→ 多个模块都用到的公共代码
2. Tree Shaking
-
移除未使用代码(只适用于 ES Module)
-
要启用:
-
使用 ES6 模块语法
-
mode: production
或手动启用sideEffects: false
-
Tree Shaking 是一种"静态分析 + 剔除无用代码"的机制 ,可以在构建过程中移除未被使用(import)的代码,从而减小最终的 Bundle 体积。
📌 通俗说:就像把一个大树的枯枝剪掉,只留下你用到的"枝干"。
Tree Shaking 的基本原理
-
基于静态 ES6 Module 分析模块依赖(即
import
/export
) -
Webpack 在构建时标记哪些导出被使用,哪些没有
-
在压缩阶段(如使用
TerserPlugin
),移除未使用的导出函数或变量
启用 Tree Shaking 的前提条件
条件 | 说明 |
---|---|
✅ 使用 ES Module(ES6 import / export ) |
CommonJS(require / module.exports )不支持 |
✅ Webpack 运行在 production 模式下 |
或显式配置压缩插件 |
✅ package.json 中设置 "sideEffects": false |
或单独标记某些文件有副作用 |
✅ 没有副作用的代码 | 纯函数 / 纯模块更容易被剔除 |
3. 懒加载(Lazy Loading)与预加载
-
使用
import()
+ 动态路由实现页面级懒加载 -
webpackPrefetch
、webpackPreload
注释来优化加载时机
1、什么是懒加载(Lazy Loading)?
懒加载 是指将模块延迟加载------不是在首页就加载,而是在用户需要它的时候再去加载对应资源(chunk)。
Webpack 如何实现懒加载?
使用 import()
动态导入语法 ,Webpack 会自动把被动态导入的模块打包成单独的 Chunk 文件,实现按需加载。
2、什么是预加载(Preload)和预取(Prefetch)?
Webpack 提供了两种注释魔法指令,来控制动态模块的"提前加载行为":
1. webpackPrefetch: true
→ 浏览器空闲时加载
import(/* webpackPrefetch: true */ './math.js');
使用时机:你希望 模块稍后会用到,可以让浏览器在空闲时偷偷下载它。
🔹 示例:页面 A 加载后就开始"预取"页面 B 的代码,等用户点击页面 B 时瞬间打开。
2. webpackPreload: true
→ 当前帧就加载
import(/* webpackPreload: true */ './utils.js');
🔹 使用时机:你希望模块尽快加载,但又不想影响主 chunk(比如大图、字体等资源)。
场景 | 使用方式 | 加载时机 | 适用对象 |
---|---|---|---|
懒加载 | import() |
事件触发加载 | 页面、组件、模块 |
预取 Prefetch | import(/* webpackPrefetch */) |
浏览器空闲 | 页面跳转提前加载 |
预加载 Preload | import(/* webpackPreload */) |
当前帧加载 | 高优先级资源加载 |
结合使用示例
// 当前不加载,等用户点击再加载
document.getElementById('btn').onclick = () => {
import(/* webpackChunkName: "math" */ './math.js').then(({ add }) => {
console.log(add(2, 3));
});
};
// 预加载某个 chunk(立即加载,但不影响主线程)
import(/* webpackPreload: true */ './preload-heavy.js');
// 预取某个模块(浏览器空闲时加载)
import(/* webpackPrefetch: true */ './about.js');