跳到主要内容

认识

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 的区别?

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 钩子中, 获取所有构建产物,

参考资料


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