【webpack】plugin进阶

52 阅读3分钟

Plugin进阶

写在前面(题外话)

其实webpack大体上的学习我早就完成了,只是人懒,不想去写博客。我感觉很多人应该都像我一样吧,懒得去写文档。不过一想到连loader都写了,有始有终干脆把plugin也写了好了。

介绍

首先来看一下官方给Plugin的定义。

插件是 webpack 生态系统的重要组成部分,为社区用户提供了一种强大方式来直接触及 webpack 的编译过程(compilation process)。插件能够 钩入(hook) 到在每个编译(compilation)中触发的所有关键事件。在编译的每一步,插件都具备完全访问 compiler 对象的能力,如果情况合适,还可以访问当前 compilation 对象。

根据介绍可以看得出来,plugin的编写逃不开两个对象compilercompilation。而这两个对象都是继承自Tapable类。而它对外暴露了 taptapAsync 和 tapPromise 等方法, 插件可以使用这些方法通过不同hooks绑定进入对应的构建进程中,并在构建过程中触发。

Hook Types

上面提到了Tapable会提供不同的hooks来使用taptapAsync 和 tapPromise来绑定进构建进程。

既然如此,来进一步了解一下,hooks有哪些

根据执行顺序区分

  • Basic 会按照顺序执行每个方法,但不关心方法的返回值
  • Waterfall 会按照顺序执行每个方法,但会接受上一个函数的返回值,也会传递返回值给下一个函数
  • Bail 当函数返回了非undefined值时,会直接阻断接下来的函数运行
  • Loop 当函数返回了非undefined值时,会重新开始该顺序的函数执行

根据执行性质区分

  • Sync 同步执行,只能通过tap绑定
  • AsyncSeries 异步串联执行,能通过taptapAsync.tapPromise绑定
  • AsyncParallel 异步并联执行,能通过taptapAsync.tapPromise绑定

为什么要分成两个大块呢,因为这两种区分方式可以进行两两组合,成为一种新的hook。所以Tapable一共给我们提供了以下这么多的hook

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");

compiler & compilation

Compiler 模块是 webpack 的支柱引擎,它通过 CLI 或 Node API 传递的所有选项,创建出一个Compiler 实例。它扩展(extend)自 Tapable 类,以便注册和调用插件。大多数面向用户的插件首,会先在 Compiler 上注册。

Compilation 模块会被 Compiler 用来创建新的编译(或新的构建)。compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。它会对应用程序的依赖图中所有模块进行字面上的编译(literal compilation)。在编译阶段,模块会被加载(loaded)、封存(sealed)、优化(optimized)、分块(chunked)、哈希(hashed)和重新创建(restored)。

compiler是构建之初就已经创建,并且贯穿webpack整个生命周期,即每次运行webpack(或运行 npm run serve 和 npm run build 等) 构建时实例,且只有一个。

compilation: compilation是到准备编译模块时,才会创建compilation对象,是 compile - make 阶段主要使用的对象。

这两个对象中的hooks如下:

compiler 钩子 | webpack 中文网 (webpackjs.com)

compilation 钩子 | webpack 中文网 (webpackjs.com)

编写plugin

class myPlugin {
  constructor (options) {
    // 获取插件配置项
    this.filename = options;
  }
  
  apply(compiler) {
    compiler.hooks.compile.tap('MyPlugin', (params) => {
      console.log('以同步方式触及 compile 钩子。');
    });
  
    compiler.hooks.run.tapAsync('MyPlugin', (source, target, routesList, callback) => {
      console.log('以异步方式触及运行钩子。');
      callback();
    })
    
    compiler.hooks.run.tapPromise('MyPlugin', (source, target, routesList) => {
      return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => {
      console.log('以异步的方式触发具有延迟操作的钩子。');
    });
    
    compiler.hooks.run.tapPromise('MyPlugin', async (source, target, routesList) => {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      console.log('以异步的方式触发具有延迟操作的钩子。');
    });
  }
}

module.exports = myPlugin