通过注释来埋点

目录

开始

插件编写

功能一

功能二

功能三

合并功能

运行代码

总结


这篇文章主要讲如何根据注释,通过babel插件自动地,给相应函数插入埋点代码,在实现埋点逻辑和业务逻辑分离的基础上,配置更加灵活

这篇文章想要达到的效果:
源代码:

//##箭头函数
//_tracker
const test1 = () => {};const test1_2 = () => {};

转译之后:

import _tracker from "tracker";
//##箭头函数
//_tracker
const test1 = () => {_tracker();
};const test1_2 = () => {};

代码中有两个函数,其中一个//_tracker的注释,另一个没有。转译之后只给有注释的函数添加埋点函数。
要达到这个效果就需要读取函数上面的注释,如果注释中有//_tracker,我们就给函数添加埋点。这样做避免了僵硬的给每个函数都添加埋点的情况,让埋点更加灵活。

下面让我们来看看怎么做

开始

准备babel入口文件index.js

const { transformFileSync } = require("@babel/core");
const path = require("path");
const tracker = require("./babel-plugin-tracker-comment.js");const pathFile = path.resolve(__dirname, "./sourceCode.js");//transform ast and generate code
const { code } = transformFileSync(pathFile, {plugins: [[tracker, { trackerPath: "tracker", commentsTrack: "_tracker" }]],
});console.log(code);

这里我们使用transformFileSyncAPI转译源代码,并将转译之后的代码打印出来。过程中,将手写的插件作为参数传入plugins: [[tracker, { trackerPath: "tracker", commentsTrack: "_tracker"}]]。除此之外,还有插件的参数

  • trackerPath表示埋点函数的路径,插件在插入埋点函数之前会检查是否已经引入了该函数,如果没有引入就需要额外引入。

  • commentsTrack标识埋点,如果函数前的注释有这个,就说明函数需要埋点。判断的标识是动态传入的,这样比较灵活

入口文件准备好了,接下来准备下源代码文件sourceCode.js

import "./index.css";//##箭头函数
//_tracker
const test1 = () => {};const test1_2 = () => {};//函数表达式
//_tracker
const test2 = function () {};const test2_1 = function () {};// 函数声明
//_tracker
function test3() {}function test3_1() {}

这里准备了三种不同的函数类型,并且各个函数类型都有一个加了注释,另一个没加作为参照对象
就差插件了,下面写插件代码babel-plugin-tracker-comment.js

插件编写

功能一

功能实现过程中,涉及到了读取函数的注释,并且判断注释中是否有//_tracker

const leadingComments = path.get("leadingComments");
const paramCommentPath = hasTrackerComments(leadingComments, options.commentsTrack);//函数实现
const hasTrackerComments = (leadingComments, comments) => {if (!leadingComments) {return null;}if (Array.isArray(leadingComments)) {const res = leadingComments.filter((item) => {return item.node.value.includes(comments);});return res[0] || null;}return null;
};

具体函数实现,接收函数前的注释,注释可能会有多个,所以需要一一判断。还接受埋点的标识。如果找到了含有注释标识的注释,就将这行注释返回。否则一律返回null,表示这个函数不需要埋点

那什么是多个注释?

这个很好理解,我们看下AST explorer[2]就知道了

a函数,前面有4个注释,三个行注释,一个块注释。
其对应的AST解析是这样的:


AST对象中,用leadingComments表示前面的注释,用trailingComments表示后面的注释。leadingComments中确实有4个注释,并且三个行注释,一个块注释,和代码对应上了。
函数要做的就是将其中含有//_tracker的comment path对象找出来

功能二

判断函数确实需要埋点之后,就要开始插入埋点函数了。但在这之前,还需要做一件事,就是检查埋点函数是否引入,如果没有引入就需要额外引入了

const { addDefault } = require("@babel/helper-module-imports");if (paramCommentPath) {//add Importconst programPath = path.hub.file.path;const importId = checkImport(programPath, options.trackerPath);state.importTackerId = importId;
}//函数实现
const checkImport = (programPath, trackPath) => {let importTrackerId = "";programPath.traverse({ImportDeclaration(path) {const sourceValue = path.get("source").node.value;if (sourceValue === trackPath) {const specifiers = path.get("specifiers.0");importTrackerId = specifiers.get("local").toString();path.stop();}},});if (!importTrackerId) {importTrackerId = addDefault(programPath, trackPath, {nameHint: programPath.scope.generateUid("tracker"),}).name;}return importTrackerId;
};

拿到import语句需要program节点。checkImport函数的实现就是在当前文件中,找出埋点函数的引入。寻找的过程中,用到了引入插件时传入的参数trackerPath。还用到了traverseAPI,用来遍历import语句
如果找到了引入,就获取引入的变量。这个变量在之后埋点的时候需要。即如果引入的变量命名了tracker2,那么埋点的时候埋点函数就是tracker2
如果没有引入,就插入引入。

addDefault就是引入path的函数,并且会返回插入引用的变量。

功能三

确定好了函数需要埋点,并且确定了埋点函数引入的变量,接下来就插入函数了。

if (paramCommentPath) {//add Importconst programPath = path.hub.file.path;const importId = checkImport(programPath, options.trackerPath);state.importTackerId = importId;insertTracker(path, state);
}
const insertTracker = (path, state) => {const bodyPath = path.get("body");if (bodyPath.isBlockStatement()) {const ast = template.statement(`${state.importTackerId}();`)();bodyPath.node.body.unshift(ast);} else {const ast = template.statement(`{${state.importTackerId}();return BODY;}`)({ BODY: bodyPath.node });bodyPath.replaceWith(ast);}
};

在生成埋点函数的时候,就用到了之前获取到的埋点函数的变量importTackerId。还有在实际插入的时候,要区分函数体是一个Block,还是直接返回的值--()=>''

合并功能

三个功能都写好了,接下来将三个功能合起来,就是我们的插件代码了

const { declare } = require("@babel/helper-plugin-utils");
const { addDefault } = require("@babel/helper-module-imports");
const { template } = require("@babel/core");//get comments path from leadingComments
const hasTrackerComments = (leadingComments, comments) => {if (!leadingComments) {return false;}if (Array.isArray(leadingComments)) {const res = leadingComments.filter((item) => {return item.node.value.includes(comments);});return res[0] || null;}return null;
};//insert path
const insertTracker = (path, param, state) => {const bodyPath = path.get("body");if (bodyPath.isBlockStatement()) {const ast = template.statement(`${state.importTackerId}(${param});`)();bodyPath.node.body.unshift(ast);} else {const ast = template.statement(`{${state.importTackerId}(${param});return BODY;}`)({ BODY: bodyPath.node });bodyPath.replaceWith(ast);}
};//check if tacker func was imported
const checkImport = (programPath, trackPath) => {let importTrackerId = "";programPath.traverse({ImportDeclaration(path) {const sourceValue = path.get("source").node.value;if (sourceValue === trackPath) {const specifiers = path.get("specifiers.0");importTrackerId = specifiers.get("local").toString();path.stop();}},});if (!importTrackerId) {importTrackerId = addDefault(programPath, trackPath, {nameHint: programPath.scope.generateUid("tracker"),}).name;}return importTrackerId;
};module.exports = declare((api, options) => {console.log("babel-plugin-tracker-comment");return {visitor: {"ArrowFunctionExpression|FunctionDeclaration|FunctionExpression": {enter(path, state) {let nodeComments = path;if (path.isExpression()) {nodeComments = path.parentPath.parentPath;}// 获取leadingCommentsconst leadingComments = nodeComments.get("leadingComments");const paramCommentPath = hasTrackerComments(leadingComments,options.commentsTrack);//查看作用域中是否有——trackerParam// 如果有注释,就插入函数if (paramCommentPath) {//add Importconst programPath = path.hub.file.path;const importId = checkImport(programPath, options.trackerPath);state.importTackerId = importId;insertTracker(path, state);}},},},};
});

在获取注释的时候,代码中并不是直接获取到pathleadingComments,这是为什么?
比如这串代码:

//_tracker
const test1 = () => {};

我们在函数中遍历得到的path是()=>{}ast的path,这个path的leadingComments其实是null,而想要获取//_tracker,我们真正需要拿到的path,是注释下面的变量声明语句。所以在代码中有判断是否为表达式,如果是,那就需要先parentPath,得到赋值表达式path,然后在parentPath,才能拿到变量声明语句

运行代码

node index.js

得到输出:

总结

这篇文章写了如何根据函数上方的注释,选择性的给函数埋点。过程详尽,例子通俗易懂,真是不可多得的一篇好文章啊
有任何问题,欢迎金友们留言评论哦~

相关文章:

  1. 通过工具babel,给函数都添加埋点[4]

  2. 通过工具babel,给埋点函数传递参数[5]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/144112.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023最受推荐的五款项目管理工具

1、进度猫 进度猫是国内一款轻量级项目管理工具,适用于实时协作的团队。 以甘特图为向导,基于任务清单todolist,支持多用户协作; 甘特图显示具体任务清单、时间和任务的进度; 对未完成任务、已完成任务进行分类管…

Vue3源码reactive和readonly对象嵌套转换,及实现shallowReadonly

前言 官方文档中对reactive的描述: 响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。 官方文档中对readonly的描述: 只读代理是深层的:对任何嵌套属性的访问都将是…

深入了解域名与SSL证书的关系

在如今数字化的世界里,网络安全成为我们关注的重要议题之一。为了确保数据在网络上传输的安全性,我们通常会采取各种安全措施,其中最常用的就是SSL证书。然而,很多人并不了解SSL证书是如何与域名相互关联的。 首先,我…

[C/C++] 数据结构 链表OJ题:相交链表(寻找两个链表的相交起始结点)

题目描述: 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意,函数返…

HTML简单介绍

且视他人之疑目如盏盏鬼火,大胆地去你的夜路。 目录 1.网页 2.Web标准 3.HTML 3.1HTML结构 3.2HTML标签​编辑 4.标签介绍 4.1排版标签 4.2文本格式化标签 4.3媒体标签 4.3.1图片标签 4.3.2 音频标签 4.3.3视频标签 5.相对路径 6.链接标签 6.1target属…

图论15-有向图-环检测+度数+欧拉回路

文章目录 1. 有向图设计1.1 私有变量标记是否有向1.2 添加边的处理,双向变单向1.3 删除边的处理,双向变单向1.4 有向图的出度和入度 2 有向图的环检测2.1 普通的算法实现换检测2.2 拓扑排序中的环检测 3 欧拉回路 1. 有向图设计 1.1 私有变量标记是否有…

燃气管网监测系统|全面保障燃气安全

根据新华日报的报道,2023年上半年,我国共发生了294起燃气事故,造成了57人死亡和190人受伤,燃气事故的发生原因有很多,其中涉及到燃气泄漏、设备故障等因素。因此,加强燃气安全管理,提高城市的安…

电磁场与电磁波part1--矢量分析

目录 1、方向导数 2、散度定理(高斯定理) 3、散度与旋度的比较 4、旋度定理(斯托克斯定理) 5、关于点乘、叉乘、梯度、散度、旋度的计算 ~~~~~~~~~~~~~~~~~~~~~~~~ 确认过眼神,是我看不懂的 ~~~~~~~~~~~~~~~~…

IDEA没有Add Framework Support解决办法

点击File—>Settings 点击第一个设置快捷键 点击apply和ok即可 我们要点击一下项目,再按快捷键ctrlk 即可

C++入门(2)—函数重载、引用

目录 一、函数重载 1、参数类型不同 2、参数个数不同 3、参数顺序不同 4、 链接中如何区分函数重载 二、引用 1、规则 2、特征 3、使用场景 做参数 做返回值 4、常引用 5、传值、传引用效率比较 6、引用和指针的区别 接上一小节C入门(1)—命名空间、缺省参数 一…

Python 如何实现适配器设计模式?什么是适配器(Adapter)设计模式?

什么是适配器设计模式? 适配器(Adapter)设计模式是一种结构型设计模式,它允许接口不兼容的类之间进行合作。适配器模式充当两个不兼容接口之间的桥梁,使得它们可以一起工作,而无需修改它们的源代码。 主要…

C++ 基础

准备工具Vscode或者Clion或者Dev C或者Vs studio 和 MSYS2 是C跨平台的重要工具链. 基础一 准备工作安装MSYS2软件 创建文件 一、基本介绍1.1C源文件1.2 代码注释1.3变量与常量1.3.1变量1.3.2 常量1.3.3 二者的区别: 1.4 关键字和标识符 二、数据类型2.1 基本数据类…

C/C++ 实现获取硬盘序列号

获取硬盘的序列号、型号和固件版本号,此类功能通常用于做硬盘绑定或硬件验证操作,通过使用Windows API的DeviceIoControl函数与物理硬盘驱动程序进行通信,发送ATA命令来获取硬盘的信息。 以下是该程序的主要功能和流程: 定义常量…

2023版Idea创建JavaWeb时,右键new没有Servlet快捷键选项

问题:右键时,没有创建servlet的快捷键,如下图: 解决方法: 1.打开idea,点击File>settings(设置),进入settings页面,如下 从上图中的Files选项中没看到有servlet选项,…

正则表达式入门教程

一、本文目标 让你明白正则表达式是什么,并对它有一些基本的了解,让你可以在自己的程序或网页里使用它。 二、如何使用本教程 文本格式约定:专业术语 元字符/语法格式 正则表达式 正则表达式中的一部分(用于分析) 对其进行匹配的源字符串 …

线程锁的应用与示例代码

为了解决这个问题,可以使用线程锁来确保在提取zip文件中的每个文件时,同一时间只有一个线程可以访问文件。这样可以避免多个线程同时访问和写入文件,从而解决race condition的问题。以下是修改后的示例代码: python import reque…

提升pip速度!设置pip全局镜像源,速度飞起!

文章目录 💢 问题 💢💯 解决方案 💯🐾 镜像源🐾 镜像全局配置🍄 Windows系统🍄 Linux和macOS系统🍄 添加环境变量的方式💢 问题 💢 由于“某些网络限制”原因,我们在使用pip安装python模块的时候速度会比较慢,这个时候我们就需要用到一些镜像源,本文将…

R语言提取文字(字符串)中的内容--正则式(2)

科学研究中有时候咱们收集到的数据很乱,不能马上进行分析,如SEER数据,用过都知道,咱们需要对数据进行清洗,从数据中提取咱们需要的东西,才能进行分析,这时候有个有用的东西叫正则式,…

2023年05月 Python(五级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 有列表L=[‘UK’,‘china’,‘lili’,“张三”],print(L[-2])的结果是?( ) A: UK B: ‘lili’,‘张三’ C: lili D: ‘UK’,‘china’,‘lili’ 答案:C 列表元素定位 第2题 …

最新宝塔反代openai官方API开发接口详细搭建教程,解决502 Bad Gateway问题

一、前言 宝塔反代openai官方API接口详细教程,实现国内使用ChatGPT502 Bad Gateway问题解决, 此方法最简单快捷,没有复杂步骤,不容易出错,即最简单,零代码、零部署的方法。 二、实现前提 一台海外服务器…