为什么需要? 🔗
作用
快速上手测试 🔗
输入:
src/
index.js
a.js
style.css
node_modules/
lodash
webpack 处理:
1 解析入口,递归解析依赖,(index.js -> a.js -> lodash + style.css
2 loader转换(css -> js模块, ES6 -> ES5)
3 构建依赖图, 划分chunk
4 生成runtime和打包代码
输出dist/
main.js(业务代码+ runtime)
vendor.js(lodash第三方库)
main.css
index.html 引入上面的文件
img/ 处理后的图片资源
npm install --save-dev webpack webpack-cli
npx webpack
源码入口 🔗
- 命令行入口, bin/webpack.js
- 核心逻辑入口, lib/webpack.js
- 核心类: lib/Compiler.js 全局编译控制, lib/Compilation.js 单次编译上下文
核心概念 🔗
解析依赖,编译转换,打包输出。
webpack.config.js配置了入口,输出,loader,插件。
loader, plugin
依赖图的构建,chunk 生成逻辑。
如何处理循环依赖?
webpack 的钩子设计 🔗
钩子设计是支持插件化架构的基础。 tapable库,通过 hook 钩子在编译流程的节点上插入自定义逻辑,扩展 webpack 的功能。
分两步: 订阅和发布。
- 先定义钩子, 及事件触发点
- 插件通过注册钩子,订阅事件,添加自定义逻辑
- 在 webpack 的编译过程中触发钩子,发布事件,执行所有注册的逻辑
钩子类型(同步/异步
- SyncHook, 同步执行,所有注册的回调按顺序执行,不关心返回值
- SyncBailHook,同步执行,若某个回调返回非 undefined, 熔断中断后续执行
- SyncWaterfallHook, 同步执行,上一个回调返回值作为下一个回调的参数,流水线。
- AsyncSeriesHook, 异步串行执行。 Asyc 异步体现在允许注册的回调函数里执行异步操作,并等异步操作完成之后继续后续注册的函数。
- 必须通过 tapAsync, tapPromise 注册订阅事件, 需要显示通知完成,如 callback(), Promise。
- Series 按注册顺序执行
-
AsyncParallelHook, 异步并行执行, 所有回调同时启动,等待全部完成。 🔗
- AsyncSeriesBailHook, 异步串行+熔断。
每个钩子都有以下核心方法:
- tap(name, callback) 注册同步,异步钩子
- tapAsync(name, callback) 注册异步钩子
- tapPromise(name, callback) 注册异步钩子
- call(...args) 触发同步钩子,传入参数
- callAsync(...args, callback) 触发异步钩子
核心对戏那个(Compiler, Compilation)通过继承 Tapable 类拥有钩子能力。
Compiler 的钩子(事件节点)。
initialize SyncHook, Compiler 实例初始化完成时触发, 插件初始化的准备工作。
entryOption, SyncBailHook, 解析入口配置之后触发,修改入口配置如动态添加入口。
beforeRun, AsyncSeriesHook, 开始编译前 run 方法执行前,清理缓存,初始化日志。
run, AsyncSeriesHook, 开始执行编译流程, 记录编译开始时间。
compile, SyncHook, 开始创建 Compilation 实例前触发, 修改编译参数。
make, AsyncParallelHook, 开始构建模块(依赖计算的起点), 触发入口模块的加载。
emit, AsyncSeriesHook, 资源输出到磁盘前, 优化资源(如压缩, 添加注释)
afterEmit, AsyncSeriesHook, 资源输出到磁盘后, 通知资源已生成如上传 CDN
done, AsyncSeriesHook, 所有编译工作完成后, 输出构建统计信息(如打包体积)。
Compilation 钩子(事件节点)。 负责模块解析,依赖处理,代码生成等具体工作。
- addEntry, SyncHook,
- buildModule, SyncHook, 触发时机时开始构建模块前,作用修改模块的 loader 配置
- successedModule, SyncHook, 时机模块构建成功后,分析模块依赖关系。
- seal, SyncHook, 编译结果密封前(不再修改模块),最终优化代码块 chunk 的结构。
- optimize, SyncHook, 开始优化代码如 tree-shaking, 自定义代码优化逻辑。
- afterOptimizeChunks, SyncHook, 代码块 chunk 优化完成后,调整 chunk 结构如合并 chunk。
- emitAsset, SyncHook, 生成单个资源文件时, 修改资源内容(如添加版权注释)。
其他核心对象的钩子。 Module 相关, NormalModule 的 loader 钩子,控制 loader 执行。 chunk 相关, ChunkTemplate 的 render 钩子,控制代码块生成逻辑。解析器相关, Parser 的 import 钩子,识别 import 语法提取依赖。
一些源码片段 🔗
🔗
在 JSDoc 里,@typedef 用来定义一个 自定义类型。
/**
* @typedef {类型} 名字
*/
这段 JSDoc 是 webpack 抽象的文件系统接口定义,方便切换硬盘 fs 和内存 memfs,从而让 webpack 的输出逻辑和具体存储方式解耦。
/**
* @typedef {object} OutputFileSystem
* @property {WriteFile} writeFile
* @property {Mkdir} mkdir
* @property {Readdir=} readdir
* @property {Rmdir=} rmdir
* @property {Unlink=} unlink
* @property {Stat} stat
* @property {LStat=} lstat
* @property {ReadFile} readFile
* @property {((path1: string, path2: string) => string)=} join
* @property {((from: string, to: string) => string)=} relative
* @property {((dirname: string) => string)=} dirname
*/
插件如何使用钩子? 🔗
插件通过注册钩子介入到了 Webpack 的编译流程中。
// 自定义插件, 在资源输出前添加版权注释。
// 插件通过apply方法接收compiler实例,调用compiler.hooks.xxx.tapAsync注册钩子。
class AddCopyrightPlugin {
apply(compiler) {
// 注册Compiler 的emit钩子
compiler.hooks.emit.tapAsync(
"AddCopyrightPlugin", // 插件名称
(compilation, callback) => {
// compilation.assets 包含所有待输出的资源。
Object.keys(compilation.assets).forEach((filename) => {
if (filename.endsWith(".js")) {
const source = compilation.assets[filename].source();
const withCopyright = `/* version @2025 xx */\n${source}`;
// 更新资源内容
compilation.assets[filename] = {
source: () => withCopyright,
size: () => withCopyright.length,
};
}
});
// 异步钩子需要调用callback表示完成。
callback();
}
);
}
}
module.exports = {
plugins: [new AddCopyrightPlugin()],
};
原理 🔗
wepack 核心仅实现基础打包逻辑,复杂功能都是通过插件+钩子实现的。高度可扩展,低耦合,核心流程与扩展逻辑分离,插件无需修改 webpack 源码即可介入任意阶段。灵活性,支持同步,异步钩子,满足不同场景的需要。
webpack 的钩子系统基于 tapable 的事件驱动架构实现
- 核心对象 Compiler, Compilation 等定义了覆盖全流程的钩子节点
- 插件通过 tap,tapAsync 注册钩子,订阅了事件,插入自定义逻辑
- 在 webpack 的编译关键节点触发钩子节点上的事件,触发钩子上注册的所有逻辑。
遇到配置上的问题 🔗
npm i 🔗
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: webpack@5.101.3
npm ERR! Found: pug@3.0.3
npm ERR! node_modules/pug
npm ERR! dev pug@"^3.0.3" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer pug@"^2.0.0" from pug-loader@2.4.0
npm ERR! node_modules/pug-loader
npm ERR! dev pug-loader@"^2.4.0" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR!
npm ERR!
npm ERR! For a full report see:
npm ERR! /Users/gonglei/.npm/_logs/2025-09-03T06_51_59_179Z-eresolve-report.txt
npm uninstall pug
npm install pug@2.0.4 --save-dev
npm install