实现一个webpack插件

Posted on 2018-07-06

webpack插件的特点:

  • 插件是一个JavaScript对象
  • 对象原型上有apply方法, 该方法有接收compiler对象参数
  • compiler可以挂载 webpack 事件钩子函数
  • 事件钩子的回调函数里能拿到编译后的 compilation 对象,如果是异步钩子还能拿到相应的 callback

插件模板示例:

function RunLoggerPlugin (options) {
  // options保存着插件的自定义配置
  this.options = options
}
RunLoggerPlugin.prototype.apply = function (compiler) {
  compiler.plugin('emit', (compilation, callback) => {
    console.log('emit RunLoggerPlugin')
  })
}

module.exports = RunLoggerPlugin

上面提到了compilercompilation、以及挂载的apply函数和webpack的钩子函数,下面就来详细介绍这些概念。

Compiler

compiler 对象是 webpack 的编译器对象,compiler 对象会在启动 webpack 的时候被初始化

// webpack/lib/webpack.js

const Compiler = require("./Compiler")

// options 为webpack配置对象
const webpack = (options, callback) => {
  // ...
  // 初始化 compiler 对象
  let compiler = new Compiler(options.context)
  // 往 compiler 添加初始化参数
  compiler.options = options

  // 往 compiler 添加 Node 环境相关方法
  new NodeEnvironmentPlugin().apply(compiler)
  
  // 遍历插件传入plugins数组, 依次执行其apply方法
  for (const plugin of options.plugins) {
    plugin.apply(compiler);
  }
  // ...
}

上述代码描述了compiler对象的创建和初始化过程, 调用compiler.options = options时, 也就相当于给 compiler 对象上绑定了所有 webpack 可自定义操作的配置,例如 loader 的配置,plugin 的配置,entry 的配置等各种原始 webpack 配置等。此时我们自定义的插件上即可通过 compiler 对象拿到 webpack 的主环境所有的信息。

同时上述代码也说明了为什么需要在插件上绑定apply方法,因为webpack启动时会依次调用插件的apply方法并传入compiler对象。

Compilation

compilation 对象继承自 compiler 对象, 所以可以获取到 compiler 对象中的属性

compilation 对象代表了一次单一的版本构建和生成资源。开发环境下,当运行 webpack 时,每当检测到一个文件变化,一次新的编译将被创建,生成一组新的编译资源以及新的 compilation 对象。一个 compilation 对象包含了 当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息

由此可见,如果涉及到需要改变编译后的资源,compilation 对象必不可少。

Tapable

Compiler Compilation 都继承自Tapablewebpack 的插件架构主要基于 Tapable 实现的。

具体请看Tabable详解

webpack的钩子函数

webpack运行过程中,会经历多个处理过程,而每个处理过程都会调用相应的钩子函数。其中钩子函数分为

  • compiler hook
  • compilation hook

compiler hook

编译过程中的事件钩子函数

钩子函数 作用 参数 类型
entry-option entry 配置项处理过之后,执行插件 / SyncBailHook
beforeRun compiler.run() 执行之前的钩子 compiler AsyncSeriesHook
run 开始编译 compiler AsyncSeriesHook
beforeCompile 编译参数创建之后 compilationParams AsyncSeriesHook
compile 真正开始的编译,在创建新 compilation 之前 compilationParams SyncHook
compilation compilation 创建完成 compilation SyncHook
make 从 entry 开始递归分析依赖,准备对每个模块进行 build compilation AsyncParallelHook
afterCompile 编译 build 过程结束 compilation AsyncSeriesHook
shouldEmit 是否该生成资源文件 compilation SyncBailHook
emit 在生成资源并输出到目录之前 compilation AsyncSeriesHook
afterEmit 在生成资源并输出到目录之后 compilation AsyncSeriesHook
done 完成编译 stats AsyncSeriesHook
failed 编译失败 error SyncHook
invalid 监听模式下,编译无效时 fileName, changeTime SyncHook

更多的钩子函数请查看官方文档

compilation hook

compile对象中名为compilation的钩子函数中会首次产出compilation,前面提到过compilation对象代表了一次单一的版本构建和生成资源。所以可以在构建或者生成资源的过程中对资源进行需要的处理。

compiler.plugin("compilation", function(compilation) {
  // 随后所有的方法都从 compilation.plugin 上得来
});

compilation对象的钩子函数如下所示

钩子函数 作用 参数
normal-module-loader 所有模块被加载 loaderContext, module
seal 编译的封闭已经开始,这个时候再也收不到任何的模块了 /
optimize webpack 已经进入优化阶段 /
optimize-modules 模块的优化  
optimize-chunks chunk 优化阶段。可以拿到模块的依赖,loader等 chunks
additional-assets 为 compilation 对象创建额外的 assets compilation
optimize-chunk-assets 优化 chunk 的 assets chunks, callback
optimize-assets 优化所有的 assets asstes, callback

更多的钩子函数请查看官方文档

示例:

compiler.plugin("compilation", function(compilation) {
  compilation.plugin('optimize', function () {
    // webpack进入优化阶段
  })
});

实现一个webpack插件