Appearance
this._pluginCompat()
提供了一种通过 this.plugin(name , fn)去在 this.hooks[name]上添加一个 name 为 fn.name 或者 'unnamed compat plugin'的异步或者同步钩子事件
例子
这个方法在 webpack 的 compiler compilation 中多次使用,如下面 compiler 中
js
//
class Compiler extends Tapable {
constructor() {
this._pluginCompat.tap('Compiler', options => {
switch (options.name) {
case 'additional-pass':
case 'before-run':
case 'run':
case 'emit':
case 'after-emit':
case 'before-compile':
case 'make':
case 'after-compile':
case 'watch-run':
options.async = true;
break;
}
});
}
}
// 在
compiler.plugin('compilation', compilation => {
compilation.plugin('optimize-chunk-assets', (chunks, callback) => {
uglifier.processAssets(compilation, this.options).then(() => {
callback();
});
});
});
compiler.plugin('done', () => {
uglifier.pruneCache(this.options);
});
那么上面的例子经历的流程是哪些?
- 触发
Tapable.prototype.plugin()
在compiler.plugin('compilation',fn)
的时候其触发了继承于 Tapable 的 plugin 方法
js
/**
* 定义了触发 this._pluginCompat 的方法
* @param {[type]} name [添加的事件流的名称] 会经历 1. 自定义的 this._pluginCompat.tap() 2. Tapable camelCase对名称进行驼峰转换 3. Tapable this.hooks 将fn按照name添加到 this.hooks[name]的事件流中
* @param {Function} fn [添加的事件流]
* @return {[type]} [description]
*/
Tapable.prototype.plugin = util.deprecate(function plugin(name, fn) {
// 事件流的名称
// 如果是数组类型,那么就会将此事件流fn 添加到 数组中每一个名称的钩子事件流中
if (Array.isArray(name)) {
name.forEach(function(name) {
this.plugin(name, fn);
}, this);
return;
}
// 执行 this._pluginCompat 钩子函数
const result = this._pluginCompat.call({
name: name,
fn: fn,
names: new Set([name]),
});
if (!result) {
throw new Error(
`Plugin could not be registered at '${name}'. Hook was not found.\n` +
"BREAKING CHANGE: There need to exist a hook at 'this.hooks'. " +
"To create a compatiblity layer for this hook, hook into 'this._pluginCompat'."
);
}
}, 'Tapable.plugin is deprecated. Use new API on `.hooks` instead');
上面提供了 name 可以为数组的方法 如:['emit','done']
那么就会将 fn 分别添加到 this.hooks.emit 和 this.hooks.done 钩子事件流中。
其会执行 this._pluginCompat.call({name: name, fn: fn, names: new Set([name]),});
那么这会触发 this._pluginCompat 事件流。按照上面定义的事件流的顺序,其会按照 'Compiler' -> 'Tapable camelCase' -> 'Tapable this.hooks' 的顺序执行,下面我们分别看这三个
- Compiler 事件
在 compiler 初始化的时候其定义了 name 为 Compiler 的_pluginCompat 事件,其主要作用就是根据 name 的名称去修改 option.async 的值。
在compiler.plugin('compilation',fn)
中其 option.name 为 compilation, 所以不会修改 async 的值 (option.async === false)
js
//
class Compiler extends Tapable {
constructor() {
this._pluginCompat.tap('Compiler', options => {
switch (options.name) {
case 'additional-pass':
case 'before-run':
case 'run':
case 'emit':
case 'after-emit':
case 'before-compile':
case 'make':
case 'after-compile':
case 'watch-run':
options.async = true;
break;
}
});
}
}
// 在
compiler.plugin('compilation', compilation => {
compilation.plugin('optimize-chunk-assets', (chunks, callback) => {
uglifier.processAssets(compilation, this.options).then(() => {
callback();
});
});
});
compiler.plugin('done', () => {
uglifier.pruneCache(this.options);
});
- Tapable 内置的 'Tapable camelCase'事件
js
function Tapable() {
// 提供了 继承 Tapable的构造函数 使用 this._pluginCompat.tap('',fn)去添加一个异步串行的方法
this._pluginCompat = new SyncBailHook(['options']);
this._pluginCompat.tap(
{
name: 'Tapable camelCase',
stage: 100,
},
options => {
// 处理事件流的 option.name
// 将事件流option.name 中-或者空格进行驼峰命名转换 并存放到 names中
options.names.add(options.name.replace(/[- ]([a-z])/g, str => str.substr(1).toUpperCase()));
}
);
}
源码如上,在 Tapable 中其内置了名称为 "Tapable camelCase",权重为 100 的 _pluginCompat
事件,其主要作用就是:将事件流 option.name 中-或者空格进行驼峰命名转换 并存放到 names 中,上面 compilation 没有-没有空格,所以名称仍然为 "compilation", options.names 中也只存在一个 "compilation"值,如果其名称为"additional-pass",那么一方面上一个事件将其 option.async 置为 true,另一方面将 additional-pass
驼峰命名转换为 additionalPass
并存放到 names 中,那么在 option.names 中就存在两个值"additional-pass","additionalPass"。
- Tapable 内置的 'Tapable this.hooks'事件
js
function Tapable() {
// 提供了 继承 Tapable的构造函数 使用 this._pluginCompat.tap('',fn)去添加一个异步串行的方法
this._pluginCompat = new SyncBailHook(['options']);
this._pluginCompat.tap(
{
name: 'Tapable this.hooks',
stage: 200,
},
options => {
let hook;
// 获取当前实例this.hooks上是否定义的当然name的钩子函数
for (const name of options.names) {
hook = this.hooks[name];
if (hook !== undefined) {
break;
}
}
if (hook !== undefined) {
//
const tapOpt = {
name: options.fn.name || 'unnamed compat plugin',
stage: options.stage || 0,
};
// 获取当前钩子函数式异步执行 还是同步执行
if (options.async) hook.tapAsync(tapOpt, options.fn);
else hook.tap(tapOpt, options.fn);
return true;
}
}
);
}
从上面的源码中我们可以看出其
获取 this.hooks 中 name 相对于的钩子事件流 ,如上面的 "this.hooks.compilation","this.hooks.additional-pass","this.hooks.additionalPass",
根据 options.async 去决定添加 tap 事件、还是 tapAsync 事件,并且其名称为 fn.name,权重为 options.stage
总结
对于this._pluginCompat
的使用方式:
- 简版
js
// 1. 继承Tapable 或者 new Tapable() 去使用 this._pluginCompat 和 this.plugin
class Compiler extends Tapable {
constructor() {
this.hooks.compilation = new SyncHook(['chunks']);
}
}
// 2. 使用 实例.plugin() 去 compilation钩子事件流上添加一个 compilation => {} 事件
compiler.plugin('compilation', compilation => {});
- 优化版
对于上面的简版,其缺点是:
不能设置事件的name、stage等。
没有设置 stage,所以只能添加 tap事件,所以上面的简版是错误的。
那么怎么去改版上面的?
js
// 1. 继承Tapable 或者 new Tapable() 去使用 this._pluginCompat 和 this.plugin
class Compiler extends Tapable {
constructor() {
this.hooks.compilation = new SyncHook(['chunks']);
// 就是添加了这一部分通过事件流是顺序执行的,在内部事件流'Tapable camelCase'事件,'Tapable this.hooks'事件之前去修改option的值
this._pluginCompat.tap('Compiler', options => {
switch (options.name) {
case 'compilation':
case 'watch-run':
options.async = true;
// options.xx = xx;
break;
}
});
}
}
// 2. 使用 实例.plugin() 去 compilation钩子事件流上添加一个 compilation => {} 事件
compiler.plugin('compilation', compilation => {});