通过注释来埋点

目录

开始

插件编写

功能一

功能二

功能三

合并功能

运行代码

总结


这篇文章主要讲如何根据注释,通过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 Import
  const 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 Import
  const 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;
          }
          // 获取leadingComments
          const leadingComments = nodeComments.get("leadingComments");
          const paramCommentPath = hasTrackerComments(leadingComments,options.commentsTrack);

          //查看作用域中是否有——trackerParam

          // 如果有注释,就插入函数
          if (paramCommentPath) {
            //add Import
            const 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]

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

010.cat、find

1、用cat进行拼接 cat命令能够显示或拼接文件内容,不过它的能力远不止如此。比如说,cat能够将标准输入数据与文件数据组合在一起。通常的做法是将stdin重定向到一个文件,然后再合并两个文件。而cat命令一次就能搞定这些操作。 用cat读取文件…

Go fsnotify简介

fsnotify是一个用Go编写的文件系统通知库。它提供了一种观察文件系统变化的机制,例如文件的创建、修改、删除、重命名和权限修改。它使用特定平台的事件通知API,例如Linux上的inotify,macOS上的FSEvents,以及Windows上的ReadDirec…

中小企业如何最大程度地利用CRM系统的潜力?

在当今竞争激烈的商业世界中,客户关系管理(CRM)数字化转型已经成为大企业成功的重要秘诀。大型跨国公司如亚马逊、苹果和微软等已经在CRM数字化方面走在了前列,实现了高度个性化的客户体验,加强了客户忠诚度。 然而&a…

手把手云开发一个小程序-(二)-uniclould的购买和默认库的使用

一,前言 因为平时喜欢记录一些文案,看小说或者上网冲浪的时候,遇到拍案叫绝的文字,就会截图保存下来,但是时间久了,手机里截图保留了很多,却不会再去看,想删除又舍不得,于是就想着自己开发个文案记录的小程序.自用的同时让有同样需求的人也能用. 目前已经把第一个版本开发完了…

【教3妹学编程-算法题】最长奇偶子数组

3妹:2哥,你有没有看到新闻, 网红快乐小赵去世了。 2哥 :啊? 这么突然 3妹:是啊, 伤心,以前还特别喜欢他的作品,幽默搞笑。 2哥:哎,人有悲欢离合, …

Open3D 进阶(17)间接平差拟合二维直线

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。 一、算法原理 见:PCL 间接平差法拟合二维直线。 二、代码实现 import numpy as np import open3d as o3d import matplotlib.pyplot as plt

关于数据mysql ->maxwell->kafka的数据传输

个人名片: 🐅作者简介:一名大三在校生,热爱生活,爱好敲码! \ 💅个人主页 🥇:holy-wangle ➡系列内容: 🖼️ tkinter前端窗口界面创建与优化 &…

鸿蒙原生应用开发-折叠屏、平板设备服务卡片适配

一、多设备卡片适配原则 为不同尺寸的卡片提供不同的功能 在卡片开发过程中请考虑适配不同尺寸的设备,特别是在折叠屏和平板设备上,设备屏幕尺寸的变化直接影响了卡片内容的展示。请发挥想象力设计具有自适应能力的卡片,避免在卡片内容不做…

git clone:SSL: no alternative certificate subject name matches target host name

git clone 时的常见错误: fatal: unable to access ‘https://ip_or_domain/xx/xx.git/’: SSL: no alternative certificate subject name matches target host name ‘ip_or_domain’ 解决办法: disable ssl verify git config --global http.sslVe…

[Jenkins] Docker 安装Jenkins及迁移流程

系统要求 最低推荐配置: 256MB可用内存1GB可用磁盘空间(作为一个Docker容器运行jenkins的话推荐10GB) 为小团队推荐的硬件配置: 1GB可用内存50 GB 可用磁盘空间 软件配置: Java 8—无论是Java运行时环境(JRE)还是Java开发工具包(JDK&#xff…

前端 react 面试题 (一)

文章目录 vue与react的区别。react的生命周期有哪些及它们的作用。setState是同步的还是异步的。如何更新数据后,立刻获取最新的dom或者更新后的数据。使用回调函数:在生命周期方法中处理: 函数式组件和class组件的区别。class组件函数式组件…

【5G PHY】5G SS/PBCH块介绍(三)

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持! 博主链接 本人就职于国际知名终端厂商,负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。 博客…

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

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

使用 Redis 构建轻量的向量数据库应用:图片搜索引擎(二)

本篇文章我们来继续聊聊轻量的向量数据库方案:Redis,如何完成整个图片搜索引擎功能。 写在前面 在上一篇文章《使用 Redis 构建轻量的向量数据库应用:图片搜索引擎(一)》中,我们聊过了构建图片搜索引擎的…

深度学习入门(第二天)——走进深度学习的世界 神经网络模型

反向传播计算方法 简单的例子: 如何让 f 值更小,就是改变x、y、z,而损失函数也是这样,那么我们分别求偏导,则能得出每个值对结果的影响 链式法则 梯度是一步一步传的 复杂的例子: 神经网络整体架构 类生…

浪涌防护器件要选对,布局布线更重要!|深圳比创达电子EMC(上)

浪涌测试,作为最常见的EMC抗干扰测试项目之一,基本上是家用消费电子必测的项目;其测试目的是为了验证产品在承受外部的浪涌冲击时能否正常工作。 一、浪涌冲击产生机理及其防护设计 浪涌冲击主要包括雷击浪涌冲击、电力系统内部的开关噪声冲…

假如我是AI Agent专家,你会问什么来测试我的水平

1. 假如我是AI Agent专家,你会问什么来测试我的水平 作为AI Agent专家,您可能需要回答一系列关于AI代理的设计、实现和优化方面的问题。以下是一些可能的问题: AI代理的基本原理:AI代理的基本工作原理是什么?它们如何…

requests库进行HTTP请求时,有时需要传递cookies参数

在使用requests库进行HTTP请求时,有时需要传递cookies参数。但是,requests库的cookies参数只能接受CookieJar对象,而不能接受字典等其他类型的cookies。 在requests库中增加对字典类型的cookies支持,使其能够接受字典类型的cooki…

linux高级篇基础理论二(详细文档、LAMP、SHELL、sed正则表达式)

♥️作者:小刘在C站 ♥️个人主页: 小刘主页 ♥️不能因为人生的道路坎坷,就使自己的身躯变得弯曲;不能因为生活的历程漫长,就使求索的 脚步迟缓。 ♥️学习两年总结出的运维经验,以及思科模拟器全套网络实验教程。专栏:云计算技…

Coder OSS Enterprise 2.3.3 Crack

Coder OSS Enterprise是一个开源开发平台,旨在简化开发人员的编码流程。该平台专为企业级组织设计,具有针对大规模软件开发运营量身定制的功能。 首先,该软件提供了一个云环境,允许开发人员在任何地方使用任何设备处理他们的代码。…