如何开发一个webpack插件

123 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第29天,点击查看活动详情

webpack现在已经是一个非常常见的工具了,里面常用的有很多,其中需要注意的是:

  • module, 通常一个module对应一个文件

  • chunk,代码块,一个chunk由多个模块组成,用于代码合并与分割

  • loader,模块转换器,把模块原有的内容按照需求转成新的内容

  • plugin, 扩展插件,可以在webpack构建流程的特定时机改变构建结果,或者产生一些副作用

整体来看,webpack是基于事件流来实现的,核心的概念就是插件机制。

插件由什么构成?

  1. 一个js函数承载所有的内容
  2. 在原型上定义的一个apply方法,插件安装的时候会被执行一次
  3. 指定一个钩子,用来特定的时机做特定的事情
  4. 对webpack实例内部做一些处理
  5. 可以调用webpack提供的回调函数

比如我们要实现一个编译完成后打印hello world的功能,可以这么写:


Class HW Class {

apply(compiler) {

compiler.hooks.done.tap(‘hw plugin’, () => console.log(‘hello world’))

         }

}


其中的compiler包含了webpack运行流程包含的webpack所有信息,options, loaders, plugins等,这个对象在启动的时候被实例化,全局唯一。

我们需要在compiler暴露的钩子上指明我们的tap配置,可以在指定的位置触发你所编写的回调函数。

那么基于核心tapable库的事件机制该怎么使用。我们来举个例子,首先初始化:

Const { SyncHook } = require(‘tapable’);

Const hook = new SyncHook([‘name’]);

然后用这个hook实例去使用,分两步:

  1. 注册监听: hook.tap
  2. 触发监听:  hook.call(…param)

看起来跟我们传统的事件机制的区别,就是事件的触发和监听都是直接关联到一个tapable实例上的,所以只要确保能拿到这个实例就可以。

如果我们自己实现一套事件的触发机制,可以这么实现:

  1. 先保存到全局的对象上,如{hookNameA: cb, hookNameB: cb2}
  2. 然后在触发的地方先生成hook,然后循环遍历全局对象,找到跟这个hook对应的回调, hook.tap(cb) hook.tap(cb2),然后再直接hook.call()

那么这样有什么好处呢,如果有多个事件,就可以通过自然地串联起来,因为tapable支持很多的类型的监听钩子。

另外需要注意的是compilation这个东西,它包含了模块资源、编译生成资源、变化的文件等。每当检测到一个文件变化,一个新的compilation就会生成,代码一次单独的编译。它也有不同的钩子暴露给开发者去使用。