最新总结,7k字一篇搞懂webpack plugin插件机制的来龙去脉,webpack再也不是我的软肋了!

如果你想学到更多实用前端知识。

可以关注我的公众号:【前端驿站Lite】,一个不止分享前端的地方 ᕦ( •̀∀•́)ᕤ

阅读收获

阅读完本篇文章,你将会有以下收获:

  1. webpack为什么会有plugin机制。
  2. webpack运行原理是怎样的。
  3. 详细分析了compiler和compilation的区别。
  4. Tapable是什么?有哪些钩子?如何使用?
  5. 如何自定义一个plugin。
  6. 3个自定义plugin实战案例。
  7. 9个常用plugin介绍与用法。

plugin机制出现原因

前面我们已经知道了,loader机制让webpack拥有了处理除js类型文件以外的能力。

那如果我们需要在项目中实现打包前自动清理上次打包生成的文件将一些文件复制到打包目录中自动生成html文件将打包产物自动上传至服务器将打包后代码进行压缩、拆分等一系列定制化功能,此时就必须借助webpack的plugin机制去实现了。

没错,webpack的plugin机制让webpack有了定制化的能力。

plugin原理

那具体如何通过plugin机制去实现这些定制化功能呢?

其实是webpack在打包过程中的不同阶段(配置文件读取完成后、打包开始前、打包完成后等阶段)会触发不同的钩子,我们只需要明确要实现的功能应该在哪个阶段,然后将具体实现代码注册为对应钩子的事件即可。

webpack运行原理

我们在了解这些钩子之前,必须要知道webpack的运行原理。

这是一个简化版的webpack打包过程,当我们执行webpack build命令后,webpack会先读取配置文件,然后根据配置文件中的配置项去初始化,创建一个compiler对象,然后调用compiler对象的run方法,初始化一个compilation对象,执行compilation中的build方法进行编译,编译完成后,触发compiler对象的done钩子,完成打包。

H62Kow

//第一步:搭建结构,读取配置参数,这里接受的是webpack.config.js中的参数
function webpack(webpackOptions) {
   
  //第二步:用配置参数对象初始化 `Compiler` 对象
  const compiler = new Compiler(webpackOptions);
  //第三步:挂载配置文件中的插件
 const {
    plugins } = webpackOptions;
 for (let plugin of plugins) {
   
   plugin.apply(compiler);
 }
  return compiler;
}
//Compiler其实是一个类,它是整个编译过程的大管家,而且是单例模式
class Compiler {
   
  constructor(webpackOptions) {
   
   //省略
  }
  
  // 第五步:创建compilation对象
 compile(callback){
   
   //虽然webpack只有一个Compiler,但是每次编译都会产出一个新的Compilation,
   //这里主要是为了考虑到watch模式,它会在启动时先编译一次,然后监听文件变化,如果发生变化会重新开始编译
   //每次编译都会产出一个新的Compilation,代表每次的编译结果
   let compilation = new Compilation(this.options);
   compilation.build(callback); //执行compilation的build方法进行编译,编译成功之后执行回调
 }

 //第四步:执行`Compiler`对象的`run`方法开始执行编译
 run(callback) {
   
   this.hooks.run.call(); //在编译前触发run钩子执行,表示开始启动编译了
   const onCompiled = () => {
   
     // 第七步:当编译成功后会触发done这个钩子执行
     this.hooks.done.call();
   };
   this.compile(onCompiled); //开始编译,成功之后调用onCompiled
  }
}
 class Compilation {
   
   constructor(webpackOptions) {
   
     this.options = webpackOptions;
     this.modules = []; //本次编译所有生成出来的模块
     this.chunks = []; //本次编译产出的所有代码块,入口模块和依赖的模块打包在一起为代码块
     this.assets = {
   }; //本次编译产出的资源文件
     this.fileDependencies = []; //本次打包涉及到的文件,这里主要是为了实现watch模式下监听文件的变化,文件发生变化后会重新编译
   }
   
   //第六步:执行compilation的build方法进行编译
   build(callback) {
   
    //这里开始做编译工作,编译成功执行callback
     
    // ... 编译过程代码省略
     
    // 编译完成后,触发callback回调
    callback()
   }
 }

compiler 与 compilation

那上面提到的compiler对象和compilation对象到底是什么呢?又有什么区别与联系?

  • compiler对象包含了webpack的所有配置信息,包括entryoutputmoduleplugins等,compiler对象会在启动webpack时,一次性地初始化创建,它是全局唯一的,可以简单理解为webpack的实例。
  • compilation对象代表一次资源的构建,通过一系列API可以访问/修改本次模块资源、编译生成的资源、变化的文件、以及被跟踪依赖的状态信息等,当我们以开发模式运行webpack时,每当检测到一个文件变化,就会创建一个新的compilation对象,所以compilation对象也是一次性的,只能用于当前的编译。

他有以下主要属性:

  • compilation.modules 解析后的所有模块
  • compilation.chunks 所有的代码分块chunk
  • compilation.assets 本次打包生成的所有文件
  • compilation.hooks compilation所有的钩子

所以说呢,compiler 代表的是整个 webpack 从启动到关闭的生命周期(终端结束,该生命周期结束), 而 compilation 只是代表了一次性的编译过程,如果是watch模式,每次监听到文件变化,都会产生一个新的 compilation,所以 compilation 代表一次资源的构建,会多次被创建,而 compiler 只会被创建一次。

我们了解了compilercompilation对象后,就可以来看一下到底有哪些钩子。

compiler钩子

compiler有很多钩子官方地址中文地址,介绍几个常用的:

  • beforeRun AsyncSeriesHook类型,开始读取配置文件前触发。
  • run AsyncSeriesHook类型,开始编译后触发。
  • watchRun,AsyncSeriesHook类型,在监听模式下,一个新的 compilation 触发之后,但在 compilation 实际开始之前触发。
  • compile SyncHook类型,一次新的编译(compilation)创建之前触发。
  • compilation SyncHook类型,一次新的编译(compilation)创建完成后触发。
  • emit AsyncSeriesHook类型,生成资源到 output 目录之前触发。
  • done AsyncSeriesHook类型,compilation编译完成后触发。
  • failed SyncHook类型,compilation编译失败后触发。

compilation钩子

compilation对象也有很多钩子官方地址中文地址,介绍几个常用的:

  • buildModule SyncHook类型,模块开始编译前,执行该钩子,可以用于修改模块内容。
  • succeedModule SyncHook类型,模块编译成功后,执行该钩子。
  • finishModules AsyncSeriesHook类型,所有模块编译完成后,执行该钩子。
  • seal SyncHook类型,在构建过程封存前触发,允许在最终资源生成之前进行一些操作。
  • optimize SyncHook类型,资源优化前触发,可以用于自定义资源优化逻辑。
  • optimizeAssets AsyncSeriesHook类型,在资源优化过程中触发,可以监听和修改资源的优化过程。
  • optimizeChunkAssets:AsyncSeriesHook类型,在块资源优化过程中触发,可用于自定义块资源的优化逻辑。
  • optimizeTree:AsyncSeriesHook类型,在资源树优化过程中触发,允许修改资源树的优化逻辑。
  • afterOptimizeTree:SyncHook类型,在资源树优化完成后触发,可用于处理优化完成后的资源树。
  • beforeHash:SyncHook类型,在计算输出文件的哈希之前触发,可以监听和修改哈希生成的逻辑。
  • afterHash:SyncHook类型,在输出文件哈希计算完成后触发,可用于处理生成的哈希值。
  • beforeModuleAssets:SyncHook类型,在生成模块资源之前触发,可用于在模块资源生成前执行一些操作。
  • moduleAsset:SyncHook类型,在生成模块资源时触发,可监听和修改模块资源的生成。
  • processAssets:AsyncSeriesHook类型,在生成资源(如 JavaScript 文件、CSS 文件等)时触发,可以监听和修改资源的生成。

每个钩子都有对应的类型,那这些类型有什么区别呢?

接下来,我们需要了解下Tapable

Tapable是什么

Tapable是一个提供事件发布订阅的工具,通过其提供的一系列钩子,我们可以注册事件,然后在不同的阶段去触发这些注册的事件。
webpack的plugin机制正是基于 Tapable 实现的,在不同编译阶段触发不同的钩子。

Tapable 官方文档提供了这九种钩子,也就是我们上面提到的钩子类型:

const {
   
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook
 } = require("tapable");

可以看到,这些钩子有两种开头,分别是Sync和Async,这两种钩子的区别是:

  • Sync开头的为同步钩子,表示注册的事件函数会同步进行执行
  • Async开头的为异步钩子,表示注册的事件函数会异步进行执行

同时呢,这些钩子还有三种结尾,分别是HookBailHookWaterfallHookLoopHook,这三种结尾的区别是:

  • Hook结尾的为普通钩子,只会按顺序挨个执行注册的事件,不会去管事件函数的返回值是什么。
    yPnrgv
  • BailHook结尾的为保险钩子,只要注册的事件函数有一个返回值不为undefined,就会停止执行后面的事件函数。
    PKPgWy
  • WaterfallHook结尾的为瀑布钩子,注册的事件函数会按顺序执行,每个事件函数的返回值会作为下一个事件函数的参数,只会影响下一个事件函数的第一个参数。
    hNLCot
  • LoopHook结尾的为循环钩子,注册的事件函数会按顺序执行,只要执行的事件返回值非undefeind,就会立即重头开始执行,直到所有的事件函数都返回undefined,这个钩子才会结束。
    72Y6M6

接下来,我们又发现,异步钩子又是以AsyncParallelAsyncSeries开头,这又有什么区别呢?

  • AsyncSeries 为异步串行钩子,注册的事件函数会按顺序挨个执行,每个事件函数执行完后,会调用回调函数,然后再执行下一个事件函数。
  • AsyncParallel 为异步并行钩子,注册的事件函数会同时执行,不会等待上一个事件函数执行完毕后再执行下一个事件函数。

下面我们就来讲一下这些钩子如何去使用。

Tapable同步钩子

同步钩子只需要调用tap方法注册事件,然后调用call方法触发事件即可。

1. SyncHook

SyncHook 是一个同步的、普通类型的 Hook,注册的事件函数会按顺序挨个执行,不会去管事件函数的返回值是什么。

const {
    SyncHook } = require('tapable');

// 初始化钩子,定义形参
const hook = new SyncHook(['name', 'age']);

// 注册事件1
hook.tap('事件1', (name, age) => {
   
  console.log('事件1执行:', name, age);
});

// 注册事件2
hook.tap('事件2', (name, age) => {
   
  console.log('事件2执行:', name, age);
});

// 触发事件,传入实参
hook.call('前端', 18);

// 执行结果
// 事件1执行: 前端 18
// 事件2执行: 前端 18

2. SyncBailHook

SyncBailHook 是一个同步的、保险类型的 Hook,意思是只要其中一个有返回了,后面的就不执行了。

const {
    SyncBailHook } = require('tapable');

// 初始化钩子,定义形参
const hook = new SyncBailHook(['name', 'age']);

// 注册事件1
hook.tap('事件1', (name, age) => {
   
  console.log('事件1执行:', name, age);
});

// 注册事件2
hook.tap('事件2', (name, age) => {
   
  console.log('事件2执行:', name, age);
  return 'abc'
});

// 注册事件3
hook.tap('事件3', (name, age) => {
   
  console.log('事件3执行:', name, age);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值