Webpack 插件机制
1. 面试题
Q: Webpack 的插件机制是如何实现的?Plugin 中 apply 方法的作用是什么?什么是 Tapable?
2. 核心解答
Webpack 的本质是一个 事件流机制。它通过 Tapable 这个库,将构建过程中的各个阶段串联起来。
(1) Tapable
Webpack 强依赖 tapable 这个独立的库。它类似于 Node.js 的 EventEmitter,但功能更强大,提供了多种类型的 Hook:
- SyncHook: 同步钩子 (串行执行,不关心前一个回调的返回值)。
- SyncBailHook: 同步熔断钩子 (如果任一回调有返回值,立即停止后续回调)。
- AsyncParallelHook: 异步并行钩子 (Promise.all)。
- AsyncSeriesHook: 异步串行钩子 (Promise.then)。
(2) Compiler 与 Compilation
Webpack 在运行过程中会实例化很多核心对象,最重要的就是这两个:
- Compiler: 代表整个 Webpack 的环境配置。它在 Webpack 启动时被实例化,包含
options, loaders, plugins。它是全局唯一的。
- Compilation: 代表一次具体的编译过程。每当检测到文件变化,就会创建一个新的 Compilation。它可以访问到当前的模块资源、编译生成的资源、变化的文件等。
(3) Plugin 的结构
一个标准的 Plugin 必须是一个类 (Class),且必须提供一个 apply(compiler) 方法。
Webpack 在启动时,会遍历所有配置的 Plugin,调用它们的 apply 方法,并把全局的 Compiler 对象传进去。Plugin 拿到 Compiler 后,就可以在 Compiler 的各种钩子上注册自己的任务。
class MyPlugin {
apply(compiler) {
// 注册 compiler 完成阶段的 Hook
compiler.hooks.done.tap('MyPlugin', (stats) => {
console.log('Build Done!');
});
// 注册 compilation 创建阶段的 Hook
compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
// 在 compilation 对象的 optimize 阶段注册 Hook
compilation.hooks.optimize.tap('MyPlugin', () => {
console.log('Optimizing assets...');
});
});
}
}
3. 面试加分项
Q: Loader 和 Plugin 的运行机制区别?
- Loader: 运行在
compilation 对象构建 Module 的阶段。它直接操作文件内容。
- Plugin: 运行在整个构建流程的各个阶段。它操作的是构建过程 (Process) 和 产物 (Assets)。
Q: 为什么要这样设计?
为了解耦。Webpack 核心代码只负责调度,具体的打包逻辑 (JS 转换、CSS 提取、HTML 生成) 全部由 Plugin 实现。这使得 Webpack 非常灵活,想加什么功能只需写个 Plugin 挂载上去即可,无需修改核心源码。
4. 总结
- Event System: Tapable.
- Objects: Compiler (Global), Compilation (Build).
- Workflow: Register -> Trigger Hooks.