理解webpack插件plugin

793 阅读4分钟

webpack的运行机制

在说webpack的插件plugin前,简要说下webpack的运行机制,分析webpack的运行,可以借助事件流机制的概念来说明 那什么是webpack的事件流呢?

Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 Webpack 通过 Tapable 来组织这条复杂的生产线。 Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 --吴浩麟《深入浅出webpack》

这里提到的Tapable是个node包,是webpack自带的模块,webpack的事件流可以理解为webpack在打包过程中暴露出来的一些事件,这些事件分别表示webpack在不同的构建时期和状态,在不同的构建周期会广播出不同的事件,我们可以去监听这些事件并挂载一些回调函数,在指定的时间下做一些我们想做的事情,这些都是基于Tapable来实现的。不同时期下webpack的打包信息都存储在compiler和compilitaion里,他们都是Tapable的'实例'。

  • Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;
  • Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。

Compiler 和 Compilation 的区别在于:Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。

下面插张webpack的运行图,可以更直观的看出不同事件下webpack在做的一些事情。本文主要说明plugin,关于运行过程就不再赘述

image

Tapable是什么

The tapable package expose many Hook classes, which can be used to create hooks for plugins.

tapable是一个node库,webpack已内置。他向外暴露很多钩子类,为插件提供挂载的钩子。

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

tapable提供的钩子可以分为同步钩子和异步钩子

image

新建钩子

tapable暴露出来的都是类方法,需要我们new对应的类方法去获得我们需要的钩子。class 接受数组参数options,参数都是string类型,options非必传。类方法会根据传参,接受同样数量的参数。

const hook = new SyncHook(["arg1", "arg2", "arg3"]);

绑定钩子

绑定钩子的方法有tap/tapAsync/tapPromise,tapable提供了绑定同步钩子和异步钩子的方法,以及如何执行对应的绑定事件方法

- Async Sync
绑定 tapAsync/tapPromise/tap tap
执行 callAsync/promise call

举个例子

// 获取钩子
const hook = new SyncHook(['arg1', 'arg2']);

// 绑定事件到webpack事件流
hook.tap('myHook', (arg1, arg2) => {console.log(arg1, arg2)} )

// 执行绑定的事件
hook.call(1, 2)

关于tapable就简单介绍到这里

plugin基础

我们在配置webpack的plugin的时候都会new一下,由此可见plugin是一个class,相应的参数也会在new plugin()的时候传入,由此可见plugin是个class是没跑了,熟悉的同学可能知道plugin其实就是 一个带有apply方法的class。 我们再说到上文说的compiler和compilation,compiler是负责webpack的编译任务,complation是负责创建bundles的,那么我们webpack的plugin就是由compiler来编译使用的。 下面简单实现一个compiler

const { SyncHook, AsyncParallelHook} = require('tapable');

class Compiler {
    // options传入plugins
    constructor(options) {
        // this.hooks都是tapable的实例  实例化钩子函数
        this.hooks = {
            accelerate: new SyncHook(["newSpeed"]),
            break: new SyncHook(),
            calculateRoutes: new AsyncParallelHook(['source', 'target', 'routesList'])
        };
        // 实例化传入plugin并执行plugin
        let plugins = options.plugins;

        if (plugins && plugins.length > 0) {
            // 调用plugin里的apply方法  this指compiler实例化的对象
            console.log('--------this----------', this);
            plugins.forEach(plugin => plugin.apply(this))
        }
    }

    run() {
        this.accelerate('xxx');
        this.break();
        this.calculateRoutes('i', 'like', 'tapable')
    }

    accelerate(params) {
        // 执行(emit)该事件
        this.hooks.accelerate.call(params)
    }

    break() {
        this.hooks.break.call()
    }

    calculateRoutes() {
        const args = Array.from(arguments);
        this.hooks.calculateRoutes.callAsync(...args,err => {
            if(err) console.log(err)
        })
    }
}

module.exports = Compiler;

下面是再写入一个我们自己的插件,看下面代码:

// 引入上面的Compiler
const Compiler = require('./Compiler')

class MyPlugin{
    constructor() {

    }
    // apply方法中接受 compiler参数
    apply(conpiler){
        // 绑定对应的事件到webpack事件流
        conpiler.hooks.break.tap("WarningLampPlugin", () => console.log('WarningLampPlugin'));
        conpiler.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));
        conpiler.hooks.calculateRoutes.tapAsync("calculateRoutes tapAsync", (source, target, routesList, callback) => {
            setTimeout(() => {
                console.log(`tapAsync to ${source}${target}${routesList}`)
                callback();
            }, 2000)
        });
    }
}


//这里类似于webpack.config.js的plugins配置
//向 plugins 属性传入 new 实例

const myPlugin = new MyPlugin();

const options = {
    plugins: [myPlugin]
}
let compiler = new Compiler(options)
compiler.run()

以上MyPlugin就是自己写的webpack插件,webpack的插件本质就是带有apply方法的class类

写在最后

以上有不足之处还请指出,谢谢看到这里~