跳到主要内容

认识

2024年04月06日
柏拉文
越努力,越幸运

一、认识


仅仅使用 Webpack 内置的打包能力很难满足项目日益复杂的构建需求。对于一个真实的项目构建场景来说,我们还需要考虑到模块打包之外的问题,比如路径别名(alias) 、全局变量注入和代码压缩等等。Webpack 很多场景的处理逻辑与核心的打包逻辑都写到一起,一来打包器本身的代码会变得十分臃肿,二来也会对原有的核心代码产生一定的侵入性,混入很多与核心流程无关的代码,不易于后期的维护。因此 ,Webpack 设计出了一套完整的插件机制,将自身的核心逻辑与插件逻辑分离,让你能按需引入插件功能,提高了 Webpack 自身的可扩展性。

Webpack 的编译过程中,本质上通过 Tapable 实现了在编译过程中的一种发布订阅者模式的插件 Plugin 机制。 Tapable 提供了一系列事件的发布订阅 API ,通过 Tapable 我们可以注册事件,从而在不同时机去触发注册的事件进行执行。Webpack 中的 Plugin 机制正是基于这种机制实现在不同编译阶段调用不同的插件从而影响编译结果。Tapable包本质上是为我们更方面创建自定义事件和触发自定义事件的库,类似于Nodejs中的EventEmitter ApiWebpack中的插件机制就是基于Tapable实现与打包流程解耦,插件的所有形式都是基于Tapable实现。

Webpack 的打包过程中,会定义一套完整的构建生命周期,从开始打包到产物输出,中途会经历一些标志性的阶段,并且在不同阶段会自动执行对应的插件钩子函数(Hook)。

Webpack 中的 Plugin 是一个类, 每一个插件都必须提供一个 apply 方法。apply 方法会接收一个 compiler 对象。 编写 Plugin 就是通过 tapable 发布订阅的能力操作 compiler 对象从而影响打包结果。其过程为: 每一个 Pluginapply 方法通过 tapableWebpack 编译准备阶段订阅对应的事件, 当编译执行到一定阶段时发布对应事件, 执行对应事件函数, 从而达到在编译阶段的不同生命周期内去触发对应的 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 对象从而影响打包结果。其过程为: 每一个 Pluginapply 方法通过 tapableWebpack 编译准备阶段订阅对应的事件, 当编译执行到一定阶段时发布对应事件, 执行对应事件函数, 从而达到在编译阶段的不同生命周期内去触发对应的 Plugin

因此, pluginloader的区别在于loader只在编译module时执行,而plugin可能在webpack工作流程的各个阶段执行。

参考资料


超硬核|带你畅游在 Webpack 插件开发者的世界