Plugin是什么
plugin 是 Webpack 中的通过在构建流程中注入各类钩子,用于扩展 Webpack 功能,完成除资源模块打包以外自动化工作的机制。它与 loader 的区别主要在它们的目标不同,loader 的操作对象是文件,而 plugin 只是在 loader 结束后基于事件流机制做一些特殊的操作,它通过广播/订阅机制,允许每个 plugin 在处理流程的特定时机介入流程的执行,从而改变特定流程的运作。通俗的说,loader 是从纵向扩展了 webpack的构建能力,而plugin是横向的,它们都是通过往 Webpack 的声明周期钩子中挂载任务函数实现的。
**
使用
配置
plugins: [new HtmlWebpackPlugin({template: './src/index.html'})]
编写示例(以移除注释为例)
class RemoveCommentsPlugin {// 该apply必须定义apply (compiler) {// 使用 emit 这个钩子compiler.hooks.emit.tap('RemoveCommentsPlugin', compilation => {// compilation:此次打包的上下文for (const name in compilation.assets) {if (name.endsWith('.js')) {const contents = compilation.assets[name].source()const noComments = contents.replace(/\/\*{2,}\/\s?/g, '')compilation.assets[name] = {source: () => noComments, // 返回修改后的新内容size: () => noComments.length // 返回内容大小}}}})}}module.exports = RemoveCommentsPlugin
底层工作流程
对插件(plugin)来说,它大体的运作只分为三个部分:
- 创建钩子: Webpack 在自身内部对象中创建出各种钩子。
- 注册事件: 插件将自己的任务函数注册到对应钩子上,交予 Webpack。
- 调用任务: 在 Webpack编译过程中,会适时触发对应的钩子来触发插件的方法。
创建钩子
Tapable
Webpack 的内部通过使用 tapable 这个库来实现各类钩子的创建,Webpack 中的 Complier 、 Compliation 等重要内部对象都继承了 Tapable 类来挂载钩子。
const {SyncHook,SyncBailHook,SyncWaterfallHook,SyncLoopHook,AsyncParallelHook,AsyncParallelBailHook,AsyncSeriesHook,AsyncSeriesBailHook,AsyncSeriesWaterfallHook} = require("tapable");
其中的钩子又可以从执行方式和同步、异步中组合,其中执行方式包含了四种类型:
| 类型 | 说明 |
|---|---|
| Basic | 简单的调用注册在钩子上的函数 |
| Waterfall | 与Basic类型的钩子类似,但它会返回上一个函数的结果以进行链式调用 |
| Bail | 允许中途退出,只要中间有任何一个函数存在返回值,便中断后续的执行 |
| Loop | 直到所有的插件都返回 undefined 后才停止的钩子,在收到任何一个非 undefined 值时,钩子会从头开始依次调用插件。 |
而异步、同步则分为3种类型:
| 类型 | 说明 |
|---|---|
| Sync | 同步钩子,只允许同步任务函数 |
| AsyncSeries | 可同步可异步,支持 callback 和 Promise 形式的异步调用,但其内的任务函数是被串行(连续)调用的。 |
| AsyncParallel | 与 AsyncSeries 类的钩子类似,但其内的任务函数是并行调用的。 |
其中同步的钩子都可以使用 fooHook.tap() 进行调用,而基于回调和 Promise 的钩子则分别通过 fooHook.tapAsync() 与 fooHook.tapPromise() 进行调用。
Compiler Hooks
多数面向用户的插件都注册在编译器的钩子上,它是 Webpack 创建实例的主引擎,代表着整个 Webpack 自启动到关闭的生命周期,它的一些常用钩子如下:
| 钩子 | 钩子类型 | 调用时机 |
|---|---|---|
| run | AsyncSeriesHook | 最开始 |
| make | AsyncParallelHook | 完成一次编译后 |
| emit | AsyncSeriesHook | 生成文件到输出目录前执行,它的回调是 compilation |
| done | AsyncSeriesHook | 最后,它的回调是 stats |
Compilation Hooks
该阶段钩子代表了用于对应 Webpack 一次编译过程。它的一些常用钩子如下:
| 钩子 | 钩子类型 | 调用时机 |
|---|---|---|
| buildModule | SyncHook | 在模块开始编译前触发,常用于修改模块 |
| finishModules | AsyncSeriesHook | 所有模块都编译成功时 |
| optimize | SyncHook | 优化阶段开始时 |
| optimizeChunks | SyncBailHook | 代码块优化阶段开始时,它的回调是 chunks ,常用于执行对代码块的优化 |
JavaScriptParser Hooks
解析器用于解析每个正在处理的模块,它的一些常用钩子如下:
| 钩子 | 钩子类型 | 调用时机 |
|---|---|---|
| import | SyncBailHook | 为每个 import 语句调用 |
| evaluate | SyncBailHook | 计算表达式时调用 |
| statement | SyncBailHook | 为代码每个一解析的句子调用 |
注册事件
在读取配置阶段,通过 new FooPlugin() 创建出了对应的插件并获得它的实例。在编译器(Compiler)实例化以后,通过 FooPlugin.apply(compiler)将编译器实例传入插件中,这也是为什么插件都必须声明 apply(compiler) 方法的原因。
在插件实例获取到编译器实例之后,就可以通过调用 compiler.plugin() 方法订阅对应 Webpack 钩子的广播的事件,并且利用编译器实例操作 Webpack。
调用任务
见 Tapable 的使用部分。
