前端sdk - 埋点

前端sdk 之小满np

安装

  • npm init -y
  • tsc --init

01 搭建环境

01-项目目录

在这里插入图片描述

  • 介绍
    • src / types 定义类型
    • src / core 核心代码
    • src / utils / pv 工具函数
      • PV:页面访问量,即PageView,用户每次对网站的访问均被记录
  • 技术架构
    • 埋点就是 数据采集-数据处理-数据分析和挖掘,如用户停留时间,用户哪个按钮点的多,等
    • 技术架构使用ts + rollup

01-2 依赖包

npm install rollup -D
npm install rollup-plugin-dts -D
npm install rollup-plugin-typescript2 -D
npm install typescript -D

01-3 rollup.config.js

import ts from "rollup-plugin-typescript2";
import path from "path"
import dts from "rollup-plugin-dts";
export default [
  {
    //入口文件
    input: "./src/core/index.ts",
    output: [
      //打包esModule
      {
        file: path.resolve(__dirname, "./dist/index.esm.js"),
        format: "es",
      },
      //打包common js
      {
        file: path.resolve(__dirname, "./dist/index.cjs.js"),
        format: "cjs",
      },
      //打包 AMD CMD UMD
      {
        file: path.resolve(__dirname, "./dist/index.js"),
        format: "umd",
        name: "tracker",
      },
      // {
      //   input: "./src/core/index.ts",
      //   file: path.resolve(__dirname, "./dist/index.js"),
      //   format: "umd",
      //   name: "tracker",
      // },
    ],
    //配置ts
    plugins: [ts()],
  },
  {
    //打包声明文件
    input: "./src/core/index.ts",
    output: {
      file: path.resolve(__dirname, "./dist/index.d.ts"),
      format: "es",
    },
    plugins: [dts()],
  },
];

01-4 tsconfig.json 28行

    /* Modules */
    "module": "ESNext",  

01-5 package.json

  • 配置脚本、还有module、browser、keywords、files等
{
  "name": "vue-sdk",
  "version": "1.0.5",
  "description": "",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "browser":"dist/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rollup -c"
  },
  "keywords": ["前端","埋点","tracker"],
  "author": "",
  "files": ["dist"],
  "license": "ISC",
  "devDependencies": {
    "rollup": "^2.76.0",
    "rollup-plugin-dts": "^4.2.2",
    "rollup-plugin-typescript2": "^0.32.1",
    "typescript": "^4.7.4"
  }
}

01-6 src / core / index.ts

export const a = 1000;
console.log("a", a);

// "type":"module",

01-7打包效果

  • cjs
    在这里插入图片描述

02 初始化 Tracher

02-1 core / index.ts

import { DefaultOptons, TrackerConfig, Options } from "../types/index";
export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
  }
  private initDef(): DefaultOptons {
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
}

02-2 types/ index.ts

/**
 * @requestUrl 接口地址
 * @historyTracker history上报
 * @hashTracker hash上报
 * @domTracker 携带Tracker-key 点击事件上报
 * @sdkVersionsdk版本
 * @extra透传字段
 * @jsError js 和 promise 报错异常上报
 */
export interface DefaultOptons {
  uuid: string | undefined,
  requestUrl: string | undefined,
  historyTracker: boolean,
  hashTracker: boolean,
  domTracker: boolean,
  sdkVersion: string | number,
  extra: Record<string, any> | undefined,
  jsError:boolean
}

//必传参数 requestUrl
export interface Options extends Partial<DefaultOptons> {
  requestUrl: string,
}

//版本
export enum TrackerConfig {
  version = '1.0.0'
}
//上报必传参数
export type reportTrackerData = {
  [key: string]: any,
  event: string,
  targetKey: string
}

03 重写history事件 监听history | hash 路由等跳转操作事件

  • PV:页面访问量,即PageView,用户每次对网站的访问均被记录
  • 主要监听了 history 和 hash
    • history API go back forward pushState replaceState
      • history 无法通过 popstate 监听 pushState replaceState 只能重写其函数 在utils/pv
    • hash 使用hashchange 监听

03-1 src / utils / pv.ts

  • 重写history事件,以便通过popstate 监听 pushState replaceState
export const createHistoryEvnent = <T extends keyof History>(type: T): () => any => {
  const origin = history[type];
  return function (this: any) {
      const res = origin.apply(this, arguments)
      var e = new Event(type)
      window.dispatchEvent(e)
      return res;
  }
}

03-2 src / core / index.ts

  • 引入 createHistoryEvnent 并且初始化之
import { DefaultOptons, TrackerConfig, Options } from "../types/index";
import { createHistoryEvnent } from "../utils/pv"
export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker()
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history['pushState'] = createHistoryEvnent('pushState')
    window.history['replaceState'] = createHistoryEvnent('replaceState')
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  private captureEvents <T>(mouseEventList:string[],targetKey:string,data?:T) {
   mouseEventList.forEach(event=>{
    window.addEventListener(event,()=>{
      console.log('监听到了');
      
    })
   }) 
  }
  private initTracker(){
    // 若是用户 开启 historyTracker 循环监听用户的事件 
    if ( this.data.historyTracker ) {
      // 'history-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(['pushState','replaceState','popstate'],'history-pv')
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(['hashChange'],'hash-pv')
    } 
  }
}

03-3 npm run build 之后 创建 index.html

  • 引入打包后的文件
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./dist/index.js"></script>
    <script>
      new tracker({
        historyTracker: true,
      });
    </script>
  </body>
</html>

03-4 效果

  • history.pushState('aaa','','/a'
    在这里插入图片描述

04 添加用户标识

  • UV(独立访客):即Unique Visitor,访问您网站的一台电脑客户端为一个访客
    • 用户唯一表示 可以在登录之后通过接口返回的id 进行设置值 提供了setUserId

04-1 src / code /index.ts

import { DefaultOptons, TrackerConfig, Options } from "../types/index";
import { createHistoryEvnent } from "../utils/pv";
export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker();
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history["pushState"] = createHistoryEvnent("pushState");
    window.history["replaceState"] = createHistoryEvnent("replaceState");
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  // uuid 用户的唯一标识
  public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
    this.data.uuid = uuid;
  }
  // 用户的自定义参数
  public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
    this.data.extra = extra;
  }
  private captureEvents<T>(
    mouseEventList: string[],
    targetKey: string,
    data?: T
  ) {
    mouseEventList.forEach((event) => {
      window.addEventListener(event, () => {
        console.log("监听到了");
      });
    });
  }
  private initTracker() {
    // 若是用户 开启 historyTracker 循环监听用户的事件
    if (this.data.historyTracker) {
      // 'history-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(
        ["pushState", "replaceState", "popstate"],
        "history-pv"
      );
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(["hashChange"], "hash-pv");
    }
  }
}

05 上报 navigator.sendBeacon

  • 为什么要使用这个去上报
    • 这个上报的机制 跟 XMLHttrequest 对比 navigator.sendBeacon 即使页面关闭了 也会完成请求 而XMLHTTPRequest 不一定

05-1 src / core / index.ts

  • reportTracker
  • sendTracker
import {
  DefaultOptons,
  TrackerConfig,
  Options,
  reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";
export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker();
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history["pushState"] = createHistoryEvnent("pushState");
    window.history["replaceState"] = createHistoryEvnent("replaceState");
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  // uuid 用户的唯一标识
  public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
    this.data.uuid = uuid;
  }
  // 用户的自定义参数
  public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
    this.data.extra = extra;
  }
  private captureEvents<T>(
    mouseEventList: string[],
    targetKey: string,
    data?: T
  ) {
    mouseEventList.forEach((event) => {
      window.addEventListener(event, () => {
        // console.log("监听到了");
        this.reportTracker({
          event,
          targetKey,
          data,
        });
      });
    });
  }
  private initTracker() {
    // 若是用户 开启 historyTracker 循环监听用户的事件
    if (this.data.historyTracker) {
      // 'history-pv' - 此参数需要与后台进行协商定义
      // this.captureEvents(
      //   ["pushState", "replaceState", "popstate"],
      //   "history-pv"
      // );
      this.captureEvents(["pushState"], "history-pv");
      this.captureEvents(["replaceState"], "history-pv");
      this.captureEvents(["popstate"], "history-pv");
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(["hashChange"], "hash-pv");
    }
  }
  // 手动上报
  public sendTracker<T extends reportTrackerData>(data: T) {
    this.reportTracker(data);
  }

  // 上报用户操作 - 监听到的时候 调用
  private reportTracker<T>(data: T) {
    // 拿到上报的data数据
    const params = Object.assign(this.data, data, {
      time: new Date().getTime(),
    });
    // 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
    let headers = {
      type: "application/x-www-form-urlencoded",
    };
    let blob = new Blob([JSON.stringify(params)], headers);
    navigator.sendBeacon(this.data.requestUrl, blob);
  }
}

05-2 写一个接口 测试埋点

  • npm run build 前端先打包后,进行测试
05-2-2 安装依赖
  • 创建后端的express文件目录,安装以下依赖
    • npm i express -S
    • npm i cros -s
05-2-2 接口 express / index.js
const express = require("express")
const cors = require("cors")

const app = express()

app.use(cors())

app.use(express.urlencoded({extended:false}))

app.post("/tracker",(req,res)=>{
  console.log('req',req.body);
  res.send(200)
})

app.listen(9000,()=>{
  console.log('监听服务端口 9000');
})
05-2-3 启动后端服务
  • node index.js
05-2-4 打开index.html

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./dist/index.js"></script>
    <script>
      new tracker({
        requestUrl:"http://localhost:9000/tracker",
        historyTracker: true,
      });
    </script>
  </body>
</html>

05-2-4 测试埋点
  • history.pushState('aaa','','/a')
  • 效果
    在这里插入图片描述

在这里插入图片描述

06 dom 上报

  • 主要是给需要监听的元素添加一个属性 用来区分是否需要监听 target-key

06-1 src / core / index.ts

import {
  DefaultOptons,
  TrackerConfig,
  Options,
  reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";

// dom节点操作 上报-01
const MouseEventList: string[] = [
  "click",
  "dblclick",
  "contextmenu",
  "mousedown",
  "mouseup",
  "mouseenter",
  "mouseout",
  "mouseover",
];

export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker();
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history["pushState"] = createHistoryEvnent("pushState");
    window.history["replaceState"] = createHistoryEvnent("replaceState");
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  // uuid 用户的唯一标识
  public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
    this.data.uuid = uuid;
  }
  // 用户的自定义参数
  public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
    this.data.extra = extra;
  }
  private captureEvents<T>(
    mouseEventList: string[],
    targetKey: string,
    data?: T
  ) {
    mouseEventList.forEach((event) => {
      window.addEventListener(event, () => {
        // console.log("监听到了");
        this.reportTracker({
          event,
          targetKey,
          data,
        });
      });
    });
  }
  private initTracker() {
    // 若是用户 开启 historyTracker 循环监听用户的事件
    if (this.data.historyTracker) {
      this.captureEvents(["pushState"], "history-pv");
      this.captureEvents(["replaceState"], "history-pv");
      this.captureEvents(["popstate"], "history-pv");
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(["hashChange"], "hash-pv");
    }
    // dom 节点上报 -03
    if (this.data.domTracker) {
      console.log('11');
      this.targetKeyReport();
    }
    // if (this.data.jsError) {
    //   this.jsError();
    // }
  }
  // 手动上报
  public sendTracker<T extends reportTrackerData>(data: T) {
    this.reportTracker(data);
  }

  // 上报用户操作 - 监听到的时候 调用
  private reportTracker<T>(data: T) {
    // 拿到上报的data数据
    const params = Object.assign(this.data, data, {
      time: new Date().getTime(),
    });
    // 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
    let headers = {
      type: "application/x-www-form-urlencoded",
    };
    let blob = new Blob([JSON.stringify(params)], headers);
    navigator.sendBeacon(this.data.requestUrl, blob);
  }
  // dom 节点上报 -02
  private targetKeyReport() {
    MouseEventList.forEach((event) => {
      window.addEventListener(event, (e) => {
        const target = e.target as HTMLElement;
        const targetKey = target.getAttribute("target-key");
        // 如果有上报属性,则需要上报
        if (targetKey) {
          this.reportTracker({
            event,
            targetKey,
          });
        }
      });
    });
  }
}

06-2 npm run build 之后 index.html 修改与添加事件

  • domTracker:true
  • 添加上报
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./dist/index.js"></script>
    <button target-key="btn">添加上报</button>
    <button>无添加</button>
    <script>
      new tracker({
        requestUrl: "http://localhost:9000/tracker",
        historyTracker: true,
        domTracker:true
      });
    </script>
  </body>
</html>

06-3 效果

在这里插入图片描述

07 js报错上报 error 事件 promise报错 unhandledrejection

07-1 src / core / index.ts

import {
  DefaultOptons,
  TrackerConfig,
  Options,
  reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";

// dom节点操作 上报-01
const MouseEventList: string[] = [
  "click",
  "dblclick",
  "contextmenu",
  "mousedown",
  "mouseup",
  "mouseenter",
  "mouseout",
  "mouseover",
];

export default class Tracher {
  public data: Options;
  constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options);
    this.initTracker();
  }
  private initDef(): DefaultOptons {
    // 修改 history路由 以便于记录埋点事件
    window.history["pushState"] = createHistoryEvnent("pushState");
    window.history["replaceState"] = createHistoryEvnent("replaceState");
    return <DefaultOptons>{
      sdkVersion: TrackerConfig.version,
      historyTracker: false,
      hashTracker: false,
      domTracker: false,
      jsError: false,
    };
  }
  // uuid 用户的唯一标识
  public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
    this.data.uuid = uuid;
  }
  // 用户的自定义参数
  public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
    this.data.extra = extra;
  }
  private captureEvents<T>(
    mouseEventList: string[],
    targetKey: string,
    data?: T
  ) {
    mouseEventList.forEach((event) => {
      window.addEventListener(event, () => {
        // console.log("监听到了");
        this.reportTracker({
          event,
          targetKey,
          data,
        });
      });
    });
  }
  private initTracker() {
    // 若是用户 开启 historyTracker 循环监听用户的事件
    if (this.data.historyTracker) {
      this.captureEvents(["pushState"], "history-pv");
      this.captureEvents(["replaceState"], "history-pv");
      this.captureEvents(["popstate"], "history-pv");
    }
    if (this.data.hashTracker) {
      // 'hash-pv' - 此参数需要与后台进行协商定义
      this.captureEvents(["hashChange"], "hash-pv");
    }
    // dom 节点上报 -03
    if (this.data.domTracker) {
      this.targetKeyReport();
    }
    // js错误上报 - 1
    if (this.data.jsError) {
      this.jsError();
    }
  }
  // 手动上报
  public sendTracker<T extends reportTrackerData>(data: T) {
    this.reportTracker(data);
  }

  // 上报用户操作 - 监听到的时候 调用
  private reportTracker<T>(data: T) {
    // 拿到上报的data数据
    const params = Object.assign(this.data, data, {
      time: new Date().getTime(),
    });
    // 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
    let headers = {
      type: "application/x-www-form-urlencoded",
    };
    let blob = new Blob([JSON.stringify(params)], headers);
    navigator.sendBeacon(this.data.requestUrl, blob);
  }
  // dom 节点上报 -02
  private targetKeyReport() {
    MouseEventList.forEach((event) => {
      window.addEventListener(event, (e) => {
        const target = e.target as HTMLElement;
        const targetKey = target.getAttribute("target-key");
        // 如果有上报属性,则需要上报
        if (targetKey) {
          this.reportTracker({
            event,
            targetKey,
          });
        }
      });
    });
  }

  // js错误上报 - 1
  private jsError() {
    this.errorEvent();
    this.promiseReject();
  }
  //捕获js报错
  private errorEvent() {
    window.addEventListener("error", (event) => {
      this.reportTracker({
        event: "error",
        targetKey: "message",
        message: event.message,
      });
    });
  }
  //捕获promise 错误
  private promiseReject() {
    window.addEventListener("unhandledrejection", (event) => {
      event.promise.catch((error) => {
        this.reportTracker({
          event: "promise",
          targetKey: "reject",
          // targetKey: "message",
          message: error,
        });
      });
    });
  }
}

07-2 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./dist/index.js"></script>
    <button target-key="btn">添加上报</button>
    <button>无添加</button>
    <script>
      new tracker({
        requestUrl: "http://localhost:9000/tracker",
        historyTracker: true,
        domTracker:true,
        jsError:true
      });
    </script>
  </body>
</html>

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值