TapableHookPlugins

news/2025/3/15 5:12:39/

以极客时间《玩转Webpack》课程学习为主的记录笔记。

源码解读

  1. webpack的命令跟踪,从node_modules/webpack/bin/ 可以看到命令内容,webpack会查看是否下载安装了webpack-cli / webpack-command。
  2. 使用webpack-cli 解析命令行信息、安装使用到的依赖,启动webpack构建。

Hook和Tapable

  1. 从webpack的文件(/node_modules/webpack/lib/webpack.js)中可见:根绝传入的options,将生成多个不同的compiler实例。
  2. Compiler(/node_modules/webpack/lib/Compiler.js) / Complization
    Compiler、Complization继承了Tapable对象,内部实现了很多的hooks。
    常见Compiler Hooks
  • entryOption:在entry配置项处理过之后,执行一个插件。
  • emit:在生成资源到输出目录之前执行。
  • done:在编译完成时执行。

常见Compilation Hooks

  • buildModule:在模块构建之前执行。
  • optimize:在优化阶段开始时执行。
  • optimizeChunks:在块优化阶段时才执行。
  • afterSeal:完成封装之后执行。
Compiler

Compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性创建,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中执行构建时,Compiler 对象负责处理所有事务。
当你运行 webpack 命令时,一个 Compiler 实例会被创建,并且在其生命周期中,它会对每个构建创建出一个新的 Compilation 对象。
Compiler 对象可以访问所有的 webpack 环境特定的功能,包括起始选项,构建的所有配置,以及注册的所有插件等。

Compilation

Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 webpack 以开发模式运行时,每当检测到一个文件变化,就会创建一个新的 Compilation,意味着 Compilation 对象描述的是一次单一的版本构建和生成资源的过程。
在构建过程中,Compilation 实例会被用来收集从入口开始的所有依赖,对这些依赖进行处理、优化、分割等操作,并最终输出编译后的资源文件到文件系统。
总结一下,Compiler 是一个 webpack 构建过程中的全局上下文,而 Compilation 是对于单次构建的上下文抽象。开发者可以通过 hooks 挂钩到 CompilerCompilation 的生命周期事件中,以扩展和控制构建过程。例如,plugin 通过监听特定的事件,可以在某个特定的构建时刻介入构建流程,添加模块、改变输出资源等。

// 源码
class Compiler {/*** @param {string} context the compilation path* @param {WebpackOptions} options options*/constructor(context, options = /** @type {WebpackOptions} */ ({})) {this.hooks = Object.freeze({/** @type {SyncHook<[]>} */initialize: new SyncHook([]),/** @type {SyncBailHook<[Compilation], boolean | undefined>} */shouldEmit: new SyncBailHook(["compilation"]),/** @type {AsyncSeriesHook<[Stats]>} */done: new AsyncSeriesHook(["stats"]),/** @type {SyncHook<[Stats]>} */afterDone: new SyncHook(["stats"]),/** @type {AsyncSeriesHook<[]>} */additionalPass: new AsyncSeriesHook([]),/** @type {AsyncSeriesHook<[Compiler]>} */beforeRun: new AsyncSeriesHook(["compiler"]),/** @type {AsyncSeriesHook<[Compiler]>} */run: new AsyncSeriesHook(["compiler"]),/** @type {AsyncSeriesHook<[Compilation]>} */emit: new AsyncSeriesHook(["compilation"]),/** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */assetEmitted: new AsyncSeriesHook(["file", "info"]),/** @type {AsyncSeriesHook<[Compilation]>} */afterEmit: new AsyncSeriesHook(["compilation"]),/** @type {SyncHook<[Compilation, CompilationParams]>} */thisCompilation: new SyncHook(["compilation", "params"]),/** @type {SyncHook<[Compilation, CompilationParams]>} */compilation: new SyncHook(["compilation", "params"]),/** @type {SyncHook<[NormalModuleFactory]>} */normalModuleFactory: new SyncHook(["normalModuleFactory"]),/** @type {SyncHook<[ContextModuleFactory]>}  */contextModuleFactory: new SyncHook(["contextModuleFactory"]),/** @type {AsyncSeriesHook<[CompilationParams]>} */beforeCompile: new AsyncSeriesHook(["params"]),/** @type {SyncHook<[CompilationParams]>} */compile: new SyncHook(["params"]),/** @type {AsyncParallelHook<[Compilation]>} */make: new AsyncParallelHook(["compilation"]),/** @type {AsyncParallelHook<[Compilation]>} */finishMake: new AsyncSeriesHook(["compilation"]),/** @type {AsyncSeriesHook<[Compilation]>} */afterCompile: new AsyncSeriesHook(["compilation"]),/** @type {AsyncSeriesHook<[]>} */readRecords: new AsyncSeriesHook([]),/** @type {AsyncSeriesHook<[]>} */emitRecords: new AsyncSeriesHook([]),/** @type {AsyncSeriesHook<[Compiler]>} */watchRun: new AsyncSeriesHook(["compiler"]),/** @type {SyncHook<[Error]>} */failed: new SyncHook(["error"]),/** @type {SyncHook<[string | null, number]>} */invalid: new SyncHook(["filename", "changeTime"]),/** @type {SyncHook<[]>} */watchClose: new SyncHook([]),/** @type {AsyncSeriesHook<[]>} */shutdown: new AsyncSeriesHook([]),/** @type {SyncBailHook<[string, string, any[]], true>} */infrastructureLog: new SyncBailHook(["origin", "type", "args"]),// TODO the following hooks are weirdly located here// TODO move them for webpack 5/** @type {SyncHook<[]>} */environment: new SyncHook([]),/** @type {SyncHook<[]>} */afterEnvironment: new SyncHook([]),/** @type {SyncHook<[Compiler]>} */afterPlugins: new SyncHook(["compiler"]),/** @type {SyncHook<[Compiler]>} */afterResolvers: new SyncHook(["compiler"]),/** @type {SyncBailHook<[string, Entry], boolean>} */entryOption: new SyncBailHook(["context", "entry"])});this.webpack = webpack;// ......}// ....
}
webpack可以理解为一种基于事件流的编程范例,一系列的插件运行。内部实现订阅和通知功能的模块就是Tapable。

Tapable

Tapable是一个类似于Nodejs EventEmiter的库,用于控制hooks的发布订阅功能的一个模块。

Tapable暴露了很多Hook,为插件提供挂载的钩子
const{
SyncHook, //同步钩子
SyncBailHook, //同步熔断钩子 当函数有任何返回值,就会在当前执行函数停止(遇到return将直接返回)
SyncWaterfallHook, //同步流水钩子 运行结果可以传递给下一个插件
SyncLoopHok, //同步循环钩子 监听函数返回true表示继续循环,返回undefine表示结束循环
AsyncParallelHook, //异步并发钩子
AsyncParallelBailHook, //异步并发熔断钩子
AsyncSeriesHook, //异步串行钩子
AsyncSeriesBailHook, //异步串行熔断钩子
AsyncSeriesWaterfallHook //异步串行流水钩子
} = require(“tapable”);

使用:const hook1 = new SyncHook(['argu1','argu2',])

使用方式:

Tabpack提供了同步&异步绑定钩子的方法,并且他们都有绑定事件和执行事件对
应的方法。

AsyncSync
绑定:tapAsync/tapPromise/tap绑定:tap
执行:callAsync/promise执行:call

简单用法:

const hook1 = new SyncHook(["arg1","arg2","arg3"]); //绑定事件流到webapck事件流
hook1.tap('hook1',(arg1,arg2,arg3)=>console.log(arg1,arg2,arg3))//1,2,3 //执行绑定的事件
hook1.call(1,2,3)

自测书写一个Plugin订阅构建实例(Compiler)中提供的hook,使用Compilation

Plugin的固定写法为:

  1. 暴露一个类。
  2. 类中存在webpack的调用入口:apply,webpack在初始化时,将主动执行Myplugin.apply,将compiler构建实例传入插件中,供挂载订阅hook节点使用。
  3. 看下方的实现代码,所有的发布hook都在compiler.hooks中,compiler.hooks.xxx就是发布的事件名称,订阅流程节点信息就是使用tap(同步)/ tapAsync(异步)绑定上事件名和方法。
// eg1:
// const Compiler = require('./Compiler')class MyPlugin{constructor() {}apply(compiler){compiler.hooks.calculateRoutes.tapPromise("calculateRoutes tapAsync", (source, target, routesList) => {return new Promise((resolve,reject)=>{setTimeout(()=>{console.log(`tapPromise to ${source} ${target} ${routesList}`)resolve();},1000)});});}
}const myPlugin = new MyPlugin();
// eg2:使用`emit`钩子在每次构建完成后输出一个文本文件
class MyExampleWebpackPlugin {// 插件必须具有apply方法apply(compiler) {// 确定在哪个阶段插入该插件compiler.hooks.emit.tapAsync('MyExampleWebpackPlugin', (compilation, callback) => {// 创建一份新的资源const mySource = '这是我的插件添加的资源内容';// 向Webpack输出列表中添加资源compilation.assets['my-plugin-output.txt'] = {source: function() {return mySource;},size: function() {return mySource.length;}};// 执行回调,表示插件处理完成callback();});}
}module.exports = MyExampleWebpackPlugin;

webpack流程

Chunk 生成算法
  1. webpack 先将 entry 中对应的 module 都生成一个新的chunk
  2. 遍历 module 的依赖列表,将依赖的 module 也加入到chunk中
  3. 如果一个依赖 module 是动态引入的模块,那么就会根据这个module创建一个新的 chunk,继续遍历依赖
  4. 重复上面的过程,直至得到所有的 chunks
AST

抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntaxtree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。树上的每个节点都表示源代码中的一种结构。
在线demo: https://esprima.org/demo/parse.html

loader开发

一个最简单的loader栗子,loader主要用于"翻译代码变成JS引擎可识别的代码“

module.exports = function(source) {console.log ('loader a is executed');return source;
};
建议使用loader-runner来简化开发流程。

loader-runner可以提供一个独立的loader运行环境,不用下载webpack,有利于调试。

// 使用举例
// 新建一个命令文件 run-loader.js:const fs = require("fs");
const path = require("path");
const { runLoaders } = require("loader-runner");runLoaders({resource: "./demo.txt",		// 要翻译的对象文件loaders: [path.resolve(__dirname, "./loaders/rawloader")], 		// 当前loader在的地方readResource: fs.readFile.bind(fs), 	// 读取文件的方式},(err, result) => (err ? console.error(err) : console.log(result))
);
运行查看结果:
node run-loader.js

http://www.ppmy.cn/news/1350772.html

相关文章

ONLYOFFICE文档8.0新功能浅探

ONLYOFFICE文档8.0新功能浅探 上个月末这个月初的几天&#xff0c;ONLYOFFICE版本更新了&#xff01;更新到了一个比较整的大的版本号&#xff0c;8.0版本&#xff0c;看来这个生产力工具的升级速度基本上能保持每年两个版本号的速度&#xff0c;还是很快的&#xff0c;一般来…

不懂编程?节点包来凑——Dynamo常用节点包推荐(上)

由于篇幅有限&#xff0c;本次文章我们分上、下两篇&#xff0c;来分享给大家。 Dynamo作为一款辅助三维设计工具&#xff0c;他可以通过图形化的编程&#xff0c;帮我们解决很多在设计或者建模过程中遇到的小问题&#xff1b;同时他作为一款可视化编程软件&#xff0c;学起来…

计算机服务器中了mkp勒索病毒如何解密,mkp勒索病毒解密流程

随着网络技术的不断发展与应用&#xff0c;越来越多的企业走向数字化办公模式&#xff0c;计算机极大地方便了企业的正常生产运营&#xff0c;但网络威胁的手段也不断增加。近期&#xff0c;云天数据恢复接到很多企业的求助&#xff0c;企业的计算机服务器遭到了mkp勒索病毒攻击…

速盾cdn:香港服务器如何用国内cdn

在国内使用香港服务器的情况下&#xff0c;可以考虑使用速盾CDN来提供加速服务。速盾CDN是一种专业的内容分发网络解决方案&#xff0c;可以通过使用不同节点的服务器来提供高速的内容传输和访问。 首先&#xff0c;使用速盾CDN可以帮助解决香港服务器与国内用户之间的延迟和带…

Android 9.0 禁用adb install 安装app功能

1.前言 在9.0的系统产品定制化开发中,在进行一些定制开发中,对于一些app需要通过属性来控制禁止安装,比如adb install也不允许安装,所以就需要 熟悉adb install的安装流程,然后来禁用adb install安装功能,接下来分析下adb 下的安装流程 2.禁用adb install 安装app功能的…

linux 下 chrome 无法在设置里面配置代理的解决方法

文章目录 [toc]解决方法查找 chrome 命令路径查看 chrome 启动文件方式一方法二 在 linux 环境下&#xff0c;使用 chrome 没办法像 firefox 一样在设置里面配置代理&#xff0c;打开 chrome 的设置会有下面的内容显示 When running Google Chrome under a supported desktop e…

探索Gorm - Golang流行的数据库ORM框架

&#x1f3f7;️个人主页&#xff1a;鼠鼠我捏&#xff0c;要死了捏的主页 &#x1f3f7;️系列专栏&#xff1a;Golang全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&…

RK3568笔记十三:Zlmedia推流测试

若该文为原创文章&#xff0c;转载请注明原文出处。 使用正点原子的屏幕竖屏用不习惯&#xff0c;所以想推流用VLC方式显示&#xff0c;而Zlmedia功能很强大&#xff0c;推流拉流都有&#xff0c;拉流在前面有提及。研究了几天&#xff0c;最后还是勇哥帮忙&#xff0c;所以知道…