阿里云飞天发布时刻,领先大模型限免,超7000万 tokens免费体验 了解详情
写点什么

Webpack 的插件机制 - Tapable

  • 2021-07-31
  • 本文字数:3719 字

    阅读完需:约 12 分钟

Webpack 的插件机制 - Tapable

前言


用了这么久的 Webpack,你一定对它的生态重要组成部分loaderplugin很好奇吧,你是否尝试过编写自己的插件呢,是否了解过 Webpack 的插件机制呢,什么?没有,那还不赶紧上车学一波!


1、tapable


Webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。Webpack 通过 Tapable 来组织这条复杂的生产线。Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。Webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。——「深入浅出 Webpack」


作为 Webpack 的核心库,tabpable承包了 Webpack 最重要的事件工作机制,包括 Webpack 源码中高频的两大对象(compilercompilation)都是继承自Tapable类的对象,这些对象都拥有Tapable的注册和调用插件的功能,并向外暴露出各自的执行顺序以及hook类型,详情可见文档


2、tapable 的钩子


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


上面是官方文档给出的 9 种钩子的类型,我们看命名就能大致推测他们的类型和区别,分成同步、异步,瀑布流、串行、并行类型、循环类型等等,钩子的目的是为了显式地声明,触发监听事件时(call)传入的参数,以及订阅该钩子的 callback 函数所接受到的参数,举个最简单的🌰


const sync = new SyncHook(['arg']) // 'arg' 为参数占位符sync.tap('Test', (arg1, arg2) => {  console.log(arg1, arg2) // a,undefined})sync.call('a', '2')
复制代码


上述代码定义了一个同步串行钩子,并声明了接收的参数的个数,可以通过hook实例对象(SyncHook本身也是继承自Hook类的)的tap方法订阅事件,然后利用call函数触发订阅事件,执行 callback 函数,值得注意的是 call 传入参数的数量需要与实例化时传递给钩子类构造函数的数组长度保持一致,否则,即使传入了多个,也只能接收到实例化时定义的参数个数。


序号钩子名称执行方式使用要点
1SyncHook同步串行不关心监听函数的返回值
2SyncBailHook同步串行只要监听函数中有一个函数的返回值不为 null,则跳过剩余逻辑
3SyncWaterfallHook同步串行上一个监听函数的返回值将作为参数传递给下一个监听函数
4SyncLoopHook同步串行当监听函数被触发的时候,如果该监听函数返回 true 时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环
5AsyncParallelHook异步并行不关心监听函数的返回值
6AsyncParallelBailHook异步并行只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到 callAsync 等触发函数绑定的回调函数,然后执行这个被绑定的回调函数
7AsyncSeriesHook异步串行不关心 callback()的参数
8AsyncSeriesBailHook异步串行callback()的参数不为 null,就会直接执行 callAsync 等触发函数绑定的回调函数
9AsyncSeriesWaterfallHook异步串行上一个监听函数的中的 callback(err, data)的第二个参数,可以作为下一个监听函数的参数

上述表格罗列了所有 hook 的使用方式和要点。


3、注册事件回调


注册事件回调有三个方法:taptapAsync 和 tapPromise,其中 tapAsync 和 tapPromise 不能用于 Sync 开头的钩子类,强行使用会报错。tap的使用方式在上文已经展示过了,就用官方文档的例子展示下tapAsync的使用方式,相比于taptapAsync需要执行 callback 函数才能确保流程会走到下一个插件中去。


myCar.hooks.calculateRoutes.tapAsync("BingMapsPlugin", (source, target, routesList, callback) => { bing.findRoute(source, target, (err, route) => {  if(err) return callback(err);  routesList.add(route);  // call the callback  callback(); });});
复制代码


4、触发事件


触发事件的三个方法是与注册事件回调的方法一一对应的,这点从方法的名字上也能看出来:call 对应 tapcallAsync 对应 tapAsync 和 promise 对应 tapPromise。一般来说,我们注册事件回调时用了什么方法,触发时最好也使用对应的方法。同样需要注意的是 callAsync 有个 callback 函数,在逻辑完毕时需要执行,一些具体用法类似于上面的注册事件类似,就不一一展开了。


5、了解机制


那么在 Webpack 中到底如何使用 tapable 调用这些 plugin 呢?


我们首先来看官网给出的编写一个 plugin 的示例


class HelloWorldPlugin {  apply(compiler) {    compiler.hooks.done.tap('Hello World Plugin', (      compilation /* compilation is passed as an argument when done hook is tapped.  */    ) => {      console.log('Hello World!');    });  }}
module.exports = HelloWorldPlugin;
复制代码


上述代码块编写了一个叫 HelloWorldPlugin 的类,它提供了一个叫apply的方法,在该方法中我们可以从外部获取到 Webpack 执行全过程中单一的compiler实例,通过compiler实例,我们可以在 Webpack 的生命周期的done节点(也就是上面我们提到的hook)tap 一个监听事件,也就是说当 Webpack 全部流程执行完毕时,监听事件将会被触发,同时stat统计信息会被传入到监听事件中,在事件中,我们就可以通过stat做一系列我们想要做的数据分析。一般来说,使用一个 Webpack 插件,需要在 Webpack 配置文件中导入(import)插件的类,new 一个实例,like this:


// Webpack.config.jsvar HelloWorldPlugin = require('hello-world');
module.exports = { // ... configuration settings here ... plugins: [new HelloWorldPlugin({ options: true })]};
复制代码


这里聪明的你一定想到了 Webpack 应该是读取了这份配置文件后获得了HelloWorldPlugin实例,并调用了实例的apply方法,在done节点上添加了监听事件!没错,让我们来追溯下 Webpack 的源码部分,在 Webpack 项目的lib/Webpack.js文件中,我们可以看到


if (options.plugins && Array.isArray(options.plugins)) {    for (const plugin of options.plugins) {  if (typeof plugin === "function") {   plugin.call(compiler, compiler);  } else {   plugin.apply(compiler);  } }}
复制代码


这段代码中options就是指配置文件导出的整个对象,这里可以看到 Webpack 循环遍历了一遍 plugins,并分别调用了他们的 apply 方法,当然如果 plugin 是function类型,就直接用call来执行,这也就是我上文提到的一般来说的例外,如果你的插件逻辑很简单,你可以直接在配置文件里写一个function,去执行你的逻辑,而不必啰嗦的写一个类或者用更纯粹的prototype去定义类的方法。到这里为止,我们已经了解了插件中的监听事件是如何注册到 Webpack 的compilecompilationtapable类)上去的,那监听事件是如何、何时被触发的呢,理论上应该是先注册完毕,后触发,这样监听事件才有意义,我们接着发现,在lib/Compiler.js中的Compiler类的run函数里有这样一段代码


const onCompiled = (err, compilation) => { if (err) return finalCallback(err);
if (this.hooks.shouldEmit.call(compilation) === false) { ... this.hooks.done.callAsync(stats, err => { if (err) return finalCallback(err); return finalCallback(null, stats); }); return; }
this.emitAssets(compilation, err => { if (err) return finalCallback(err);
if (compilation.hooks.needAdditionalPass.call()) { ... this.hooks.done.callAsync(stats, err => { ... }); return; }
this.emitRecords(err => { if (err) return finalCallback(err);
... this.hooks.done.callAsync(stats, err => { if (err) return finalCallback(err); return finalCallback(null, stats); }); }); });};... this.compile(onCompiled);
复制代码


回调函数onCompiled会在compile过程结束时被调用,无论走到哪个 if 逻辑中,this.hooks.done.callAsync都会被执行,也就是说在 done 节点上注册的监听事件会按照顺序依次被触发执行。接着我们再向上追溯,包裹了onCompiled函数的run函数是在lib/Webpack.js中被执行的


if (Array.isArray(options)) {    ...} else if (typeof options === "object") {    ... compiler = new Compiler(options.context); compiler.options = options; if (options.plugins && Array.isArray(options.plugins)) {  for (const plugin of options.plugins) {   if (typeof plugin === "function") {    plugin.call(compiler, compiler);   } else {    plugin.apply(compiler);   }  } }} else { ...}if (callback) { ... compiler.run(callback);}
复制代码


刚好在plugin.apply()的后面,所以是符合先注册监听事件,再触发的逻辑顺序的。


是不是已经有点乱了,来来来,我们用流程图简单捋一下。

插件的注册执行流程图示



6、总结


tapable 作为 Webpack 的核心库,承接了 Webpack 最重要的事件流的运转,它巧妙的钩子设计很好的将实现与流程解耦开来,真正实现了插拔式的功能模块,在 Webpack 中最核心的负责编译的 Compiler 和负责创建的 bundles 的 Compilation 都是 Tapable 的实例,可以说想要真正读懂 Webpack,tapable 的知识储备是必不可少的,它的一些设计思想也是很值得我们借鉴的,本文只是对 tapable 的一些 api 以及 Webpack 如何使用 tapable 串起了整个插件流工作机制做了介绍。



头图:Unsplash

作者:丁楠

原文:https://mp.weixin.qq.com/s/qWq46-7EJb0Byo1H3SDHCg

原文:Webpack 的插件机制 - Tapable

来源:微医大前端技术 - 微信公众号 [ID:wed_fed]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021-07-31 22:004283

评论

发布
暂无评论
发现更多内容

首届OpenHarmony竞赛训练营结营颁奖,75所高校学子助力建设开源生态

OpenHarmony开发者

OpenHarmony

软件测试/测试开发丨性能测试体系学习笔记

测试人

软件测试

一个java文件的JVM之旅 | 京东物流技术团队

京东科技开发者

Java JVM 类加载机制 企业号11月PK榜

“箭在弦上”的边缘计算,更需要冷静和智慧

脑极体

服务器

DDD技术方案落地实践 | 京东云技术团队

京东科技开发者

架构 领域驱动设计 DDD 企业号11月PK榜

一物一码需求,标签制作功能轻松解决

草料二维码

二维码 二维码生成 标签制作 一物一码

矢量图设计软件层出不穷,CorelDRAW为何无人能替?

淋雨

设计 矢量图 CorelDraw 绘画 设计软件

荣誉 | 观测云登榜「2023 中国好 SaaS TOP 10 SaaS 企业 」

观测云

可观测性 SaaS

Luminar Neo ai技术图像编辑工具 中文版 支持M1

繁星

Luminar Neo 图像处理工具

分布式AI在LLM时代的技术深度探索

不在线第一只蜗牛

人工智能 AI lee

Unity中国全面支持OpenHarmony游戏开发,多款游戏率先完成适配

最新动态

OpenAI 深夜炸场,更强更便宜;英特尔 CEO 分享三大失败原因;黄仁勋说成龙长得像他丨 RTE 开发者日报 Vol.79

声网

体验问题哪里找?点击链接获取答案~ | 京东云技术团队

京东科技开发者

测试 用户体验 用户体验分析 企业号11月PK榜

如何使用 NFTScan NFT API 在 Arbitrum 网络上开发 Web3 应用

NFT Research

NFT\ NFTScan nft工具 API 接口

Mac 版截图工具链

Eric 老乌龟

macos 工具

销售易取得500强客户背后的实践与进化

B Impact

Mac剪切板管理工具:Paste 4.1.2中文版

繁星

Paste中文版 剪切板管理

完蛋!我被 Out of Memory 包围了! | 京东云技术团队

京东科技开发者

Java Linux 内存泄漏 Out Of Memory 企业号11月PK榜

免费好用的10款在线画板软件推荐,你值得拥有!

彭宏豪95

效率 科技 在线白板 办公软件 团队协作工具

inBuilder低代码平台新特性推荐-第5期

inBuilder低代码平台

低代码

聊聊低代码技术

互联网工科生

软件开发 低代码

云电脑与5G网络的结合将会带来什么

青椒云云电脑

云电脑

低代码工具的常见用例与受众市场

树上有只程序猿

低代码

瓴羊X阿里云上的Salesforce联合解决方案正式发布

ToB行业头条

使用openpyxl库读取Excel文件数据

不在线第一只蜗牛

工具 数据 Excel 教程

喜报 | MIAOYUN通过2023年度四川省“专精特新”中小企业认定!

MIAOYUN

专精特新 MIAOYUN 高新技术企业 专精特新中小企业 专精特新企业

领跑同一阵营!百分点科技入选Forrester AI/ML权威报告

百分点科技技术团队

人工智能 数据科学 百分点科技

华为云开源 | 线下meetup · 电子科技大学站圆满收官

华为云开源

云原生 开源项目 开源社区

Webpack 的插件机制 - Tapable_大前端_微医大前端技术_InfoQ精选文章