webpack的工作机制是基于事件流,将各个插件串联起来,而核心就是tapable
对象
核心概念:
- tapable 对象,是
webpack
为plugin
创建hook
的包,中文文档 - 开发webpack插件最重要两个资源是对象:compiler和compilation,这两货都是扩展了
tapable
对象- Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;
- compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。
Compiler 和 Compilation 的区别在于:Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译。
webpack
插件由以下组成:
- 一个 JavaScript 命名函数。
- 在插件函数的 prototype 上定义一个
apply
方法。 - 指定一个绑定到 webpack 自身的事件钩子。
- 处理 webpack 内部实例的特定数据。
- 功能完成后调用 webpack 提供的回调。
下面是一个简单的插件结构:
这里呢
emit
是complier
上的钩子,tap是为这个钩子注册监听函数,第一个参数MyExampleWebpackPlugin
是监听器名称(自己随便定),第二个参数是回调会在钩子执行的时候被调用,回调传入的参数是compilation
对象
// 定义一个class
class MyExampleWebpackPlugin {
// 定义 apply方法,参数是 compiler
apply(compiler) {
// 附加一个指定的钩子
compiler.hooks.emit.tap(
'MyExampleWebpackPlugin',
(compilation) => {
// Manipulate the build using the plugin API provided by webpack
compilation.addModule(/* ... */);
compilation.hooks.buildModule.tap('MyExampleWebpackPlugin', () => {
//do something...
});
}
);
}
}
在同步钩子中, tap
是唯一的绑定方法,异步钩子通常支持异步插件
// promise: 绑定promise钩子的API
myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => {
// return a promise
return google.maps.findRoute(source, target).then(route => {
routesList.add(route);
});
});
// tapAsync:绑定异步钩子的API
compilation.hooks.additionalAssets.tapAsync('MyPlugin', callback => {
download('https://img.shields.io/npm/v/webpack.svg', function(resp) {
if(resp.status === 200) {
compilation.assets['webpack-version.svg'] = toAsset(resp);
callback();
} else {
callback(new Error('[webpack-example-plugin] Unable to download the image'));
}
});
});
tapable
的钩子类型:
每一个钩子都可以tap 一个或者多个函数, 他们如何运行,取决于他们的钩子类型
- 基本的钩子, (钩子类名没有waterfall, Bail, 或者 Loop 的 ), 这个钩子只会简单的调用每个tap进去的函数
-
Waterfall
, 一个waterfall 钩子,也会调用每个tap进去的函数,不同的是,他会从每一个函数传一个返回的值到
下一个函数 -
Bail
, Bail 钩子允许更早的退出,当任何一个tap进去的函数,返回任何值, bail类会停止执行其他的函数执行.(类似 Promise.race()) -
Loop
, TODO(我.... 这里也没描述,应该是写文档得时候 还没想好这个要怎么写,我尝试看他代码去补全,不
过可能需要点时间.)
此外,钩子可以是同步的,也可以是异步的,Sync, AsyncSeries 和 AsyncParallel ,从名字就可以看出,哪些是可以绑定异步函数的
-
Sync
, 一个同步钩子只能tap同步函数(myHook.tap()
), 不然会报错. -
AsyncSeries
, 一个 async-series 钩子 可以tap 同步钩子, 基于回调的钩子(我估计是类似chunk的东西)和一个基于promise的钩子(使用myHook.tap(), myHook.tapAsync() 和 myHook.tapPromise()
).他会按顺序的调用每个方法. -
AsyncParallel
, 一个 async-parallel 钩子跟上面的 async-series 一样 不同的是他会把异步钩子并行执行(并行执行就是把异步钩子全部一起开启,不按顺序执行).
钩子总结:
tapable
导出的钩子,根据名字分为同步和异步钩子
钩子注册方式
:同步钩子只能使用 tap()
,异步钩子使用 tapAsync()
tapPromise()
钩子调用方式
:同步钩子只能使用call()
,异步钩子使用promise()
callAsync()
测试效果请看这里
const {
SyncHook, //同步执行多个函数;
SyncBailHook,//同步执行多个函数,有return就退出;
SyncWaterfallHook,//同步执行多个函数,下一个的函数的参数就是上一个的返回值;
SyncLoopHook,//
AsyncParallelHook,//并行执行多个异步函数;
AsyncParallelBailHook,//无意义;都并行执行了,没有什么提前结束,webpack源码也没有用到
AsyncSeriesHook,//串行执行多个异步函数;
AsyncSeriesBailHook,//串行执行多个异步函数,callback有返回值就退出;
AsyncSeriesWaterfallHook//串行执行多个异步函数,下一个的函数的参数就是上一个函数的callback返回值;
} = require("tapable");
实战,编写一个webpack插件
class FileListPlugin{
apply (compiler) {
compiler.hooks.emit.tap('FileListPlugin', function(compilation) {
// 在生成文件中,创建一个头部字符串:
var filelist = 'In this build:\n\n';
// 遍历所有编译过的资源文件,
// 对于每个文件名称,都添加一行内容。
for (var filename in compilation.assets) {
filelist += ('- '+ filename +'\n');
}
// 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
compilation.assets['filelist.md'] = {
// 返回文件内容
source: function() {
// 既可以是代表文本文件的字符串,也可以是代表二进制文件的 Buffer
return filelist;
},
// 返回文件大小
size: function() {
return filelist.length;
}
};
});
}
}
自定义钩子:
const SyncHook = require('tapable').SyncHook;
// 具有 `apply` 方法……
if (compiler.hooks.myCustomHook) throw new Error('Already in use');
compiler.hooks.myCustomHook = new SyncHook(['a', 'b', 'c'])
//为这个钩子注册监听函数
compiler.hooks.myCustomHook .tap("plugin-name",()=>{
//钩子 myCustomHook 被执行时会调用这个回调函数
}),
// 在你想要触发钩子的位置/时机下调用执行钩子,并传入3个参数
compiler.hooks.myCustomHook.call(a, b, c);
webpack插件调用顺序:
是按照plugins数组的顺序执行,而webpack chain插件的before、after方法是改变这个plugins数组的顺序,那么插件是可以指定再某个插件之前或之后执行,但是如果是异步钩子插件,无法指定在该插件执行完毕之后执行