认识
一、认识
仅仅使用 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 的区别
loader
本质上是一个函数, 在 Webpack
进入构建阶段后,首先接受待处理的资源文件路径, 会通过 IO
接口读取文件内容,之后调用 LoaderRunner
并将文件内容以 source
参数形式传递到 Loader
数组,source
数据在 Loader
数组内首先经过pitch
阶段读取资源文件内容再经过normal
阶段处理资源文件内容,最终以标准 JavaScript
代码提交给 Webpack
主流程,以此实现内容翻译功能。
Webpack
中的 Plugin
是一个类, 每一个插件都必须提供一个 apply
方法。apply
方法会接收一个 compiler
对象。 编写 Plugin
就是通过 tapable
发布订阅的能力操作 compiler
对象从而影响打包结果。其过程为: 每一个 Plugin
的 apply
方法通过 tapable
在 Webpack
编译准备阶段订阅对应的事件, 当编译执行到一定阶段时发布对应事件, 执行对应事件函数, 从而达到在编译阶段的不同生命周期内去触发对应的 Plugin
。
因此, plugin
和loader
的区别在于loader
只在编译module
时执行,而plugin
可能在webpack
工作流程的各个阶段执行。