插件是 webpack 生态系统中的关键组成部分,它们为社区提供了一种强大的方式来介入 webpack 的编译过程。插件能够挂载到每次编译过程中触发的关键事件。在每一步中,插件都将完全访问 compiler
,并在适用时访问当前的 compilation
。
我们首先介绍 tapable
工具,它提供了 webpack 插件接口的骨干。
这个小型库是 webpack 中的一个核心工具,但也可以在其他地方用于提供类似的插件接口。webpack 中的许多对象都扩展了 Tapable
类。该类暴露了 tap
、tapAsync
和 tapPromise
方法,插件可以使用它们注入将在编译过程中触发的自定义构建步骤。
请参阅文档了解更多信息。理解这三个 tap
方法以及提供它们的 hook 至关重要。将注明扩展 Tapable
的对象(例如 compiler)、它们提供的 hook 以及每个 hook 的类型(例如 SyncHook
)。
根据所使用的 hook 和应用的 tap
方法,插件可以以不同的方式运行。这种工作方式与 Tapable
提供的hook密切相关。编译器 hook 都指明了底层的 Tapable
hook,从而指示哪些 tap
方法可用。
因此,根据您 tap
到哪个事件,插件可能以不同的方式运行。例如,当挂载到 compile
阶段时,只能使用同步的 tap
方法。
compiler.hooks.compile.tap('MyPlugin', (params) => {
console.log('Synchronously tapping the compile hook.');
});
然而,对于使用 AsyncHook
的 run
事件,我们可以利用 tapAsync
或 tapPromise
(以及 tap
)。
compiler.hooks.run.tapAsync(
'MyPlugin',
(source, target, routesList, callback) => {
console.log('Asynchronously tapping the run hook.');
callback();
}
);
compiler.hooks.run.tapPromise('MyPlugin', (source, target, routesList) => {
return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => {
console.log('Asynchronously tapping the run hook with a delay.');
});
});
compiler.hooks.run.tapPromise(
'MyPlugin',
async (source, target, routesList) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log('Asynchronously tapping the run hook with a delay.');
}
);
总而言之,有多种方式可以 hook
到 compiler
,每种方式都允许您的插件以其认为合适的方式运行。
为了向编译过程提供一个自定义 hook,供其他插件 tap
介入,您需要执行以下操作:
为编译 hook 创建一个模块作用域的 WeakMap
const compilationHooks = new WeakMap<Compilation, MyHooks>();
interface MyHooks {
custom: SyncHook<[number, string]>;
}
在您的插件上创建一个静态方法
static getCompilationHooks(compilation: Compilation) : MyHooks {
let hooks = compilationHooks.get(compilation);
if(hooks === undefined) {
compilationHooks.set(compilation, hooks = {
custom: new SyncHook()
});
}
return hooks;
}
在您的插件中像下面这样调用 hook
const hooks = MyPlugin.getCompilationHooks(compilation);
hooks.custom.call(1, 'hello');
其他插件也可以访问您的自定义 hook
import MyPlugin from 'my-plugin';
const hooks = MyPlugin.getCompilationHooks(compilation);
hooks.custom.tap('OtherPlugin', (n, s) => {
// magic
});
同样,请参阅 tapable
的文档,以了解更多关于不同 hook 类及其工作原理的信息。
插件可以通过 ProgressPlugin
报告进度,它默认将进度消息打印到 stderr。为了启用进度报告,在运行 webpack CLI 时传递 --progress
参数。
可以通过向 ProgressPlugin
的 reportProgress
函数传递不同的参数来定制打印输出。
要报告进度,插件必须使用 context: true
选项 tap
介入一个 hook。
compiler.hooks.emit.tapAsync(
{
name: 'MyPlugin',
context: true,
},
(context, compiler, callback) => {
const reportProgress = context && context.reportProgress;
if (reportProgress) reportProgress(0.95, 'Starting work');
setTimeout(() => {
if (reportProgress) reportProgress(0.95, 'Done work');
callback();
}, 1000);
}
);
reportProgress
函数可以带这些参数调用:
reportProgress(percentage, ...args);
percentage
:此参数未被使用;相反,ProgressPlugin
将根据当前 hook 计算百分比。...args
:任意数量的字符串,它们将被传递给 ProgressPlugin
处理程序以报告给用户。请注意,只有一部分 compiler 和 compilation hook 支持 reportProgress
函数。有关完整列表,请参阅ProgressPlugin
。
日志 API 自 webpack 4.37 版本发布以来可用。当stats 配置
中启用 logging
和/或启用基础设施日志
时,插件可以记录消息,这些消息将以相应的日志格式(stats、infrastructure)打印出来。
compilation.getLogger('PluginName')
进行日志记录。这种日志记录存储在 Stats 中并相应地格式化。用户可以对其进行过滤和导出。compiler.getInfrastructureLogger('PluginName')
进行日志记录。使用 infrastructure
日志不会存储在 Stats 中,因此不会被格式化。它通常直接记录到控制台/仪表板/GUI。用户可以对其进行过滤。compilation.getLogger ? compilation.getLogger('PluginName') : console
,以便为使用不支持 compilation
对象上 getLogger
方法的旧版本 webpack 的情况提供回退。请参阅编译器 hook 部分,以获取所有可用的 compiler
hook 及其提供的参数的详细列表。