Plugin是什么

plugin 是 Webpack 中的通过在构建流程中注入各类钩子,用于扩展 Webpack 功能,完成除资源模块打包以外自动化工作的机制。它与 loader 的区别主要在它们的目标不同,loader 的操作对象是文件,而 plugin 只是在 loader 结束后基于事件流机制做一些特殊的操作,它通过广播/订阅机制,允许每个 plugin 在处理流程的特定时机介入流程的执行,从而改变特定流程的运作。通俗的说,loader 是从纵向扩展了 webpack的构建能力,而plugin是横向的,它们都是通过往 Webpack 的声明周期钩子中挂载任务函数实现的。
**

使用

配置

  1. plugins: [
  2. new HtmlWebpackPlugin({template: './src/index.html'})
  3. ]

编写示例(以移除注释为例)

  1. class RemoveCommentsPlugin {
  2. // 该apply必须定义
  3. apply (compiler) {
  4. // 使用 emit 这个钩子
  5. compiler.hooks.emit.tap('RemoveCommentsPlugin', compilation => {
  6. // compilation:此次打包的上下文
  7. for (const name in compilation.assets) {
  8. if (name.endsWith('.js')) {
  9. const contents = compilation.assets[name].source()
  10. const noComments = contents.replace(/\/\*{2,}\/\s?/g, '')
  11. compilation.assets[name] = {
  12. source: () => noComments, // 返回修改后的新内容
  13. size: () => noComments.length // 返回内容大小
  14. }
  15. }
  16. }
  17. })
  18. }
  19. }
  20. module.exports = RemoveCommentsPlugin

底层工作流程

对插件(plugin)来说,它大体的运作只分为三个部分:

  1. 创建钩子: Webpack 在自身内部对象中创建出各种钩子。
  2. 注册事件: 插件将自己的任务函数注册到对应钩子上,交予 Webpack。
  3. 调用任务: 在 Webpack编译过程中,会适时触发对应的钩子来触发插件的方法。

创建钩子

Tapable

Webpack 的内部通过使用 tapable 这个库来实现各类钩子的创建,Webpack 中的 ComplierCompliation 等重要内部对象都继承了 Tapable 类来挂载钩子。

  1. const {
  2. SyncHook,
  3. SyncBailHook,
  4. SyncWaterfallHook,
  5. SyncLoopHook,
  6. AsyncParallelHook,
  7. AsyncParallelBailHook,
  8. AsyncSeriesHook,
  9. AsyncSeriesBailHook,
  10. AsyncSeriesWaterfallHook
  11. } = require("tapable");

其中的钩子又可以从执行方式和同步、异步中组合,其中执行方式包含了四种类型:

类型 说明
Basic 简单的调用注册在钩子上的函数
Waterfall 与Basic类型的钩子类似,但它会返回上一个函数的结果以进行链式调用
Bail 允许中途退出,只要中间有任何一个函数存在返回值,便中断后续的执行
Loop 直到所有的插件都返回 undefined 后才停止的钩子,在收到任何一个非 undefined 值时,钩子会从头开始依次调用插件。

而异步、同步则分为3种类型:

类型 说明
Sync 同步钩子,只允许同步任务函数
AsyncSeries 可同步可异步,支持 callbackPromise 形式的异步调用,但其内的任务函数是被串行(连续)调用的。
AsyncParallel 与 AsyncSeries 类的钩子类似,但其内的任务函数是并行调用的。

其中同步的钩子都可以使用 fooHook.tap() 进行调用,而基于回调和 Promise 的钩子则分别通过 fooHook.tapAsync()fooHook.tapPromise() 进行调用。

Compiler Hooks

多数面向用户的插件都注册在编译器的钩子上,它是 Webpack 创建实例的主引擎,代表着整个 Webpack 自启动到关闭的生命周期,它的一些常用钩子如下:

钩子 钩子类型 调用时机
run AsyncSeriesHook 最开始
make AsyncParallelHook 完成一次编译后
emit AsyncSeriesHook 生成文件到输出目录前执行,它的回调是 compilation
done AsyncSeriesHook 最后,它的回调是 stats

Compilation Hooks

该阶段钩子代表了用于对应 Webpack 一次编译过程。它的一些常用钩子如下:

钩子 钩子类型 调用时机
buildModule SyncHook 在模块开始编译前触发,常用于修改模块
finishModules AsyncSeriesHook 所有模块都编译成功时
optimize SyncHook 优化阶段开始时
optimizeChunks SyncBailHook 代码块优化阶段开始时,它的回调是 chunks ,常用于执行对代码块的优化

JavaScriptParser Hooks

解析器用于解析每个正在处理的模块,它的一些常用钩子如下:

钩子 钩子类型 调用时机
import SyncBailHook 为每个 import 语句调用
evaluate SyncBailHook 计算表达式时调用
statement SyncBailHook 为代码每个一解析的句子调用

注册事件

在读取配置阶段,通过 new FooPlugin() 创建出了对应的插件并获得它的实例。在编译器(Compiler)实例化以后,通过 FooPlugin.apply(compiler)将编译器实例传入插件中,这也是为什么插件都必须声明 apply(compiler) 方法的原因。

在插件实例获取到编译器实例之后,就可以通过调用 compiler.plugin() 方法订阅对应 Webpack 钩子的广播的事件,并且利用编译器实例操作 Webpack。

调用任务

见 Tapable 的使用部分。