rollup 插件架构-驱动设计 PluginDriver
文章目录
- Graph
- PluginDriver
- 生成 PluginDriver 实例和 PluginCache 缓存
- 创建插件上下文 pluginContext
- 初始化 pluginContext 缓存设置、方法
- 插件中使用缓存
- 可替换的 replace pluginContext
- PluginDriver 提供 asyn、first、parallel 等类型 hook
- getSortedPlugins 运行时收集并存储插件对应 hook
- sync hook
- parallel hook
- this.runHookSync
- this.runHook
- 和 webpack 插件系统区别
Graph
- 在依赖图谱 Graph 中创建 PluginDriver
- Graph 负责整个 Rollup 打包过程中模块的解析、转化、生成,所以在Graph 中创建 PlunginDriver 能够获得整个打包生命周期的模块信息
const graph = new Graph(inputOptions, watcher);
PluginDriver
生成 PluginDriver 实例和 PluginCache 缓存
- 整个 Rollup 构建生命周期中,通过 pluginDriver 这个实例去触发 hook
export default class Graph {readonly acornParser: typeof acorn.Parser;readonly cachedModules = new Map<string, ModuleJSON>();// ...entryModules: Module[] = [];readonly fileOperationQueue: Queue;readonly moduleLoader: ModuleLoader;readonly modulesById = new Map<string, Module | ExternalModule>();needsTreeshakingPass = false;phase: BuildPhase = BuildPhase.LOAD_AND_PARSE;readonly pluginDriver: PluginDriver;// ...constructor (private readonly options: NormalizedInputOptions, watcher: RollupWatcher | null) {// 根据用户设置配置插件缓存this.pluginCache = options.cache?.plugins || Object.create(null);// ...this.pluginDriver = new PluginDriver(this, options, options.plugins, this.pluginCache); // 给插件封装能调用 graph 的方法// ...}
创建插件上下文 pluginContext
- pluginContext 是交给用户在插件中可以调用的相关方法、属性
- 可以看到多个插件的上下文共享同一个 pluginCache、graph
export class PluginDriver {// ... 相关方法private readonly pluginContexts: ReadonlyMap<Plugin, PluginContext>;private readonly plugins: readonly Plugin[];private readonly sortedPlugins = new Map<AsyncPluginHooks, Plugin[]>();private readonly unfulfilledActions = new Set<HookAction>();constructor(private readonly graph: Graph,private readonly options: NormalizedInputOptions,userPlugins: readonly Plugin[],private readonly pluginCache: Record<string, SerializablePluginCache> | undefined,basePluginDriver?: PluginDriver) {// ...this.plugins = [...(basePluginDriver ? basePluginDriver.plugins : []), ...userPlugins];const existingPluginNames = new Set<string>();// 为每个插件创建一个 pluginContext this.pluginContexts = new Map(this.plugins.map(plugin => [plugin,getPluginContext(plugin, pluginCache, graph, options, this.fileEmitter, existingPluginNames)]));// ...}
}
初始化 pluginContext 缓存设置、方法
- 将每个 plugin 按照传递的配置设置缓存 key,存放进 Graph 的 pluginCache 集合中保存
export function getPluginContext(plugin: Plugin,pluginCache: Record<string, SerializablePluginCache> | void,graph: Graph,options: NormalizedInputOptions,fileEmitter: FileEmitter,existingPluginNames: Set<string>
): PluginContext {let cacheable = true;if (typeof plugin.cacheKey !== 'string') {if ( // 插件没写 name 不缓存plugin.name.startsWith(ANONYMOUS_PLUGIN_PREFIX) ||plugin.name.startsWith(ANONYMOUS_OUTPUT_PLUGIN_PREFIX) ||existingPluginNames.has(plugin.name)) {cacheable = false;} else {existingPluginNames.add(plugin.name);}}let cacheInstance: PluginCache;if (!pluginCache) {cacheInstance = NO_CACHE;} else if (cacheable) {// 根据插件传递的配置设置缓存 keyconst cacheKey = plugin.cacheKey || plugin.name;cacheInstance = createPluginCache( // 封装操作 cache 缓存的操作(get、has、set、delete)// 在创建时已经根据 key 分配了对象pluginCache[cacheKey] || (pluginCache[cacheKey] = Object.create(null)));} else {cacheInstance = getCacheForUncacheablePlugin(plugin.name);}// 返回给开发者可以调用的方法、属性return {addWatchFile(id) {if (graph.phase >= BuildPhase.GENERATE) {return this.error(errorInvalidRollupPhaseForAddWatchFile());}graph.watchFiles[id] = true;},cache: cacheInstance,// ...};
}// 记录缓存操作
export function createPluginCache(cache: SerializablePluginCache): PluginCache {return {delete(id: string) {return delete cache[id];},get(id: string) {const item = cache[id];if (!item) return;item[0] = 0;return item[1];},has(id: string) {const item = cache[id];if (!item) return false;item[0] = 0;return true;},set(id: string, value: any) {cache[id] = [0, value];}};
}
插件中使用缓存
- 插件 cache 的内容都会放在 Graph 的 pluginCache 中,在分配缓存时已经根据插件的 key 进行了设置,所以在插件中可以直接 this.cache 进行使用而不必担心和其它插件的缓存冲突
{name: "test-plugin",buildStart() {if (!this.cache.has("cache")) {this.cache.set("cache", "cache something");} else {// 第二次执行rollup的时候会执行console.log(this.cache.get("cache"));}},
}
可替换的 replace pluginContext
- pluginContext 会根据不同的 hook,动态增加属性、方法,比如 transform hook
- 在通过 pluginDriver.hookReduceArg0 调用 transform hook时,第四个参数即是替换后的 pluginContext
code = await pluginDriver.hookReduceArg0('transform',[currentSource, id],transformReducer,(pluginContext, plugin): TransformPluginContext => { pluginName = plugin.name;return {...pluginContext, // 在原来 context 的基础上再添加额外的属性addWatchFile(id: string) {transformDependencies.push(id); // 收集插件中通过 this.addWatchFile 添加的文件 idpluginContext.addWatchFile(id);},cache: customTransformCache? pluginContext.cache: getTrackedPluginCache(pluginContext.cache, useCustomTransformCache),};});
PluginDriver 提供 asyn、first、parallel 等类型 hook
- rollup 根据不同场景提供了不同类型的 hook
- async:该钩子也可以返回一个解析为相同类型的值的 Promise;否则,该钩子被标记为 sync。
- first:如果有多个插件实现此钩子,则钩子按顺序运行,直到钩子返回一个不是 null 或 undefined 的值。
- sequential:如果有多个插件实现此钩子,则所有这些钩子将按指定的插件顺序运行。如果钩子是 async,则此类后续钩子将等待当前钩子解决后再运行。
- parallel:如果有多个插件实现此钩子,则所有这些钩子将按指定的插件顺序运行。如果钩子是 async,则此类后续钩子将并行运行,而不是等待当前钩子。
export class PluginDriver {// ...hookFirstSync<H extends SyncPluginHooks & FirstPluginHooks>(hookName: H,parameters: Parameters<FunctionPluginHooks[H]>,replaceContext?: ReplaceContext): ReturnType<FunctionPluginHooks[H]> | null {// ...}async hookParallel<H extends AsyncPluginHooks & ParallelPluginHooks>(hookName: H,parameters: Parameters<FunctionPluginHooks[H]>,replaceContext?: ReplaceContext): Promise<void> {// ...}// ...
}
getSortedPlugins 运行时收集并存储插件对应 hook
- Rollup 通过 getSortedPlugins 对插件的对应 hook 进行排序后收集在 this.sortedPlugins 集合中存储
- 根据 pre、normal、post 顺序排序每个hook
// 抽取插件中有对应 hookName 的插件
private getSortedPlugins( hookName: keyof FunctionPluginHooks | AddonHooks,validateHandler?: (handler: unknown, hookName: string, plugin: Plugin) => void
): Plugin[] {return getOrCreate( // 根据 hookName 抽取所有符合的插件,运行时收集对应 hook 的插件放进 this.sortedPlugins 里this.sortedPlugins,hookName,() => getSortedValidatedPlugins(hookName, this.plugins, validateHandler) // 抽取插件中有对应 hookName 的插件);
}export function getOrCreate<K, V>(map: Map<K, V>, key: K, init: () => V): V {const existing = map.get(key);if (existing !== undefined) {return existing;}const value = init();map.set(key, value);return value;
}export function getSortedValidatedPlugins(hookName: keyof FunctionPluginHooks | AddonHooks,plugins: readonly Plugin[],validateHandler = validateFunctionPluginHandler
): Plugin[] {const pre: Plugin[] = [];const normal: Plugin[] = [];const post: Plugin[] = [];// 遍历所有插件,根据指定hook(options、transform、...)提取插件for (const plugin of plugins) {const hook = plugin[hookName];if (hook) {if (typeof hook === 'object') {validateHandler(hook.handler, hookName, plugin);if (hook.order === 'pre') {pre.push(plugin);continue;}if (hook.order === 'post') {post.push(plugin);continue;}} else {validateHandler(hook, hookName, plugin);}normal.push(plugin);}}// 根据 pre、normal、post 顺序排序每个hookreturn [...pre, ...normal, ...post];
}
sync hook
- 同步执行 hook,返回第一个非 null 的结果
// chains synchronously, first non-null result stops and returnshookFirstSync<H extends SyncPluginHooks & FirstPluginHooks>(hookName: H,parameters: Parameters<FunctionPluginHooks[H]>,replaceContext?: ReplaceContext): ReturnType<FunctionPluginHooks[H]> | null {for (const plugin of this.getSortedPlugins(hookName)) {const result = this.runHookSync(hookName, parameters, plugin, replaceContext);if (result != null) return result;}return null;}
parallel hook
- 用于并行执行的 hook,忽略返回值
- 通过 Promise.all 并行执行
async hookParallel<H extends AsyncPluginHooks & ParallelPluginHooks>(hookName: H,parameters: Parameters<FunctionPluginHooks[H]>,replaceContext?: ReplaceContext): Promise<void> {const parallelPromises: Promise<unknown>[] = [];for (const plugin of this.getSortedPlugins(hookName)) { // getSortedPlugins 根据 hookName 提取对应的插件if ((plugin[hookName] as { sequential?: boolean }).sequential) { // 非顺序执行就先存起来然后并行执行await Promise.all(parallelPromises);parallelPromises.length = 0;await this.runHook(hookName, parameters, plugin, replaceContext);} else {parallelPromises.push(this.runHook(hookName, parameters, plugin, replaceContext)); // 将调用过程放进 Promise.then 任务中,等待 parallelPromises.push 同步任务收集完成后执行}}await Promise.all(parallelPromises); // 并行执行所有收集到的 runHook }
this.runHookSync
- 同步调用时,通过 this.pluginContexts.get(plugin) 获取到插件上下文供插件开发者使用
private runHookSync<H extends SyncPluginHooks>(hookName: H,parameters: Parameters<FunctionPluginHooks[H]>,plugin: Plugin,replaceContext?: ReplaceContext): ReturnType<FunctionPluginHooks[H]> {const hook = plugin[hookName]!;const handler = typeof hook === 'object' ? hook.handler : hook;let context = this.pluginContexts.get(plugin)!;if (replaceContext) {context = replaceContext(context, plugin);}try {// eslint-disable-next-line @typescript-eslint/ban-typesreturn (handler as Function).apply(context, parameters);} catch (error_: any) {return error(errorPluginError(error_, plugin.name, { hook: hookName }));}}
this.runHook
- 异步调用时,如果是结果也是 Promise,会暂存然后等待所有Promise都执行结束
private runHook<H extends AsyncPluginHooks | AddonHooks>(hookName: H,parameters: unknown[],plugin: Plugin,replaceContext?: ReplaceContext | null): Promise<unknown> {// We always filter for plugins that support the hook before running itconst hook = plugin[hookName];const handler = typeof hook === 'object' ? hook.handler : hook;let context = this.pluginContexts.get(plugin)!; // 获取插件的上下文,上下文通过 PluginDriver 封装了一系列和 graph 等相关的方法if (replaceContext) { //transform 钩子需要往 plugin 上下文中添加额外的内容context = replaceContext(context, plugin);}let action: [string, string, Parameters<any>] | null = null;return Promise.resolve().then(() => {if (typeof handler !== 'function') {return handler;}const hookResult = (handler as Function).apply(context, parameters); // 执行插件对应的 hookName 钩子if (!hookResult?.then) {return hookResult;}action = [plugin.name, hookName, parameters]; // 如果钩子返回 Promise 存储对应操作this.unfulfilledActions.add(action);return Promise.resolve(hookResult).then(result => { // action was fulfilledthis.unfulfilledActions.delete(action!); // 钩子返回 Promise 完成清除对应未 fullfilled 的操作return result;});}).catch(error_ => {if (action !== null) {this.unfulfilledActions.delete(action);}return error(errorPluginError(error_, plugin.name, { hook: hookName }));});}
和 webpack 插件系统区别
- Rollup 通过抽象化一个 PluginDriver 的实例负责专门驱动插件的调用,并且 PluginDriver 和 Graph 绑定,能够共享打包过程的信息;webpack 通过 tapable 进行订阅发布,本身可以脱离 webpack 使用
- 在运行模式上,Rollup 是运行时根据 hookName 收集对应的插件 ,然后对插件进行排序后存储,通过 runHook 或 runHookSync 进行调用;webpack 通过订阅发布,先注册插件,然后在生命周期的流程中调用。总的来说 Rollup 的运行时收集比起 webpack 具有一点点内存优势
相关文章:
rollup 插件架构-驱动设计 PluginDriver
文章目录 GraphPluginDriver生成 PluginDriver 实例和 PluginCache 缓存创建插件上下文 pluginContext初始化 pluginContext 缓存设置、方法插件中使用缓存可替换的 replace pluginContextPluginDriver 提供 asyn、first、parallel 等类型 hookgetSortedPlugins 运行时收集并存…...
netty实现mqtt(IOT)
springbootnettymqtt服务端实现 springbootnettymqtt客户端实现 MQTT协议基本讲解(结合netty) 李兴华netty视频教程中mqtt讲解 EMQX官网、mqttx客户端 IOT云平台 simple(6)springboot netty实现IOT云平台基本的架构(mqtt、Rabbitmq&…...
基于STC12C5A60S2系列1T 8051单片机的液晶显示器LCD1602显示汉字的功能
基于STC12C5A60S2系列1T 8051单片机的液晶显示器LCD1602显示汉字的功能 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍LCD1602字符型液晶显示器介绍一、LCD1602字符型…...
Springboot+Redis:实现缓存 减少对数据库的压力
🎉🎉欢迎光临,终于等到你啦🎉🎉 🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀 🌟持续更新的专栏Redis实战与进阶 本专栏讲解Redis从原理到实践 …...
springboot组件的单例模式和分布式分析
springboot组件的单例模式和分布式分析 一、基本概念 在Spring Boot应用中,单例模式是非常常见的一种设计模式,它被广泛应用于Bean的生命周期管理。Spring容器默认会将所有的Component、Service、Repository和Controller注解标记的类作为单例对象进行实…...
Linux:zip命令介绍
简介 zip命令可以用来解压缩文件,或者对文件进行打包操作。zip是个使用广泛的压缩程序,文件经它压缩后会另外产生具有“.zip”扩展名的压缩文件。 语法 zip [选项] [参数] 选项 -A:调整可执行的自动解压缩文件; -b<工作目录&g…...
远程桌面无法连接怎么办?
远程桌面无法连接是指在尝试使用远程桌面功能时出现连接失败的情况。这种问题可能会给工作和生活带来极大的不便,因此我们需要寻找解决办法。在讨论解决方案之前,我们先来了解一下【天联】组网的优势。 【天联】组网的优势有很多。它能够解决复杂网络环境…...
HarmonyOS实战开发-拼图、如何实现获取图片,以及图片裁剪分割的功能。
介绍 该示例通过ohos.multimedia.image和ohos.multimedia.mediaLibrary接口实现获取图片,以及图片裁剪分割的功能。 效果预览 使用说明: 使用预置相机拍照后启动应用,应用首页会读取设备内的图片文件并展示获取到的第一个图片,…...
【LeetCode热题100】【二叉树】二叉树的最近公共祖先
题目链接:236. 二叉树的最近公共祖先 - 力扣(LeetCode) 二叉树皆可递归,可以递归查找两个节点的所在地,如果两个节点一个在root的左子树一个在右子树,说明root就是公共祖先,并且因为是递归&…...
动态规划专练( 1049.最后一块石头的重量Ⅱ)
1049.最后一块石头的重量Ⅱ 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x < y。那么粉碎的可能结果如…...
2024年最佳WordPress插件
我喜欢的最佳WordPress插件(也是经验丰富的WordPress开发者强烈推荐的)。所有这些插件都是编码干净、超快且一流的。我还包括了对我不喜欢的插件的想法……只为了让你有进一步的了解。 目录 隐藏 1 古腾堡块: 2 内容: 3 缓存…...
Docker 安装 RocketMQ
目录 一、新建两个配置文件 1.1 创建docker-compose.yml文件 1.2 .新建broker.conf文件 二、运行 三、可视化界面 一、新建两个配置文件 1.1 创建docker-compose.yml文件 version: 3.5 services:rmqnamesrv:image: foxiswho/rocketmq:servercontainer_name: rmqnamesrvports…...
计算机网络——交换机和路由器
目录 前言 引言 交换机是用来做什么的? 与路由器有什么区别? 网关 子网掩码 网关、路由 前言 本博客是博主用于复习计算机网络的博客,如果疏忽出现错误,还望各位指正。 这篇博客是在B站掌芝士zzs这个UP主的视频的总结&am…...
Redis Pipelining 底层原理分析及实践
作者:vivo 互联网服务器团队-Wang Fei Redis是一种基于客户端-服务端模型以及请求/响应的TCP服务。在遇到批处理命令执行时,Redis提供了Pipelining(管道)来提升批处理性能。本文结合实践分析了Spring Boot框架下Redis的Lettuce客户端和Redisson客户端对P…...
milvus各组件的结构体分析
milvus各组件的结构体分析 各组件启动,需要构建各组件的结构体,一共8个。 runComponent(ctx, localMsg, wg, components.NewRootCoord, metrics.RegisterRootCoord) runComponent(ctx, localMsg, wg, components.NewProxy, metrics.RegisterProxy) run…...
vue2和vue3 全选
vue3 <template><input type"checkbox" v-model"selectAll" />全选<ul><li v-for"item in list" :key"item.id">{{ item.value }} <input type"checkbox" v-model"item.check" />…...
Java中的Set、List、Map的区别及主要实现类方法
Java中的Set、List、Map的区别 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型),JAVA集合可以存储和操作数目不固定的一组数据。 所有的JAVA集合都位于 java.util包中! JAVA集合只能存放引…...
gitignore:常用说明
示例: Java HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/** !**/src/test/**### IntelliJ IDEA.idea *.iws *.iml *.ipr### NetBeans/nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ logs/### VS Code.vscode/ 说明&#…...
HarmonyOS NEXT应用开发—在Native侧实现进度通知功能
介绍 本示例通过模拟下载场景介绍如何将Native的进度信息实时同步到ArkTS侧。 效果图预览 使用说明 点击“Start Download“按钮后,Native侧启动子线程模拟下载任务Native侧启动子线程模拟下载,并通过Arkts的回调函数将进度信息实时传递到Arkts侧 实…...
水利自动化控制系统平台介绍
水利自动化控制系统平台介绍 在当今社会,水资源的管理和保护日益成为全球关注的重要议题。随着科技的进步和信息化的发展,水利监测系统作为一种集成了现代信息技术、自动化控制技术以及环境监测技术的综合性平台,正在逐步改变传统的水利管理模…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
