插件向第三方开发者展示了webpack引擎的全部潜力。使用分段构建回调,开发人员可以将自己的行为引入webpack构建过程中。构建插件比构建加载器要高级一些,因为你需要了解一些webpack的底层内部结构,以便与它们挂钩。准备阅读一些源代码!

在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

一个webpack的插件由以下几部分组成:

  • 一个 JS 命名函数或者类
  • 在其原型中定义apply方法
  • 指定要进入的事件钩子
  • 操作webpack内部实例特定的数据
  • 在功能完成后调用webpack提供的回调函数

下面是一个简单的插件实例:

// A JavaScript class.
class MyExampleWebpackPlugin {
  // 定义' apply '作为它的原型方法,compiler作为它的参数
  apply(compiler) {
    // 指定要绑定的事件钩子
    compiler.hooks.emit.tapAsync(
      'MyExampleWebpackPlugin',
      (compilation, callback) => {
        console.log('This is an example plugin!');
        console.log(
          'Here’s the `compilation` object which represents a single build of assets:',
          compilation
        );

        // 使用webpack提供的插件API来构建
        compilation.addModule(/* ... */);

        callback();
      }
    );
  }
}

在插件开发中最重要的两个资源就是compiler和compilation对象。理解它们的角色是扩展 webpack 引擎重要的第一步。

compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。

compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。

# 基本插件架构

  • 插件是由「具有 apply 方法的 prototype 对象」所实例化出来的
  • 这个 apply 方法在安装插件时,会被 webpack compiler 调用一次
  • apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象
class HelloWorldPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('Hello World Plugin', (
      stats /* stats is passed as an argument when done hook is tapped.  */
    ) => {
      console.log('Hello World!');
    });
  }
}

module.exports = HelloWorldPlugin;

# 异步事件钩子

# 同步

tap方法

class DonePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    compiler.hooks.done.tap("DonePlugin", (stats) => {
      console.log("Hello ", this.options.name);
    });
  }
}
module.exports = DonePlugin;

# 异步

当我们使用tapAsync方法进入插件时,我们需要调用回调函数,它作为函数的最后一个参数提供。

class DonePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    compiler.hooks.done.tapAsync("DonePlugin", (stats, callback) => {
      console.log("Hello ", this.options.name);
      callback();
    });
  }
}
module.exports = DonePlugin;

当我们使用tapPromise方法访问插件时,我们需要返回一个promise,当异步任务完成时它会解析。

class DonePlugin {
  constructor(options) {
    this.options = options;
  }
  apply(compiler) {
    compiler.hooks.done.tapPromise("DonePlugin", (stats, callback) => {
      return new Promise((resolve, reject) => {
        console.log("Hello ", this.options.name);
        callback();
      });
    });
  }
}
module.exports = DonePlugin;

# 使用插件

要安装这个插件,只需要在你的 webpack 配置的 plugin 数组中添加一个实例

const DonePlugin = require("./plugins/DonePlugin");
module.exports = {
  entry: "./src/index.js",
  output: {
    path: path.resolve("build"),
    filename: "bundle.js",
  },
  plugins: [new DonePlugin({ name: "zhufeng" })],
};

Webpack 启动后,在读取配置的过程中会先执行 new DonePlugin(options) 初始化一个 DonePlugin 获得其实例。 在初始化 compiler 对象后,再调用 donePlugin.apply(compiler) 给插件实例传入 compiler 对象。 插件实例在获取到 compiler 对象后,就可以通过 compiler.hooks 监听到 Webpack 广播出来的事件。 并且可以通过 compiler 对象去操作 Webpack。

参考:

Writing a Plugin (opens new window)

Webpack原理-编写Plugin (opens new window)