认识
一、认识
仅仅使用 Webpack
内置的打包能力很难满足项目日益复杂的构建需求。对于一个真实的项目构建场景来说,我们还需要考虑到模块打包之外的问题,比如路径别名(alias
) 、全局变量注入和代码压缩等等。Webpack
很多场景的处理逻辑与核心的打包逻辑都写到一起,一来打包器本身的代码会变得十分臃肿,二来也会对原有的核心代码产生一定的侵入性,混入很多与核心流程无关的代码,不易于后期的维护。因此 ,Webpack
设计出了一套完整的插件机制,将自身的核心逻辑与插件逻辑分离,让你能按需引入插件功能,提高了 Webpack
自身的可扩展性。
在 Webpack
的编译过程中,本质上通过 Tapable
实现了在编译过程中的一种发布订阅者模式的插件 Plugin
机制。 Tapable
提供了一系列事件的发布订阅 API
,通过 Tapable
我们可以注册事件,从而在不同时机去触发注册的事件进行执行。Webpack
中的 Plugin
机制正是基于这种机制实现在不同编译阶段调用不同的插件从而影响编译结果。Tapable
包本质上是为我们更方面创建自定义事件和触发自定义事件的库,类似于Nodejs
中的EventEmitter Api
。Webpack
中的插件机制就是基于Tapable
实现与打包流程解耦,插件的所有形式都是基于Tapable
实现。
Webpack
的打包过程中,会定义一套完整的构建生命周期,从开始打包到产物输出,中途会经历一些标志性的阶段,并且在不同阶段会自动执行对应的插件钩子函数(Hook
)。
Webpack
中的 Plugin
是一个类, 每一个插件都必须提供一个 apply
方法。apply
方法会接收一个 compiler
对象。 编写 Plugin
就是通过 tapable
发布订阅的能力操作 compiler
对象从而影响打包结果。其过程为: 每一个 Plugin
的 apply
方法通过 tapable
在 Webpack
编译准备阶段订阅对应的事件, 当编译执行到一定阶段时发布对应事件, 执行对应事件函数, 从而达到在编译阶段的不同生命周期内去触发对应的 Plugin
。
二、语法
2.1 配置语法
plugins: [new Plugin1(), new Plugin2()],
2.2 编写语法
export default class Plugin1 {
constructor(options){
this.options = options;
}
apply(compiler) {
// 方式一: tap
compiler.hooks.run.tap('Plugin1', () => {
console.log('Plugin1');
});
// 方式二: tapAsync
compiler.hooks.run.tapAsync('Plugin1', () => {
console.log('Plugin1');
});
// 方式三: tapPromise
compiler.hooks.run.tapPromise('Plugin1', () => {
console.log('Plugin1');
});
}
}
三、沉淀与思考
3.1 loader 与 plugin 的区别?
Webpack
中的 loader
本质上是一个函数, 接收文件内容源代码作为入参, 同时返回处理后的结果。Loader
用于转换模块的源代码。它们在模块被加入到依赖图之前执行,主要负责对文件内容进行预处理,如将 TypeScript
转译为 JavaScript
、将 SCSS
转换为 CSS
等。Loader
按照配置的规则匹配特定文件,依次处理文件内容,支持链式调用(多个 Loader
串联执行)。Loader
的执行顺序为: Pitch
阶段, 从左到右顺序执行, Normal
阶段, 从右到左逆序执行。
function loader(sourceCode, sourceMap?, data?){
console.log(this);
}
loader.pitch = function(remainingRequest, previousRequest, data){
}
module.exports = loader;
Webpack
中的 Plugin
是一个类, 每一个插件都必须提供一个 apply
方法。apply
方法会接收一个 compiler
对象。Plugin
用于扩展 Webpack
的整体构建流程和功能, 它们可以在整个编译周期内介入, 从初始化、编译、打包到输出等各个环节发挥作用。比如在 compiler.run
阶段、compiler.compile
阶段、compiler.make
阶段、compiler.emit
阶段、compilation.optimizeAssets
等。
class MyPlugin {
constructor(options){
}
apply(compiler){
compiler.hook.compilation.tap("MyPlugin", (compilation)=>{
});
}
}
3.2 列举 Plugin 常用的 Hooks?
Compiler
层级 Hooks
:
-
beforeRun
: 在Compiler.run
开始之前调用,可用于在构建开始前做一些预处理。 -
run
: 当Webpack
开始执行构建时触发,这时Compiler
已经准备好,适合进行一些全局初始化操作。 -
watchRun
: 在watch
模式下,每次检测到文件变更、重新构建前调用。 -
compile
: 当Compiler
开始创建新的Compilation
实例之前调用,适用于修改编译配置或插件间的协调。 -
compilation
: 在每次创建Compilation
实例时调用,传入Compilation
对象,允许插件对具体的编译过程进行干预。 -
afterCompile
: 编译完成后调用,此时所有模块和Chunk
都已生成,可以进一步操作Compilation
。 -
emit
:在输出资源之前触发,允许插件修改、添加或删除最终生成的文件。 -
afterEmit
: 资源输出之后调用,适合做一些后处理操作,如通知系统构建完成。 -
done
: 构建流程全部完成后调用,传入构建统计信息(stats
),适用于记录日志、构建报告等。 -
failed
: 当构建失败时触发,插件可在此处捕获错误进行处理。 -
invalid
: 在watch
模式下,当文件变动导致构建无效时触发,通常用于清理缓存或记录状态。
Compilation
层级 Hooks
:
-
compilation.hooks.afterProcessAssets
-
compilation.hooks.processAssets
钩子中, 获取所有构建产物,