webpack中有很多钩子,每个钩子都订阅了注册的plugin,等到这个钩子被触发时执行当前钩子订阅的plugin方法,注意webpack钩子会按特定的顺序来执行每一个注册的插件,问题来了,这个顺序规则是怎么实现的呢,webpack又对我们注册的插件做了什么?下面来简单分析下
各位大佬,小生献丑了~~
一个简单地plugin
// /plugins/LogStartBuildtips.js
const pluginName = 'LogStartBuildtips';
class LogStartBuildtips{
apply(compiler) {
compiler.hooks.run.tap(pluginName, compiltion => {
console.log('webpack build starting!!!');
})
}
}
module.exports = LogStartBuildtips;
在webpack配置文件注册
const LogStartBuildtips = require('./plugins/LogStartBuildtips')
module.exports = {
// ......
plugins:[
new LogStartBuildtips()
]
}
logStartBuildtips这个插件就是在webpack打包中提示正在打包(咳咳,我不瞎)。执行打包,可看到打印的信息,此处略过,,
我们关心这个过程,webpack做了什么?
简单分析plugin机制
那先看看webpack.js
// ./node_modules/webpack/lib/webpack.js
const webpack = (options, callback) => {
let compiler;
compiler = new Compiler(options.context);
compiler.options = options;
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else { // new 一个构造函数 返回的是一个对象
plugin.apply(compiler); // 通过apply来注册插件,将插件添加到对应的钩子中
}
}
}
console.log(compiler.hooks.run); // <== 在这打印
return compiler;
};
删减了很多,主要看对插件的处理。插件有两种类型,对象或函数,对于我们常用的插件一般都是插件的实例,所以一般为对象,走else。
plugin.apply(compiler),what?看了插件源码的大佬,不难发现很多插件,都有个apply方法,要它干啥?
我觉的就是webpack给我们暴露的一个接口,apply的执行用来将插件的注册到对应的钩子,apply参数compiler就是webpack完整配置信息,里面也有我们手写的plugin-LogStartBuildtips,我们实现的那个plugin,就是注册到了run这个钩子。有吗?打印,眼见为实
在run这个钩子里的taps数组可以看到,该钩子一共被注册了两个plugin,一个是webpack内部使用的插件CachePlugin,另一个就是我们写的插件。
webpack有很多钩子:
// ./node_modules/webpack/lib/Compiler.js
const {
Tapable,
SyncHook,
SyncBailHook,
AsyncParallelHook,
AsyncSeriesHook
} = require("tapable");
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
shouldEmit: new SyncBailHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]),
additionalPass: new AsyncSeriesHook([]),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]), // <== 我们的run钩子
// .....
afterPlugins: new SyncHook(["compiler"]),
afterResolvers: new SyncHook(["compiler"]),
entryOption: new SyncBailHook(["context", "entry"])
};
// .....
}
// ......
}
这些钩子在Compiler实例创建阶段,就都初始化好了。Compiler继承自Tapable,并且后面每个钩子都会new一个从tapable导出的构造函数,这些构造函数是干什么的?下面来聊聊tapable
tapable
tapable是什么?webpack就是一个事件流,这个事件流是一个个插件组成的,而保证这些插件有序的运行就是tapable的作用,它是采用发布订阅的架构模式。tapable中有很多钩子方法,用来搜集注册的plugin。简单来说Tapable就是webpack用来创建钩子的库。
webpack有很多钩子,在不同的生命周期被触发,通俗的比喻下,webpack整个执行过程就像一个工厂的流水线,Tapable的钩子方法就是这条流水线上的一个个工人,在工程被创建时,给每个工人分配到不同的加工地点(webpack钩子),工人到达地点开始准备(new Tapable钩子方法),接着工人拿到要对产品加工的工具(plugin),等到有产品过来就用这些工具按特定顺序(plugin按规则执行)对其加工,加工完后再放回流水线,给下一个工人(webpack钩子)继续加工,直到所有产品出了流水线(打包完成)。
那看看这些工人的加工规则,tapable提供了9个钩子方法
写在中间
- Sync*
- SyncHook --> 同步串行钩子,不关心返回值
- SyncBailHook --> 同步串行钩子,如果返回值不为null 则跳过之后的函数
- SyncLoopHook --> 同步循环,如果返回值为true 则继续执行,返回值为false则跳出循环
- SyncWaterfallHook --> 同步串行,上一个函数返回值会传给下一个监听函数
- Async*
- AsyncParallel*:异步并发
- AsyncParallelBailHook --> 异步并发,只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数
- AsyncParallelHook --> 异步并发,不关心返回值
- AsyncSeries*:异步串行
- AsyncSeriesHook --> 异步串行,不关心callback()的参数
- AsyncSeriesBailHook --> 异步串行,callback()的参数不为null,就会忽略后续的函数,直接执行callAsync函数绑定的回调函数
- AsyncSeriesWaterfallHook --> 异步串行,上一个函数的callback(err, data)的第二个参数会传给下一个监听函数
- AsyncParallel*:异步并发
www.programmersought.com/article/145…,这个网址对每个钩子方法介绍的更详细
webpack插件注册方式:同步插件一般用tap;异步插件有三种tap、tapAsync、tapSync
总结
那webpack对我们注册的插件做了什么?
webpack先创建compiler,初始化其内部的所有钩子,然后会遍历我们在webpack配置文件配置的插件,执行apply(compiler)将我们写的插件方法通过tapable的钩子方法注册到对应的webpack钩子上,等到该钩子在编译时被触发,在按tapable的钩子方法的规则来执行插件的方法。
webpack的插件机制⼤体可以总结为三个步骤:
- 创建:webpack在其内部对象上创建各种钩⼦;
- 注册:插件通过tapable的钩子方法将⾃⼰的⽅法注册到对应webpack钩⼦上,交给webpack;
- 调⽤:webpack编译过程中,会适时地触发相应钩⼦,因此也就触发了插件的⽅法;