Webpack 构建流程
约 1189 字大约 4 分钟
2025-06-17
以下是 Webpack 构建的完整流程的深度解析,涵盖从启动到输出的每个关键环节及其底层机制:
一、初始化阶段
1、配置准备
- 多来源合并:合并 CLI 参数 (
--mode=production
)、配置文件 (webpack.config.js
) 和默认配置 - 环境变量注入:通过
DefinePlugin
替换process.env.NODE_ENV
- 配置验证:校验
entry
/output
/loader
等关键配置的合法性
2、核心对象实例化
Compiler(环境配置)
- 继承
Tapable
,管理 40+ 个生命周期钩子(如beforeRun
/compile
/emit
) - 持有完整的配置信息
this.options
Compilation(单次构建上下文)
- 存储模块依赖图、Chunk 关系、生成的 assets
- 每次构建(包括 watch 模式下的增量构建)都会创建新实例
3、插件系统初始化
执行顺序
按配置数组顺序同步调用各插件的 apply
方法
钩子类型
SyncHook
(同步)AsyncSeriesHook
(异步串行)AsyncParallelHook
(异步并行)
钩子示例
// 插件注册示例
export class MyPlugin {
apply(compiler) {
compiler.hooks.done.tap("MyPlugin", (stats) => {
console.log("构建完成!");
});
}
}
二、编译阶段(构建模块依赖图)
1、入口解析
路径解析规则
resolve: {
extensions: ['.js', '.ts'], // 自动补全后缀
alias: { '@': path.resolve('src') }, // 路径别名
modules: ['node_modules'] // 模块搜索目录
}
动态入口支持
entry: () => Promise.resolve("./src/main.js");
2、递归构建依赖树
- 循环检测:避免循环引用导致无限递归
- 并行加载:通过
asyncLib
库实现异步并发处理
3、模块处理流水线
关键步骤
- Resolver:
- 使用
enhanced-resolve
定位模块绝对路径
- 使用
- Loader 执行:
- Pitch 阶段:从右到左执行
loader.pitch()
- Normal 阶段:从左到右执行
loader()
- 数据流:支持链式传递(前一个 loader 的结果是下一个的输入)
- Pitch 阶段:从右到左执行
- AST 解析:
- 使用
acorn
生成 AST - 识别
import/require
语句收集依赖
- 使用
- 模块实例化:
- 包含源码、依赖数组、hash 等元信息
- 缓存到
compilation.modules
中
三、优化阶段(代码加工)
1、Chunk 生成
分组策略
- 初始 Chunk(entry 直接引用)
- 异步 Chunk(
import()
动态加载) - Runtime Chunk(Webpack 运行时代码)
代码分割
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
}
2、高级优化
Tree Shaking
- 标记未使用的
export
(通过SideEffectsFlagPlugin
) - 使用
TerserPlugin
删除死代码
Scope Hoisting
- 将模块合并到单一函数作用域
- 减少闭包数量提升运行性能
3. 插件优化介入
时机钩子
optimizeChunks
:修改 Chunk 分组optimizeModules
:修改模块内容afterOptimizeTree
:优化完成后
四、生成阶段(输出结果)
1. 模板渲染
模板类型
MainTemplate
:处理入口 ChunkChunkTemplate
:处理普通 ChunkRuntimeTemplate
:生成运行时代码
渲染流程
// 伪代码示例
for (const chunk of chunks) {
const source = template.render(chunk);
assets[chunk.filename] = source;
}
2. 资源生成
文件 Hash
[contenthash]
:基于文件内容生成[chunkhash]
:基于 Chunk 内容生成
SourceMap
- 通过
devtool
配置生成策略(如source-map
/eval-cheap-source-map
)
3. 写入磁盘
输出前钩子
emit
:最后修改 assets 的机会afterEmit
:文件已写入磁盘
文件系统交互
- 开发模式使用内存文件系统 (
memfs
) - 生产模式直接写入物理磁盘
五、收尾阶段
1. 统计信息生成
Metrics 收集
- 构建时间
- Chunk 大小分布
- 模块数量统计
自定义报告
stats: {
colors: true,
modules: false,
children: false
}
2、清理工作
- 临时文件清理:如删除旧的缓存文件
- 内存释放:清除不再使用的数据结构
3、回调通知
- 用户回调:执行配置中的
callback(err, stats)
- Watch 模式:准备接收文件变化事件
六、高级流程(附加场景)
1. Watch 模式
文件监听
- 基于
chokidar
库实现 - 配置
watchOptions.poll
可轮询检测
增量构建
- 复用未变更模块的缓存
- 仅重新构建受影响的部分
2、HMR 热更新
// HMR 运行时逻辑
if (module.hot) {
module.hot.accept("./module.js", () => {
// 模块更新后的回调
});
}
通信流程
- WebSocket 接收 hash 通知
- 下载更新的 manifest 和 chunk
- 执行模块替换逻辑
3、持久化缓存
cache: {
type: 'filesystem',
version: '1.0' // 缓存版本标识
}
缓存级别
- 模块解析结果
- Chunk 生成结果
- 构建环境快照
关键设计思想
- 事件驱动架构:通过 Tapable 实现插件系统
- 一切皆模块:JS/CSS/图片等统一处理
- 依赖即资源:通过 AST 静态分析建立依赖图
- 增量处理:缓存 + 依赖追踪实现高效 rebuild
理解完整流程后,可以更精准地:
- 优化构建性能(如配置缓存、并行处理)
- 编写高效 Loader/Plugin
- 调试构建问题(通过
stats
或profiling
)
更新日志
2025/8/24 08:17
查看所有更新日志
e7112
-1于