webpack和loader一样,都是webpack非常重要的组成部分,插件对比loader能做更多事情,毫不夸张的说插件可以控制整个webpack构建流程,但是同样的,他的学习成本也相当大,今天就带你认识插件,入门都不算,只是认识。
插件简介
插件是什么?插件就是一个构造函数,这个构造函数上面挂载着一个apply
函数:
// es5 构造函数 function MyPlugin() { } MyPlugin.apply = function (compiler) { } // es6 class 语法 class MyPlugin { apply(compiler) { } }
Webpack
在启动时会调用插件对象的apply
函数,并且会向这个函数传递一个核心对象compiler
,通过compiler
,插件内可以注册 compiler
对象及其子对象的钩子(Hook)回调,例如:
class MyPlugin { apply(compiler) { compiler.hooks.compile.tap('MyPlugin', (params) => { console.log('以同步方式触及 compile 钩子。'); }); } }
示例中可以很看到log
输出以同步方式触及 compile 钩子。
,上面挂载的钩子是编译阶段的钩子,也只有同步的钩子,当然还有其他异步的钩子tapAsync
、tapPromise
。
上面的代码的意思是,通过compiler
挂载钩子,钩子就是hooks
,hooks.compile
就是指定编译阶段的钩子,compile
就是钩子的名称,compile.tap
就是这个阶段的钩子触发的方式,有tap
、tapAsync
、tapPromise
。
Tapable
上面说的那些玩意全都靠着Tapable
这个类来实现的,参考:Tapable。
webpack
的钩子全都围绕着这个核心库来实现的,因为内部依赖的方法不同,所以并不是所有的tap(xxx)
都可以使用,这里注意了,它并不是像某些库,你不传回调就给你返回一个Promise
,可以使用同步或者异步来调用。
它是根据钩子的编写和作用,来使用什么tap(xxx)
,由于钩子太多了,文档写的并不好,甚至记录的不全,所以需要你看源码再来确定使用什么。
这一块实在是太绕了,重点就是让你看源码,源码怎么看?硬着头皮看。
webpack核心对象
上面说到了Tapable
,webpack
就有两个核心的对象使用Tapable
来创建的钩子,如下:
- Compiler:全局构建管理器,下面取自官网的解释
Compiler
模块是 webpack 的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自Tapable
类,用来注册和调用插件。 大多数面向用户的插件会首先在Compiler
上注册。在为 webpack 开发插件时,你可能需要知道每个钩子函数是在哪里调用的。想要了解这些内容,请在 webpack 源码中搜索
hooks.<hook name>.call
。
- Compilation:单次构建过程的管理器,官网解释
Compilation
模块会被Compiler
用来创建新的 compilation 对象(或新的 build 对象)。compilation
实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。
当然还有其他的核心对象,课程中这一章只是提及,我就不记录了,后面应该会讲到,避免学习焦虑。
hooks
的使用会有两个重点,1. 使用的时机,上面的示例有过提及,2. 触发时传递的对象。
compiler.hooks.compilation
:
- 时机:Webpack 刚启动完,创建出
compilation
对象后触发; - 参数:当前编译的
compilation
对象。
compiler.hooks.make
:
- 时机:正式开始构建时触发;
- 参数:同样是当前编译的
compilation
对象。
compilation.hooks.optimizeChunks
:
- 时机:
seal
函数中,chunk
集合构建完毕后触发; - 参数:
chunks
集合与chunkGroups
集合。
compiler.hooks.done
:
- 时机:编译完成后触发;
- 参数:
stats
对象,包含编译过程中的各类统计信息。
每个钩子传递的上下文参数不同,但主要包含如下几种类型(以 Webpack5 为例):
- complation 对象:构建管理器,提供如下接口:
addModule
:用于添加模块addEntry
:添加新的入口模块emitAsset
:用于添加产物文件getDependencyReference
:从给定模块返回对依赖项的引用- 等等。
- compiler 对象:全局构建管理器,提供如下接口:
createChildCompiler
:创建子compiler
对象,子对象将继承原始 Compiler 对象的所有配置数据;createCompilation
:创建compilation
对象,可以借此实现并行编译;close
:结束编译;getCache
:获取缓存接口getInfrastructureLogger
:获取日志对象;- 等等。
- module 对象:资源模块,有诸如
NormalModule/RawModule/ContextModule
等子类型,提供如下接口:
identifier
:读取模块的唯一标识符;getCurrentLoader
:获取当前正在执行的 Loader 对象;originalSource
:读取模块原始内容;serialize/deserialize
:模块序列化与反序列化函数,用于实现持久化缓存issuer
:模块的引用者;isEntryModule
:用于判断该模块是否为入口文件;- 等等。
- chunk 对象:模块封装容器,提供如下接口:
addModule
:添加模块,之后该模块会与 Chunk 中其它模块一起打包,生成最终产物;removeModule
:删除模块;containsModule
:判断是否包含某个特定模块;size
:推断最终构建出的产物大小;hasRuntime
:判断 Chunk 中是否包含运行时代码;updateHash
:计算 Hash 值。
- stats 对象:构建过程收集到的统计信息,包括模块构建耗时、模块依赖关系、产物文件列表等。
上面的这些来自课程中,个人对其进行了一定的删减,尊重原作者,同时课程中表示参考资料很少,并且在快速迭代,建议通过源码学习,所以我旨意让其难以理解,但是需要知道有这么一个玩意,好对应的查看源码。
总结
webpack
的插件学习是一件任重而道远的事情,参考资料少外加内容多,可能学习插件就是阅读源码的起点。
课程中后面讲的是第三方插件的源码解读,内容讲的大概是这个插件使用了什么钩子,为什么要在这么阶段使用这个钩子,多看看第三方库也能培养阅读源码的能力,同时也能提升自己。