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,关于运行过程就不再赘述

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提供的钩子可以分为同步钩子和异步钩子

新建钩子
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类
写在最后
以上有不足之处还请指出,谢谢看到这里~