Skip to content

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);
});

那么上面的例子经历的流程是哪些?

  1. 触发 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.emitthis.hooks.done 钩子事件流中。

其会执行 this._pluginCompat.call({name: name, fn: fn, names: new Set([name]),}); 那么这会触发 this._pluginCompat 事件流。按照上面定义的事件流的顺序,其会按照 'Compiler' -> 'Tapable camelCase' -> 'Tapable this.hooks' 的顺序执行,下面我们分别看这三个

  1. 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);
});
  1. 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"

  1. 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的使用方式:

  1. 简版
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 => {});
  1. 优化版

对于上面的简版,其缺点是:

  • 不能设置事件的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 => {});

上次更新: