自定义webpack插件

580 阅读5分钟

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

前面我们说到了webpack插件的基础使用以及原理,这一章我们将看看如何自己创建一个webpack插件。

回顾

我们在上一章webpack plugins中已经说到了,一个webpack实际上一个apply函数,此函数是基于webpack提供的compiler对象。以及其自带的事件钩子。从而可以让我们在一个编译过程的任何阶段我们都可以处理我们所需要处理的事情。因此一个插件应该由以下几部分组成:

  1. 一个函数,此函数可以是对象中的具体函数,也可以是自定义的一个函数
  2. 函数的原型链上需要有一个apply方法,此方法参数有一个,那就是compiler.
  3. 一些webpack基于plugins提供的事件钩子。
  4. 回调函数。 因此我们可以将一个插件需要的格式写出:
class MyPlugin {
    constructor(options){
    }
    apply(compiler) {
        compiler.hooks.事件名称.事件类型('插件名称',(compilation)=>{
            // 处理自己所需要处理的事件
            ....
            // 处理完成后的回调
            callBack();
        })
    }
}

compiler常见的事件名称

上面写到的例子中的hooks后所对应的事件名称为compiler所拥有的事件名称,下面我们来看下都有哪些常见的事件名称

run

开始读取records前,一般用于插件开始启动的时候。用来处理在编译开始所需要执行一些事件。使用方法为:

apply(compiler){
    compiler.hooks.run.tag('插件名称',(compilation)=>{
        console.log('编译开始');
    })
}

watchRun

监听编译,当编译开始的时候会触发此方法,但是实际上此方法是在编译以前就执行了。使用方法:

apply(compiler){
    compiler.hooks.watchRun.tag('插件名称',(compilation)=>{
        console.log('编译开始');
    })
}

beforeRun

compiler.run()方法前执行。

beforeCompile

在正式编译之前,执行此方法

compiler

在正式编译的时候,触发此方法。

afterCompiler

在编译执行完前触发此方法

emit

在编译完成后将资源生成到output配置的目录之前触发此方法。

afterEmit

在将编译后的资源生成到output配置的目录之后触发此方法。

done

编译完成后触发此方法

failed

编译失败后会触发此方法 以上为常用的钩子,如果想要看更多的事件名称,可以通过complier钩子查看

函数的注册

我们都知道,任何一个方法都需要通过一种注册方式,将方法绑定到我们需要用的地方。在webpack中对于plugins的事件注册。在上一章中我们介绍过了。是通过三种方式来注册,分别是tap,tapAsync,tapPromise.下面本别说下具体的使用:

tap

用来注册同步以及异步钩子。使用如下

apply(compiler){
    compiler.hooks.watchRun.tag('插件名称',(compilation)=>{
        console.log('编译开始');
    })
}

tapAsync

注册异步的钩子,通过,回调函数告知异步方法执行完成,使用如下:

apply(compiler){
    compiler.hooks.watchRun.tagAsync('插件名称',(compilation,callback)=>{
        console.log('编译开始');
        callback();
    })
}

tapPromise

tapAsync方法一样,注册异步的钩子,但是其通过返回一个Promise来告知执行完成。因此我们的方法有两种实现方式

  1. 返回Promise函数,通过then来执行
apply(compiler){
    compiler.hooks.watchRun.tapPromise('插件名称',(compilation)=>{
        return new Promise(resolve=>{}).then(()=>{
            console.log('异步编译开始');
        })
    })
}
  1. 通过async来执行返回的数据
apply(compiler){
    compiler.hooks.watchRun.tapPromise('插件名称',async (compilation)=>{
        await new Promise(resolve=>{});
         console.log('异步编译开始');
    })
}

自定义钩子

我们都知道实际上compiler是继承webpack中的tapable对象的。因此我们只需要在我们自己的组件中去获取相应的方法。我们就可以在我们需要的地方自定义钩子,然后挂载到compiler上即可。因此我们可以通过以下几步来创建一个自定义的钩子

调用tapable中的方法

当前tapable中有很多的方法,我们只需要去其中找到我们想要的方法即可,然后引入,具体有哪些方法,可以去看源码哦。常见的方法有:SyncHook,SyncBailHook,AsyncParallelHook,AsyncSeriesHook.今天我们使用SyncHook来注册一个同步的钩子.代码如下:

const {SyncHook} = require('tapable')

自定义钩子挂载到compiler

我们需要在创建一个方法,也就是自定义钩子,然后挂载到compiler上,由于compiler是一个对象,因此其挂载也就是直接赋值给其对象即可,代码如下:

apply(compiler){
    compiler.hooks.myHook = new SyncHooke(参数);
}

注册自定义钩子

我们值需要通过上面说过的注册方法,然后注册自定义的钩子即可.代码如下

apply(compiler){
    compiler.hooks.myHook.tap('插件名称',(自定义钩子的参数)=>{
    })
}

触发自定义钩子

在我们所需要的使用自定义钩子的地方执行方法即可,例如:

compiler.hooks.myHook(参数)
// 或者
compiler.hooks.myHook.call(参数)

上面就是我们需要自定义一个plugins的全部知识点。那么我们来实现一个具体的插件吧。此插件的作用是在emit的时候生成一个资源列表的文档,同时将文档输出到mySourceList.md文件中。代码结构如下

class MyPlugin{
    apply(compiler) {
        compiler.hooks.emit.tapAsync('MyPlugin',(compilation,callback)=>{
           // 在生成文件中,创建一个头部字符串:
            let filelist = '此编译中产生的文件如下:\n\n';

            // 遍历所有编译过的资源文件,
            // 对于每个文件名称,都添加一行内容。
            for (var filename in compilation.assets) {
                filelist += ('- '+ filename +'\n');
            }

            // 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
            compilation.assets['mySourceList.md'] = {
                source: function() {
                    return filelist;
                },
                size: function() {
                    return filelist.length;
                }
            };

            callback(); 
        })
    }
}
module.exports = MyPlugin;