Webpack Compiler 源码全面解析
定位:Compiler 是 Webpack 的"大脑",负责调度整个构建流程,管理生命周期、插件系统和全局资源。
1. 源码结构与核心类定义
源码文件 : lib/Compiler.js
继承关系 : 基于 Tapable
实现事件钩子机制。
javascript
const { Tapable } = require("tapable");
class Compiler extends Tapable {
constructor(context) {
super();
this.context = context; // 上下文(配置、输入路径等)
this.hooks = { /* 生命周期钩子 */ };
this.options = {}; // 合并后的配置(webpack.config.js)
this.running = false; // 构建状态标记
this.compilation = null; // 当前活动的 Compilation 实例
}
}
关键点:
- 继承
Tapable
,具备事件发布订阅能力。 - 管理全局状态(如
running
标记是否正在构建)。
2. 生命周期钩子(Hooks)
Compiler 定义了完整的构建阶段钩子,供插件监听。
javascript
class Compiler {
constructor() {
// 核心钩子列表(部分)
this.hooks = {
environment: new SyncHook([]), // 环境准备完毕
afterEnvironment: new SyncHook([]),
initialize: new SyncHook([]),
run: new AsyncSeriesHook(["compiler"]), // 构建启动
compile: new SyncHook(["params"]), // 开始编译
compilation: new SyncHook(["compilation", "params"]), // Compilation 创建
make: new AsyncParallelHook(["compilation"]), // 模块构建阶段
emit: new AsyncSeriesHook(["compilation"]), // 生成资源前
afterEmit: new AsyncSeriesHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]), // 构建完成
failed: new SyncHook(["error"]) // 构建失败
};
}
}
钩子触发顺序:
environment
→afterEnvironment
→initialize
run
→compile
→compilation
→make
→emit
→afterEmit
→done
- 若出错 →
failed
3. 构建流程源码解析
入口方法 : Compiler.run()
javascript
class Compiler {
run(callback) {
if (this.running) return callback(new ConcurrentCompilationError());
this.running = true;
// 1. 触发 run 钩子(插件可在此阶段拦截)
this.hooks.run.callAsync(this, (err) => {
if (err) return callback(err);
// 2. 执行构建(核心逻辑)
this.compile((err, compilation) => {
if (err) return callback(err);
// 3. 触发 emit 钩子(插件可修改最终资源)
this.hooks.emit.callAsync(compilation, (err) => {
if (err) return callback(err);
// 4. 输出文件到磁盘
this.outputFileSystem.writeFile(...);
// 5. 触发 done 钩子,传递统计信息
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, () => {
this.running = false;
callback(null, stats);
});
});
});
});
}
compile(callback) {
// 创建 Compilation 实例(单次构建的上下文)
const params = this.newCompilationParams();
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
this.hooks.compilation.call(compilation, params);
// 触发 make 钩子(插件开始构建模块)
this.hooks.make.callAsync(compilation, (err) => {
if (err) return callback(err);
// 编译完成,进入优化阶段
compilation.finish(err => {
compilation.seal(err => {
callback(err, compilation);
});
});
});
}
}
流程解析:
- 启动阶段 :
run()
方法检查状态,触发run
钩子。 - 编译阶段 :
- 调用
compile()
创建Compilation
实例。 - 触发
compile
和compilation
钩子,通知插件。 - 触发
make
钩子,插件(如EntryPlugin
)开始递归构建模块依赖图。
- 调用
- 优化与生成 :
compilation.seal()
执行代码优化(Tree Shaking、代码分割等)。- 触发
emit
钩子,插件可修改最终资源(如添加文件注释)。 - 写入文件到磁盘。
- 收尾阶段 : 触发
done
钩子,传递统计信息(stats
)。
4. 插件系统工作原理
插件注册 : 通过 compiler.apply()
或配置中的 plugins
数组加载。
javascript
// 示例:一个简单插件监听 done 钩子
class MyPlugin {
apply(compiler) {
compiler.hooks.done.tap("MyPlugin", stats => {
console.log("构建完成!耗时:", stats.toJson().time);
});
}
}
插件触发逻辑:
- 初始化阶段 : 在
compiler.run()
前,所有插件通过apply()
方法注册钩子监听。 - 构建阶段: 各生命周期钩子按顺序触发,执行插件的回调逻辑。
5. 文件监听与热更新
源码逻辑 : watch()
方法实现持续监听文件变化。
javascript
class Compiler {
watch(watchOptions, handler) {
// 1. 创建 Watching 实例
const watching = new Watching(this, watchOptions, handler);
return watching;
}
}
class Watching {
constructor(compiler, watchOptions, handler) {
this.compiler = compiler;
// 2. 初始化文件监听器(基于 chokidar 库)
this.watcher = this.compiler.watchFileSystem.watch(
watchOptions,
(err, filesModified) => {
// 3. 文件变化时触发重新构建
this.invalidate();
}
);
}
invalidate() {
// 4. 重新执行 Compiler.run()
this.compiler.run((err, stats) => { ... });
}
}
关键点:
- 基于
watchFileSystem
(默认使用内存文件系统)监听文件变化。 - 文件变化后触发
invalidate()
,重新执行构建流程。
6. Compiler 与 Compilation 的关系
- Compiler :
- 全局唯一,管理多次构建(如
watch
模式下的重复构建)。 - 负责生命周期钩子和插件调度。
- 全局唯一,管理多次构建(如
- Compilation :
- 单次构建的上下文,由
Compiler
创建。 - 管理模块、依赖、Chunk 等具体资源。
- 单次构建的上下文,由
创建 Compilation 的源码:
javascript
class Compiler {
newCompilation(params) {
const compilation = new Compilation(this);
this.hooks.compilation.call(compilation, params);
return compilation;
}
}
7. 核心设计思想总结
- 事件驱动架构 : 通过
Tapable
钩子实现插件化,解耦核心逻辑与扩展功能。 - 分层管理 :
Compiler
负责全局调度,Compilation
处理单次构建细节。- 模块构建、优化、代码生成分阶段执行。
- 可扩展性: 插件系统允许开发者介入任何构建阶段(如修改配置、优化代码、添加资源)。
- 高效监听: 基于文件系统监视器和内存缓存,实现快速增量构建。
附:关键流程图解
scss
启动命令 → Compiler.run()
↓
触发 run 钩子 → 插件逻辑
↓
Compiler.compile() → 创建 Compilation
↓
触发 make 钩子 → EntryPlugin 构建入口模块
↓
模块递归构建 → Resolver → ModuleFactory → Parser
↓
Compilation.seal() → 优化 Chunk
↓
触发 emit 钩子 → 插件修改资源
↓
写入文件系统 → 触发 done 钩子
通过深入 Compiler 源码,可以清晰理解 Webpack 如何协调构建流程,以及如何通过插件机制实现高度灵活性。