webpack
你们应该都耳熟能详了。我的感受,webpack
的本质就是让一堆的 Loader
和 Plugin
在webpack
的可支配范围内,有序可控的执行,最终生成一堆可在浏览器中执行的 code 和 一些状态信息。而这些 Loader
和 Plugin
,有用户自定义的,也有webpack 本身内部定义的。javascript
Loader
的运行机制,不是这篇文章讲述的内容,有须要的朋友,能够看下我以前的这篇文章:webpack之 loader。html
webpack
的设计思想仍是很好的,我以为这个思想和渐进加强有殊途同归之妙。它本身实现一套打包的主流程,而后在 Complier
和 Complation
对象上暴露出一些钩子,这些钩子起到了相似于生命周期
的做用,容许用户在不一样的打包阶段经过钩子来增长用户所需的各类各样的功能。webpack
聪明的将一些不肯定因素抛给使用者去处理,也就是:标准我来定,细节和扩展你本身弄。java
举个栗子,Complier
对象上的钩子,就有这么多 compiler钩子node
那么这些钩子是什么呢?它们其实都是 tapable
的实例。webpack
那么咱们怎么调用这些钩子呢?经过编写 webpack Plugin
。git
今天咱们来分析下 webpack
插件机制的基石 - tapable
。考虑到 wepback4
,是使用 tapable
的 1.1.0
版本,因此这篇文章就用 1.1.0
的版原本分析。github
注意:
1.x.x
版本和0.x.x
版本,使用方式是有区别的,代码也被重构过。web
若是对 tapable
还未有了解的朋友,能够参考下这里:npm
tapable@v1.1.0segmentfault
我以为tapable
整个库其实就是一套 发布订阅模式
的实现,相似 nodejs 的 EventEmitter
。
有的道友说是生产者消费者模式
, 发布订阅模式本质上也是一种生产者消费者模式,至于他们的区别,应该就是发布订阅模式的功能更单一,而生产者消费者模式的抽象级别更高。究竟是 发布订阅模式
仍是 生产者消费者模式
,我不能肯定,有待考究,聪明的你若是知道的话,能够评论告诉下我哦。
tapable
支持三种方式注册插件名称 ,分别是 tap
, tapAsync
, tapPromise
。
tap
表示使用的同步钩子,tapAsync
和 tapPromise
表示使用的是异步钩子。
与此对应的,支持三种调用方式 call
, callAsync
,promise
,注意须要一一对应。
用 SyncHook
举个栗子
const { SyncHook } = require('tapable');
// 初始化时传入参数名称
const myHook = new SyncHook(['name', 'age']);
// 添加事件
myHook.tap('pluginName1', (name, age) => console.log('pluginName1', name, age))
myHook.tap('pluginName2', (name, age) => console.log('pluginName2', name, age))
// 触发
myHook.call('jk', 26);
// 输出
// pluginName1 jk 26
// pluginName2 jk 26
复制代码
有木有发现和咱们使用 addEventListener
添加事件很是类似?是的,就是这么像。因此不要怕它,咱们只须要注意, 声明 Hook
时,传入预置的参数名称,而后用 tap
监听事件,用 call
传入参数触发事件。
用起来和 jQuery
的 on
与 trigger
一个意思,固然内部处理流程是不同。
这里咱们用的是 SyncHook
,还有 Async
类型的 Hook
,也就是异步钩子,用起来也是相似的。
tapAsync
监听,callAsync
触发
tapPromise
监听,promise
触发
代码以下:
const { AsyncParallelHook } = require("tapable");
class Model {
constructor() {
this.hooks = {
asyncHook: new AsyncParallelHook(['name']),
promiseHook: new AsyncParallelHook(['age'])
};
}
callAsyncHook(name, callback) {
this.hooks.asyncHook.callAsync(name, err => {
if (err) return callback(err);
callback(null);
});
}
callPromiseHook(age) {
return this.hooks.promiseHook.promise(age).then(res => console.log(res));
}
}
const model = new Model();
// Async 方式监听事件
model.hooks.asyncHook.tapAsync('AsyncPluginName', (name, callback) => {
const pluginName = 'AsyncPluginName';
setTimeout( () => {
console.log(pluginName, name);
}, 2000);
});
model.callAsyncHook('jk');
// 2秒后输出:AsyncPluginName jk
// Promise 方式监听事件
model.hooks.promiseHook.tapPromise('PromisePluginName', (age) => {
const pluginName = 'PromisePluginName';
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(pluginName, age);
resolve(pluginName);
}, 4000);
});
});
model.callPromiseHook(26);
// 4秒后输出:PromisePluginName 26
复制代码
其余的类型,可参考官方文档
const pluginName = 'HelloWorldPlugin';
class HelloWorldPlugin {
apply(complier) {
compiler.hooks.someHook.tapAsync(
pluginName,
(compilation, callback) => {
// Do something async...
setTimeout( () => {
console.log('Done with async work...');
callback();
}, 1000);
}
)
}
}
module.exports = HelloWorldPlugin;
复制代码
由于 webpack 在初始化 时,会遍历 plugins
参数中的实例,依次调用实例的 apply
方法,并将 complier
做为参数。
源码出自: webpack\lib\webpack.js
到这里应该能明白,为何 webpack
的插件须要按照那样的格式去写了。
目录结构如图
MyPlugin.js 代码以下,我这里是直接 copy HTMLWebpackPlugin
官方文档的。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const pluginName = 'MyPlugin';
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(pluginName, (compilation) => {
console.log('The compiler is starting a new compilation...')
// Staic Plugin interface |compilation |HOOK NAME | register listener
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
pluginName, // <-- Set a meaningful name here for stacktraces
(data, cb) => {
// Manipulate the content
data.html += 'The Magic Footer'
// Tell webpack to move on
cb(null, data)
}
)
})
}
}
module.exports = MyPlugin;
复制代码
注意一点:HTMLWebpackPlugin
默认安装 3.2.0
版本,这个版本还只是支持旧版的插件写法,没有 HtmlWebpackPlugin.getHooks
这个方法。我这里安装的是最新的master分支。
一切就绪后,npm run prod
,可看到 dist/index.html
的内容以下:
一个简单的插件算是写好并运行成功了。
但愿本文能对读者有帮助。
若是有错误的地方,还请指出。
谢谢阅读。
代码在此:webpack-plugin-test