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.