# 一.plugin的实现
当我们需要一个构建功能时 webpack 本身暂未支持的,我们便可以通过寻找合适的 webpack 插件来帮助实现需要的功能,或者开发一个 webpack 插件来满足项目的构建需求。
# 1.一个简单的 plugin
plugin 的实现可以是一个类,使用时传入相关配置来创建一个实例,然后放到配置的plugins
字段中,而 plugin 实例中最重要的方法是apply
,该方法在 webpack compiler 安装插件时会被调用一次,apply
接受 webpack compiler 对象实例的引用,你可以在 compiler 对象实例上注册各种事件钩子函数,来影响 webpack 的所有构建流程,以便完成更多其他的构建任务。
下边的这个例子,是一个可以创建 webpack 构建温江列表 markdown 的 plugin,实现上相对简单,但呈现了一个 webpack plugin 的基本形态。
class FileIsPlugin {
constructor(options) {}
apply(compiler) {
compiler.hooks.emit.tap("FileIsPlugin", (compilation) => {
// 在compiler的emit hook中注册一个方法,当webpack执行到这个阶段时会调用这个方法
var filelist = "In this build:\n\n" // 给生成的markdown文件创建一个简单标题
for (var filename in compilation.assets) {
filelist += "- " + filename + "\n" // 遍历所有编译后的资源,每一个文件添加一行说明
}
compilation.assets["filelist.md"] = {
// 将列表作为一个新的文件资源插入到webpack构建结果中
source: function() {
return filelist
},
size: function() {
return tilelist.length
},
}
})
}
}
module.exports = FileListPlugin
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 2.开发和调试
创建一个 js 代码文件,在 webpack 配置文件中引用这个文件的代码,照样运行 webpack 构建查看结果即可。
const FileIsPlugin = requrie("./plugins/FileIsPlugin.js") // 假如我们上述那个例子的代码是 ./plugins/FileIsPlugin这个文件
module.exports = {
plugins: [
new FileIsPlugin(), // 实例化这个插件,有的时候需要传入对应的配置
],
}
2
3
4
5
6
# 3.事件钩子
webpack 中的事件钩子,基本覆盖了 webpack 构建流程中的每一个步骤,你可以在这些步骤中注册自己的处理函数,来添加额外的功能。
this.hooks = {
shouldEmit: new SyncBaiHook(["compilation"]), //这里的声明的事件钩子函数接收的参数是
compilation,
done: new AsyncSeriesHook(["stats"]), //这里接受的参数是stats,以此类推
additionalPass: new AsyncSeriesHOok([]),
beforeRun: new AsyncSeriesHook(["compilation"]),
run: new AsyncSeriesHook(["compilation"]),
emit: new AsyncSeriesHook(["compilation"]),
afterEmit: new AsyncSeriesHook(["compilation"]),
thisCompilations: new SyncHook(["compilation", "params"]),
}
2
3
4
5
6
7
8
9
10
11
从这你可以看到各个事件钩子函数接收的参数是什么,你还会发现事件钩子会有不同的类型,例如SyncBaiHook
,AsyncSeriesHook
,SyncHook
。
# 4.事件钩子类型
上述提到的 webpack compiler 中使用了多种类型的事件钩子,根据其名称可以区分出是同步还是异步,对于通不过的事件钩子来说,注册事件的方法只有tap
可用,例如上述shouldEmit
应该这样注册事件函数的:
apply(compiler){
compiler.hooks.shouldEmit.tap('PluginName',(compilation)=>{
...
})
}
2
3
4
5
但如果是异步的事件钩子,那么可以使用tabPromise
或者tabAsync
来注册事件函数,tabPromise
要求方法返回Promise
以便处理异步,而tapAsync
则是需要用callback
来返回结构,例如:
compiler.hooks.done.tapPromise("PluginName", (state) => {
return new Promise((resolve, reject) => {
fs.writeFile("path/to/file", stats.toJson(), (err) =>
err ? reject(err) : resolve()
)
})
})
// 或者
compiler.hooks.done.tapAsync("PluginName", (stats, callback) => {
fs.writeFile("path/to/file", stats.toJson(), (err) => callback(err))
})
// 如果插件处理中没有异步操作要求的话,也可以用同步的方式
compiler.hooks.done.tap("PluginName", (stats, callback) => {
callback(fa.writeFileSync("path/to/file", stats.toJson()))
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然而 tabable 这个工具库提供的钩子类型远不止这几种,多样化的钩子类型,主要是为了能够覆盖多种使用场景:
- 连续地执行注册的事件函数
- 并行的执行注册的事件函数
- 一个接一个地执行注册的事件函数,从前边的事件函数获取输入,即瀑布流的方式
- 异步地执行注册的事件函数
- 在允许时停止执行注册的事件函数,一旦一个方法返回了一个非
undefined
的值,就跳出执行流
除了同步和异步的区别,我们再参考上述这一些使用场景,以及官方文档的 Plugin API,进一步将事件钩子类型做一个区分。
名称带有parallel
的,注册的事件函数会并行调用,如:
- AsyncParallelHook
- AsyncParallelBailHook
名称带有
bail
的,注册的事件函数会被顺序调用,直至一个处理方法有返回值(ParalleBail 的事件函数则会并行调用,第一个返回值会被使用) - SyncBailHook
- AsyncParalleBailHook
通过上面的名称可以看出,有一些类型是可以结合到一起的,如
AsyncParalleBailHook
,这样它就具备了更加多样化的特性。