目录

Vite插件实现UniApp埋点

前言

最近接到埋点需求,需要对Uni项目的App端和小程序进行埋点。经过研究社区提供的方案后,发现并没有一个完全符合公司业务需求的方案。因此,决定自己动手实现一个定制化的埋点方案。

方案以及实现原理

前提条件是使用Vite脚手架搭建的Uni项目。

解决方案是编写一个Vite插件,在构建过程中预先修改模板字符串。借助Vue自带的vue/compiler-sfc插件,我们可以将Vue代码解析为AST(抽象语法树),通过AST精确修改模板代码,无需使用复杂的正则表达式进行字符串匹配和修改。这种方案适用于大多数Vue项目。需要注意的是,由于这种操作会修改项目代码,建议在完成后编写测试进行验证,推荐使用Vitest进行测试。

代码

typescript 复制代码
由于小程序端以及app端并没有window,所以需要使用混入的方式
// utils.ts
export const 你的埋点函数 = {
  methods: {
    sendMd: (code: string) => {
    // ... 处理你的业务
      uni.$emit("sendMd", code);
    }
  }
};

APP.VUE
export const createApp = () => {
   // ***
  app.mixin('你的埋点函数');
  return { app, Pinia };
};
xml 复制代码
// pages.vue 你的页面
    
<template>
    <view>
        <view @click=test data-md="code1">
                // ....
        </view>
    </view>
</template>
javascript 复制代码
// inject-click-handler.ts vite插件
import { parse } from "vue/compiler-sfc";
import { Plugin } from "vite";

export default (): Plugin => {
  return {
    name: "inject-click-handler",
    transform(code, id) {
      try {
        if (!/.vue$/.test(id)) return null;
        const parseCode = parse(code);
        if (!parseCode) return null;
        if (!parseCode.descriptor?.template?.content) return null;
        const dataMdRegex = /<[^>]*\bdata-md="([^"]*)"[^>]*>/g; // 匹配data-md,这里需要根据业务进行调整。
        // 匹配当前文件是否有埋点标识,在继续往下遍历ast
        const { content, ast } = parseCode.descriptor.template;
        // 返回null的时候表示不修改任何代码
        if (!content.match(dataMdRegex)) {
          return null;
        }
        // 获取template模板
        let $code = parseCode.descriptor.template.content;
        // 需要修改的节点数组
        const nodeArray = [];
        // 递归ast节点
        const handleEachAst = (node) => {
          if (node?.props.length) {
             // 查找我们在页面写的data-md
            const isMd = node?.props?.find(
              (item) =>
                item?.name === "data-md" ||
                (item.name === "bind" && item?.arg?.content === "data-md")
            );
            // 查找当前元素是否有点击函数并追加混入的埋点函数
            if (!!isMd) {
              const findVueClickEvent = node?.props?.find(
                (item) => item.name === "on" && item.type === 7
              );
              let pushFn = "";
              const mdContent =
                isMd.name === "data-md" ? `'${isMd.value.content}'` : isMd.exp.content;
              if (findVueClickEvent) {
                 // 检查是否一个函数   sendMd是混入的埋点函数
                const isFunctionCall = /^\s*[a-zA-Z_$][0-9a-zA-Z_$]*\s*([^)]*)\s*$/;
                if (!isFunctionCall.test(findVueClickEvent.exp.content)) {
                  findVueClickEvent.exp.content += "()";
                }
                pushFn = `@${findVueClickEvent.arg.content}="sendMd('sendMd',${mdContent});${findVueClickEvent.exp.content}"`;
              } else {
                pushFn = `@click="sendMd('sendMd',${mdContent})"`;
              }
              const nodeStr = node.loc.source.replace(findVueClickEvent.loc.source, pushFn);
              nodeArray.push({
                source: node.loc.source,
                replaceSource: nodeStr
              });
            }
          }
          if (Array.isArray(node?.children)) {
            node.children.forEach((item) => {
              item.props && handleEachAst(item);
            });
          }
         
          if (nodeArray.length) {
            nodeArray.forEach((item) => {
              $code = $code.replace(item.source, item.replaceSource);
            });
          }
        };

        handleEachAst(ast);
        return {
          code: code.replace(parseCode.descriptor.template.content, $code)
        };
      } catch (e) {
        return null;
      }
    }
  };
};
arduino 复制代码
vite.config.js
plugins: [
  InjectClickHandler(), // 必须写在uniPlugin前面
  uniPlugin,
  // ...
]
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
浪裡遊22 分钟前
uniapp常用组件
开发语言·前端·uni-app
五点六六六23 分钟前
Restful API 前端接口模型架构浅析
前端·javascript·设计模式
筱筱°25 分钟前
Vue 路由守卫
前端·javascript·vue.js
customer0829 分钟前
【开源免费】基于SpringBoot+Vue.JS智慧生活商城系统(JAVA毕业设计)
java·vue.js·spring boot
前端小张同学41 分钟前
前端Vue后端Nodejs 实现 pdf下载和预览,如何实现?
前端·javascript·node.js
独孤求败Ace43 分钟前
第59天:Web攻防-XSS跨站&反射型&存储型&DOM型&接受输出&JS执行&标签操作&SRC复盘
前端·xss
天空之枫1 小时前
node-sass替换成Dart-sass(全是坑)
前端·css·sass
SecPulse1 小时前
xss注入实验(xss-lab)
服务器·前端·人工智能·网络安全·智能路由器·github·xss
路遥努力吧1 小时前
el-input 不可编辑,但是点击的时候出现弹窗/或其他操作面板,并且带可清除按钮
前端·vue.js·elementui
绝顶少年1 小时前
确保刷新页面后用户登录状态不会失效,永久化存储用户登录信息
前端